1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-12-22 13:17:06 +00:00

Functioning Discord Rich Presence!

This commit is contained in:
EliteMasterEric 2024-09-19 10:03:16 -04:00
parent a128afedf7
commit 8fd84f9d13
11 changed files with 331 additions and 171 deletions

View file

@ -106,6 +106,11 @@
"target": "hl",
"args": ["-debug"]
},
{
"label": "Windows / Debug (Discord)",
"target": "windows",
"args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS", "-DFEATURE_DISCORD_RPC"]
},
{
"label": "Windows / Debug (FlxAnimate Test)",
"target": "windows",

View file

@ -7,13 +7,6 @@
"ref": "a1eab7b9bf507b87200a3341719054fe427f3b15",
"url": "https://github.com/FunkinCrew/FlxPartialSound.git"
},
{
"name": "discord_rpc",
"type": "git",
"dir": null,
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
"url": "https://github.com/FunkinCrew/linc_discord-rpc"
},
{
"name": "flixel",
"type": "git",
@ -115,6 +108,13 @@
"ref": "147294123f983e35f50a966741474438069a7a8f",
"url": "https://github.com/FunkinCrew/hxcpp-debugger"
},
{
"name": "hxdiscord_rpc",
"type": "git",
"dir": null,
"ref": "main",
"url": "https://github.com/MAJigsaw77/hxdiscord_rpc"
},
{
"name": "hxjsonast",
"type": "git",
@ -155,6 +155,13 @@
"ref": "fe3368f611a84a19afc03011353945ae4da8fffd",
"url": "https://github.com/FunkinCrew/lime"
},
{
"name": "linc_discord-rpc",
"type": "git",
"dir": null,
"ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5",
"url": "https://github.com/FunkinCrew/haxe-discord-rpc"
},
{
"name": "mconsole",
"type": "git",

View file

@ -457,10 +457,9 @@ class Project extends HXProject {
// Should default to true on workspace builds and false on release builds.
REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop());
// Should be true on release, non-tester builds.
// Should be true on desktop, release, non-tester builds.
// We don't want testers to accidentally leak songs to their Discord friends!
// TODO: Re-enable this.
FEATURE_DISCORD_RPC.apply(this, false && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this));
// Should be true only on web builds.
// Audio context issues only exist there.
@ -618,7 +617,7 @@ class Project extends HXProject {
}
if (FEATURE_DISCORD_RPC.isEnabled(this)) {
addHaxelib('discord_rpc'); // Discord API
addHaxelib('hxdiscord_rpc'); // Discord API
}
if (FEATURE_NEWGROUNDS.isEnabled(this)) {

View file

@ -1,42 +1,42 @@
package funkin;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.ui.debug.charting.ChartEditorState;
import funkin.ui.transition.LoadingState;
import flixel.FlxState;
import flixel.addons.transition.FlxTransitionableState;
import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond;
import flixel.addons.transition.TransitionData;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.FlxSprite;
import flixel.system.debug.log.LogStyle;
import flixel.util.FlxColor;
import funkin.util.macro.MacroUtil;
import funkin.util.WindowUtil;
import funkin.play.PlayStatePlaylist;
import openfl.display.BitmapData;
import funkin.data.story.level.LevelRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.dialogue.conversation.ConversationRegistry;
import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry;
import funkin.data.dialogue.speaker.SpeakerRegistry;
import funkin.data.freeplay.album.AlbumRegistry;
import funkin.data.freeplay.player.PlayerRegistry;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.notestyle.NoteStyleRegistry;
import funkin.data.song.SongRegistry;
import funkin.data.event.SongEventRegistry;
import funkin.data.stage.StageRegistry;
import funkin.data.story.level.LevelRegistry;
import funkin.modding.module.ModuleHandler;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.modding.module.ModuleHandler;
import funkin.play.PlayStatePlaylist;
import funkin.ui.debug.charting.ChartEditorState;
import funkin.ui.title.TitleState;
import funkin.ui.transition.LoadingState;
import funkin.util.CLIUtil;
import funkin.util.CLIUtil.CLIParams;
import funkin.util.macro.MacroUtil;
import funkin.util.TimerUtil;
import funkin.util.TrackerUtil;
import funkin.util.WindowUtil;
import openfl.display.BitmapData;
#if FEATURE_DISCORD_RPC
import Discord.DiscordClient;
import funkin.api.discord.DiscordClient;
#end
/**
@ -125,10 +125,10 @@ class InitState extends FlxState
// DISCORD API SETUP
//
#if FEATURE_DISCORD_RPC
DiscordClient.initialize();
DiscordClient.instance.init();
Application.current.onExit.add(function(exitCode) {
DiscordClient.shutdown();
lime.app.Application.current.onExit.add(function(exitCode) {
DiscordClient.instance.shutdown();
});
#end

View file

@ -1,91 +0,0 @@
package funkin.api.discord;
import Sys.sleep;
#if FEATURE_DISCORD_RPC
import discord_rpc.DiscordRpc;
#end
class DiscordClient
{
#if FEATURE_DISCORD_RPC
public function new()
{
trace("Discord Client starting...");
DiscordRpc.start(
{
clientID: "814588678700924999",
onReady: onReady,
onError: onError,
onDisconnected: onDisconnected
});
trace("Discord Client started.");
while (true)
{
DiscordRpc.process();
sleep(2);
// trace("Discord Client Update");
}
DiscordRpc.shutdown();
}
public static function shutdown()
{
DiscordRpc.shutdown();
}
static function onReady()
{
DiscordRpc.presence(
{
details: "In the Menus",
state: null,
largeImageKey: 'icon',
largeImageText: "Friday Night Funkin'"
});
}
static function onError(_code:Int, _message:String)
{
trace('Error! $_code : $_message');
}
static function onDisconnected(_code:Int, _message:String)
{
trace('Disconnected! $_code : $_message');
}
public static function initialize()
{
var DiscordDaemon = sys.thread.Thread.create(() -> {
new DiscordClient();
});
trace("Discord Client initialized");
}
public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float)
{
var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0;
if (endTimestamp > 0)
{
endTimestamp = startTimestamp + endTimestamp;
}
DiscordRpc.presence(
{
details: details,
state: state,
largeImageKey: 'icon',
largeImageText: "Friday Night Funkin'",
smallImageKey: smallImageKey,
// Obtained times are in milliseconds so they are divided so Discord can use it
startTimestamp: Std.int(startTimestamp / 1000),
endTimestamp: Std.int(endTimestamp / 1000)
});
// trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp');
}
#end
}

View file

@ -0,0 +1,188 @@
package funkin.api.discord;
#if FEATURE_DISCORD_RPC
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types;
import sys.thread.Thread;
class DiscordClient
{
static final CLIENT_ID:String = "814588678700924999";
public static var instance(get, never):DiscordClient;
static var _instance:Null<DiscordClient> = null;
static function get_instance():DiscordClient
{
if (DiscordClient._instance == null) _instance = new DiscordClient();
if (DiscordClient._instance == null) throw "Could not initialize singleton DiscordClient!";
return DiscordClient._instance;
}
var handlers:DiscordEventHandlers;
private function new()
{
trace('[DISCORD] Initializing event handlers...');
handlers = DiscordEventHandlers.create();
handlers.ready = cpp.Function.fromStaticFunction(onReady);
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
handlers.errored = cpp.Function.fromStaticFunction(onError);
}
public function init():Void
{
trace('[DISCORD] Initializing connection...');
// Discord.initialize(CLIENT_ID, handlers, true, null);
Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null);
createDaemon();
}
var daemon:Thread = null;
function createDaemon():Void
{
daemon = Thread.create(doDaemonWork);
}
function doDaemonWork():Void
{
while (true)
{
trace('[DISCORD] Performing client update...');
#if DISCORD_DISABLE_IO_THREAD
Discord.updateConnection();
#end
Discord.runCallbacks();
Sys.sleep(2);
}
}
public function shutdown():Void
{
trace('[DISCORD] Shutting down...');
Discord.shutdown();
}
public function setPresence(params:DiscordClientPresenceParams):Void
{
trace('[DISCORD] Updating presence... (${params})');
Discord.updatePresence(buildPresence(params));
}
function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence
{
var presence = DiscordRichPresence.create();
// Presence should always be playing the game.
presence.type = DiscordActivityType_Playing;
// Text when hovering over the large image. We just leave this as the game name.
presence.largeImageText = "Friday Night Funkin'";
// State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]"
presence.state = cast(params.state, Null<String>);
// Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed").
presence.details = cast(params.details, Null<String>);
// The large image displaying what the user is doing.
// This should probably be album art.
// IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like.
// presence.largeImageKey = "icon";
presence.largeImageKey = "https://f4.bcbits.com/img/a3122193953_16.jpg";
// The small inset image for what the user is doing.
// This can be the opponent's health icon?
presence.smallImageKey = cast(params.smallImageKey, Null<String>);
// Start timestamp, used to power elapsed/remaining data
// presence.startTimestamp
// End timestamp, used to power elapsed/remaining data
// presence.endTimestamp
return presence;
}
// TODO: WHAT THE FUCK get this pointer bullfuckery out of here
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
{
trace('[DISCORD] Client has connected!');
final username:String = request[0].username;
final globalName:String = request[0].username;
final discriminator:Int = Std.parseInt(request[0].discriminator);
if (discriminator != 0)
{
trace('[DISCORD] User: ${username}#${discriminator} (${globalName})');
}
else
{
trace('[DISCORD] User: @${username} (${globalName})');
}
}
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
{
trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"');
}
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
{
trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"');
}
// public var type(get, set):DiscordActivityType;
// public var state(get, set):String;
// public var details(get, set):String;
// public var startTimestamp(get, set):Int;
// public var endTimestamp(get, set):Int;
// public var largeImageKey(get, set):String;
// public var largeImageText(get, set):String;
// public var smallImageKey(get, set):String;
// public var smallImageText(get, set):String;
//
//
// public var partyId(get, set)
// public var partySize(get, set)
// public var partyMax(get, set)
// public var partyPrivacy(get, set)
//
// public var buttons(get, set)
//
// public var matchSecret(get, set)
// public var joinSecret(get, set)
// public var spectateSecret(get, set)
}
typedef DiscordClientPresenceParams =
{
/**
* The first row of text below the game title.
*/
var state:String;
/**
* The second row of text below the game title.
* Use `null` to display no text.
*/
var details:Null<String>;
/**
* A large, 4-row high image to the left of the content.
*/
var ?largeImageKey:String;
/**
* A small, inset image to the bottom right of `largeImageKey`.
*/
var ?smallImageKey:String;
}
#end

View file

@ -15,8 +15,8 @@ import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.ui.FlxBar;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import flixel.util.FlxStringUtil;
import flixel.util.FlxTimer;
import funkin.api.newgrounds.NGio;
import funkin.audio.FunkinSound;
import funkin.audio.VoicesGroup;
@ -44,12 +44,12 @@ import funkin.play.cutscene.dialogue.Conversation;
import funkin.play.cutscene.VanillaCutscenes;
import funkin.play.cutscene.VideoCutscene;
import funkin.play.notes.NoteDirection;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.notes.NoteSplash;
import funkin.play.notes.NoteSprite;
import funkin.play.notes.notestyle.NoteStyle;
import funkin.play.notes.Strumline;
import funkin.play.notes.SustainTrail;
import funkin.play.notes.notekind.NoteKindManager;
import funkin.play.scoring.Scoring;
import funkin.play.song.Song;
import funkin.play.stage.Stage;
@ -68,7 +68,7 @@ import openfl.display.BitmapData;
import openfl.geom.Rectangle;
import openfl.Lib;
#if FEATURE_DISCORD_RPC
import Discord.DiscordClient;
import funkin.api.discord.DiscordClient;
#end
/**
@ -447,10 +447,10 @@ class PlayState extends MusicBeatSubState
#if FEATURE_DISCORD_RPC
// Discord RPC variables
var storyDifficultyText:String = '';
var iconRPC:String = '';
var detailsText:String = '';
var detailsPausedText:String = '';
var discordRPCDifficulty:String = '';
var discordRPCIcon:String = '';
var discordRPCDetailsText:String = '';
var discordRPCDetailsPausedText:String = '';
#end
/**
@ -984,7 +984,12 @@ class PlayState extends MusicBeatSubState
}
#if FEATURE_DISCORD_RPC
DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} (${discordRPCDifficulty})',
details: discordRPCDetailsPausedText,
smallImageKey: discordRPCIcon
});
#end
}
}
@ -1073,8 +1078,12 @@ class PlayState extends MusicBeatSubState
}
#if FEATURE_DISCORD_RPC
// Game Over doesn't get his own variable because it's only used here
DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: 'Game Over - ${discordRPCDetailsText}',
smallImageKey: discordRPCIcon
});
#end
}
else if (isPlayerDying)
@ -1285,14 +1294,25 @@ class PlayState extends MusicBeatSubState
Countdown.resumeCountdown();
#if FEATURE_DISCORD_RPC
if (startTimer.finished)
if (Conductor.instance.songPosition > 0)
{
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true,
currentSongLengthMs - Conductor.instance.songPosition);
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
// currentSongLengthMs - Conductor.instance.songPosition);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
}
else
{
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
}
#end
@ -1318,16 +1338,28 @@ class PlayState extends MusicBeatSubState
#end
#if FEATURE_DISCORD_RPC
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause)
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
{
if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song
+ ' ('
+ storyDifficultyText
+ ')', iconRPC, true,
currentSongLengthMs
- Conductor.instance.songPosition);
if (Conductor.instance.songPosition > 0.0)
{
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
}
else
DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
{
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true,
// currentSongLengthMs - Conductor.instance.songPosition);
}
}
#end
@ -1344,8 +1376,15 @@ class PlayState extends MusicBeatSubState
#end
#if FEATURE_DISCORD_RPC
if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText,
currentSong.song + ' (' + storyDifficultyText + ')', iconRPC);
if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause)
{
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
}
#end
super.onFocusLost();
@ -1641,6 +1680,10 @@ class PlayState extends MusicBeatSubState
iconP2.zIndex = 850;
add(iconP2);
iconP2.cameras = [camHUD];
#if FEATURE_DISCORD_RPC
discordRPCIcon = currentCharacterData.opponent;
#end
}
//
@ -1758,26 +1801,19 @@ class PlayState extends MusicBeatSubState
function initDiscord():Void
{
#if FEATURE_DISCORD_RPC
storyDifficultyText = difficultyString();
iconRPC = currentSong.player2;
discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase();
// To avoid having duplicate images in Discord assets
switch (iconRPC)
{
case 'senpai-angry':
iconRPC = 'senpai';
case 'monster-christmas':
iconRPC = 'monster';
case 'mom-car':
iconRPC = 'mom';
}
// String that contains the mode defined here so it isn't necessary to call changePresence for each mode
detailsText = isStoryMode ? 'Story Mode: Week $storyWeek' : 'Freeplay';
detailsPausedText = 'Paused - $detailsText';
// Determine the details strings once and reuse them.
discordRPCDetailsText = PlayStatePlaylist.isStoryMode ? 'Story Mode: Week ${PlayStatePlaylist.campaignId}' : 'Freeplay';
discordRPCDetailsPausedText = 'Paused - $discordRPCDetailsText';
// Updating Discord Rich Presence.
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} [${discordRPCDifficulty}]',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
#end
}
@ -1972,7 +2008,13 @@ class PlayState extends MusicBeatSubState
#if FEATURE_DISCORD_RPC
// Updating Discord Rich Presence (with Time Left)
DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs);
DiscordClient.instance.setPresence(
{
state: '${currentChart.songName} (${discordRPCDifficulty})',
details: discordRPCDetailsText,
smallImageKey: discordRPCIcon
});
// DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, currentSongLengthMs);
#end
if (startTimestamp > 0)

View file

@ -323,6 +323,11 @@ class BaseCharacter extends Bopper
this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]);
}
public function getHealthIconId():String
{
return _data?.healthIcon?.id ?? Constants.DEFAULT_HEALTH_ICON;
}
public function initHealthIcon(isOpponent:Bool):Void
{
if (!isOpponent)
@ -332,7 +337,7 @@ class BaseCharacter extends Bopper
trace('[WARN] Player 1 health icon not found!');
return;
}
PlayState.instance.iconP1.configure(_data.healthIcon);
PlayState.instance.iconP1.configure(_data?.healthIcon);
PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way.
}
else
@ -342,7 +347,7 @@ class BaseCharacter extends Bopper
trace('[WARN] Player 2 health icon not found!');
return;
}
PlayState.instance.iconP2.configure(_data.healthIcon);
PlayState.instance.iconP2.configure(_data?.healthIcon);
}
}

View file

@ -55,6 +55,9 @@ import lime.utils.Assets;
import openfl.display.BlendMode;
import funkin.data.freeplay.style.FreeplayStyleRegistry;
import funkin.data.song.SongData.SongMusicData;
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;
#end
/**
* Parameters used to initialize the FreeplayState.
@ -313,7 +316,7 @@ class FreeplayState extends MusicBeatSubState
#if FEATURE_DISCORD_RPC
// Updating Discord Rich Presence
DiscordClient.changePresence('In the Menus', null);
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
#end
var isDebug:Bool = false;

View file

@ -28,7 +28,7 @@ import funkin.ui.story.StoryMenuState;
import funkin.ui.Prompt;
import funkin.util.WindowUtil;
#if FEATURE_DISCORD_RPC
import Discord.DiscordClient;
import funkin.api.discord.DiscordClient;
#end
#if newgrounds
import funkin.ui.NgPrompt;
@ -55,8 +55,7 @@ class MainMenuState extends MusicBeatState
override function create():Void
{
#if FEATURE_DISCORD_RPC
// Updating Discord Rich Presence
DiscordClient.changePresence("In the Menus", null);
DiscordClient.instance.setPresence({state: "In the Menus", details: null});
#end
FlxG.cameras.reset(new FunkinCamera('mainMenu'));

View file

@ -24,6 +24,9 @@ import funkin.ui.transition.LoadingState;
import funkin.ui.transition.StickerSubState;
import funkin.util.MathUtil;
import openfl.utils.Assets;
#if FEATURE_DISCORD_RPC
import funkin.api.discord.DiscordClient;
#end
class StoryMenuState extends MusicBeatState
{
@ -218,7 +221,7 @@ class StoryMenuState extends MusicBeatState
#if FEATURE_DISCORD_RPC
// Updating Discord Rich Presence
DiscordClient.changePresence('In the Menus', null);
DiscordClient.instance.setPresence({state: 'In the Menus', details: null});
#end
}