This commit is contained in:
lunarcleint
2024-03-24 13:22:56 -07:00
32 changed files with 662 additions and 180 deletions
+1 -2
View File
@@ -85,6 +85,5 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish
- Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support
- Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support
- Credits to Smokey555 for the backup Animate Atlas to spritesheet code
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) for video cutscene/mp4 support
- Credits to Aidan63 for [discord-rpc](https://github.com/Aidan63/linc_discord-rpc) for discord rpc integration
- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration)
</details>
+50
View File
@@ -0,0 +1,50 @@
import funkin.backend.utils.DiscordUtil;
function onGameOver() {
DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")");
}
function onDiscordPresenceUpdate(e) {
var data = e.presence;
if(data.button1Label == null)
data.button1Label = "Codename Engine Discord";
if(data.button1Url == null)
data.button1Url = "https://discord.gg/2NTCdsQvx4";
}
function onPlayStateUpdate() {
DiscordUtil.changeSongPresence(
PlayState.instance.detailsText,
(PlayState.instance.paused ? "Paused - " : "") + PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")",
PlayState.instance.inst,
PlayState.instance.getIconRPC()
);
}
function onMenuLoaded(name:String) {
// Name is either "Main Menu", "Freeplay", "Title Screen", "Options Menu", "Credits Menu", "Beta Warning", "Update Available Screen", "Update Screen"
DiscordUtil.changePresenceSince("In the Menus", null);
}
function onEditorTreeLoaded(name:String) {
switch(name) {
case "Character Editor":
DiscordUtil.changePresenceSince("Choosing a Character", null);
case "Chart Editor":
DiscordUtil.changePresenceSince("Choosing a Chart", null);
case "Stage Editor":
DiscordUtil.changePresenceSince("Choosing a Stage", null);
}
}
function onEditorLoaded(name:String, editingThing:String) {
switch(name) {
case "Character Editor":
DiscordUtil.changePresenceSince("Editing a Character", editingThing);
case "Chart Editor":
DiscordUtil.changePresenceSince("Editing a Chart", editingThing);
case "Stage Editor":
DiscordUtil.changePresenceSince("Editing a Stage", editingThing);
}
}
+3 -1
View File
@@ -75,10 +75,12 @@ class Update {
// vswhere.exe its used to find any visual studio related installations on the system, including full visual studio ide installations, visual studio build tools installations, and other related components - Nex
if (Compiler.getBuildTarget().toLowerCase() == "windows" && new Process('"C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -property catalog_productDisplayVersion').exitCode(true) == 1) {
prettyPrint("Installing Microsoft Visual Studio Community (Dependency)");
// thanks to @crowplexus for these two lines! - Nex
Sys.command("curl -# -O https://download.visualstudio.microsoft.com/download/pr/3105fcfe-e771-41d6-9a1c-fc971e7d03a7/8eb13958dc429a6e6f7e0d6704d43a55f18d02a253608351b6bf6723ffdaf24e/vs_Community.exe");
Sys.command("vs_Community.exe --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.19041 -p");
FileSystem.deleteFile("vs_Community.exe");
FileSystem.deleteFile("vs_Community.exe");
prettyPrint('If it didn\'t say it before: Because of this component if you want to compile you have to restart the device.');
Sys.print("Do you wish to do it now [y/n]? ");
if(Sys.stdin().readLine().toLowerCase() == "y") Sys.command("shutdown /r /t 0 /f");
+6 -5
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<libraries>
<!-- OpenFL & Lime (Required for Flixel) -->
<lib name="openfl" version="9.2.2"/>
<lib name="lime" version="8.1.2"/>
<lib name="openfl" version="9.2.2" />
<lib name="lime" version="8.1.2" />
<!-- Flixel -->
<git name="flixel" url="https://github.com/FNF-CNE-Devs/flixel" />
@@ -10,13 +10,14 @@
<!-- Other Libraries -->
<git name="hscript-improved" url="https://github.com/FNF-CNE-Devs/hscript-improved" ref="custom-classes" />
<git name="discord-rpc" url="https://github.com/Aidan63/linc_discord-rpc" />
<git name="flxanimate" url="https://github.com/FNF-CNE-Devs/flxanimate" />
<git name="hxdiscord_rpc" url="https://github.com/NeeEoo/hxdiscord_rpc" /> <!-- Since original repo wants non fork of discord-rpc -->
<!-- <lib name="hxdiscord_rpc" /> -->
<lib name="hxvlc" />
<!-- Documentation and other features -->
<lib name="away3d" />
<git name="away3d" url="https://github.com/FNF-CNE-Devs/away3d" />
<!-- <lib name="dox" /> -->
<lib name="format" />
<lib name="markdown" />
</libraries>
</libraries>
+8 -3
View File
@@ -161,6 +161,13 @@
<!-- _______________________________ Libraries ______________________________ -->
<!-- KEEP BOTH OF THESE AS THERE ARE SOME PARTICULAR CASES! -->
<define name="disable-version-check"/>
<haxedef name="disable-version-check"/>
<haxedef name="no-deprecation-warnings"/>
<haxedef name="DISCORD_DISABLE_IO_THREAD" if="DISCORD_RPC"/>
<haxelib name="flixel" />
<haxelib name="flixel-addons" />
@@ -170,11 +177,9 @@
<haxelib name="flxanimate" />
<haxelib name="hscript-improved" />
<haxelib name="discord-rpc" if="DISCORD_RPC"/>
<haxelib name="hxdiscord_rpc" if="DISCORD_RPC"/>
<haxelib name="markdown" />
<haxedef name="no-deprecation-warnings"/>
<!-- _________________________________ Additional stuff _______________________________ -->
<section if="COMPILE_ALL_CLASSES">
@@ -79,12 +79,14 @@ class GlobalScript {
}
public static function event<T:CancellableEvent>(name:String, event:T):T {
if (scripts != null) scripts.call(name, [event]);
if (scripts != null)
scripts.event(name, event);
return event;
}
public static function call(name:String, ?args:Array<Dynamic>) {
if (scripts != null) scripts.call(name, args);
if (scripts != null)
scripts.call(name, args);
}
public static function onModSwitch(newMod:String) {
call("destroy");
@@ -93,7 +95,8 @@ class GlobalScript {
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
var path = Paths.script('data/global/LIB_$i');
var script = Script.create(path);
if (script is DummyScript) continue;
if (script is DummyScript)
continue;
script.remappedNames.set(script.fileName, '$i:${script.fileName}');
scripts.add(script);
script.load();
@@ -1,12 +1,8 @@
package funkin.backend.scripting.events;
#if DISCORD_RPC
import discord_rpc.DiscordRpc.DiscordPresenceOptions;
#end
final class DiscordPresenceUpdateEvent extends CancellableEvent {
/**
* Object containing all of the data for the presence. Can be altered.
*/
public var presenceData:#if DISCORD_RPC DiscordPresenceOptions #else Dynamic #end;
public var presence:funkin.backend.utils.DiscordUtil.DPresence;
}
-1
View File
@@ -120,7 +120,6 @@ class Main extends Sprite
ShaderResizeFix.init();
Logs.init();
Paths.init();
DiscordUtil.init();
#if GLOBAL_SCRIPT
funkin.backend.scripting.GlobalScript.init();
#end
+1 -1
View File
@@ -55,7 +55,7 @@ class MainState extends FlxState {
Main.refreshAssets();
ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder);
DiscordUtil.reloadJsonData();
DiscordUtil.init();
EventsData.reloadEvents();
TitleState.initialized = false;
+5 -59
View File
@@ -3,7 +3,6 @@ package funkin.backend.system.github;
#if GITHUB_API
import haxe.Json;
import haxe.Exception;
import haxe.Http;
#end
// TODO: Document further and perhaps make this a Haxelib.
@@ -18,7 +17,7 @@ class GitHub {
public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array<GitHubRelease> {
#if GITHUB_API
try {
var data = Json.parse(__requestOnGitHubServers('https://api.github.com/repos/${user}/${repository}/releases'));
var data = Json.parse(HttpUtil.requestText('https://api.github.com/repos/${user}/${repository}/releases'));
if (!(data is Array))
throw __parseGitHubException(data);
@@ -41,7 +40,7 @@ class GitHub {
public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array<GitHubContributor> {
#if GITHUB_API
try {
var data = Json.parse(__requestOnGitHubServers('https://api.github.com/repos/${user}/${repository}/contributors'));
var data = Json.parse(HttpUtil.requestText('https://api.github.com/repos/${user}/${repository}/contributors'));
if (!(data is Array))
throw __parseGitHubException(data);
@@ -63,7 +62,7 @@ class GitHub {
public static function getOrganization(org:String, ?onError:Exception->Void):GitHubOrganization {
#if GITHUB_API
try {
var data = Json.parse(__requestOnGitHubServers('https://api.github.com/orgs/$org'));
var data = Json.parse(HttpUtil.requestText('https://api.github.com/orgs/$org'));
if (Reflect.hasField(data, "documentation_url"))
throw __parseGitHubException(data);
@@ -86,7 +85,7 @@ class GitHub {
public static function getOrganizationMembers(org:String, ?onError:Exception->Void):Array<GitHubContributor> {
#if GITHUB_API
try {
var data = Json.parse(__requestOnGitHubServers('https://api.github.com/orgs/$org/members'));
var data = Json.parse(HttpUtil.requestText('https://api.github.com/orgs/$org/members'));
if (Reflect.hasField(data, "documentation_url"))
throw __parseGitHubException(data);
@@ -111,7 +110,7 @@ class GitHub {
try {
var url = 'https://api.github.com/users/$user';
var data = Json.parse(__requestOnGitHubServers(url));
var data = Json.parse(HttpUtil.requestText(url));
if (Reflect.hasField(data, "documentation_url"))
throw __parseGitHubException(data);
@@ -134,59 +133,6 @@ class GitHub {
public static inline function filterReleases(releases:Array<GitHubRelease>, keepPrereleases:Bool = true, keepDrafts:Bool = false)
return #if GITHUB_API [for(release in releases) if (release != null && (!release.prerelease || (release.prerelease && keepPrereleases)) && (!release.draft || (release.draft && keepDrafts))) release] #else releases #end;
public static function __requestOnGitHubServers(url:String) {
var r = null;
#if GITHUB_API
var h = new Http(url);
h.setHeader("User-Agent", "request");
h.onStatus = function(s) {
if(isRedirect(s))
r = __requestOnGitHubServers(h.responseHeaders.get("Location"));
};
h.onData = function(d) {
if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
h.request(false);
#end
return r;
}
public static function __requestBytesOnGitHubServers(url:String) {
var r = null;
#if GITHUB_API
var h = new Http(url);
h.setHeader("User-Agent", "request");
h.onStatus = function(s) {
if(isRedirect(s))
r = __requestBytesOnGitHubServers(h.responseHeaders.get("Location"));
};
h.onBytes = function(d) {
if(r == null) r = d;
}
h.onError = function(e) {
throw e;
}
h.request(false);
#end
return r;
}
private static function isRedirect(status:Int):Bool {
switch (status) {
// 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex
case 301 | 302 | 307 | 308 :
trace("Redirected with status code: " + status);
return true;
}
return false;
}
private static function __parseGitHubException(obj:Dynamic):GitHubException {
#if GITHUB_API
var msg:String = "(No message)";
@@ -37,4 +37,21 @@ class Utils {
return expr;
#end
}
public static macro function safeSet(variable:Null<Expr>, value:Null<Expr>):Null<Expr> {
#if macro
return macro if (${value} != null) ${variable} = ${value};
#end
}
public static macro function safeSetWrapper(variable:Null<Expr>, value:Null<Expr>, wrapper:Null<Expr>):Null<Expr> {
#if macro
return macro if (${value} != null) ${variable} = ${wrapper}(${value});
#end
}
public static macro function safeReflection(variable:Null<Expr>, value:Null<Expr>, field:Null<Expr>):Null<Expr> {
#if macro
return macro if (Reflect.hasField(${value}, ${field})) ${variable} = Reflect.field(${value}, ${field});
#end
}
}
@@ -92,6 +92,8 @@ class UpdateAvailableScreen extends MusicBeatState {
add(skipButton);
oldPos = FlxG.mouse.getScreenPosition();
DiscordUtil.call("onMenuLoaded", ["Update Available Screen"]);
}
var destY:Float = 0;
@@ -61,6 +61,8 @@ class UpdateScreen extends MusicBeatState {
updater.execute();
FlxG.camera.addShader(rainbowShader = new CustomShader("engine/updaterShader"));
DiscordUtil.call("onMenuLoaded", ["Update Screen"]);
}
+396 -65
View File
@@ -1,62 +1,122 @@
package funkin.backend.utils;
import funkin.backend.scripting.events.DiscordPresenceUpdateEvent;
import funkin.backend.scripting.events.CancellableEvent;
import funkin.backend.scripting.*; // lazy
import flixel.util.typeLimit.OneOfTwo;
import openfl.display.BitmapData;
import funkin.backend.system.macros.Utils;
import haxe.Json;
import flixel.sound.FlxSound;
#if DISCORD_RPC
import discord_rpc.DiscordRpc;
import hxdiscord_rpc.Discord;
import hxdiscord_rpc.Types;
import sys.thread.Thread;
import Sys;
import lime.app.Application;
#end
class DiscordUtil {
public static var currentID:String = null;
class DiscordUtil
{
public static var currentID(default, set):String = null;
public static var discordThread:#if DISCORD_RPC Thread #else Dynamic #end = null;
public static var ready:Bool = false;
public static var data:DiscordJson = null;
public static var initialized:Bool = false;
private static var stopThread:Bool = false;
public static function init() {
public static var user:#if DISCORD_RPC DUser #else Dynamic #end = null;
public static var lastPresence:#if DISCORD_RPC DPresence #else Dynamic #end = null;
public static var config:#if DISCORD_RPC DiscordJson #else Dynamic #end = null;
public static var script:Script;
// Constants
public static var REPLY_NO:Int = Discord.REPLY_NO;
public static var REPLY_YES:Int = Discord.REPLY_YES;
public static var REPLY_IGNORE:Int = Discord.REPLY_IGNORE;
public static var PARTY_PRIVATE:Int = Discord.PARTY_PRIVATE;
public static var PARTY_PUBLIC:Int = Discord.PARTY_PUBLIC;
public static function init()
{
#if DISCORD_RPC
reloadJsonData();
discordThread = Thread.create(function() {
if (initialized)
return;
initialized = true;
discordThread = Thread.create(function()
{
while (true)
{
while(!ready) {
Sys.sleep(1/60);
while (!stopThread)
{
#if DISCORD_DISABLE_IO_THREAD
Discord.UpdateConnection();
#end
Discord.RunCallbacks();
Sys.sleep(2);
}
trace("Processing Discord RPC...");
DiscordRpc.process();
Sys.sleep(2);
Sys.sleep(1); // to reduce cpu
}
});
Application.current.onExit.add(function(exitCode) {
shutdown();
});
Application.current.onExit.add(function(_) shutdown());
#end
}
public static function reloadJsonData() {
public static function reloadJsonData()
{
#if DISCORD_RPC
data = {};
var jsonPath = Paths.json("config/discord");
if (Assets.exists(jsonPath)) {
try {
data = Json.parse(Assets.getText(jsonPath));
} catch(e) {
if (Assets.exists(jsonPath))
{
try
config = Json.parse(Assets.getText(jsonPath))
catch (e)
Logs.trace('Couldn\'t load Discord RPC configuration: ${e.toString()}', ERROR);
}
}
data.setFieldDefault("clientID", "1027994136193810442");
data.setFieldDefault("logoKey", "icon");
data.setFieldDefault("logoText", Application.current.meta.get('title'));
else
{
config = {};
}
changeClientID(data.clientID);
config.logoKey = config.logoKey.getDefault("icon");
config.logoText = config.logoText.getDefault(Application.current.meta.get('title'));
config.clientID = config.clientID.getDefault("1027994136193810442");
currentID = config.clientID;
#end
}
public static function changePresence(details:String, state:String, ?smallImageKey : String) {
public static function event<T:CancellableEvent>(name:String, event:T):T
{
if (script != null)
script.call(name, [event]);
return event;
}
public static function call(name:String, ?args:Array<Dynamic>)
{
if (script != null)
script.call(name, args);
}
public static function loadScript()
{
if (script != null)
{
call("destroy");
script = FlxDestroyUtil.destroy(script);
}
script = Script.create(Paths.script('data/discord'));
// script.setParent(DiscordUtil);
script.load();
}
public static function changePresence(details:String, state:String, ?smallImageKey:String)
{
#if DISCORD_RPC
changePresenceAdvanced({
state: state,
@@ -66,12 +126,14 @@ class DiscordUtil {
#end
}
public static function changeSongPresence(details:String, state:String, audio:FlxSound, ?smallImageKey : String) {
public static function changeSongPresence(details:String, state:String, audio:FlxSound, ?smallImageKey:String)
{
#if DISCORD_RPC
var start:Float = 0;
var end:Float = 0;
if (audio != null && audio.playing) {
if (audio != null && audio.playing)
{
start = Date.now().getTime();
end = start + (audio.length - audio.time);
}
@@ -86,66 +148,335 @@ class DiscordUtil {
#end
}
public static function changePresenceAdvanced(data:#if DISCORD_RPC DiscordPresenceOptions #else Dynamic #end) {
public static function changePresenceSince(details:String, state:String, ?smallImageKey:String, ?time:Float)
{
#if DISCORD_RPC
if (data == null) return;
if(time == null)
time = Date.now().getTime();
if (data.largeImageKey == null)
data.largeImageKey = DiscordUtil.data.logoKey;
if (data.largeImageText == null)
data.largeImageText = DiscordUtil.data.logoText;
#if GLOBAL_SCRIPT
var event = funkin.backend.scripting.GlobalScript.event("onDiscordPresenceUpdate", EventManager.get(DiscordPresenceUpdateEvent).recycle(data));
if (event.cancelled) return;
#end
DiscordRpc.presence(data);
changePresenceAdvanced({
state: state,
details: details,
smallImageKey: smallImageKey,
startTimestamp: Std.int(time / 1000)
});
#end
}
public static function changeClientID(id:String) {
@:noCompletion public static function fixString(str:String)
{
return new cpp.ConstCharStar(cast(str, String));
}
@:noCompletion public static function toString(str:cpp.ConstCharStar)
{
return cast(str, String);
}
public static function changePresenceAdvanced(data:DPresence)
{
#if DISCORD_RPC
if (currentID != null) {
DiscordRpc.shutdown();
if (data == null)
return;
// copy last presence
if (data.largeImageKey == null)
data.largeImageKey = config.logoKey;
if (data.largeImageText == null)
data.largeImageText = config.logoText;
var evt = EventManager.get(DiscordPresenceUpdateEvent).recycle(data);
#if GLOBAL_SCRIPT
// kept for "backwards compat"
funkin.backend.scripting.GlobalScript.event("onDiscordPresenceUpdate", evt);
#end
event("onDiscordPresenceUpdate", evt);
if (evt.cancelled)
return;
data = evt.presence;
lastPresence = data;
var dp:DiscordRichPresence = DiscordRichPresence.create();
// TODO: make this use a reflection-like macro
Utils.safeSetWrapper(dp.state, data.state, fixString);
Utils.safeSetWrapper(dp.details, data.details, fixString);
Utils.safeSet(dp.startTimestamp, data.startTimestamp);
Utils.safeSet(dp.endTimestamp, data.endTimestamp);
Utils.safeSetWrapper(dp.largeImageKey, data.largeImageKey, fixString);
Utils.safeSetWrapper(dp.largeImageText, data.largeImageText, fixString);
Utils.safeSetWrapper(dp.smallImageKey, data.smallImageKey, fixString);
Utils.safeSetWrapper(dp.smallImageText, data.smallImageText, fixString);
Utils.safeSetWrapper(dp.partyId, data.partyId, fixString);
Utils.safeSet(dp.partySize, data.partySize);
Utils.safeSet(dp.partyMax, data.partyMax);
Utils.safeSet(dp.partyPrivacy, data.partyPrivacy);
Utils.safeSetWrapper(dp.matchSecret, data.matchSecret, fixString);
Utils.safeSetWrapper(dp.joinSecret, data.joinSecret, fixString);
Utils.safeSetWrapper(dp.spectateSecret, data.spectateSecret, fixString);
Utils.safeSet(dp.instance, data.instance);
if (data.matchSecret == null && data.joinSecret == null && data.spectateSecret == null)
{
Utils.safeSetWrapper(dp.button1Label, data.button1Label, fixString);
Utils.safeSetWrapper(dp.button1Url, data.button1Url, fixString);
Utils.safeSetWrapper(dp.button2Label, data.button2Label, fixString);
Utils.safeSetWrapper(dp.button2Url, data.button2Url, fixString);
}
ready = false;
DiscordRpc.start({
clientID: id,
onReady: function() {
Logs.trace('Discord RPC started');
ready = true;
},
onError: onError,
onDisconnected: onDisconnected
});
currentID = id;
Discord.UpdatePresence(cpp.RawConstPointer.addressOf(dp));
#end
}
public static function shutdown() {
public static function clearPresence() {
#if DISCORD_RPC
DiscordRpc.shutdown();
Discord.ClearPresence();
#end
}
private static function set_currentID(id:String):String
{
if (currentID == id)
return id;
#if DISCORD_RPC
if (currentID != null)
shutdown();
var handlers:DiscordEventHandlers = DiscordEventHandlers.create();
handlers.ready = cpp.Function.fromStaticFunction(onReady);
handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected);
handlers.errored = cpp.Function.fromStaticFunction(onError);
handlers.joinGame = cpp.Function.fromStaticFunction(onJoin);
handlers.joinRequest = cpp.Function.fromStaticFunction(onJoinReq);
handlers.spectateGame = cpp.Function.fromStaticFunction(onSpectate);
Discord.Initialize(id, cpp.RawPointer.addressOf(handlers), 1, null);
stopThread = false;
loadScript();
#end
return currentID = id;
}
public static function shutdown()
{
ready = false;
stopThread = true;
#if DISCORD_RPC
Discord.Shutdown();
#end
call("destroy");
script = FlxDestroyUtil.destroy(script);
}
public static function respond(userId:String, reply:Int)
{
#if DISCORD_RPC
Discord.Respond(fixString(userId), reply);
#end
}
// HANDLERS
#if DISCORD_RPC
static function onError(_code:Int, _message:String) {
Logs.trace('Discord RPC Error: ${_message} (Code: $_code)', ERROR);
private static function onReady(request:cpp.RawConstPointer<DiscordUser>):Void
{
user = DUser.initRaw(request);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.logText("Connected to User " + user.globalName + " ("),
Logs.logText(user.handle, GRAY),
Logs.logText(")")
], INFO);
ready = true;
call("onReady", [user]);
}
static function onDisconnected(_code:Int, _message:String) {
Logs.trace('Discord RPC Disconnected: ${_message} (Code: $_code)', WARNING);
private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void
{
var finalMsg:String = cast(message, String);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.logText("Disconnected ("),
Logs.logText('$errorCode: $finalMsg', RED),
Logs.logText(")")
], INFO);
call("onReady", [errorCode, cast(finalMsg, String)]);
}
private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void
{
var finalMsg:String = cast(message, String);
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.logText('Error ($errorCode: $finalMsg)', RED)
], ERROR);
call("onError", [errorCode, cast(finalMsg, String)]);
}
private static function onJoin(joinSecret:cpp.ConstCharStar):Void
{
Logs.traceColored([Logs.logText("[Discord] ", BLUE), Logs.logText("Someone has just joined", GREEN)], INFO);
call("onJoinGame", [cast(joinSecret, String)]);
}
private static function onSpectate(spectateSecret:cpp.ConstCharStar):Void
{
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.logText("Someone started spectating your game", YELLOW)
], INFO);
call("onSpectateGame", [cast(spectateSecret, String)]);
}
private static function onJoinReq(request:cpp.RawConstPointer<DiscordUser>):Void
{
Logs.traceColored([
Logs.logText("[Discord] ", BLUE),
Logs.logText("Someone has just requested to join", YELLOW)
], WARNING);
var req:DUser = DUser.initRaw(request);
call("onJoinRequest", [req]);
}
#end
}
typedef DiscordJson = {
typedef DiscordJson =
{
var ?clientID:String;
var ?logoKey:String;
var ?logoText:String;
}
}
@:noCustomClass
final class DUser
{
/**
* The username + discriminator if they have it
**/
public var handle:String;
/**
* The user id, aka 860561967383445535
**/
public var userId:String;
/**
* The user's username
**/
public var username:String;
/**
* The #number from before discord changed to usernames only, if the user has changed to a username them its just a 0
**/
public var discriminator:Int;
/**
* The user's avatar filename
**/
public var avatar:String;
/**
* The user's display name
**/
public var globalName:String;
/**
* If the user is a bot or not
**/
public var bot:Bool;
/**
* Idk check discord docs
**/
public var flags:Int;
/**
* If the user has nitro
**/
public var premiumType:NitroType;
private function new()
{
}
public static function initRaw(req:cpp.RawConstPointer<DiscordUser>)
{
return init(cpp.ConstPointer.fromRaw(req).ptr);
}
public static function init(userData:cpp.Star<DiscordUser>)
{
var d = new DUser();
d.userId = userData.userId;
d.username = userData.username;
d.discriminator = Std.parseInt(userData.discriminator);
d.avatar = userData.avatar;
d.globalName = userData.globalName;
d.bot = userData.bot;
d.flags = userData.flags;
d.premiumType = userData.premiumType;
if (d.discriminator != 0)
d.handle = '${d.username}#${d.discriminator}';
else
d.handle = '${d.username}';
return d;
}
/**
* Calling this function gets the BitmapData of the user
**/
public function getAvatar(size:Int = 256):BitmapData
return BitmapData.fromBytes(HttpUtil.requestBytes('https://cdn.discordapp.com/avatars/$userId/$avatar.png?size=$size'));
}
enum abstract NitroType(Int) to Int from Int
{
var NONE = 0;
var NITRO_CLASSIC = 1;
var NITRO = 2;
var NITRO_BASIC = 3;
}
typedef DPresence =
{
var ?state:String; /* max 128 bytes */
var ?details:String; /* max 128 bytes */
var ?startTimestamp:OneOfTwo<Int, haxe.Int64>;
var ?endTimestamp:OneOfTwo<Int, haxe.Int64>;
var ?largeImageKey:String; /* max 32 bytes */
var ?largeImageText:String; /* max 128 bytes */
var ?smallImageKey:String; /* max 32 bytes */
var ?smallImageText:String; /* max 128 bytes */
var ?partyId:String; /* max 128 bytes */
var ?partySize:Int;
var ?partyMax:Int;
var ?partyPrivacy:Int;
var ?matchSecret:String; /* max 128 bytes */
var ?joinSecret:String; /* max 128 bytes */
var ?spectateSecret:String; /* max 128 bytes */
var ?instance:OneOfTwo<Int, cpp.Int8>;
var ?button1Label:String; /* max 32 bytes */
var ?button1Url:String; /* max 512 bytes */
var ?button2Label:String; /* max 32 bytes */
var ?button2Url:String; /* max 512 bytes */
}
typedef DEvents =
{
var ?ready:DUser->Void;
var ?disconnected:(errorCode:Int, message:String) -> Void;
var ?errored:(errorCode:Int, message:String) -> Void;
var ?joinGame:String->Void;
var ?spectateGame:String->Void;
var ?joinRequest:DUser->Void;
}
+71
View File
@@ -0,0 +1,71 @@
package funkin.backend.utils;
import haxe.Http;
class HttpUtil
{
public static var userAgent:String = "request";
public static function requestText(url:String)
{
var r = null;
var h = new Http(url);
h.setHeader("User-Agent", userAgent);
h.onStatus = function(s)
{
if (isRedirect(s))
r = requestText(h.responseHeaders.get("Location"));
};
h.onData = function(d)
{
if (r == null)
r = d;
}
h.onError = function(e)
{
throw e;
}
h.request(false);
return r;
}
public static function requestBytes(url:String)
{
var r = null;
var h = new Http(url);
h.setHeader("User-Agent", userAgent);
h.onStatus = function(s)
{
if (isRedirect(s))
r = requestBytes(h.responseHeaders.get("Location"));
};
h.onBytes = function(d)
{
if (r == null)
r = d;
}
h.onError = function(e)
{
throw e;
}
h.request(false);
return r;
}
private static function isRedirect(status:Int):Bool
{
switch (status)
{
// 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex
case 301 | 302 | 307 | 308:
Logs.traceColored([Logs.logText('[Connection Status] ', BLUE), Logs.logText('Redirected with status code: ', YELLOW), Logs.logText('$status', GREEN)], WARNING);
return true;
}
return false;
}
}
@@ -224,7 +224,7 @@ class CharacterEditor extends UIState {
FlxG.cameras.add(uiCamera);
FlxG.cameras.add(animsCamera);
character = new Character(0,0,__character, false, false);
character = new Character(0,0, __character, false, false);
character.debugMode = true;
character.cameras = [charCamera];
@@ -257,6 +257,8 @@ class CharacterEditor extends UIState {
Framerate.memoryCounter.alpha = 0.4;
Framerate.codenameBuildField.alpha = 0.4;
}
DiscordUtil.call("onEditorLoaded", ["Character Editor", __character]);
}
override function destroy() {
@@ -33,6 +33,8 @@ class CharacterSelection extends EditorTreeMenu
}));
main = new OptionsScreen("Character Editor", "Select a character to edit", list);
DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
}
override function createPost() {
+3 -1
View File
@@ -510,6 +510,8 @@ class Charter extends UIState {
dataDisplay.updateHitbox();
dataDisplay.screenCenter(Y);
dataDisplay.cameras = [charterCamera]; dataDisplay.x = -dataDisplay.width; add(dataDisplay);*/
DiscordUtil.call("onEditorLoaded", ["Chart Editor", __song + " (" + __diff + ")"]);
}
override function destroy() {
@@ -548,7 +550,7 @@ class Charter extends UIState {
notesGroup.autoSort = false;
var noteCount:Int = 0;
for (strL in PlayState.SONG.strumLines)
for (note in strL.notes) noteCount++;
noteCount += strL.notes.length;
notesGroup.preallocate(noteCount);
var notesCreated:Int = 0;
@@ -46,6 +46,8 @@ class CharterSelection extends EditorTreeMenu {
}));
main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list);
DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
}
override function createPost() {
+1 -2
View File
@@ -87,8 +87,7 @@ class GameOverSubstate extends MusicBeatSubstate
lossSFX = FlxG.sound.play(Paths.sound(lossSFXName));
Conductor.changeBPM(gameOverSongBPM);
DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")");
DiscordUtil.call("onGameOver", []);
gameoverScript.call("postCreate");
}
+1
View File
@@ -106,6 +106,7 @@ class NoteGroup extends FlxTypedGroup<Note> {
if (members == null)
return null;
// TODO: maybe lastIndexOf
var index:Int = members.indexOfFromLast(Object);
if (index < 0)
+1 -1
View File
@@ -784,7 +784,7 @@ class PlayState extends MusicBeatState
* This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts.
*/
public dynamic function updateDiscordPresence()
DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.meta.displayName + " (" + difficulty + ")", inst, getIconRPC());
DiscordUtil.call("onPlayStateUpdate", []);
/**
* Starts a cutscene.
+2
View File
@@ -29,6 +29,8 @@ class BetaWarningState extends MusicBeatState {
var off = Std.int((FlxG.height - (disclaimer.y + disclaimer.height)) / 2);
disclaimer.y += off;
titleAlphabet.y += off;
DiscordUtil.call("onMenuLoaded", ["Beta Warning"]);
}
public override function update(elapsed:Float) {
+1 -1
View File
@@ -116,7 +116,7 @@ class FreeplayState extends MusicBeatState
}
}
DiscordUtil.changePresence("In the Menus", null);
DiscordUtil.call("onMenuLoaded", ["Freeplay"]);
super.create();
+1 -1
View File
@@ -32,7 +32,7 @@ class MainMenuState extends MusicBeatState
{
super.create();
DiscordUtil.changePresence("In the Menus", null);
DiscordUtil.call("onMenuLoaded", ["Main Menu"]);
CoolUtil.playMenuSong();
+1 -1
View File
@@ -116,7 +116,7 @@ class StoryMenuState extends MusicBeatState {
changeWeek(0, true);
DiscordUtil.changePresence("In the Menus", null);
DiscordUtil.call("onMenuLoaded", ["Story Menu"]);
CoolUtil.playMenuSong();
}
+2
View File
@@ -36,6 +36,8 @@ class TitleState extends MusicBeatState
startIntro();
super.create();
DiscordUtil.call("onMenuLoaded", ["Title Screen"]);
}
var logoBl:FlxSprite;
+53 -14
View File
@@ -1,5 +1,7 @@
package funkin.menus.credits;
import funkin.options.PlayerSettings;
import flixel.util.FlxColor;
import funkin.backend.system.github.GitHub;
import funkin.options.type.GithubIconOption;
import flixel.text.FlxText;
@@ -9,44 +11,74 @@ using StringTools;
class CreditsCodename extends funkin.options.OptionsScreen {
public var error:Bool = false;
public var author:String = "FNF-CNE-Devs";
public var mainDevCol:FlxColor = 0xFF9C35D5;
public var minContrCol:FlxColor = 0xFFB4A7DA;
public var totalContributions:Int = 0;
public override function new()
{
super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes).");
checkUpdate();
displayList();
tryUpdating(true);
}
// blame the secondary threads if the code has to look this bad - Nex
private var _canReset:Bool = true;
private var _downloadingSteps:Int = 0;
public override function update(elapsed:Float) {
if (funkin.options.PlayerSettings.solo.controls.RESET && checkUpdate()) {
super.update(elapsed);
if (_downloadingSteps == 2) {
_downloadingSteps = 0;
_canReset = true;
displayList();
} else if (_downloadingSteps == 1) {
_downloadingSteps = 0;
_canReset = true;
updateMenuDesc();
}
super.update(elapsed);
else if(_canReset && PlayerSettings.solo.controls.RESET) tryUpdating();
}
public function tryUpdating(forceDisplaying:Bool = false) {
updateMenuDesc("Downloading List...");
_canReset = false;
Main.execAsync(function() {
if(checkUpdate() || forceDisplaying) _downloadingSteps = 2;
else _downloadingSteps = 1;
});
}
public override function updateMenuDesc(?txt:String) {
if(!_canReset) return;
super.updateMenuDesc(txt);
parent.treeParent.pathDesc.applyMarkup(parent.treeParent.pathDesc.text, [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFF9C35D5), "*")]);
updateMarkup();
}
public function updateMarkup() {
if(parent == null || parent.treeParent == null) return;
var text:String = parent.treeParent.pathDesc.text;
parent.treeParent.pathDesc.text = "";
parent.treeParent.pathDesc.applyMarkup(text, [
new FlxTextFormatMarkerPair(new FlxTextFormat(mainDevCol), '*'),
new FlxTextFormatMarkerPair(new FlxTextFormat(FlxColor.interpolate(minContrCol, mainDevCol, Options.contributors[curSelected].contributions / totalContributions)), '~')
]);
}
public function checkUpdate():Bool {
var curTime:Float = Date.now().getTime();
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits
if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex
Options.lastUpdated = curTime;
error = false;
//Main.execAsync(function() {
var idk = GitHub.getContributors(author, "CodenameEngine", function(e) {
error = true;
var errMsg:String = 'Error while trying to download contributors list:\n${CoolUtil.removeIP(e.message)}';
Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR);
funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING);
});
//});
if(error) return false;
Options.contributors = idk;
trace('List Updated!');
trace('Contributors list Updated!');
var errorOnMain:Bool = false;
var idk2 = GitHub.getOrganizationMembers(author, function(e) {
@@ -55,7 +87,10 @@ class CreditsCodename extends funkin.options.OptionsScreen {
Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR);
funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING);
});
if(!errorOnMain) Options.mainDevs = [for(m in idk2) m.id];
if(!errorOnMain) {
Options.mainDevs = [for(m in idk2) m.id];
trace('Main Devs list Updated!');
}
return true;
}
@@ -70,13 +105,17 @@ class CreditsCodename extends funkin.options.OptionsScreen {
remove(members[0], true);
}
var totalContributions = 0;
totalContributions = 0;
for(c in Options.contributors) totalContributions += c.contributions;
for(c in Options.contributors) {
var desc:String = 'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account';
if(Options.mainDevs.contains(c.id)) desc += " *- Public member of the main Devs!*";
var opt:GithubIconOption = new GithubIconOption(c, desc);
var opt:GithubIconOption = new GithubIconOption(c, 'Total Contributions: ~${c.contributions}~ / *${totalContributions}* (~${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%~) - Select to open GitHub account');
if(Options.mainDevs.contains(c.id)) {
opt.desc += " *- Public member of the main Devs!*";
@:privateAccess opt.__text.color = mainDevCol;
}
add(opt);
}
updateMenuDesc();
}
}
@@ -43,6 +43,8 @@ class CreditsMain extends TreeMenu {
main = new OptionsScreen('Credits', 'The people who made this possible!', selectables);
super.create();
DiscordUtil.call("onMenuLoaded", ["Credits Menu"]);
}
/**
+2
View File
@@ -37,6 +37,8 @@ class OptionsMenu extends TreeMenu {
CoolUtil.playMenuSong();
DiscordUtil.call("onMenuLoaded", ["Options Menu"]);
var bg:FlxSprite = new FlxSprite(-80).loadAnimatedGraphic(Paths.image('menus/menuBGBlue'));
// bg.scrollFactor.set();
bg.scale.set(1.15, 1.15);
+2 -5
View File
@@ -70,11 +70,8 @@ class OptionsScreen extends FlxTypedSpriteGroup<OptionType> {
}
public function updateMenuDesc(?customTxt:String) {
if (parent.treeParent == null) return;
var text:String = members[curSelected].desc;
if (customTxt != null) text = customTxt;
parent.treeParent.updateDesc(text);
if (parent == null || parent.treeParent == null) return;
parent.treeParent.updateDesc(customTxt != null ? customTxt : members[curSelected].desc);
}
public dynamic function onClose(o:OptionsScreen) {}
+14 -8
View File
@@ -19,10 +19,10 @@ class GithubIconOption extends TextOption
return usePortrait = value;
}
public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true) {
public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true, waitUntilLoad:Float = 0.25) {
super(customName == null ? user.login : customName, desc, callback == null ? function() CoolUtil.openURL(user.html_url) : callback);
this.user = user;
this.icon = new GithubUserIcon(user, size);
this.icon = new GithubUserIcon(user, size, waitUntilLoad);
this.usePortrait = usePortrait;
add(icon);
}
@@ -30,22 +30,28 @@ class GithubIconOption extends TextOption
class GithubUserIcon extends FlxSprite
{
private var loading:Bool = false;
public var waitUntilLoad:Null<Float>;
private var user:Dynamic;
private var size:Int;
public override function new(user:Dynamic, size:Int = 96) {
public override function new(user:Dynamic, size:Int = 96, waitUntilLoad:Float = 0.25) {
this.user = user;
this.size = size;
this.waitUntilLoad = waitUntilLoad;
super();
makeGraphic(size, size, FlxColor.TRANSPARENT);
antialiasing = true;
}
override function update(elapsed:Float) {
if(waitUntilLoad > 0) waitUntilLoad -= elapsed;
super.update(elapsed);
}
final mutex = new sys.thread.Mutex();
override function drawComplex(camera:FlxCamera):Void { // Making the image downlaod only if the player actually sees it on the screeeeen - Nex
if(!loading) {
loading = true;
if(waitUntilLoad <= 0) {
waitUntilLoad = null;
Main.execAsync(function() {
var key:String = 'GITHUB-USER:${user.login}';
var bmap:Dynamic = FlxG.bitmap.get(key);
@@ -57,7 +63,7 @@ class GithubUserIcon extends FlxSprite
var bytes = null;
if(unfLink) {
try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}?size=$size')
try bytes = HttpUtil.requestBytes('${user.avatar_url}?size=$size')
catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)', RED)], ERROR);
if(bytes != null) {
@@ -68,7 +74,7 @@ class GithubUserIcon extends FlxSprite
if(planB) {
if(unfLink) user = GitHub.getUser(user.login, function(e) Logs.traceColored([Logs.logText('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR)); // Api part - Nex
try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size')
try bytes = HttpUtil.requestBytes('${user.avatar_url}&size=$size')
catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR);
if(bytes != null) bmap = BitmapData.fromBytes(bytes);