mirror of
https://github.com/SashLilac/cambridge.git
synced 2024-11-22 17:09:02 -06:00
rich presence!!
This commit is contained in:
parent
67abf35a28
commit
995fd7fee9
BIN
discord-rpc.dll
Normal file
BIN
discord-rpc.dll
Normal file
Binary file not shown.
252
discordRPC.lua
Normal file
252
discordRPC.lua
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
local ffi = require "ffi"
|
||||||
|
local discordRPClib = ffi.load("discord-rpc")
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
typedef struct DiscordRichPresence {
|
||||||
|
const char* state; /* max 128 bytes */
|
||||||
|
const char* details; /* max 128 bytes */
|
||||||
|
int64_t startTimestamp;
|
||||||
|
int64_t endTimestamp;
|
||||||
|
const char* largeImageKey; /* max 32 bytes */
|
||||||
|
const char* largeImageText; /* max 128 bytes */
|
||||||
|
const char* smallImageKey; /* max 32 bytes */
|
||||||
|
const char* smallImageText; /* max 128 bytes */
|
||||||
|
const char* partyId; /* max 128 bytes */
|
||||||
|
int partySize;
|
||||||
|
int partyMax;
|
||||||
|
const char* matchSecret; /* max 128 bytes */
|
||||||
|
const char* joinSecret; /* max 128 bytes */
|
||||||
|
const char* spectateSecret; /* max 128 bytes */
|
||||||
|
int8_t instance;
|
||||||
|
} DiscordRichPresence;
|
||||||
|
|
||||||
|
typedef struct DiscordUser {
|
||||||
|
const char* userId;
|
||||||
|
const char* username;
|
||||||
|
const char* discriminator;
|
||||||
|
const char* avatar;
|
||||||
|
} DiscordUser;
|
||||||
|
|
||||||
|
typedef void (*readyPtr)(const DiscordUser* request);
|
||||||
|
typedef void (*disconnectedPtr)(int errorCode, const char* message);
|
||||||
|
typedef void (*erroredPtr)(int errorCode, const char* message);
|
||||||
|
typedef void (*joinGamePtr)(const char* joinSecret);
|
||||||
|
typedef void (*spectateGamePtr)(const char* spectateSecret);
|
||||||
|
typedef void (*joinRequestPtr)(const DiscordUser* request);
|
||||||
|
|
||||||
|
typedef struct DiscordEventHandlers {
|
||||||
|
readyPtr ready;
|
||||||
|
disconnectedPtr disconnected;
|
||||||
|
erroredPtr errored;
|
||||||
|
joinGamePtr joinGame;
|
||||||
|
spectateGamePtr spectateGame;
|
||||||
|
joinRequestPtr joinRequest;
|
||||||
|
} DiscordEventHandlers;
|
||||||
|
|
||||||
|
void Discord_Initialize(const char* applicationId,
|
||||||
|
DiscordEventHandlers* handlers,
|
||||||
|
int autoRegister,
|
||||||
|
const char* optionalSteamId);
|
||||||
|
|
||||||
|
void Discord_Shutdown(void);
|
||||||
|
|
||||||
|
void Discord_RunCallbacks(void);
|
||||||
|
|
||||||
|
void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
||||||
|
|
||||||
|
void Discord_ClearPresence(void);
|
||||||
|
|
||||||
|
void Discord_Respond(const char* userid, int reply);
|
||||||
|
|
||||||
|
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
||||||
|
]]
|
||||||
|
|
||||||
|
local discordRPC = {} -- module table
|
||||||
|
|
||||||
|
-- proxy to detect garbage collection of the module
|
||||||
|
discordRPC.gcDummy = newproxy(true)
|
||||||
|
|
||||||
|
local function unpackDiscordUser(request)
|
||||||
|
return ffi.string(request.userId), ffi.string(request.username),
|
||||||
|
ffi.string(request.discriminator), ffi.string(request.avatar)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- callback proxies
|
||||||
|
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
|
||||||
|
-- luajit.org/ext_ffi_semantics.html
|
||||||
|
local ready_proxy = ffi.cast("readyPtr", function(request)
|
||||||
|
if discordRPC.ready then
|
||||||
|
discordRPC.ready(unpackDiscordUser(request))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local disconnected_proxy = ffi.cast("disconnectedPtr", function(errorCode, message)
|
||||||
|
if discordRPC.disconnected then
|
||||||
|
discordRPC.disconnected(errorCode, ffi.string(message))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local errored_proxy = ffi.cast("erroredPtr", function(errorCode, message)
|
||||||
|
if discordRPC.errored then
|
||||||
|
discordRPC.errored(errorCode, ffi.string(message))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local joinGame_proxy = ffi.cast("joinGamePtr", function(joinSecret)
|
||||||
|
if discordRPC.joinGame then
|
||||||
|
discordRPC.joinGame(ffi.string(joinSecret))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local spectateGame_proxy = ffi.cast("spectateGamePtr", function(spectateSecret)
|
||||||
|
if discordRPC.spectateGame then
|
||||||
|
discordRPC.spectateGame(ffi.string(spectateSecret))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local joinRequest_proxy = ffi.cast("joinRequestPtr", function(request)
|
||||||
|
if discordRPC.joinRequest then
|
||||||
|
discordRPC.joinRequest(unpackDiscordUser(request))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- helpers
|
||||||
|
local function checkArg(arg, argType, argName, func, maybeNil)
|
||||||
|
assert(type(arg) == argType or (maybeNil and arg == nil),
|
||||||
|
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
|
||||||
|
argName, func, argType))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkStrArg(arg, maxLen, argName, func, maybeNil)
|
||||||
|
if maxLen then
|
||||||
|
assert(type(arg) == "string" and arg:len() <= maxLen or (maybeNil and arg == nil),
|
||||||
|
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
|
||||||
|
argName, func, maxLen))
|
||||||
|
else
|
||||||
|
checkArg(arg, "string", argName, func, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkIntArg(arg, maxBits, argName, func, maybeNil)
|
||||||
|
maxBits = math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53
|
||||||
|
local maxVal = 2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
|
||||||
|
assert(type(arg) == "number" and math.floor(arg) == arg
|
||||||
|
and arg < maxVal and arg >= -maxVal
|
||||||
|
or (maybeNil and arg == nil),
|
||||||
|
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
|
||||||
|
argName, func, maxVal))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function wrappers
|
||||||
|
function discordRPC.initialize(applicationId, autoRegister, optionalSteamId)
|
||||||
|
local func = "discordRPC.Initialize"
|
||||||
|
checkStrArg(applicationId, nil, "applicationId", func)
|
||||||
|
checkArg(autoRegister, "boolean", "autoRegister", func)
|
||||||
|
if optionalSteamId ~= nil then
|
||||||
|
checkStrArg(optionalSteamId, nil, "optionalSteamId", func)
|
||||||
|
end
|
||||||
|
|
||||||
|
local eventHandlers = ffi.new("struct DiscordEventHandlers")
|
||||||
|
eventHandlers.ready = ready_proxy
|
||||||
|
eventHandlers.disconnected = disconnected_proxy
|
||||||
|
eventHandlers.errored = errored_proxy
|
||||||
|
eventHandlers.joinGame = joinGame_proxy
|
||||||
|
eventHandlers.spectateGame = spectateGame_proxy
|
||||||
|
eventHandlers.joinRequest = joinRequest_proxy
|
||||||
|
|
||||||
|
discordRPClib.Discord_Initialize(applicationId, eventHandlers,
|
||||||
|
autoRegister and 1 or 0, optionalSteamId)
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.shutdown()
|
||||||
|
discordRPClib.Discord_Shutdown()
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.runCallbacks()
|
||||||
|
discordRPClib.Discord_RunCallbacks()
|
||||||
|
end
|
||||||
|
-- http://luajit.org/ext_ffi_semantics.html#callback :
|
||||||
|
-- It is not allowed, to let an FFI call into a C function (runCallbacks)
|
||||||
|
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
|
||||||
|
-- Usually this attempt is caught by the interpreter first and the C function
|
||||||
|
-- is blacklisted for compilation.
|
||||||
|
-- solution:
|
||||||
|
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for
|
||||||
|
-- the surrounding Lua function that invokes such a message polling function."
|
||||||
|
jit.off(discordRPC.runCallbacks)
|
||||||
|
|
||||||
|
function discordRPC.updatePresence(presence)
|
||||||
|
local func = "discordRPC.updatePresence"
|
||||||
|
checkArg(presence, "table", "presence", func)
|
||||||
|
|
||||||
|
-- -1 for string length because of 0-termination
|
||||||
|
checkStrArg(presence.state, 127, "presence.state", func, true)
|
||||||
|
checkStrArg(presence.details, 127, "presence.details", func, true)
|
||||||
|
|
||||||
|
checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true)
|
||||||
|
checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true)
|
||||||
|
|
||||||
|
checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true)
|
||||||
|
checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true)
|
||||||
|
checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true)
|
||||||
|
checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true)
|
||||||
|
checkStrArg(presence.partyId, 127, "presence.partyId", func, true)
|
||||||
|
|
||||||
|
checkIntArg(presence.partySize, 32, "presence.partySize", func, true)
|
||||||
|
checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true)
|
||||||
|
|
||||||
|
checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true)
|
||||||
|
checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true)
|
||||||
|
checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true)
|
||||||
|
|
||||||
|
checkIntArg(presence.instance, 8, "presence.instance", func, true)
|
||||||
|
|
||||||
|
local cpresence = ffi.new("struct DiscordRichPresence")
|
||||||
|
cpresence.state = presence.state
|
||||||
|
cpresence.details = presence.details
|
||||||
|
cpresence.startTimestamp = presence.startTimestamp or 0
|
||||||
|
cpresence.endTimestamp = presence.endTimestamp or 0
|
||||||
|
cpresence.largeImageKey = presence.largeImageKey
|
||||||
|
cpresence.largeImageText = presence.largeImageText
|
||||||
|
cpresence.smallImageKey = presence.smallImageKey
|
||||||
|
cpresence.smallImageText = presence.smallImageText
|
||||||
|
cpresence.partyId = presence.partyId
|
||||||
|
cpresence.partySize = presence.partySize or 0
|
||||||
|
cpresence.partyMax = presence.partyMax or 0
|
||||||
|
cpresence.matchSecret = presence.matchSecret
|
||||||
|
cpresence.joinSecret = presence.joinSecret
|
||||||
|
cpresence.spectateSecret = presence.spectateSecret
|
||||||
|
cpresence.instance = presence.instance or 0
|
||||||
|
|
||||||
|
discordRPClib.Discord_UpdatePresence(cpresence)
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.clearPresence()
|
||||||
|
discordRPClib.Discord_ClearPresence()
|
||||||
|
end
|
||||||
|
|
||||||
|
local replyMap = {
|
||||||
|
no = 0,
|
||||||
|
yes = 1,
|
||||||
|
ignore = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
-- maybe let reply take ints too (0, 1, 2) and add constants to the module
|
||||||
|
function discordRPC.respond(userId, reply)
|
||||||
|
checkStrArg(userId, nil, "userId", "discordRPC.respond")
|
||||||
|
assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"")
|
||||||
|
discordRPClib.Discord_Respond(userId, replyMap[reply])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- garbage collection callback
|
||||||
|
getmetatable(discordRPC.gcDummy).__gc = function()
|
||||||
|
discordRPC.shutdown()
|
||||||
|
ready_proxy:free()
|
||||||
|
disconnected_proxy:free()
|
||||||
|
errored_proxy:free()
|
||||||
|
joinGame_proxy:free()
|
||||||
|
spectateGame_proxy:free()
|
||||||
|
joinRequest_proxy:free()
|
||||||
|
end
|
||||||
|
|
||||||
|
return discordRPC
|
41
main.lua
41
main.lua
@ -1,4 +1,45 @@
|
|||||||
|
discordRPC = require("discordRPC")
|
||||||
|
appId = "599778517789573120"
|
||||||
|
|
||||||
|
function discordRPC.ready(userId, username, discriminator, avatar)
|
||||||
|
print(string.format("Discord: ready (%s, %s, %s, %s)", userId, username, discriminator, avatar))
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.disconnected(errorCode, message)
|
||||||
|
print(string.format("Discord: disconnected (%d: %s)", errorCode, message))
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.errored(errorCode, message)
|
||||||
|
print(string.format("Discord: error (%d: %s)", errorCode, message))
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.joinGame(joinSecret)
|
||||||
|
print(string.format("Discord: join (%s)", joinSecret))
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.spectateGame(spectateSecret)
|
||||||
|
print(string.format("Discord: spectate (%s)", spectateSecret))
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordRPC.joinRequest(userId, username, discriminator, avatar)
|
||||||
|
print(string.format("Discord: join request (%s, %s, %s, %s)", userId, username, discriminator, avatar))
|
||||||
|
discordRPC.respond(userId, "yes")
|
||||||
|
end
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
|
|
||||||
|
discordRPC.initialize(appId, true)
|
||||||
|
local now = os.time(os.date("*t"))
|
||||||
|
presence = {
|
||||||
|
startTimestamp = now,
|
||||||
|
details = "Loading game...",
|
||||||
|
state = "",
|
||||||
|
largeImageKey = "",
|
||||||
|
largeImageText = "",
|
||||||
|
smallImageKey = "",
|
||||||
|
smallImageText = ""
|
||||||
|
}
|
||||||
|
|
||||||
math.randomseed(os.time())
|
math.randomseed(os.time())
|
||||||
highscores = {}
|
highscores = {}
|
||||||
require "load.graphics"
|
require "load.graphics"
|
||||||
|
@ -5,6 +5,9 @@ function GameScene:new(game_mode, ruleset)
|
|||||||
self.game = game_mode()
|
self.game = game_mode()
|
||||||
self.ruleset = ruleset()
|
self.ruleset = ruleset()
|
||||||
self.game:initialize(self.ruleset)
|
self.game:initialize(self.ruleset)
|
||||||
|
presence.details = "In game"
|
||||||
|
presence.state = game_mode.name
|
||||||
|
discordRPC.updatePresence(presence)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GameScene:update()
|
function GameScene:update()
|
||||||
|
@ -22,6 +22,10 @@ function ConfigScene:new()
|
|||||||
-- load current config
|
-- load current config
|
||||||
self.config = config.input
|
self.config = config.input
|
||||||
self.input_state = 1
|
self.input_state = 1
|
||||||
|
|
||||||
|
presence.details = "In menus"
|
||||||
|
presence.state = "Changing input config"
|
||||||
|
discordRPC.updatePresence(presence)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfigScene:update()
|
function ConfigScene:update()
|
||||||
|
@ -47,6 +47,9 @@ function ModeSelectScene:new()
|
|||||||
ruleset = current_ruleset,
|
ruleset = current_ruleset,
|
||||||
select = "mode",
|
select = "mode",
|
||||||
}
|
}
|
||||||
|
presence.details = "In menus"
|
||||||
|
presence.state = "Choosing a mode"
|
||||||
|
discordRPC.updatePresence(presence)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ModeSelectScene:update()
|
function ModeSelectScene:update()
|
||||||
|
@ -6,8 +6,13 @@ local main_menu_screens = {
|
|||||||
ExitScene,
|
ExitScene,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local mainmenuidle = {"Idle", "Twiddling their thumbs", "Admiring the main menu's BG", "Waiting for spring to come"}
|
||||||
|
|
||||||
function TitleScene:new()
|
function TitleScene:new()
|
||||||
self.main_menu_state = 1
|
self.main_menu_state = 1
|
||||||
|
presence.details = "In menus"
|
||||||
|
presence.state = mainmenuidle[math.random(#mainmenuidle)]
|
||||||
|
discordRPC.updatePresence(presence)
|
||||||
end
|
end
|
||||||
|
|
||||||
function TitleScene:update()
|
function TitleScene:update()
|
||||||
|
Loading…
Reference in New Issue
Block a user