This script contains a discord
namespace containing functions that interact with Discord Rich Presence. Discord Rich Presence in the player's Playing a game status on Discord. The code is very intuitive and simple to use, but can only be run on Ren'Py 8, as the module this script depends on doesn't have a Py2 version. That's okay though - I recommend you to update to Ren'Py 8 in any case!
An Application set up on the Discord Developer Portal is required for every game supporting Rich Presence. After the App is created, you will receive the necessary Application ID to insert into settings.rpy, and it is also where all the images you plan on displaying in the presence need to be uploaded first. What it is and how to work with it is covered under the Wiki tab on this GitHub page, local copy of which is included in the code files.
Finally, this project is under the MIT License. This means you can use, modify and/or distribute this script, as long as I am credited ("Lezalith" is enough, but a link to my website with Ren'Py content, LezCave.com, is greatly appreciated!) and as long as the LICENSE.txt file stays included.
- Download of one of two versions of a Discord Rich Presence.
- Variables that you need to check out before using this script.
- Functions and Screen Actions that allow you to interact with the Discord Presence.
- First part of the Rich Presence elements described, examples included.
- Second part of the Rich Presence elements described, examples included.
- Notes about compatibility and limitations.
- Examples of using this script inside both
screen
andlabel
.
To get the script, download one of the releases on the right side of the GitHub page, under the Releases section. Here are the files that you need to put into your game folder:
-
python-packages folder contains the pypresence modules that handle everything Discord-related.
-
RenPy_Discord_Presence folder contains this script's files:
- rich_presence.rpy holds the entire script's code.
- settings.rpy contains the two Related Variables described below.
- discord_developer_portal.md, local copy of the Wiki tab.
- README.md, local copy of this document (in Clean Versions only, it is in the base folder in Project Versions).
- LICENSE.TXT, a file containing the MIT License that I ask you keep when downloading the files.
There are two versions for every release:
Project Version contains the whole code of this repository. It is a project that can be launched from the Ren'Py Launcher and that shows how simple it is to update the presence status from both screens and labels, utilizing the set
and update
functions and their screen actions counterparts, both described below. Simply launch the project and keep an eye on your Discord profile.
All of the code related to the project is located in its script.rpy file, with insignificants modifications to screens.rpy and gui.rpy.
Clean Version does not contain project files and only contains the files listed above, meaning you can just copy everything over to your own project and you're good to go!
There are some important variables in the settings.rpy file that you need to visit. Here is what they do:
application_id
takes a string with an Application ID of your Application set up on Discord Developer Portal. This has to be set in order for this script to work, having an invalid ID set will throw an error when launching the game.
define discord.application_id = "10208ABCDEFGHIJ2795"
main_menu_state
takes a dictionary. Keys are strings of properties corresponding to presence elements, and values are their values.
This is the state shown in the presence anytime the game launches and/or enters the main menu. Below is what it looks like by default.
define discord.main_menu_state = { "details" : "In the Main Menu.",
"large_image" : "lezalith"}
There's also the start_state
. Just like main_menu_state
, this is a set of properties, ones that are set when the game starts.
Script acknowledges this by reaching the start label, name of which you need to set in the start_label
variable. It can also be a list of label names instead, in case you have multiple labels this game can Start at.
Unlike main_menu_state
, start_state
doesn't have to be set, and start_label
can be set to None to ignore its functionality.
define discord.start_state = { "details" : "Reading the Story.",
"large_image" : "lezalith"}
define discord.start_label = "start"
Finally, there are log variables controlling what is printed out. Prints are recorded in the game's console and in the log.txt file. There are three of them:
# Shows whether the Presence was initialized and closed correctly.
define discord.log_important = True
# Records properties whenever they change with set or update methods.
define discord.log_properties = True
# Notes whenever the properties get rolled back or loaded from a save file, and what they were restored into.
define discord.log_restore = True
I recommend leaving these on, it's information your players can pass on to you in case they're having troubles with your game.
Functions used to interact with the presence are defined inside the discord
namespace. They're meant to be used inside label
s and init python
blocks.
Here are the core two:
discord.set
takes property names corresponding to presence elements for arguments. All elements are listed below.
If some properties are already set in the presence, they are discarded and only the passed properties are kept.
An exception to this is the start
property, which sticks to time since this session's launch unless specified.
discord.set(details = "Setting new Discord Rich Presence.")
discord.update
takes property names for arguments the same way discord.set
does.
Difference between the two is that discord.update
keeps the current properties as they are and only changes the ones provided.
discord.set(details = "Setting new Discord Rich Presence.")
discord.update(state = "State got changed while details stayed the same!")
discord.set
and discord.update
have Screen Actions counterparts, intuitive alternatives to Function(discord.set)
and Function(discord.update)
.
Same rules apply for both Actions:
- They are sensitive as long as Presence was successfully initialized.
- They are selected if properties given to the Action inside the screen match currently shown properties. Exception to this is
start
- if it's not specified inside the screen but is present in currently shown properties, it is considered to be"start_time
in the comparison.
Here's an example of a simple screen with both Actions present. Almost identical example is below.
screen both_screen_actions():
vbox:
align (0.5, 0.5)
textbutton "This one sets the state only.":
action discord.Set(state = "Example State set.")
textbutton "This sets details and leaves the state alone.":
action discord.Update(details = "Details added while state was kept!")
There are many things that can be shown inside Rich Presence. Below is a screenshot of a couple elements of Rich Presence highlighted, with their property name equivalent below. All the property names are listed below the screenshot with a short example using discord.update
.
In the preview project, dictionary with all the properties for this example is stored in the discord.first_example
variable and discord.main_menu_state
is redirected to it.
details
takes a string, and is the upper line of text shown in the Presence.
discord.update(details = "Testing Discord Rich Presence.")
state
takes a string, and is the lower line of text shown in the Presence.
discord.update(state = "It's super easy in Ren'Py 8!")
large_image
takes a string that needs to correspond with an image uploaded onto the Discord Application.
It is the larger image shown on the left side. If it doesn't find an image with that name, it displays a placeholder question mark image.
discord.update(large_image = "lezalith")
small_image
takes a string that needs to correspond with an image uploaded onto the Discord Application.
It is the smaller image, shown at the bottom right of the large_image
. Unlike large_image
, if it doesn't find an image with that name it shows nothing.
If no large_image
is set or found, small_image
is used in its place and no smaller image at the bottom right is shown.
discord.update(large_image = "lezalith", small_image = "lezalith")
start
is a time from which Time Elapsed is calculated. It can take four different values:
"start_time"
makes Time Elapsed refer to thestart_time
variable, where time since this game session's launch is recorded."new_time"
inserts a new unix timestamp, reseting Time Elapsed to 0:0. It does not overwrite the recordedstart_time
by itself, but you can change that one directly if you need to for some reason.None
which results in Time Elapsed being hidden.- Unix timestamp from which Time Elapsed is calculated.
If start
is not specified in the properties displayed, it is always present with the value of "start_time"
.
discord.update(start = "start_time")
Overall, state shown on the screenshot can be accomplished with the following properties passed:
discord.set(details = "Testing Discord Rich Presence.",
state = "It's super easy in Ren'Py 8!",
large_image = "lezalith",
large_text = "Large Image Tooltip!",
small_image = "lezalith",
small_text = "Small Image Tooltip!")
As you can see, there are two more property names included there that I haven't mentioned - large_text
and small_text
. These are text tooltips that appear when the respective images are hovered by a cursor.
The final screenshot covers all the remaining rich presence properties. state
was already covered above, however it is required for the party_size
property to work.
In the preview project, dictionary with all the properties for this example is stored in the discord.second_example
variable.
end
takes an unix timestamp and calculates Time Left until that timestamp.
Setting this pushes start
completely aside and Time Elapsed with it and displays Time Left instead.
discord.update(end = time.time() + 3000)
party_size
takes a list of two ints. This is used for multiplayer games, where the two numbers represent the current party size and max party size respectively. We can still use it with visual novels if we're creative enough.
For the party_size
to be visible, state
has to be also provided.
discord.update(state = "Reading a Chapter", party_size = [1, 5])
buttons
takes a list of up to two dicts, and allows for buttons to be added into the Presence. The dict consists of two keys, label
being the text written inside the button, and url
being the link opened upon clicking it.
Note: Clicking buttons in the Presence in your own profile does nothing. Have others test them for you if you have button(s) in your projects!
discord.update(buttons = [ dict(label = "Discord Presence Example Button", url = "https://github.com/Lezalith/RenPy_Discord_Presence"),
dict(label = "Lezalith's Promotion Button!", url = "https://www.lezcave.com") ]
Putting all of that together, state shown on the second example screenshot was created with these properties:
discord.set(state = "Reading a Chapter",
end = time.time() + 3000,
party_size = [1, 5],
buttons = [ dict(label = "Discord Presence Example Button", url = "https://github.com/Lezalith/RenPy_Discord_Presence"),
dict(label = "Lezalith's Promotion Button!", url = "https://www.lezcave.com") ])
Discord presence only works for users who have the Discord desktop application installed. For players that do not have Discord installed, this entire code will simply do nothing. discord
namespace is still defined with all the properties and methods, but none of the methods do anything.
This means that players who have Discord (or have it but aren't logged in) can enjoy the benefits while those who do not aren't hindered in any way.
All presence properties are compatible with both saving games and rollbacking dialogue:
- Loading a saved game will restore the properties that were present when the game was saved.
start
possibly preserves the value ofstart_time
and refers to time since starting this game session. - Rollbacking past a change in the presence will return the presence to the original state, as would be expected.
This script uses the config.label_callback
variable to set start_state
, meaning you can't use it yourself. If you're fine with start_state
functionality not present, you can delete its define
statement in rich_presence.rpy.
This will be fixed with Ren'Py 8.1 - a new variable called config.label_callbacks
will be present, one that can take multiple functions just like all the other callbacks. The line is already prepared in the file, and I'll release an updated version of this script myself once Ren'Py 8.1 comes out.
A Discord Desktop Application running on the same machine as the game that's updating the presence, has updates on the player's profile shown instantly. This is not always the case for other Discord Desktop Apps - even ones where the account of the player themselves are logged in, curiously enough.
How often this delay occurs seems to correlate with the frequency of the presence updates and especially affects the clear
method, but you shouldn't worry about it too much, as it seems to be about 12s on average when it does occur.
Connecting to the Discord Rich Presence multiple times in quick succession will result in the connection not being established. In practice, this happens when you...
- ...launch and quit...
- ...reload...
- ...start and return to the main menu in...
...the game too many (approximately 4) times too fast (span of approximately 40s).
This makes the game unresponsive for the approximate span since the oldest successful connection. Restarting the game doesn't fix this, and the game won't launch again until the timer runs out.
My guess is that it's a precaution against malicious exploits on Discord's part and cannot be affected by Python code, but as is the case with Update Delays it's not a big issue - while possible for creators (especially through reloads), players should fulfill these requirements incredibly rarely, if ever.
Here is an example of two textbutton
s in a screen, both using the discord.Set
Screen Action.
screen screen_example():
vbox:
align (0.5, 0.5)
textbutton "This one sets the state only.":
action discord.Set(state = "Example State.")
textbutton "This sets details and leaves the state alone.":
action discord.Set(details = "Example Details.")
Below are the two example labels found inside this preview project.
Here is the first label, showcasing rollback and save/load functionality.
label long_example():
$ discord.set(details = "Inside a Preview", state = "Save & Load")
"Welcome to the Save/Load preview label."
"Feel free to save in any of the following places - just right-click and select a save slot."
scene scarian_island
$ discord.set(details = "Hanging out on Scarian Island", state = "Swimming")
"An island in the middle of the ocean."
"Swim around as much as you want, you won't find any sharks here!"
"Moving on..."
scene cerise_chantry
$ discord.set(details = "Walking around Cerise", state = "Visiting the Garden")
"A peaceful chantry with a rose garden."
"Can you hear the sound of silence?"
"Of course you can, this project has no audio."
"Moving on..."
scene gamboge_peninsula
$ discord.set(details = "On Gamboge Peninsula", state = "Sunbathing")
"A desert oasis with a strange name."
"Be careful not to get a heatstroke."
"Moving on..."
scene minty_meadows
$ discord.set(details = "Traversing Minty Meadows", state = "Chasing Cats")
"A beautiful land filled with flowers and bushes."
"For some reason, there are cats running everywhere around you."
"Okay, that's all the locations. Ready to head back?"
scene
$ discord.set(details = "Inside a Preview", state = "Save & Load")
"You can now right-click again and {b}load{/b} a previously saved game."
"Presence properties will be reverted to those present when you saved, and you can even rollback further from there!"
"Moving past this line returns you to the main screen, restoring {b}`main_menu_state{/b} with {b}start{/b} set to {b}\"start_time\"{/b}."
return
And here is the second label, showing off all the functionality. Everything is explained by the dialogue lines.
label functionality_example():
"This label shows how discord presence can be changed inside labels!"
"Since it is set as the {b}start_label{/b} in {b}settings.rpy{/b}, entering it has already set presence to {b}start_state{/b}."
"After this line, {b}discord.set{/b} will be called to set presence to only contain a {b}state{/b} text line."
$ discord.set(state = "Update after the first line.")
"And it is so."
"After this line, {b}discord.update{/b} is called to change the {b}details{/b} property, while keeping others as they are."
$ discord.update(details = "This wasn't here before!")
"Presence now contains one more piece of info."
"After this line, {b}start{/b} property is changed to {b}\"new_time\"{/b}, which will reset Time Elapsed to 0:0."
$ discord.update(start = "new_time")
"The time since the first launch can always be restored by changing {b}start{/b} property to {b}\"start_time\"{/b}"
"Done after this line."
$ discord.update(start = "start_time")
"Finally, changing the {b}start{/b} property to {b}None{/b} hides Time Elapsed altogether."
"Again, done after this line."
$ discord.update(start = None)
"As you can see, Time Elapsed is no longer visible."
"Last method is {b}discord.clear{/b}, which can be called to clear the presence of all properties."
"This hides the game completely, as if nothing was being played."
$ discord.clear()
"Presence all cleared and hidden!"
"You will now return to the main menu. {b}main_menu_state{/b} will be restored with {b}start{/b} being automatically set to {b}\"start_time\"{/b}."
return
If you need help setting up this script or the App on Discord's Dev Portal, you can contact me on Discord itself. Either write me a DM, Lezalith (LezCave.com)#2853, or preferably join my server! It's a server dedicated to my website, LezCave.com, where I post Ren'Py scripts and tutorials. Come say hello!
A good alternative is pinging me on the official Ren'Py Discord server, where I'm one of the Moderators, so I'm very active there. You can send me an email at Lezalith@gmail.com if you want to be extra formal, but I don't check my emails very often. Consider also letting me know on Discord that you've sent me one, so I notice it and can reply to it.
Finally, if you want to be extra supportive, you can donate a dollar or two to my Paypal.me. I'll use the money to manage my website and dedicate more time to posting new articles!