diff --git a/Makefile b/Makefile index 83be326..abb0a93 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ CXX=g++ CPPFLAGS=-std=c++2a -Iinclude -Igame/include -Ilib/include -DMA_NO_PULSEAUDIO -LDLIBS= +LDLIBS=-Llib/bin SOURCES=$(wildcard graphics/*.cpp logging/*.cpp models/*.cpp level/*.cpp audio/*.cpp game/*.cpp game/entities/*.cpp game/UI/*.cpp util/*.cpp main.cpp) OBJS=$(patsubst %.cpp, build/%.o, $(SOURCES)) VER=vA1 +USE_DISCORD=1 ifndef PDCURSES_BACKEND ifeq ($(OS),Windows_NT) @@ -24,25 +25,46 @@ endif ifeq ($(USE_NCURSES), 1) LDLIBS+= -lncursesw else - LDLIBS+= -Llib/bin -lpdcurses + LDLIBS+= -lpdcurses endif ifndef PDCURSES_BACKEND $(error "OS not detected or supported - please specify PDCurses backend (wincon, x11, sdl2)) endif +ifeq ($(USE_DISCORD), 1) + $(shell mkdir -p lib/bin) + $(shell mkdir -p lib/include) + ifeq ($(UNAME_S),Linux) + $(shell cp -u lib/discord-rpc-linux/linux-dynamic/include/*.h lib/include) + $(shell cp -u lib/discord-rpc-linux/linux-dynamic/lib/libdiscord-rpc.so lib/bin) + LDLIBS+= -ldiscord-rpc + CPPFLAGS+= -DUSE_DISCORD + else + USE_DISCORD=0 + endif +endif + +$(shell mkdir -p lib/include) +$(shell cp lib/miniaudio/miniaudio.h lib/include/miniaudio.h) + build: build-pdcurses build: $(OBJS) - cp lib/miniaudio/miniaudio.h lib/include/miniaudio.h - $(CXX) $(CPPFLAGS) -o Spylike-$(VER) $(OBJS) $(LDLIBS) + $(CXX) $(CPPFLAGS) -o Spylike-$(VER) $(OBJS) $(LDLIBS) -Wl,-rpath=lib/bin build/%.o: %.cpp mkdir -p $(@D) $(CXX) $(CPPFLAGS) -c -o $@ $(LDLIBS) $^ -debug: CPPFLAGS+= -g -O0 +debug: CPPFLAGS+= -g -O0 -v debug: build +clean: + rm -rf lib/bin/* + rm -rf lib/include/* + rm -rf build/* + rm -rf lib/src/* + build-pdcurses: cd lib && mkdir -p bin cd lib && mkdir -p include diff --git a/game/game.cpp b/game/game.cpp index e5722ec..b5c91f4 100644 --- a/game/game.cpp +++ b/game/game.cpp @@ -1,5 +1,12 @@ #include #include +#include +#include +#include +#include +#include +#include + #include "rendering.h" #include "scheduling.h" #include "camera.h" @@ -14,11 +21,10 @@ #include "obstacle.h" #include "game.h" #include "geometry.h" -#include -#include -#include -#include -#include + +#ifdef USE_DISCORD +#include "discord_rpc.h" +#endif extern SpylikeLogger LOGGER; @@ -28,6 +34,20 @@ std::string formatSeconds(int seconds) { return std::to_string(minutes) + ":" + std::to_string(seconds); } +GameManager::GameManager() { + #ifdef USE_DISCORD + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.disconnected = handleDiscordDisconnected; + handlers.errored = handleDiscordError; + handlers.joinGame = handleDiscordJoin; + handlers.spectateGame = handleDiscordSpectate; + handlers.joinRequest = handleDiscordJoinRequest; + Discord_Initialize(DISCORD_CLIENT_ID, &handlers, 1, NULL); + #endif +} + + void GameManager::RunLevelTask::update() { auto& theMap = manager.map; manager.camera->clearScreen(); @@ -120,6 +140,15 @@ void GameManager::loadLevel(Level level) { map->registerEntity(entPair.first, entPair.second); } if (!audioManager->isPlaying()) audioManager->playMusic("1-1.wav", 0.25); + #ifdef USE_DISCORD + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = level.title.c_str(); + discordPresence.details = "Playing a level"; + discordPresence.largeImageKey = "rg_logo"; + Discord_UpdatePresence(&discordPresence); + Discord_RunCallbacks(); + #endif } // Note: You must close any active menus, before showing a new one. diff --git a/game/include/game.h b/game/include/game.h index fcb86b6..f87ee23 100644 --- a/game/include/game.h +++ b/game/include/game.h @@ -20,6 +20,11 @@ #include #include +#ifdef USE_DISCORD +#include "discord_rpc.h" +const char* const DISCORD_CLIENT_ID="1189761699511812186"; +#endif + extern SpylikeLogger LOGGER; inline Level load_from_file(std::string path) { @@ -84,11 +89,16 @@ inline Level load_from_file(std::string path) { } entities[ent] = Coordinate(entX, entY); } - return Level(levelType, width, height, entities); + std::string filename = path.substr(path.find_last_of("/\\") + 1); + std::string title = filename.substr(0, filename.find(".spm")); + return Level(title, levelType, width, height, entities); } class GameManager : public EventHandler, public std::enable_shared_from_this { bool paused; + #ifdef USE_DISCORD + DiscordEventHandlers handlers; + #endif FrameScheduler scheduler = FrameScheduler(20); NcursesTerminalScreen screen = NcursesTerminalScreen(80, 30); std::shared_ptr eventManager; @@ -127,7 +137,27 @@ class GameManager : public EventHandler, public std::enable_shared_from_thisuserId); + LOGGER.log("Discord RPC connected (User ID: " + uID + ")", DEBUG); + } + + static void handleDiscordDisconnected(int errcode, const char* message) {} + + static void handleDiscordError(int errcode, const char* message) {} + + static void handleDiscordJoin(const char* secret) {} + + static void handleDiscordSpectate(const char* secret) {} + + static void handleDiscordJoinRequest(const DiscordUser* request) {} + #endif + public: + GameManager(); void loadLevel(Level level); void pause(); void quit(); diff --git a/include/levelmap.h b/include/levelmap.h index 0f382fb..c4855da 100644 --- a/include/levelmap.h +++ b/include/levelmap.h @@ -107,11 +107,12 @@ class LevelMap : public std::enable_shared_from_this { }; struct Level { + std::string title; WorldType worldType; int width; int height; std::map, Coordinate> entities; - Level(WorldType wt, int w, int h, std::map, Coordinate> entities) : worldType{wt}, width{w}, height{h}, entities{entities} {} + Level(std::string title, WorldType wt, int w, int h, std::map, Coordinate> entities) : title{title}, worldType{wt}, width{w}, height{h}, entities{entities} {} }; #endif diff --git a/lib/discord-rpc-linux/linux-dynamic/include/discord_register.h b/lib/discord-rpc-linux/linux-dynamic/include/discord_register.h new file mode 100644 index 0000000..16fb42f --- /dev/null +++ b/lib/discord-rpc-linux/linux-dynamic/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/lib/discord-rpc-linux/linux-dynamic/include/discord_rpc.h b/lib/discord-rpc-linux/linux-dynamic/include/discord_rpc.h new file mode 100644 index 0000000..2fd8683 --- /dev/null +++ b/lib/discord-rpc-linux/linux-dynamic/include/discord_rpc.h @@ -0,0 +1,124 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordButton { + const char* label; + const char* url; +} DiscordButton; + +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; + int partyPrivacy; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; + const DiscordButton* buttons; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* globalName; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* user); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*debug)(char isOut, const char* opcodeName, const char* message, uint32_t messageLength); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* user); + void (*invited)(/* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type, + const DiscordUser* user, + const DiscordRichPresence* activity, + const char* sessionId, + const char* channelId, + const char* messageId); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +#define DISCORD_PARTY_PRIVATE 0 +#define DISCORD_PARTY_PUBLIC 1 + +#define DISCORD_ACTIVITY_ACTION_TYPE_JOIN 1 +#define DISCORD_ACTIVITY_ACTION_TYPE_SPECTATE 2 + +#define DISCORD_ACTIVITY_TYPE_PLAYING 0 +#define DISCORD_ACTIVITY_TYPE_STREAMING 1 +#define DISCORD_ACTIVITY_TYPE_LISTENING 2 +#define DISCORD_ACTIVITY_TYPE_WATCHING 3 +#define DISCORD_ACTIVITY_TYPE_CUSTOM 4 +#define DISCORD_ACTIVITY_TYPE_COMPETING 5 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_AcceptInvite(const char* userId, + /* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type, + const char* sessionId, + const char* channelId, + const char* messageId); + +DISCORD_EXPORT void Discord_OpenActivityInvite(/* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type); +DISCORD_EXPORT void Discord_OpenGuildInvite(const char* code); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/lib/discord-rpc-linux/linux-dynamic/lib/libdiscord-rpc.so b/lib/discord-rpc-linux/linux-dynamic/lib/libdiscord-rpc.so new file mode 100644 index 0000000..2252159 Binary files /dev/null and b/lib/discord-rpc-linux/linux-dynamic/lib/libdiscord-rpc.so differ diff --git a/lib/discord-rpc-linux/linux-static/include/discord_register.h b/lib/discord-rpc-linux/linux-static/include/discord_register.h new file mode 100644 index 0000000..16fb42f --- /dev/null +++ b/lib/discord-rpc-linux/linux-static/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/lib/discord-rpc-linux/linux-static/include/discord_rpc.h b/lib/discord-rpc-linux/linux-static/include/discord_rpc.h new file mode 100644 index 0000000..2fd8683 --- /dev/null +++ b/lib/discord-rpc-linux/linux-static/include/discord_rpc.h @@ -0,0 +1,124 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordButton { + const char* label; + const char* url; +} DiscordButton; + +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; + int partyPrivacy; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; + const DiscordButton* buttons; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* globalName; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* user); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*debug)(char isOut, const char* opcodeName, const char* message, uint32_t messageLength); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* user); + void (*invited)(/* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type, + const DiscordUser* user, + const DiscordRichPresence* activity, + const char* sessionId, + const char* channelId, + const char* messageId); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +#define DISCORD_PARTY_PRIVATE 0 +#define DISCORD_PARTY_PUBLIC 1 + +#define DISCORD_ACTIVITY_ACTION_TYPE_JOIN 1 +#define DISCORD_ACTIVITY_ACTION_TYPE_SPECTATE 2 + +#define DISCORD_ACTIVITY_TYPE_PLAYING 0 +#define DISCORD_ACTIVITY_TYPE_STREAMING 1 +#define DISCORD_ACTIVITY_TYPE_LISTENING 2 +#define DISCORD_ACTIVITY_TYPE_WATCHING 3 +#define DISCORD_ACTIVITY_TYPE_CUSTOM 4 +#define DISCORD_ACTIVITY_TYPE_COMPETING 5 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_AcceptInvite(const char* userId, + /* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type, + const char* sessionId, + const char* channelId, + const char* messageId); + +DISCORD_EXPORT void Discord_OpenActivityInvite(/* DISCORD_ACTIVITY_ACTION_TYPE_ */ int8_t type); +DISCORD_EXPORT void Discord_OpenGuildInvite(const char* code); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/lib/discord-rpc-linux/linux-static/lib/libdiscord-rpc.a b/lib/discord-rpc-linux/linux-static/lib/libdiscord-rpc.a new file mode 100644 index 0000000..ea5a933 Binary files /dev/null and b/lib/discord-rpc-linux/linux-static/lib/libdiscord-rpc.a differ diff --git a/logo.png b/logo.png index 620f03f..6856e14 100644 Binary files a/logo.png and b/logo.png differ