From c21893ac4ff18abe47231f56963d21c427629664 Mon Sep 17 00:00:00 2001 From: Madiwka3 Date: Mon, 6 Sep 2021 07:34:02 +0600 Subject: [PATCH] Added Discord RPC --- .gitignore | 3 +- debuggame.sh | 5 +- main.lua | 37 +++++-- src/baseGame.lua | 8 +- src/discordRPC.lua | 252 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 294 insertions(+), 11 deletions(-) create mode 100644 src/discordRPC.lua diff --git a/.gitignore b/.gitignore index 8315ce9..4451a67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ +applicationId.lua conf.lua game.love game.zip -debuggame.sh \ No newline at end of file +debuggame.sh diff --git a/debuggame.sh b/debuggame.sh index 9ff32cd..1d8517c 100755 --- a/debuggame.sh +++ b/debuggame.sh @@ -2,5 +2,6 @@ rm game.love rm game.zip zip -r game * mv game.zip game.love -love game.love -rm game.love \ No newline at end of file +cp ../libdiscord-rpc.so libdiscord-rpc.so +love game.love --fused +rm game.love diff --git a/main.lua b/main.lua index bed7d79..956a870 100644 --- a/main.lua +++ b/main.lua @@ -1,13 +1,13 @@ --CALLING OTHER LUA FILES -<<<<<<< HEAD -love.filesystem.setIdentity( "pong" ) -love.filesystem.createDirectory( "a" ) -======= ->>>>>>> 66f507d752eff25dc46468da097e4d08624fced6 -require "src/dependencies" +require "src/dependencies" +local content = love.filesystem.read("src/libdiscord-rpc.so") +print(content) +love.filesystem.write("libdiscord-rpc.so", content) +local discordRPC = require("src/discordRPC") +local appId = require("applicationId") --CANCELLED ATTEMPETED SHADING (NOT WORKING) local shader_code = [[ @@ -32,6 +32,7 @@ showTouchControls = false --0.9 VARIABLES +startTime = os.time(os.date("*t")) globalMessage = "none" globalAnimation = "none" globalMessageTime = 0 @@ -945,7 +946,16 @@ function love.load() smallfont = love.graphics.newFont("font.ttf", 25) scorefont = love.graphics.newFont("font.ttf", 60) love.graphics.setFont(smallfont) + discordRPC.initialize(appId, true) + local now = os.time(os.date("*t")) + presence = { + state = "Enjoying Pong", + details = "Main Menu", + largeImageKey = "pongnew", + largeImageText = "Nuclear Pong", + } + nextPresenceUpdate = 0 --push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, { -- fullscreen = isFullscreen, -- resizable = true, @@ -1041,6 +1051,11 @@ function love.update(dt) globalMessage = "none" end end + if nextPresenceUpdate < love.timer.getTime() then + discordRPC.updatePresence(presence) + nextPresenceUpdate = love.timer.getTime() + 2.0 + end + discordRPC.runCallbacks() if not lowcpu then if (love.timer.getFPS() < 50 and gameState ~= "animation") then countinglowcpu = countinglowcpu + 1 @@ -1415,6 +1430,7 @@ function hardmanager(diff) AGAINST_AI = 1 gameState = "base" end + startTime = os.time(os.date("*t")) end function dangerChecker() --CHECK IF CONTROLS ARE DUPLICATING @@ -1941,6 +1957,13 @@ end function serveBot() --THIS IS USED TO CHANGE TEXT/BALL DIRECTION ON DIFFERENT SERVES --print("servebot called") + presence = { + state = "Playing Pong", + details = player1score .. " : " .. player2score, + startTimestamp = startTime, + largeImageKey = "pongnew", + largeImageText = "Nuclear Pong", + } if (gameState == "1serve") then updateTEXT = "" if (gameMode ~= "practice") then @@ -2513,7 +2536,7 @@ end return false end function showMessage() - print (globalMessage) + --print (globalMessage) if globalMessage ~= "none" then love.graphics.printf("Low CPU mode active", 0, VIRTUAL_WIDTH/2, VIRTUAL_HEIGHT/2, "center") end diff --git a/src/baseGame.lua b/src/baseGame.lua index a095320..cbeaed5 100644 --- a/src/baseGame.lua +++ b/src/baseGame.lua @@ -365,7 +365,7 @@ end function debugCheck(dt) if (gameState == "menu") then - updateTEXT = "0.8 Galaxy" + updateTEXT = "0.9 Pacific" end dangerChecker() elapsed = elapsed + dt @@ -838,6 +838,12 @@ function menuDraw() end love.keyboard.mouseisReleased = false elseif gameState == "menu" then + presence = { + state = "Enjoying Pong", + details = "Main Menu", + largeImageKey = "pongnew", + largeImageText = "Nuclear Pong", + } mymenu:butt(gameState, VIRTUAL_WIDTH, VIRTUAL_HEIGHT, buttons, sounds, "middle") love.keyboard.mouseisReleased = false elseif gameState == "difficulty" then diff --git a/src/discordRPC.lua b/src/discordRPC.lua new file mode 100644 index 0000000..26cbf19 --- /dev/null +++ b/src/discordRPC.lua @@ -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 \ No newline at end of file