mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
g/jak2: initial Discord RPC implementation (#2100)
Notable things:
- This assert is hit when trying to save the pc-settings file, NYI
e630b50690/game/kernel/common/Symbol4.h (L14)
so right now settings aren't persisted. But RPC defaults to on
- The existing functions can probably be made generic based off the game
version, but I didn't spend time refactoring them yet as they aren't
really ready to be used in jak 2 yet (we have no screenshots for the
levels for example)
This commit is contained in:
parent
bf10f1edd4
commit
f699675ede
|
@ -6,9 +6,17 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
#include "game/runtime.h"
|
||||
|
||||
int gDiscordRpcEnabled;
|
||||
int64_t gStartTime;
|
||||
static const char* APPLICATION_ID = "938876425585434654";
|
||||
|
||||
static const std::map<GameVersion, std::string> rpc_client_ids = {
|
||||
{GameVersion::Jak1, "938876425585434654"},
|
||||
{GameVersion::Jak2, "1060390251694149703"}};
|
||||
|
||||
static const std::map<std::string, std::string> jak1_level_names = {
|
||||
{"intro", "Intro"},
|
||||
{"title", "Title screen"},
|
||||
|
@ -29,12 +37,34 @@ static const std::map<std::string, std::string> jak1_level_names = {
|
|||
{"lavatube", "Lava Tube"},
|
||||
{"citadel", "Gol and Maia's Citadel"},
|
||||
{"finalboss", "Final Boss"}};
|
||||
|
||||
static const std::map<std::string, std::string> jak1_level_name_remap = {{"jungleb", "jungle"},
|
||||
{"sunkenb", "sunken"},
|
||||
{"robocave", "maincave"},
|
||||
{"darkcave", "maincave"}};
|
||||
|
||||
void handleDiscordReady(const DiscordUser* user) {
|
||||
lg::info("Discord: connected to user {}#{} - {}", user->username, user->discriminator,
|
||||
user->userId);
|
||||
}
|
||||
|
||||
void handleDiscordDisconnected(int errcode, const char* message) {
|
||||
lg::info("Discord: disconnected ({}: {})", errcode, message);
|
||||
}
|
||||
|
||||
void handleDiscordError(int errcode, const char* message) {
|
||||
lg::info("Discord: error ({}: {})", errcode, message);
|
||||
}
|
||||
|
||||
void handleDiscordJoin(const char* /*secret*/) {}
|
||||
void handleDiscordJoinRequest(const DiscordUser* /*request*/) {}
|
||||
void handleDiscordSpectate(const char* /*secret*/) {}
|
||||
|
||||
void init_discord_rpc() {
|
||||
if (g_game_version != GameVersion::Jak1 && g_game_version != GameVersion::Jak2) {
|
||||
lg::error("Game version unsupported for Discord RPC - {}", fmt::underlying(g_game_version));
|
||||
return;
|
||||
}
|
||||
gDiscordRpcEnabled = 1;
|
||||
DiscordEventHandlers handlers;
|
||||
memset(&handlers, 0, sizeof(handlers));
|
||||
|
@ -44,7 +74,7 @@ void init_discord_rpc() {
|
|||
handlers.joinGame = handleDiscordJoin;
|
||||
handlers.joinRequest = handleDiscordJoinRequest;
|
||||
handlers.spectateGame = handleDiscordSpectate;
|
||||
Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL);
|
||||
Discord_Initialize(rpc_client_ids.at(g_game_version).c_str(), &handlers, 1, NULL);
|
||||
}
|
||||
|
||||
void set_discord_rpc(int state) {
|
||||
|
@ -68,15 +98,16 @@ const char* jak1_get_full_level_name(const char* level_name) {
|
|||
const char* time_of_day_str(float time) {
|
||||
int hour = static_cast<int>(time);
|
||||
|
||||
if (hour >= 0 && hour <= 9)
|
||||
if (hour >= 0 && hour <= 9) {
|
||||
return "green-sun";
|
||||
else if (hour < 22)
|
||||
} else if (hour < 22) {
|
||||
return "day";
|
||||
else if (hour < 25)
|
||||
} else if (hour < 25) {
|
||||
return "evening";
|
||||
else
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// convert time of day float to a 24-hour hh:mm format string
|
||||
std::string get_time_of_day(float time) {
|
||||
|
@ -95,20 +126,3 @@ int indoors(const char* level_name) {
|
|||
!strcmp(level_name, "robocave") || !strcmp(level_name, "darkcave") ||
|
||||
!strcmp(level_name, "lavatube") || !strcmp(level_name, "citadel");
|
||||
}
|
||||
|
||||
void handleDiscordReady(const DiscordUser* user) {
|
||||
printf("\nDiscord: connected to user %s#%s - %s\n", user->username, user->discriminator,
|
||||
user->userId);
|
||||
}
|
||||
|
||||
void handleDiscordDisconnected(int errcode, const char* message) {
|
||||
printf("\nDiscord: disconnected (%d: %s)\n", errcode, message);
|
||||
}
|
||||
|
||||
void handleDiscordError(int errcode, const char* message) {
|
||||
printf("\nDiscord: error (%d: %s)\n", errcode, message);
|
||||
}
|
||||
|
||||
void handleDiscordJoin(const char* /*secret*/) {}
|
||||
void handleDiscordJoinRequest(const DiscordUser* /*request*/) {}
|
||||
void handleDiscordSpectate(const char* /*secret*/) {}
|
||||
|
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "common/versions.h"
|
||||
|
||||
#include "third-party/discord-rpc/include/discord_rpc.h"
|
||||
|
||||
extern int gDiscordRpcEnabled;
|
||||
extern int64_t gStartTime;
|
||||
|
||||
void init_discord_rpc();
|
||||
void set_discord_rpc(int state);
|
||||
const char* jak1_get_full_level_name(const char* level_name);
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#include "common/common_types.h"
|
||||
// Discord RPC
|
||||
struct DiscordRichPresence;
|
||||
extern int gDiscordRpcEnabled;
|
||||
extern int64_t gStartTime;
|
||||
namespace jak1 {
|
||||
/*!
|
||||
* Initialize global variables based on command line parameters
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "common/symbols.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
#include "game/discord.h"
|
||||
#include "game/kernel/common/Symbol4.h"
|
||||
#include "game/kernel/common/fileio.h"
|
||||
#include "game/kernel/common/kboot.h"
|
||||
|
@ -502,6 +503,79 @@ void pc_set_levels(u32 lev_list) {
|
|||
Gfx::set_levels(levels);
|
||||
}
|
||||
|
||||
void update_discord_rpc(u32 discord_info) {
|
||||
if (gDiscordRpcEnabled) {
|
||||
DiscordRichPresence rpc;
|
||||
char state[128];
|
||||
char large_image_key[128];
|
||||
char large_image_text[128];
|
||||
char small_image_key[128];
|
||||
char small_image_text[128];
|
||||
auto info = discord_info ? Ptr<DiscordInfo>(discord_info).c() : NULL;
|
||||
if (info) {
|
||||
// Get the data from GOAL
|
||||
int orbs = (int)*Ptr<float>(info->orb_count).c();
|
||||
int gems = (int)*Ptr<float>(info->gem_count).c();
|
||||
char* status = Ptr<String>(info->status).c()->data();
|
||||
char* level = Ptr<String>(info->level).c()->data();
|
||||
auto cutscene = Ptr<Symbol4<u32>>(info->cutscene)->value();
|
||||
float time = *Ptr<float>(info->time_of_day).c();
|
||||
float percent_completed = info->percent_completed;
|
||||
|
||||
// Construct the DiscordRPC Object
|
||||
// TODO - take nice screenshots with the various time of days once the graphics is in a final
|
||||
// state
|
||||
const char* full_level_name =
|
||||
"unknown"; // jak1_get_full_level_name(Ptr<String>(info->level).c()->data());
|
||||
memset(&rpc, 0, sizeof(rpc));
|
||||
if (!indoors(level)) {
|
||||
char level_with_tod[128];
|
||||
strcpy(level_with_tod, level);
|
||||
strcat(level_with_tod, "-");
|
||||
strcat(level_with_tod, time_of_day_str(time));
|
||||
strcpy(large_image_key, level_with_tod);
|
||||
} else {
|
||||
strcpy(large_image_key, level);
|
||||
}
|
||||
strcpy(large_image_text, full_level_name);
|
||||
if (!strcmp(full_level_name, "unknown")) {
|
||||
strcpy(large_image_key, full_level_name);
|
||||
strcpy(large_image_text, level);
|
||||
}
|
||||
rpc.largeImageKey = large_image_key;
|
||||
if (cutscene != offset_of_s7()) {
|
||||
strcpy(state, "Watching a cutscene");
|
||||
} else {
|
||||
strcpy(state, fmt::format("{:.0f}% | Orbs: {} | Gems: {}", percent_completed,
|
||||
std::to_string(orbs), std::to_string(gems))
|
||||
.c_str());
|
||||
strcpy(large_image_text, fmt::format(" | {:.0f}% | Orbs: {} | Gems: {}", percent_completed,
|
||||
std::to_string(orbs), std::to_string(gems))
|
||||
.c_str());
|
||||
}
|
||||
rpc.largeImageText = large_image_text;
|
||||
rpc.state = state;
|
||||
if (!indoors(level)) {
|
||||
strcpy(small_image_key, time_of_day_str(time));
|
||||
strcpy(small_image_text, "Time of day: ");
|
||||
strcat(small_image_text, get_time_of_day(time).c_str());
|
||||
} else {
|
||||
strcpy(small_image_key, "");
|
||||
strcpy(small_image_text, "");
|
||||
}
|
||||
rpc.smallImageKey = small_image_key;
|
||||
rpc.smallImageText = small_image_text;
|
||||
rpc.startTimestamp = gStartTime;
|
||||
rpc.details = status;
|
||||
rpc.partySize = 0;
|
||||
rpc.partyMax = 0;
|
||||
Discord_UpdatePresence(&rpc);
|
||||
}
|
||||
} else {
|
||||
Discord_ClearPresence();
|
||||
}
|
||||
}
|
||||
|
||||
void InitMachine_PCPort() {
|
||||
// PC Port added functions
|
||||
|
||||
|
@ -550,8 +624,8 @@ void InitMachine_PCPort() {
|
|||
make_function_symbol_from_c("pc-mkdir-file-path", (void*)mkdir_path);
|
||||
|
||||
// discord rich presence
|
||||
// make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc);
|
||||
// make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc);
|
||||
make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc);
|
||||
make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc);
|
||||
|
||||
// profiler
|
||||
make_function_symbol_from_c("pc-prof", (void*)prof_event);
|
||||
|
|
|
@ -52,4 +52,16 @@ struct MouseInfo {
|
|||
// (speedx float :offset 92)
|
||||
// (speedy float :offset 108)
|
||||
};
|
||||
|
||||
struct DiscordInfo {
|
||||
u32 orb_count; // (pointer float)
|
||||
u32 gem_count; // (pointer float)
|
||||
u32 death_count; // (pointer int32)
|
||||
u32 status; // string
|
||||
u32 level; // string
|
||||
u32 cutscene; // symbol - bool
|
||||
u32 time_of_day; // (pointer float
|
||||
float percent_completed; // float
|
||||
};
|
||||
|
||||
} // namespace jak2
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include "common/util/unicode_util.h"
|
||||
#include "common/versions.h"
|
||||
|
||||
#include "game/discord.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" {
|
||||
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
||||
|
@ -24,9 +22,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|||
}
|
||||
#endif
|
||||
|
||||
// Discord RPC
|
||||
extern int64_t gStartTime;
|
||||
|
||||
/*!
|
||||
* Set up logging system to log to file.
|
||||
* @param verbose : should we print debug-level messages to stdout?
|
||||
|
@ -87,10 +82,6 @@ int main(int argc, char** argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// set up discord stuff
|
||||
gStartTime = time(nullptr);
|
||||
init_discord_rpc();
|
||||
|
||||
if (disable_avx2) {
|
||||
// for debugging the non-avx2 code paths, there's a flag to manually disable.
|
||||
printf("Note: AVX2 code has been manually disabled.\n");
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "common/util/FileUtil.h"
|
||||
#include "common/versions.h"
|
||||
|
||||
#include "game/discord.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/kernel/common/fileio.h"
|
||||
#include "game/kernel/common/kdgo.h"
|
||||
|
@ -329,6 +330,10 @@ RuntimeExitStatus exec_runtime(int argc, char** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
// set up discord stuff
|
||||
gStartTime = time(nullptr);
|
||||
init_discord_rpc();
|
||||
|
||||
// initialize graphics first - the EE code will upload textures during boot and we
|
||||
// want the graphics system to catch them.
|
||||
if (enable_display) {
|
||||
|
@ -385,5 +390,6 @@ RuntimeExitStatus exec_runtime(int argc, char** argv) {
|
|||
}
|
||||
lg::info("GOAL Runtime Shutdown (code {})", fmt::underlying(MasterExit));
|
||||
munmap(g_ee_main_mem, EE_MAIN_MEM_SIZE);
|
||||
Discord_Shutdown();
|
||||
return MasterExit;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
("dma-bucket.o" "dma-bucket")
|
||||
("dma-disasm.o" "dma-disasm")
|
||||
("pad.o" "pad")
|
||||
("pckernel-h.o" "pckernel-h") ;; added
|
||||
("gs.o" "gs")
|
||||
("display-h.o" "display-h")
|
||||
("geometry.o" "geometry")
|
||||
|
@ -231,6 +232,7 @@
|
|||
("game-task.o" "game-task")
|
||||
("game-save.o" "game-save")
|
||||
("settings.o" "settings")
|
||||
("pckernel.o" "pckernel") ;; added
|
||||
("mood-tables.o" "mood-tables")
|
||||
("mood-tables2.o" "mood-tables2")
|
||||
("mood.o" "mood")
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
("dma-buffer.o" "dma-buffer")
|
||||
("dma-bucket.o" "dma-bucket")
|
||||
("dma-disasm.o" "dma-disasm")
|
||||
("pckernel-h.o" "pckernel-h") ;; added
|
||||
("pad.o" "pad")
|
||||
("gs.o" "gs")
|
||||
("display-h.o" "display-h")
|
||||
|
@ -231,6 +232,7 @@
|
|||
("game-task.o" "game-task")
|
||||
("game-save.o" "game-save")
|
||||
("settings.o" "settings")
|
||||
("pckernel.o" "pckernel") ;; added
|
||||
("mood-tables.o" "mood-tables")
|
||||
("mood-tables2.o" "mood-tables2")
|
||||
("mood.o" "mood")
|
||||
|
|
|
@ -32,6 +32,15 @@
|
|||
|
||||
;; og:ignore-form:all-texture-tweak-adjust
|
||||
|
||||
(defmacro dm-lambda-boolean-flag (val)
|
||||
"helper macro for making boolean buttons that don't just access symbols directly"
|
||||
`,(lambda (arg (msg debug-menu-msg))
|
||||
(if (= msg (debug-menu-msg press))
|
||||
(not! ,val)
|
||||
)
|
||||
,val)
|
||||
)
|
||||
|
||||
;; DECOMP BEGINS
|
||||
|
||||
;; this file is debug only
|
||||
|
@ -5890,6 +5899,17 @@
|
|||
)
|
||||
(debug-menu-append-item s5-0 (debug-menu-make-task-menu arg0))
|
||||
(debug-menu-append-item s5-0 (debug-menu-make-play-menu arg0))
|
||||
(debug-menu-append-item
|
||||
s5-0
|
||||
(debug-menu-make-from-template
|
||||
arg0
|
||||
'(menu
|
||||
"PC Settings"
|
||||
(flag "Discord RPC" #t ,(dm-lambda-boolean-flag (-> *pc-settings* discord-rpc?)))
|
||||
(function "Save" #f ,(lambda () (commit-to-file *pc-settings*)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
arg0
|
||||
)
|
||||
|
|
|
@ -1657,6 +1657,8 @@
|
|||
(with-profiler 'actors *profile-actors-color*
|
||||
(suspend)
|
||||
)
|
||||
(#when PC_PORT
|
||||
(update *pc-settings*))
|
||||
)
|
||||
)
|
||||
(set! *dproc* #f)
|
||||
|
|
|
@ -36,6 +36,18 @@
|
|||
)
|
||||
)
|
||||
|
||||
(defconstant SCE_SEEK_SET 0)
|
||||
(defconstant SCE_SEEK_CUR 1)
|
||||
(defconstant SCE_SEEK_END 2)
|
||||
|
||||
(defmacro file-stream-valid? (fs)
|
||||
`(>= (the-as int (-> ,fs file)) 0)
|
||||
)
|
||||
|
||||
(defmacro file-stream-tell (fs)
|
||||
`(file-stream-seek ,fs 0 SCE_SEEK_CUR)
|
||||
)
|
||||
|
||||
(defmethod new file-stream ((allocation symbol) (type-to-make type) (arg0 string) (arg1 symbol))
|
||||
"Allocate a file-stream and open it."
|
||||
(let ((a0-1 (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
|
||||
|
|
|
@ -251,7 +251,17 @@
|
|||
"dma/dma-buffer.gc"
|
||||
"dma/dma-bucket.gc"
|
||||
"dma/dma-disasm.gc"
|
||||
"ps2/pad.gc"
|
||||
)
|
||||
|
||||
(goal-src "engine/ps2/pad.gc" "pckernel-h")
|
||||
|
||||
(goal-src-sequence
|
||||
;; prefix
|
||||
"engine/"
|
||||
|
||||
:deps
|
||||
("$OUT/obj/pad.o"
|
||||
"$OUT/obj/dma-disasm.o")
|
||||
"gfx/hw/gs.gc"
|
||||
"gfx/hw/display-h.gc"
|
||||
"geometry/geometry.gc"
|
||||
|
@ -557,7 +567,18 @@
|
|||
"gfx/background/prototype.gc"
|
||||
"collide/main-collide.gc"
|
||||
"gfx/hw/video.gc"
|
||||
"game/main.gc"
|
||||
)
|
||||
|
||||
(goal-src "engine/game/main.gc" "pckernel" "video")
|
||||
|
||||
(goal-src-sequence
|
||||
;; prefix
|
||||
"engine/"
|
||||
|
||||
:deps
|
||||
("$OUT/obj/main.o"
|
||||
"$OUT/obj/video.o")
|
||||
|
||||
"collide/collide-cache.gc"
|
||||
"collide/collide-debug.gc"
|
||||
"entity/relocate.gc"
|
||||
|
@ -4653,6 +4674,10 @@
|
|||
`("$OUT/iso/0COMMON.TXT")
|
||||
)
|
||||
|
||||
;; Custom or Modified Code
|
||||
(goal-src "pc/pckernel-h.gc" "dma-buffer")
|
||||
(goal-src "pc/pckernel.gc" "video")
|
||||
|
||||
;; used for the type consistency test.
|
||||
(group-list "all-code"
|
||||
`(,@(reverse *all-gc*))
|
||||
|
|
|
@ -218,4 +218,12 @@
|
|||
)
|
||||
(define-extern pc-prof (function string pc-prof-event none))
|
||||
|
||||
(define-extern *pc-settings-folder* string)
|
||||
(define-extern *pc-settings-built-sha* string)
|
||||
|
||||
(define-extern pc-filepath-exists? (function string symbol))
|
||||
(define-extern pc-mkdir-file-path (function string none))
|
||||
(define-extern pc-filter-debug-string? (function string float symbol))
|
||||
(declare-type discord-info structure)
|
||||
(define-extern pc-discord-rpc-update (function discord-info none))
|
||||
(define-extern pc-discord-rpc-set (function int none))
|
||||
|
|
|
@ -458,6 +458,21 @@
|
|||
(cat-string<-string_to_charp arg0 arg1 s4-0)
|
||||
)
|
||||
)
|
||||
(defmacro is-whitespace-char? (c)
|
||||
;; 32 = space
|
||||
;; 9 = \t
|
||||
;; 13 = \r
|
||||
;; 10 = \n
|
||||
`(or (= ,c 32)
|
||||
(= ,c 9)
|
||||
(= ,c 13)
|
||||
(= ,c 10)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro not-whitespace-char? (c)
|
||||
`(not (is-whitespace-char? ,c))
|
||||
)
|
||||
|
||||
(defun string-skip-whitespace ((arg0 (pointer uint8)))
|
||||
"Skip over spaces, tabs, r's and n's"
|
||||
|
@ -784,6 +799,16 @@
|
|||
;; up from 256 bytes in jak 1
|
||||
(define *temp-string* (new 'global 'string 2048 (the-as string #f)))
|
||||
|
||||
(defmacro string-format (&rest args)
|
||||
"Formats into *temp-string* and returns it, for in-place string formating.
|
||||
DO NOT USE *temp-string* WITH THIS MACRO! It is read as input AFTER all of the args evaluate."
|
||||
|
||||
`(begin
|
||||
(format (clear *temp-string*) ,@args)
|
||||
*temp-string*
|
||||
)
|
||||
)
|
||||
|
||||
(kmemclose)
|
||||
|
||||
|
||||
|
|
100
goal_src/jak2/pc/pckernel-h.gc
Normal file
100
goal_src/jak2/pc/pckernel-h.gc
Normal file
|
@ -0,0 +1,100 @@
|
|||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
|
||||
#|
|
||||
|
||||
This file contains code that we need for the PC port of the game specifically.
|
||||
It should be included as part of the game engine package (engine.cgo).
|
||||
|
||||
This file contains various types and functions to store PC-specific information
|
||||
and also to communicate between the game (GOAL) and the operating system.
|
||||
This way we can poll, change and display information about the system the game
|
||||
is running on, such as:
|
||||
- display devices and their settings, such as fullscreen, DPI, refresh rate, etc.
|
||||
- audio devices and their settings, such as audio latency, channel number, etc.
|
||||
- graphics devices and their settings, such as resolution, FPS, anisotropy, shaders, etc.
|
||||
- input devices and their settings, such as controllers, keyboards, mice, etc.
|
||||
- information about the game window (position, size)
|
||||
- PC-specific goodies, enhancements, fixes and settings.
|
||||
- whatever else.
|
||||
|
||||
If you do not want to include these PC things, you should exclude it from the build system.
|
||||
|
||||
|#
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; constants
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(deftype pckernel-version (int64)
|
||||
((build int16 :offset 0)
|
||||
(revision int16 :offset 16)
|
||||
(minor int16 :offset 32)
|
||||
(major int16 :offset 48)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro static-pckernel-version (major minor rev build)
|
||||
`(new 'static 'pckernel-version :major ,major :minor ,minor :revision ,rev :build ,build))
|
||||
;; version: 0.0.0.1
|
||||
(defglobalconstant PC_KERNEL_VERSION (static-pckernel-version 0 0 0 1))
|
||||
(defconstant PC_KERNEL_VER_MAJOR (-> PC_KERNEL_VERSION major))
|
||||
(defconstant PC_KERNEL_VER_MINOR (-> PC_KERNEL_VERSION minor))
|
||||
|
||||
|
||||
;; All of the configuration for the PC port in GOAL. Access things from here!
|
||||
;; Includes some methods to change parameters.
|
||||
(deftype pc-settings (basic)
|
||||
((version pckernel-version) ;; version of this settings
|
||||
|
||||
(discord-rpc? symbol) ;; enable discord rich presence integration
|
||||
)
|
||||
|
||||
(:methods
|
||||
(new (symbol type) _type_)
|
||||
(update (_type_) none)
|
||||
(update-to-os (_type_) none)
|
||||
(reset (_type_) none)
|
||||
(read-from-file (_type_ string) symbol)
|
||||
(load-settings (_type_) int)
|
||||
(write-to-file (_type_ string) symbol)
|
||||
(commit-to-file (_type_) none)
|
||||
)
|
||||
)
|
||||
|
||||
(deftype discord-info (structure)
|
||||
((orb-count (pointer float))
|
||||
(gem-count (pointer float))
|
||||
(death-count (pointer int32))
|
||||
(status string)
|
||||
(level string)
|
||||
(cutscene? symbol)
|
||||
(time-of-day (pointer float))
|
||||
(percent-complete float)))
|
||||
|
||||
(defconstant PC_TEMP_STRING_LEN 512)
|
||||
(define *pc-temp-string* (new 'global 'string PC_TEMP_STRING_LEN (the string #f)))
|
||||
(define *pc-settings* (the pc-settings #f))
|
||||
(format 0 "PC kernel version: ~D.~D~%" PC_KERNEL_VER_MAJOR PC_KERNEL_VER_MINOR)
|
||||
|
||||
(define *pc-temp-string-1* (new 'global 'string 2048 (the string #f)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; resets
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(defmethod reset pc-settings ((obj pc-settings))
|
||||
"Set the default settings"
|
||||
(set! (-> obj version) PC_KERNEL_VERSION)
|
||||
(set! (-> obj discord-rpc?) #t)
|
||||
(none))
|
||||
|
||||
(defmacro with-pc (&rest body)
|
||||
"encapsulates the code around PC-specific checks"
|
||||
`(#when PC_PORT (when (and *pc-settings*)
|
||||
,@body
|
||||
))
|
||||
)
|
318
goal_src/jak2/pc/pckernel.gc
Normal file
318
goal_src/jak2/pc/pckernel.gc
Normal file
|
@ -0,0 +1,318 @@
|
|||
;;-*-Lisp-*-
|
||||
(in-package goal)
|
||||
|
||||
#|
|
||||
|
||||
This file contains new code that we need for the PC port of the game specifically.
|
||||
It should be included as part of the game engine package (engine.cgo).
|
||||
|
||||
This file contains various types and functions to store PC-specific information
|
||||
and also to communicate between the game (GOAL) and the operating system.
|
||||
This way we can poll, change and display information about the system the game
|
||||
is running on, such as:
|
||||
- display devices and their settings, such as fullscreen, DPI, refresh rate, etc.
|
||||
- audio devices and their settings, such as audio latency, channel number, etc.
|
||||
- graphics devices and their settings, such as resolution, FPS, anisotropy, shaders, etc.
|
||||
- input devices and their settings, such as controllers, keyboards, mice, etc.
|
||||
- information about the game window (position, size)
|
||||
- PC-specific goodies, enhancements, fixes and settings.
|
||||
- whatever else.
|
||||
|
||||
If you do not want to include these PC things, you should exclude it from the build system.
|
||||
|
||||
|#
|
||||
|
||||
|
||||
(defmethod update-to-os pc-settings ((obj pc-settings))
|
||||
"Update settings from GOAL to the C kernel."
|
||||
(pc-discord-rpc-set (if (-> obj discord-rpc?) 1 0))
|
||||
(none))
|
||||
|
||||
(defmethod update pc-settings ((obj pc-settings))
|
||||
"Update settings to/from PC kernel. Call this at the start of every frame.
|
||||
This will update things like the aspect-ratio, which will be used for graphics code later."
|
||||
|
||||
(update-to-os obj)
|
||||
|
||||
(let ((info (new 'stack 'discord-info)))
|
||||
(set! (-> info orb-count) (&-> *game-info* skill-total))
|
||||
(set! (-> info gem-count) (&-> *game-info* gem-total))
|
||||
(set! (-> info death-count) (&-> *game-info* total-deaths))
|
||||
(set! (-> info status) "Playing Jak 2™")
|
||||
;; grab the name of the level we're in
|
||||
(if (and *level* (level-get-target-inside *level*))
|
||||
(set! (-> info level) (symbol->string (-> (level-get-target-inside *level*) name)))
|
||||
(set! (-> info level) "unknown"))
|
||||
(set! (-> info cutscene?) #f) ;; TODO - cutscenes don't work yet
|
||||
(set! (-> info time-of-day) (&-> *time-of-day-context* time))
|
||||
(set! (-> info percent-complete) (calculate-percentage *game-info*))
|
||||
|
||||
;; TODO - wrapping in `with-profiler` causes an error, fix it
|
||||
(pc-discord-rpc-update info)
|
||||
)
|
||||
|
||||
(none))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; file IO
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
|
||||
(defmacro file-stream-seek-until (fs func-name)
|
||||
`(let ((done? #f)
|
||||
(tell -1))
|
||||
|
||||
(until done?
|
||||
|
||||
(let ((read (file-stream-read ,fs (-> *pc-temp-string* data) PC_TEMP_STRING_LEN)))
|
||||
(cond
|
||||
((zero? read)
|
||||
(set! (-> *pc-temp-string* data read) 0)
|
||||
(true! done?)
|
||||
)
|
||||
(else
|
||||
(dotimes (i read)
|
||||
(when (,func-name (-> *pc-temp-string* data i))
|
||||
(true! done?)
|
||||
(set! tell (+ i (- (file-stream-tell ,fs) read)))
|
||||
(set! i read)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
)
|
||||
|
||||
)
|
||||
(if (!= tell -1)
|
||||
(file-stream-seek ,fs tell SCE_SEEK_SET)
|
||||
tell
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro file-stream-read-until (fs func-name)
|
||||
`(let ((read (file-stream-read ,fs (-> *pc-temp-string* data) PC_TEMP_STRING_LEN)))
|
||||
(dotimes (i read)
|
||||
(when (,func-name (-> *pc-temp-string* data i))
|
||||
(set! (-> *pc-temp-string* data i) 0)
|
||||
(file-stream-seek ,fs (+ i (- (file-stream-tell ,fs) read)) SCE_SEEK_SET)
|
||||
(set! i read)
|
||||
)
|
||||
)
|
||||
*pc-temp-string*
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro is-whitespace-or-bracket? (c)
|
||||
`(or (is-whitespace-char? ,c) (= #x28 ,c) (= #x29 ,c))
|
||||
)
|
||||
|
||||
(defun file-stream-seek-past-whitespace ((file file-stream))
|
||||
(file-stream-seek-until file not-whitespace-char?)
|
||||
)
|
||||
|
||||
(defun file-stream-read-word ((file file-stream))
|
||||
(file-stream-read-until file is-whitespace-or-bracket?)
|
||||
;(format 0 "word ~A~%" *pc-temp-string*)
|
||||
)
|
||||
|
||||
(defmacro file-stream-getc (fs)
|
||||
`(let ((buf 255))
|
||||
(file-stream-read ,fs (& buf) 1)
|
||||
;(format 0 "getc got #x~X~%" buf)
|
||||
buf
|
||||
)
|
||||
)
|
||||
|
||||
(defun file-stream-read-int ((file file-stream))
|
||||
(file-stream-seek-past-whitespace file)
|
||||
(file-stream-read-word file)
|
||||
(string->int *pc-temp-string*)
|
||||
)
|
||||
|
||||
(defun file-stream-read-float ((file file-stream))
|
||||
(file-stream-seek-past-whitespace file)
|
||||
(file-stream-read-word file)
|
||||
(string->float *pc-temp-string*)
|
||||
)
|
||||
|
||||
(defun file-stream-read-symbol ((file file-stream))
|
||||
(file-stream-seek-past-whitespace file)
|
||||
(file-stream-read-word file)
|
||||
(string->symbol *pc-temp-string*)
|
||||
)
|
||||
|
||||
(defmacro pc-settings-read-throw-error (fs msg)
|
||||
"not an actual throw..."
|
||||
`(begin
|
||||
(format 0 "pc settings read error: ~S~%" ,msg)
|
||||
(file-stream-close ,fs)
|
||||
(return #f)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro with-settings-scope (bindings &rest body)
|
||||
(let ((fs (first bindings)))
|
||||
`(begin
|
||||
(file-stream-seek-past-whitespace ,fs)
|
||||
(when (!= #x28 (file-stream-getc ,fs))
|
||||
(pc-settings-read-throw-error ,fs "invalid char, ( not found")
|
||||
)
|
||||
|
||||
,@body
|
||||
|
||||
(file-stream-seek-past-whitespace ,fs)
|
||||
(when (!= #x29 (file-stream-getc ,fs))
|
||||
(pc-settings-read-throw-error ,fs "invalid char, ) not found")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro file-stream-get-next-char-ret (fs)
|
||||
`(begin
|
||||
(file-stream-seek-past-whitespace ,fs)
|
||||
(let ((c (file-stream-getc ,fs)))
|
||||
(file-stream-seek ,fs -1 SCE_SEEK_CUR)
|
||||
c))
|
||||
)
|
||||
|
||||
(defmacro file-stream-get-next-char (fs)
|
||||
`(begin
|
||||
(file-stream-seek-past-whitespace ,fs)
|
||||
(file-stream-getc ,fs)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro dosettings (bindings &rest body)
|
||||
"iterate over a list of key-value pairs like so: (<key> <value>) (<key> <value>) ...
|
||||
the name of key is stored in *pc-temp-string*"
|
||||
(let ((fs (first bindings)))
|
||||
`(let ((c -1))
|
||||
(while (begin (file-stream-seek-past-whitespace ,fs) (set! c (file-stream-getc ,fs)) (= #x28 c))
|
||||
(file-stream-read-word ,fs)
|
||||
|
||||
,@body
|
||||
|
||||
(set! c (file-stream-get-next-char ,fs))
|
||||
(when (!= #x29 c)
|
||||
(pc-settings-read-throw-error ,fs (string-format "invalid char, ) not found, got #x~X ~A" c *pc-temp-string*))
|
||||
)
|
||||
)
|
||||
(file-stream-seek ,fs -1 SCE_SEEK_CUR)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod read-from-file pc-settings ((obj pc-settings) (filename string))
|
||||
"read settings from a file"
|
||||
|
||||
(if (not filename)
|
||||
(return #f))
|
||||
|
||||
(let ((file (new 'stack 'file-stream filename 'read)))
|
||||
(when (not (file-stream-valid? file))
|
||||
(return #f))
|
||||
|
||||
(let ((version PC_KERNEL_VERSION))
|
||||
(with-settings-scope (file)
|
||||
(case-str (file-stream-read-word file)
|
||||
(("settings")
|
||||
(set! version (the pckernel-version (file-stream-read-int file)))
|
||||
(cond
|
||||
((and (= (-> version major) PC_KERNEL_VER_MAJOR)
|
||||
(= (-> version minor) PC_KERNEL_VER_MINOR))
|
||||
;; minor difference
|
||||
)
|
||||
(else
|
||||
;; major difference
|
||||
(format 0 "PC kernel version mismatch! Got ~D.~D vs ~D.~D~%" PC_KERNEL_VER_MAJOR PC_KERNEL_VER_MINOR (-> version major) (-> version minor))
|
||||
(file-stream-close file)
|
||||
(return #f)
|
||||
)
|
||||
)
|
||||
(dosettings (file)
|
||||
(case-str *pc-temp-string*
|
||||
(("discord-rpc?") (set! (-> obj discord-rpc?) (file-stream-read-symbol file)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
(file-stream-close file)
|
||||
)
|
||||
|
||||
(format 0 "pc settings file read: ~A~%" filename)
|
||||
|
||||
#t
|
||||
)
|
||||
|
||||
(defmethod write-to-file pc-settings ((obj pc-settings) (filename string))
|
||||
"write settings to a file"
|
||||
|
||||
(if (not filename)
|
||||
(return #f))
|
||||
|
||||
(let ((file (new 'stack 'file-stream filename 'write)))
|
||||
(if (not (file-stream-valid? file))
|
||||
(return #f))
|
||||
|
||||
(format file "(settings #x~X~%" (-> obj version))
|
||||
(format file " (discord-rpc? ~A)~%" (-> obj discord-rpc?))
|
||||
(format file " )~%")
|
||||
(file-stream-close file)
|
||||
)
|
||||
|
||||
(format 0 "pc settings file write: ~A~%" filename)
|
||||
|
||||
#t
|
||||
)
|
||||
|
||||
(defmethod commit-to-file pc-settings ((obj pc-settings))
|
||||
"commits the current settings to the file"
|
||||
;; auto load settings if available
|
||||
|
||||
(format (clear *pc-temp-string-1*) "~S/pc-settings.gc" *pc-settings-folder*)
|
||||
(pc-mkdir-file-path *pc-temp-string-1*)
|
||||
;; symbol -> string in C++ nyi for jak2 symbols
|
||||
;; (write-to-file obj *pc-temp-string-1*)
|
||||
(none))
|
||||
|
||||
(defmethod load-settings pc-settings ((obj pc-settings))
|
||||
"load"
|
||||
|
||||
(format (clear *pc-temp-string-1*) "~S/pc-settings.gc" *pc-settings-folder*)
|
||||
(if (pc-filepath-exists? *pc-temp-string-1*)
|
||||
(begin
|
||||
(format 0 "[PC] PC Settings found at '~S'...loading!~%" *pc-temp-string-1*)
|
||||
(unless (read-from-file obj *pc-temp-string-1*)
|
||||
(format 0 "[PC] PC Settings found at '~S' but could not be loaded, using defaults!~%" *pc-temp-string-1*)
|
||||
(reset obj)))
|
||||
(format 0 "[PC] PC Settings not found at '~S'...initializing with defaults!~%" *pc-temp-string-1*))
|
||||
0)
|
||||
|
||||
(defmethod new pc-settings ((allocation symbol) (type-to-make type))
|
||||
"make a new pc-settings"
|
||||
(let ((obj (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
|
||||
(reset obj)
|
||||
;; auto load settings if available
|
||||
;; if saved settings are corrupted or not found, use defaults
|
||||
|
||||
(load-settings obj)
|
||||
|
||||
obj))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; PC settings
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
(define *pc-settings* (new 'global 'pc-settings))
|
Loading…
Reference in a new issue