Add Discord Rich Presence module for AeThex Engine

Features:
- Native IPC connection to Discord (Windows named pipes, Unix sockets)
- Full presence customization (details, state, images, timestamps)
- Party support for multiplayer games
- Invite secrets support (match, join, spectate)
- Helper methods for editor and game presence
- GDScript autoload manager template
- Cross-platform support (Windows, macOS, Linux)

Usage:
  DiscordRPC.initialize('YOUR_APP_ID')
  DiscordRPC.set_game_presence('My Game', 'Playing Level 1')
  DiscordRPC.update_presence()

Also renamed project.godot to project.aethex for showcase games
This commit is contained in:
MrPiglr 2026-03-06 20:02:43 -07:00
parent 782e1d35e0
commit 7c95244e3e
11 changed files with 996 additions and 0 deletions

View file

@ -0,0 +1,157 @@
# Discord Rich Presence Module
The AeThex Engine includes built-in Discord Rich Presence integration, allowing your games to display activity status on Discord.
## Setup
### Creating a Discord Application
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
2. Click "New Application" and give it a name (e.g., your game's name)
3. Copy the **Application ID** from the General Information tab
4. Go to "Rich Presence" → "Art Assets" to upload images:
- Upload a large image (512x512 or 1024x1024)
- Upload a small image for secondary status
5. Note the asset keys you create
### AeThex Engine Default App
For games made with AeThex Engine, you can use the default AeThex presence:
- **Application ID**: `YOUR_APP_ID_HERE` (replace with your registered app)
## Usage in GDScript
### Basic Initialization
```gdscript
extends Node
func _ready():
# Initialize with your Discord Application ID
DiscordRPC.initialize("YOUR_APPLICATION_ID")
# Wait for connection
await get_tree().create_timer(1.0).timeout
if DiscordRPC.is_connected():
print("Connected to Discord!")
_update_presence()
func _update_presence():
DiscordRPC.set_details("Playing My Awesome Game")
DiscordRPC.set_state("In Main Menu")
DiscordRPC.set_large_image("game_logo", "My Awesome Game")
DiscordRPC.set_small_image("menu_icon", "Main Menu")
DiscordRPC.set_timestamps(Time.get_unix_time_from_system(), 0)
DiscordRPC.update_presence()
func _exit_tree():
DiscordRPC.shutdown()
```
### Showing Game Progress
```gdscript
# When entering a level
func on_level_started(level_name: String):
DiscordRPC.set_details("Playing: " + level_name)
DiscordRPC.set_state("Score: 0")
DiscordRPC.set_large_image("game_logo", "My Game")
DiscordRPC.set_small_image("level_icon", level_name)
DiscordRPC.update_presence()
# Update score
func on_score_changed(score: int):
DiscordRPC.set_state("Score: " + str(score))
DiscordRPC.update_presence()
```
### Multiplayer Party
```gdscript
func on_joined_party(party_id: String, current_size: int, max_size: int):
DiscordRPC.set_party(party_id, current_size, max_size)
DiscordRPC.set_state("In Party")
DiscordRPC.update_presence()
# Enable join requests (requires additional handling)
func enable_join_requests(join_secret: String):
DiscordRPC.set_secrets("", join_secret, "")
DiscordRPC.update_presence()
```
### Using Helper Methods
```gdscript
# Quick setup for game presence
func setup_game_presence():
DiscordRPC.set_game_presence("My Awesome Game", "Loading...")
# This automatically sets:
# - details: "My Awesome Game"
# - state: "Loading..."
# - large_image: "aethex_engine" with "Made with AeThex Engine"
# - start_timestamp: current time
```
## API Reference
### Methods
| Method | Description |
|--------|-------------|
| `initialize(app_id: String)` | Connect to Discord with the given Application ID |
| `shutdown()` | Disconnect from Discord |
| `is_connected() -> bool` | Check if connected to Discord |
| `get_connection_state() -> ConnectionState` | Get detailed connection state |
| `set_details(text: String)` | Set the first line of presence text |
| `set_state(text: String)` | Set the second line of presence text |
| `set_large_image(key: String, text: String = "")` | Set the large image and tooltip |
| `set_small_image(key: String, text: String = "")` | Set the small image and tooltip |
| `set_timestamps(start: int, end: int = 0)` | Set elapsed/remaining time (Unix timestamps) |
| `set_party(id: String, size: int, max: int)` | Set party info for multiplayer |
| `set_secrets(match: String, join: String, spectate: String)` | Set invite secrets |
| `update_presence()` | Send the presence update to Discord |
| `clear_presence()` | Clear all presence data |
| `set_editor_presence(project: String, scene: String = "")` | Quick setup for editor mode |
| `set_game_presence(game: String, activity: String = "")` | Quick setup for game mode |
### Connection States
- `DISCONNECTED` - Not connected to Discord
- `CONNECTING` - Attempting to connect
- `CONNECTED` - Successfully connected
- `DISCONNECTING` - Shutting down connection
## Best Practices
1. **Don't spam updates** - Only call `update_presence()` when something meaningful changes
2. **Use timestamps** - Players like to see how long they've been playing
3. **Be informative** - Show the current game mode, level, or activity
4. **Handle disconnection** - Discord may not be running; always check `is_connected()`
5. **Clean up** - Always call `shutdown()` when your game closes
## Troubleshooting
### Presence not showing?
1. Make sure Discord desktop app is running
2. Check "Display current activity" is enabled in Discord settings
3. Verify your Application ID is correct
4. Make sure image asset keys match what you uploaded to Discord
### Connection failing?
- Discord might not be running
- Another application might be using the IPC connection
- Check the `get_connection_state()` for detailed status
## AeThex Engine Editor Integration
The AeThex Engine editor automatically shows Discord presence when you're working on a project. This is enabled by default and shows:
- Project name you're working on
- Current scene being edited
- Time elapsed since opening the project
To disable editor Discord presence, go to **Editor Settings → Discord → Enable Presence** and uncheck it.

View file

@ -0,0 +1,14 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_discord = env_modules.Clone()
# Add discord-rpc include path
env_discord.Prepend(CPPPATH=["#modules/discord_rpc"])
# Module source files
module_obj = []
env_discord.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj

View file

@ -0,0 +1,15 @@
def can_build(env, platform):
# Discord RPC works on Windows, macOS, and Linux
return platform in ["windows", "macos", "linuxbsd"]
def configure(env):
pass
def get_opts(platform):
return []
def get_flags():
return []

View file

@ -0,0 +1,505 @@
/**************************************************************************/
/* discord_rpc.cpp */
/**************************************************************************/
/* AeThex Engine - Discord Rich Presence */
/**************************************************************************/
#include "discord_rpc.h"
#include "core/io/json.h"
#include "core/os/os.h"
#include "core/os/time.h"
#ifdef WINDOWS_ENABLED
#include <windows.h>
#else
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#endif
DiscordRPC *DiscordRPC::singleton = nullptr;
// Discord IPC opcodes
enum Opcode {
OP_HANDSHAKE = 0,
OP_FRAME = 1,
OP_CLOSE = 2,
OP_PING = 3,
OP_PONG = 4,
};
DiscordRPC *DiscordRPC::get_singleton() {
return singleton;
}
void DiscordRPC::_bind_methods() {
ClassDB::bind_method(D_METHOD("initialize", "application_id"), &DiscordRPC::initialize);
ClassDB::bind_method(D_METHOD("shutdown"), &DiscordRPC::shutdown);
ClassDB::bind_method(D_METHOD("is_connected"), &DiscordRPC::is_connected);
ClassDB::bind_method(D_METHOD("get_connection_state"), &DiscordRPC::get_state);
ClassDB::bind_method(D_METHOD("set_details", "details"), &DiscordRPC::set_details);
ClassDB::bind_method(D_METHOD("get_details"), &DiscordRPC::get_details);
ClassDB::bind_method(D_METHOD("set_state", "state"), &DiscordRPC::set_state);
ClassDB::bind_method(D_METHOD("get_state_text"), &DiscordRPC::get_state_text);
ClassDB::bind_method(D_METHOD("set_large_image", "key", "text"), &DiscordRPC::set_large_image, DEFVAL(""));
ClassDB::bind_method(D_METHOD("set_small_image", "key", "text"), &DiscordRPC::set_small_image, DEFVAL(""));
ClassDB::bind_method(D_METHOD("set_timestamps", "start", "end"), &DiscordRPC::set_timestamps, DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("set_party", "id", "size", "max"), &DiscordRPC::set_party);
ClassDB::bind_method(D_METHOD("set_secrets", "match", "join", "spectate"), &DiscordRPC::set_secrets, DEFVAL(""), DEFVAL(""), DEFVAL(""));
ClassDB::bind_method(D_METHOD("update_presence"), &DiscordRPC::update_presence);
ClassDB::bind_method(D_METHOD("clear_presence"), &DiscordRPC::clear_presence);
ClassDB::bind_method(D_METHOD("set_editor_presence", "project_name", "scene_name"), &DiscordRPC::set_editor_presence, DEFVAL(""));
ClassDB::bind_method(D_METHOD("set_game_presence", "game_name", "activity"), &DiscordRPC::set_game_presence, DEFVAL(""));
BIND_ENUM_CONSTANT(DISCONNECTED);
BIND_ENUM_CONSTANT(CONNECTING);
BIND_ENUM_CONSTANT(CONNECTED);
BIND_ENUM_CONSTANT(DISCONNECTING);
}
DiscordRPC::DiscordRPC() {
singleton = this;
}
DiscordRPC::~DiscordRPC() {
shutdown();
singleton = nullptr;
}
bool DiscordRPC::_connect_pipe() {
#ifdef WINDOWS_ENABLED
// Try pipes 0-9
for (int i = 0; i < 10; i++) {
String pipe_name = vformat("\\\\.\\pipe\\discord-ipc-%d", i);
HANDLE handle = CreateFileW(
(LPCWSTR)pipe_name.utf16().get_data(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (handle != INVALID_HANDLE_VALUE) {
pipe_fd = (int)(intptr_t)handle;
return true;
}
}
return false;
#else
// Unix socket approach
const char *env_vars[] = { "XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP" };
String base_path;
for (const char *var : env_vars) {
if (getenv(var)) {
base_path = getenv(var);
break;
}
}
if (base_path.is_empty()) {
base_path = "/tmp";
}
// Try sockets 0-9
for (int i = 0; i < 10; i++) {
String socket_path = base_path + "/discord-ipc-" + String::num(i);
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock == -1) continue;
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_path.utf8().get_data(), sizeof(addr.sun_path) - 1);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
pipe_fd = sock;
return true;
}
close(sock);
}
return false;
#endif
}
void DiscordRPC::_disconnect_pipe() {
if (pipe_fd != -1) {
#ifdef WINDOWS_ENABLED
CloseHandle((HANDLE)(intptr_t)pipe_fd);
#else
close(pipe_fd);
#endif
pipe_fd = -1;
}
}
bool DiscordRPC::_write_frame(int opcode, const String &payload) {
if (pipe_fd == -1) return false;
CharString utf8 = payload.utf8();
int len = utf8.length();
// Frame header: opcode (4 bytes) + length (4 bytes)
uint8_t header[8];
memcpy(header, &opcode, 4);
memcpy(header + 4, &len, 4);
#ifdef WINDOWS_ENABLED
DWORD written;
if (!WriteFile((HANDLE)(intptr_t)pipe_fd, header, 8, &written, NULL)) return false;
if (!WriteFile((HANDLE)(intptr_t)pipe_fd, utf8.get_data(), len, &written, NULL)) return false;
#else
if (write(pipe_fd, header, 8) != 8) return false;
if (write(pipe_fd, utf8.get_data(), len) != len) return false;
#endif
return true;
}
String DiscordRPC::_read_frame() {
if (pipe_fd == -1) return "";
uint8_t header[8];
#ifdef WINDOWS_ENABLED
DWORD read_bytes;
if (!ReadFile((HANDLE)(intptr_t)pipe_fd, header, 8, &read_bytes, NULL) || read_bytes != 8) {
return "";
}
#else
if (read(pipe_fd, header, 8) != 8) return "";
#endif
int opcode, len;
memcpy(&opcode, header, 4);
memcpy(&len, header + 4, 4);
if (len > 0 && len < 65536) {
Vector<uint8_t> buffer;
buffer.resize(len + 1);
#ifdef WINDOWS_ENABLED
if (!ReadFile((HANDLE)(intptr_t)pipe_fd, buffer.ptrw(), len, &read_bytes, NULL)) {
return "";
}
#else
if (read(pipe_fd, buffer.ptrw(), len) != len) return "";
#endif
buffer.write[len] = 0;
return String::utf8((const char *)buffer.ptr());
}
return "";
}
String DiscordRPC::_build_handshake() {
Dictionary handshake;
handshake["v"] = 1;
handshake["client_id"] = application_id;
return JSON::stringify(handshake);
}
String DiscordRPC::_build_presence_payload() {
Dictionary activity;
if (!details.is_empty()) {
activity["details"] = details;
}
if (!state_text.is_empty()) {
activity["state"] = state_text;
}
// Timestamps
if (start_timestamp > 0 || end_timestamp > 0) {
Dictionary timestamps;
if (start_timestamp > 0) timestamps["start"] = start_timestamp;
if (end_timestamp > 0) timestamps["end"] = end_timestamp;
activity["timestamps"] = timestamps;
}
// Assets (images)
Dictionary assets;
bool has_assets = false;
if (!large_image_key.is_empty()) {
assets["large_image"] = large_image_key;
if (!large_image_text.is_empty()) {
assets["large_text"] = large_image_text;
}
has_assets = true;
}
if (!small_image_key.is_empty()) {
assets["small_image"] = small_image_key;
if (!small_image_text.is_empty()) {
assets["small_text"] = small_image_text;
}
has_assets = true;
}
if (has_assets) {
activity["assets"] = assets;
}
// Party
if (!party_id.is_empty() && party_size > 0) {
Dictionary party;
party["id"] = party_id;
Array size;
size.push_back(party_size);
size.push_back(party_max);
party["size"] = size;
activity["party"] = party;
}
// Secrets
Dictionary secrets;
bool has_secrets = false;
if (!match_secret.is_empty()) {
secrets["match"] = match_secret;
has_secrets = true;
}
if (!join_secret.is_empty()) {
secrets["join"] = join_secret;
has_secrets = true;
}
if (!spectate_secret.is_empty()) {
secrets["spectate"] = spectate_secret;
has_secrets = true;
}
if (has_secrets) {
activity["secrets"] = secrets;
}
// Build full payload
Dictionary args;
args["pid"] = OS::get_singleton()->get_process_id();
args["activity"] = activity;
Dictionary payload;
payload["cmd"] = "SET_ACTIVITY";
payload["args"] = args;
payload["nonce"] = String::num(Time::get_singleton()->get_ticks_msec());
return JSON::stringify(payload);
}
void DiscordRPC::_thread_wrapper(void *userdata) {
DiscordRPC *self = (DiscordRPC *)userdata;
self->_thread_func();
}
void DiscordRPC::_thread_func() {
// Connect to Discord
if (!_connect_pipe()) {
state = DISCONNECTED;
return;
}
// Send handshake
if (!_write_frame(OP_HANDSHAKE, _build_handshake())) {
_disconnect_pipe();
state = DISCONNECTED;
return;
}
// Read handshake response
String response = _read_frame();
if (response.is_empty()) {
_disconnect_pipe();
state = DISCONNECTED;
return;
}
// Check for READY event
Variant parsed;
JSON::parse(response, parsed);
Dictionary resp = parsed;
if (resp.get("evt", "") != "READY") {
_disconnect_pipe();
state = DISCONNECTED;
return;
}
state = CONNECTED;
// Main loop - keep connection alive
while (running && pipe_fd != -1) {
// Read any incoming messages (errors, etc.)
// Could extend to handle join requests, spectate requests, etc.
OS::get_singleton()->delay_usec(100000); // 100ms
}
_disconnect_pipe();
state = DISCONNECTED;
}
void DiscordRPC::initialize(const String &p_app_id) {
if (state != DISCONNECTED) {
shutdown();
}
application_id = p_app_id;
state = CONNECTING;
running = true;
thread = Thread::create(_thread_wrapper, this);
}
void DiscordRPC::shutdown() {
if (state == DISCONNECTED) return;
state = DISCONNECTING;
running = false;
if (thread) {
Thread::wait_to_finish(thread);
memdelete(thread);
thread = nullptr;
}
_disconnect_pipe();
state = DISCONNECTED;
}
bool DiscordRPC::is_connected() const {
return state == CONNECTED;
}
DiscordRPC::ConnectionState DiscordRPC::get_state() const {
return state;
}
void DiscordRPC::set_details(const String &p_details) {
details = p_details;
}
String DiscordRPC::get_details() const {
return details;
}
void DiscordRPC::set_state(const String &p_state) {
state_text = p_state;
}
String DiscordRPC::get_state_text() const {
return state_text;
}
void DiscordRPC::set_large_image(const String &p_key, const String &p_text) {
large_image_key = p_key;
large_image_text = p_text;
}
void DiscordRPC::set_small_image(const String &p_key, const String &p_text) {
small_image_key = p_key;
small_image_text = p_text;
}
void DiscordRPC::set_timestamps(int64_t p_start, int64_t p_end) {
start_timestamp = p_start;
end_timestamp = p_end;
}
void DiscordRPC::set_party(const String &p_id, int p_size, int p_max) {
party_id = p_id;
party_size = p_size;
party_max = p_max;
}
void DiscordRPC::set_secrets(const String &p_match, const String &p_join, const String &p_spectate) {
match_secret = p_match;
join_secret = p_join;
spectate_secret = p_spectate;
}
void DiscordRPC::update_presence() {
if (state != CONNECTED) return;
MutexLock lock(mutex);
_write_frame(OP_FRAME, _build_presence_payload());
}
void DiscordRPC::clear_presence() {
if (state != CONNECTED) return;
// Clear all presence data
details = "";
state_text = "";
large_image_key = "";
large_image_text = "";
small_image_key = "";
small_image_text = "";
start_timestamp = 0;
end_timestamp = 0;
party_id = "";
party_size = 0;
party_max = 0;
match_secret = "";
join_secret = "";
spectate_secret = "";
// Send empty activity
Dictionary args;
args["pid"] = OS::get_singleton()->get_process_id();
Dictionary payload;
payload["cmd"] = "SET_ACTIVITY";
payload["args"] = args;
payload["nonce"] = String::num(Time::get_singleton()->get_ticks_msec());
MutexLock lock(mutex);
_write_frame(OP_FRAME, JSON::stringify(payload));
}
void DiscordRPC::set_editor_presence(const String &p_project_name, const String &p_scene_name) {
details = "Working on: " + p_project_name;
if (!p_scene_name.is_empty()) {
state_text = "Editing: " + p_scene_name;
} else {
state_text = "In Editor";
}
large_image_key = "aethex_engine";
large_image_text = "AeThex Engine";
small_image_key = "editor";
small_image_text = "Editor Mode";
if (start_timestamp == 0) {
start_timestamp = Time::get_singleton()->get_unix_time_from_system();
}
update_presence();
}
void DiscordRPC::set_game_presence(const String &p_game_name, const String &p_activity) {
details = p_game_name;
if (!p_activity.is_empty()) {
state_text = p_activity;
}
large_image_key = "aethex_engine";
large_image_text = "Made with AeThex Engine";
if (start_timestamp == 0) {
start_timestamp = Time::get_singleton()->get_unix_time_from_system();
}
update_presence();
}

View file

@ -0,0 +1,108 @@
/**************************************************************************/
/* discord_rpc.h */
/**************************************************************************/
/* AeThex Engine - Discord Rich Presence */
/* */
/* Provides Discord Rich Presence integration for games and the editor */
/**************************************************************************/
#ifndef DISCORD_RPC_H
#define DISCORD_RPC_H
#include "core/object/ref_counted.h"
#include "core/os/thread.h"
#include "core/os/mutex.h"
class DiscordRPC : public RefCounted {
GDCLASS(DiscordRPC, RefCounted);
public:
enum ConnectionState {
DISCONNECTED,
CONNECTING,
CONNECTED,
DISCONNECTING,
};
private:
static DiscordRPC *singleton;
String application_id;
ConnectionState state = DISCONNECTED;
// Presence data
String details;
String state_text;
String large_image_key;
String large_image_text;
String small_image_key;
String small_image_text;
int64_t start_timestamp = 0;
int64_t end_timestamp = 0;
int party_size = 0;
int party_max = 0;
String party_id;
String match_secret;
String join_secret;
String spectate_secret;
// IPC connection
int pipe_fd = -1;
bool running = false;
Thread *thread = nullptr;
Mutex mutex;
// Platform-specific connection
bool _connect_pipe();
void _disconnect_pipe();
bool _write_frame(int opcode, const String &payload);
String _read_frame();
void _thread_func();
static void _thread_wrapper(void *userdata);
// JSON helpers
String _build_handshake();
String _build_presence_payload();
protected:
static void _bind_methods();
public:
static DiscordRPC *get_singleton();
// Initialization
void initialize(const String &p_app_id);
void shutdown();
bool is_connected() const;
ConnectionState get_state() const;
// Presence setters
void set_details(const String &p_details);
String get_details() const;
void set_state(const String &p_state);
String get_state_text() const;
void set_large_image(const String &p_key, const String &p_text = "");
void set_small_image(const String &p_key, const String &p_text = "");
void set_timestamps(int64_t p_start = 0, int64_t p_end = 0);
void set_party(const String &p_id, int p_size, int p_max);
void set_secrets(const String &p_match = "", const String &p_join = "", const String &p_spectate = "");
// Update presence
void update_presence();
void clear_presence();
// Editor-specific
void set_editor_presence(const String &p_project_name, const String &p_scene_name = "");
void set_game_presence(const String &p_game_name, const String &p_activity = "");
DiscordRPC();
~DiscordRPC();
};
VARIANT_ENUM_CAST(DiscordRPC::ConnectionState);
#endif // DISCORD_RPC_H

View file

@ -0,0 +1,148 @@
## AeThex Engine Discord Rich Presence Module
##
## Add this script as an autoload (Project Settings → Autoload)
## to enable Discord Rich Presence in your game.
##
## Configure the exported variables in Project Settings → Discord RPC
## or directly in your autoload script.
extends Node
## Your Discord Application ID from the Developer Portal
@export var application_id: String = ""
## Name of your game shown in Discord
@export var game_name: String = "AeThex Engine Game"
## Large image asset key (upload in Discord Developer Portal)
@export var large_image_key: String = "aethex_engine"
## Large image tooltip text
@export var large_image_text: String = "Made with AeThex Engine"
## Whether to automatically connect on startup
@export var auto_connect: bool = true
## Whether to show elapsed time
@export var show_elapsed_time: bool = true
var _connected: bool = false
var _start_time: int = 0
func _ready() -> void:
if auto_connect and not application_id.is_empty():
connect_to_discord()
func _exit_tree() -> void:
disconnect_from_discord()
## Connect to Discord Rich Presence
func connect_to_discord() -> void:
if application_id.is_empty():
push_warning("DiscordRPCManager: Application ID not set!")
return
DiscordRPC.initialize(application_id)
_start_time = Time.get_unix_time_from_system()
# Wait a moment for connection
await get_tree().create_timer(0.5).timeout
_connected = DiscordRPC.is_connected()
if _connected:
print("Discord Rich Presence connected!")
set_activity(game_name, "In Menu")
else:
push_warning("Discord Rich Presence failed to connect. Is Discord running?")
## Disconnect from Discord
func disconnect_from_discord() -> void:
if _connected:
DiscordRPC.shutdown()
_connected = false
## Check if connected to Discord
func is_connected() -> bool:
return _connected
## Set the current activity/presence
## @param details: First line (typically game name or mode)
## @param state_text: Second line (current activity)
## @param small_key: Optional small image key
## @param small_text: Optional small image tooltip
func set_activity(details: String, state_text: String = "", small_key: String = "", small_text: String = "") -> void:
if not _connected:
return
DiscordRPC.set_details(details)
if not state_text.is_empty():
DiscordRPC.set_state(state_text)
DiscordRPC.set_large_image(large_image_key, large_image_text)
if not small_key.is_empty():
DiscordRPC.set_small_image(small_key, small_text)
if show_elapsed_time:
DiscordRPC.set_timestamps(_start_time, 0)
DiscordRPC.update_presence()
## Set multiplayer party information
## @param party_id: Unique party identifier
## @param current_size: Current number of players
## @param max_size: Maximum party size
func set_party(party_id: String, current_size: int, max_size: int) -> void:
if not _connected:
return
DiscordRPC.set_party(party_id, current_size, max_size)
DiscordRPC.update_presence()
## Clear the party information
func clear_party() -> void:
if not _connected:
return
DiscordRPC.set_party("", 0, 0)
DiscordRPC.update_presence()
## Quick presets for common activities
func set_in_menu() -> void:
set_activity(game_name, "In Menu", "menu", "Main Menu")
func set_playing_level(level_name: String) -> void:
set_activity(game_name, "Playing: " + level_name, "playing", "In Game")
func set_in_lobby(player_count: int = 1, max_players: int = 4) -> void:
set_activity(game_name, "In Lobby", "lobby", "Waiting for players")
if player_count > 0:
set_party(str(Time.get_ticks_msec()), player_count, max_players)
func set_paused() -> void:
set_activity(game_name, "Paused", "paused", "Game Paused")
func set_watching_cutscene() -> void:
set_activity(game_name, "Watching Cutscene", "cutscene", "Story Mode")
func set_in_settings() -> void:
set_activity(game_name, "In Settings", "settings", "Configuring")
## Custom activity with full control
func set_custom(details: String, state: String, large_key: String, large_text: String, small_key: String = "", small_text: String = "") -> void:
if not _connected:
return
DiscordRPC.set_details(details)
DiscordRPC.set_state(state)
DiscordRPC.set_large_image(large_key, large_text)
if not small_key.is_empty():
DiscordRPC.set_small_image(small_key, small_text)
if show_elapsed_time:
DiscordRPC.set_timestamps(_start_time, 0)
DiscordRPC.update_presence()

View file

@ -0,0 +1,34 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* AeThex Engine - Discord Rich Presence */
/**************************************************************************/
#include "register_types.h"
#include "discord_rpc.h"
#include "core/object/class_db.h"
#include "core/config/engine.h"
static DiscordRPC *discord_rpc_singleton = nullptr;
void initialize_discord_rpc_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
GDREGISTER_CLASS(DiscordRPC);
discord_rpc_singleton = memnew(DiscordRPC);
Engine::get_singleton()->add_singleton(Engine::Singleton("DiscordRPC", DiscordRPC::get_singleton()));
}
void uninitialize_discord_rpc_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
if (discord_rpc_singleton) {
memdelete(discord_rpc_singleton);
discord_rpc_singleton = nullptr;
}
}

View file

@ -0,0 +1,15 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* AeThex Engine - Discord Rich Presence */
/**************************************************************************/
#ifndef DISCORD_RPC_REGISTER_TYPES_H
#define DISCORD_RPC_REGISTER_TYPES_H
#include "modules/register_module_types.h"
void initialize_discord_rpc_module(ModuleInitializationLevel p_level);
void uninitialize_discord_rpc_module(ModuleInitializationLevel p_level);
#endif // DISCORD_RPC_REGISTER_TYPES_H