mirror of
https://github.com/open-goal/jak-project
synced 2026-08-11 11:33:51 -04:00
game: refactor discord code and improve jak 2 support (#2714)
The Discord RPC code has been cleaned up and split up between game versions. For Jak 2, the Discord integration now shows large images for all levels (with corresponding day and nighttime variants if required) and small images for time of day and various states like being on the jetboard, driving a zoomer, playing as Daxter, etc. TODO: - [x] mission specific images and detection - [x] detect side missions properly - [x] `onin-game` detection    --------- Co-authored-by: ManDude <7569514+ManDude@users.noreply.github.com>
This commit is contained in:
@@ -282,10 +282,6 @@ bool GameTextFontBank::valid_char_range(const char in) const {
|
||||
std::string GameTextFontBank::convert_game_to_utf8(const char* in) const {
|
||||
std::string temp;
|
||||
std::string result;
|
||||
std::string test = in;
|
||||
if (test.find("America") != std::string::npos) {
|
||||
printf("");
|
||||
}
|
||||
while (*in) {
|
||||
auto remap = find_encode_to_utf8(in);
|
||||
if (remap != nullptr) {
|
||||
|
||||
@@ -7210,6 +7210,16 @@
|
||||
(scene-subtitles-disabled #x30e)
|
||||
(board-name #x30f)
|
||||
(progress-unknown-continue #x310)
|
||||
|
||||
;; PC port IDs START
|
||||
(discord-rpc-not-in-mission #x1100)
|
||||
(discord-rpc-in-side-mission #x1101)
|
||||
(discord-rpc-red-gun-training #x1102)
|
||||
(discord-rpc-yellow-gun-training #x1103)
|
||||
(discord-rpc-blue-gun-training #x1104)
|
||||
(discord-rpc-dark-gun-training #x1105)
|
||||
(discord-rpc-onin-game #x1106)
|
||||
(discord-rpc-whack #x1107)
|
||||
)
|
||||
;; ---text-id-h:text-id
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> -f ${CMA
|
||||
set_source_files_properties(kernel/asm_funcs.asm PROPERTIES COMPILE_FLAGS "-g")
|
||||
set(RUNTIME_SOURCE
|
||||
external/discord.cpp
|
||||
external/discord_jak1.cpp
|
||||
external/discord_jak2.cpp
|
||||
graphics/display.cpp
|
||||
graphics/gfx.cpp
|
||||
graphics/jak2_texture_remap.cpp
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
;; NOTE : we compile using the fixed v2 encoding because it's what we use.
|
||||
(file "$DECOMP/assets/game_text.txt") ;; this is the decompiler-generated file!
|
||||
;; add custom files down here
|
||||
(file "game/assets/jak2/text/game_custom_text_en.gs")
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
(group-name "common")
|
||||
(language-id 0 7)
|
||||
(text-version jak2)
|
||||
|
||||
(#x1100
|
||||
"Not in a mission"
|
||||
"Not in a mission"
|
||||
)
|
||||
|
||||
(#x1101
|
||||
"Playing a side mission"
|
||||
"Playing a side mission"
|
||||
)
|
||||
|
||||
(#x1102
|
||||
"Playing Scatter Gun Training"
|
||||
"Playing Scatter Gun Training"
|
||||
)
|
||||
|
||||
(#x1103
|
||||
"Playing Blaster Training"
|
||||
"Playing Blaster Training"
|
||||
)
|
||||
|
||||
(#x1104
|
||||
"Playing Vulcan Fury Training"
|
||||
"Playing Vulcan Fury Training"
|
||||
)
|
||||
|
||||
(#x1105
|
||||
"Playing Peace Maker Training"
|
||||
"Playing Peace Maker Training"
|
||||
)
|
||||
|
||||
(#x1106
|
||||
"Playing Onin's Game"
|
||||
"Playing Onin's Game"
|
||||
)
|
||||
|
||||
(#x1107
|
||||
"Playing Metal Head Mash"
|
||||
"Playing Metal Head Mash"
|
||||
)
|
||||
Vendored
+9
-53
@@ -4,7 +4,6 @@
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "common/log/log.h"
|
||||
|
||||
@@ -17,32 +16,6 @@ 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"},
|
||||
{"training", "Geyser Rock"},
|
||||
{"village1", "Sandover Village"},
|
||||
{"beach", "Sentinel Beach"},
|
||||
{"jungle", "Forbidden Jungle"},
|
||||
{"misty", "Misty Island"},
|
||||
{"firecanyon", "Fire Canyon"},
|
||||
{"village2", "Rock Village"},
|
||||
{"swamp", "Boggy Swamp"},
|
||||
{"rolling", "Precursor Basin"},
|
||||
{"sunken", "Lost Precursor City"},
|
||||
{"ogre", "Mountain Pass"},
|
||||
{"village3", "Volcanic Crater"},
|
||||
{"snow", "Snowy Mountain"},
|
||||
{"maincave", "Spider Cave"},
|
||||
{"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);
|
||||
@@ -82,31 +55,18 @@ void set_discord_rpc(int state) {
|
||||
}
|
||||
|
||||
// get full level name from symbol name ("village1" -> "Sandover Village")
|
||||
const char* jak1_get_full_level_name(const char* level_name) {
|
||||
const char* get_full_level_name(const std::map<std::string, std::string>& level_names,
|
||||
const std::map<std::string, std::string>& level_name_remap,
|
||||
const char* level_name) {
|
||||
// ignore sublevels
|
||||
auto it = jak1_level_name_remap.find(level_name);
|
||||
auto actual_level_name = it == jak1_level_name_remap.end() ? level_name : it->second;
|
||||
auto it = level_name_remap.find(level_name);
|
||||
auto actual_level_name = it == level_name_remap.end() ? level_name : it->second;
|
||||
|
||||
const auto& nice_name = jak1_level_names.find(actual_level_name);
|
||||
if (nice_name != jak1_level_names.end()) {
|
||||
const auto& nice_name = level_names.find(actual_level_name);
|
||||
if (nice_name != level_names.end()) {
|
||||
return nice_name->second.c_str();
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
// time of day string to append to level name for icons
|
||||
const char* time_of_day_str(float time) {
|
||||
int hour = static_cast<int>(time);
|
||||
|
||||
if (hour >= 0 && hour <= 9) {
|
||||
return "green-sun";
|
||||
} else if (hour < 22) {
|
||||
return "day";
|
||||
} else if (hour < 25) {
|
||||
return "evening";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// convert time of day float to a 24-hour hh:mm format string
|
||||
@@ -119,10 +79,6 @@ std::string get_time_of_day(float time) {
|
||||
}
|
||||
|
||||
// are we in an area that's indoors, i.e. not affected by time of day?
|
||||
int indoors(const char* level_name) {
|
||||
return !strcmp(level_name, "intro") || !strcmp(level_name, "title") ||
|
||||
!strcmp(level_name, "jungleb") || !strcmp(level_name, "sunken") ||
|
||||
!strcmp(level_name, "sunkenb") || !strcmp(level_name, "maincave") ||
|
||||
!strcmp(level_name, "robocave") || !strcmp(level_name, "darkcave") ||
|
||||
!strcmp(level_name, "lavatube") || !strcmp(level_name, "citadel");
|
||||
bool indoors(std::vector<std::string> indoor_levels, const char* level_name) {
|
||||
return std::find(indoor_levels.begin(), indoor_levels.end(), level_name) != indoor_levels.end();
|
||||
}
|
||||
|
||||
Vendored
+7
-3
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "common/versions/versions.h"
|
||||
@@ -11,10 +14,11 @@ 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);
|
||||
const char* time_of_day_str(float time);
|
||||
std::string get_time_of_day(float time);
|
||||
int indoors(const char* level_name);
|
||||
const char* get_full_level_name(const std::map<std::string, std::string>& level_names,
|
||||
const std::map<std::string, std::string>& level_name_remap,
|
||||
const char* level_name);
|
||||
bool indoors(std::vector<std::string> indoor_levels, const char* level_name);
|
||||
|
||||
void handleDiscordReady(const DiscordUser* user);
|
||||
void handleDiscordDisconnected(int errcode, const char* message);
|
||||
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
#include "discord_jak1.h"
|
||||
|
||||
namespace jak1 {
|
||||
const std::map<std::string, std::string> level_names = {{"intro", "Intro"},
|
||||
{"title", "Title screen"},
|
||||
{"training", "Geyser Rock"},
|
||||
{"village1", "Sandover Village"},
|
||||
{"beach", "Sentinel Beach"},
|
||||
{"jungle", "Forbidden Jungle"},
|
||||
{"misty", "Misty Island"},
|
||||
{"firecanyon", "Fire Canyon"},
|
||||
{"village2", "Rock Village"},
|
||||
{"swamp", "Boggy Swamp"},
|
||||
{"rolling", "Precursor Basin"},
|
||||
{"sunken", "Lost Precursor City"},
|
||||
{"ogre", "Mountain Pass"},
|
||||
{"village3", "Volcanic Crater"},
|
||||
{"snow", "Snowy Mountain"},
|
||||
{"maincave", "Spider Cave"},
|
||||
{"lavatube", "Lava Tube"},
|
||||
{"citadel", "Gol and Maia's Citadel"},
|
||||
{"finalboss", "Final Boss"}};
|
||||
|
||||
// for remapping sub-level names to the matching one in level_names
|
||||
const std::map<std::string, std::string> level_name_remap = {{"jungleb", "jungle"},
|
||||
{"sunkenb", "sunken"},
|
||||
{"robocave", "maincave"},
|
||||
{"darkcave", "maincave"}};
|
||||
|
||||
// levels that are not affected by time of day
|
||||
const std::vector<std::string> indoor_levels = {
|
||||
"intro", "title", "jungleb", "sunken", "sunkenb",
|
||||
"maincave", "robocave", "darkcave", "lavatube", "citadel",
|
||||
};
|
||||
|
||||
// time of day string to append to level name for icons
|
||||
const char* time_of_day_str(float time) {
|
||||
int hour = static_cast<int>(time);
|
||||
|
||||
if (hour >= 0 && hour <= 9) {
|
||||
return "green-sun";
|
||||
} else if (hour < 22) {
|
||||
return "day";
|
||||
} else if (hour < 25) {
|
||||
return "evening";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
} // namespace jak1
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "discord.h"
|
||||
|
||||
namespace jak1 {
|
||||
extern const std::map<std::string, std::string> level_names;
|
||||
extern const std::map<std::string, std::string> level_name_remap;
|
||||
extern const std::vector<std::string> indoor_levels;
|
||||
const char* time_of_day_str(float time);
|
||||
} // namespace jak1
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
#include "discord_jak2.h"
|
||||
|
||||
namespace jak2 {
|
||||
const std::map<std::string, std::string> level_names = {
|
||||
{"intro", "Intro"},
|
||||
{"title", "Title screen"},
|
||||
{"prison", "Prison"},
|
||||
{"forexit", "Fortress (Escape)"},
|
||||
{"fordump", "Fortress (Ammo Dump)"},
|
||||
{"forresc", "Fortress (Rescue Friends)"},
|
||||
{"ctyslum", "Haven City (Slums)"},
|
||||
{"ctyport", "Haven City (Port)"},
|
||||
{"ctyfarm", "Haven City (Gardens)"},
|
||||
{"ctyind", "Haven City (Industrial Zone)"},
|
||||
{"ctymark", "Haven City (Bazaar)"},
|
||||
{"ctypal", "Haven City (Palace Area)"},
|
||||
{"ctygen", "Haven City (Main Town)"},
|
||||
{"stadium", "Stadium"},
|
||||
{"stadiumb", "Stadium (Class 3 Race)"},
|
||||
{"stadiumc", "Stadium (Class 2 Race)"},
|
||||
{"stadiumd", "Stadium (Class 1 Race)"},
|
||||
{"skatea", "Stadium (JET-Board Course)"},
|
||||
{"palshaft", "Palace Lobby"},
|
||||
{"palcab", "Palace Cable"},
|
||||
{"palroof", "Palace Roof"},
|
||||
{"throne", "Throne Room"},
|
||||
{"palent", "Palace Interior"},
|
||||
{"vinroom", "Power Station"},
|
||||
{"oracle", "Oracle"},
|
||||
{"onintent", "Onin's Tent"},
|
||||
{"hiphog", "Hip Hog"},
|
||||
{"hideout", "Underground Hideout"},
|
||||
{"gungame", "Gun Course"},
|
||||
{"caspad", "Landing Pad"},
|
||||
{"castle", "Weapons Factory"},
|
||||
{"ruins", "Dead Town"},
|
||||
{"atoll", "Pumping Station"},
|
||||
{"sewer", "Sewer"},
|
||||
{"strip", "Strip Mine"},
|
||||
{"tomb", "Mar's Tomb"},
|
||||
{"dig", "Dig Site"},
|
||||
{"drill", "Drill Platform"},
|
||||
{"mountain", "Mountain Temple"},
|
||||
{"forest", "Haven Forest"},
|
||||
{"mincan", "No Man's Canyon"},
|
||||
{"consite", "Construction Site"},
|
||||
{"under", "Underport"},
|
||||
{"nest", "Metal Head Nest"},
|
||||
{"village1", "Sandover Village"},
|
||||
};
|
||||
|
||||
// for remapping sub-level names to the matching one in level_names
|
||||
const std::map<std::string, std::string> level_name_remap = {
|
||||
{"forexita", "forexit"}, {"forexitb", "forexit"}, {"fordumpa", "fordump"},
|
||||
{"fordumpb", "fordump"}, {"fordumpc", "fordump"}, {"forresca", "forresc"},
|
||||
{"forrescb", "forresc"}, {"ctysluma", "ctyslum"}, {"ctyslumb", "ctyslum"},
|
||||
{"ctyslumc", "ctyslum"}, {"ctyfarma", "ctyfarm"}, {"ctyfarmb", "ctyfarm"},
|
||||
{"ctyinda", "ctyind"}, {"ctyindb", "ctyind"}, {"ctymarka", "ctymark"},
|
||||
{"ctymarkb", "ctymark"}, {"ctygena", "ctygen"}, {"ctygenb", "ctygen"},
|
||||
{"ctygenc", "ctygen"}, {"tomba", "tomb"}, {"tombb", "tomb"},
|
||||
{"tombboss", "tomb"}, {"tombc", "tomb"}, {"tombd", "tomb"},
|
||||
{"tombe", "tomb"}, {"dig1", "dig"}, {"dig3a", "dig"},
|
||||
{"drillmid", "drill"}, {"nestb", "nest"}, {"sewesc", "sewer"},
|
||||
{"garage", "stadium"}, {"casboss", "castle"}, {"introcst", "intro"},
|
||||
{"underb", "under"}, {"sagehut", "ruins"}, {"atollext", "atoll"},
|
||||
{"mtnext", "mountain"}, {"consiteb", "consite"}, {"drillmtn", "drill"},
|
||||
{"drillb", "drill"}, {"forestb", "forest"}, {"sewerb", "sewer"},
|
||||
{"sewescb", "sewer"},
|
||||
};
|
||||
|
||||
// levels that are not affected by time of day
|
||||
const std::vector<std::string> indoor_levels = {
|
||||
"intro", "introcst", "title", "prison", "forexita", "forexitb", "fordumpa", "fordumpb",
|
||||
"fordumpc", "forresca", "forrescb", "tomba", "tombb", "tombc", "tombd", "dig1",
|
||||
"dig3a", "dig3b", "palshaft", "sewer", "sewesc", "castle", "tombe", "tombboss",
|
||||
"gungame", "hideout", "vinroom", "under", "onintent", "oracle", "hiphog", "casboss"};
|
||||
|
||||
// time of day string to append to level name for icons
|
||||
const char* time_of_day_str(float time) {
|
||||
int hour = static_cast<int>(time);
|
||||
|
||||
if (hour > 6 && hour < 19) {
|
||||
return "day";
|
||||
} else {
|
||||
return "night";
|
||||
}
|
||||
}
|
||||
} // namespace jak2
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "discord.h"
|
||||
|
||||
namespace jak2 {
|
||||
extern const std::map<std::string, std::string> level_names;
|
||||
extern const std::map<std::string, std::string> level_name_remap;
|
||||
extern const std::vector<std::string> indoor_levels;
|
||||
const char* time_of_day_str(float time);
|
||||
} // namespace jak2
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/string_util.h"
|
||||
|
||||
#include "game/external/discord.h"
|
||||
#include "game/external/discord_jak1.h"
|
||||
#include "game/graphics/display.h"
|
||||
#include "game/graphics/gfx.h"
|
||||
#include "game/graphics/sceGraphicsInterface.h"
|
||||
@@ -440,9 +440,10 @@ void update_discord_rpc(u32 discord_info) {
|
||||
auto flutflut = Ptr<Symbol>(info->flutflut)->value;
|
||||
char* status = Ptr<String>(info->status).c()->data();
|
||||
char* level = Ptr<String>(info->level).c()->data();
|
||||
const char* full_level_name = jak1_get_full_level_name(Ptr<String>(info->level).c()->data());
|
||||
const char* full_level_name =
|
||||
get_full_level_name(level_names, level_name_remap, Ptr<String>(info->level).c()->data());
|
||||
memset(&rpc, 0, sizeof(rpc));
|
||||
if (!indoors(level)) {
|
||||
if (!indoors(indoor_levels, level)) {
|
||||
char level_with_tod[128];
|
||||
strcpy(level_with_tod, level);
|
||||
strcat(level_with_tod, "-");
|
||||
@@ -475,22 +476,16 @@ void update_discord_rpc(u32 discord_info) {
|
||||
strcpy(state, "Intro");
|
||||
} else if (cutscene != offset_of_s7()) {
|
||||
strcpy(state, "Watching a cutscene");
|
||||
strcpy(large_image_text, fmt::format("Cells: {} | Orbs: {} | Flies: {} | Deaths: {}",
|
||||
std::to_string(cells), std::to_string(orbs),
|
||||
std::to_string(scout_flies), std::to_string(deaths))
|
||||
.c_str());
|
||||
} else {
|
||||
strcpy(state, "Cells: ");
|
||||
strcat(state, std::to_string(cells).c_str());
|
||||
strcat(state, " | Orbs: ");
|
||||
strcat(state, std::to_string(orbs).c_str());
|
||||
strcat(state, " | Flies: ");
|
||||
strcat(state, std::to_string(scout_flies).c_str());
|
||||
strcpy(state, fmt::format("Cells: {} | Orbs: {} | Flies: {}", std::to_string(cells),
|
||||
std::to_string(orbs), std::to_string(scout_flies))
|
||||
.c_str());
|
||||
|
||||
strcat(large_image_text, " | Cells: ");
|
||||
strcat(large_image_text, std::to_string(cells).c_str());
|
||||
strcat(large_image_text, " | Orbs: ");
|
||||
strcat(large_image_text, std::to_string(orbs).c_str());
|
||||
strcat(large_image_text, " | Flies: ");
|
||||
strcat(large_image_text, std::to_string(scout_flies).c_str());
|
||||
strcat(large_image_text, " | Deaths: ");
|
||||
strcat(large_image_text, std::to_string(deaths).c_str());
|
||||
strcat(large_image_text, fmt::format(" | Deaths: {}", std::to_string(deaths)).c_str());
|
||||
}
|
||||
rpc.largeImageText = large_image_text;
|
||||
rpc.state = state;
|
||||
@@ -501,7 +496,7 @@ void update_discord_rpc(u32 discord_info) {
|
||||
strcpy(small_image_key, "flutflut");
|
||||
strcpy(small_image_text, "Riding on Flut Flut");
|
||||
} else {
|
||||
if (!indoors(level)) {
|
||||
if (!indoors(indoor_levels, 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());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "kmachine.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -7,9 +8,10 @@
|
||||
#include "common/log/log.h"
|
||||
#include "common/symbols.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "common/util/FontUtils.h"
|
||||
#include "common/util/string_util.h"
|
||||
|
||||
#include "game/external/discord.h"
|
||||
#include "game/external/discord_jak2.h"
|
||||
#include "game/graphics/display.h"
|
||||
#include "game/graphics/jak2_texture_remap.h"
|
||||
#include "game/kernel/common/Symbol4.h"
|
||||
@@ -517,28 +519,47 @@ void update_discord_rpc(u32 discord_info) {
|
||||
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();
|
||||
int orbs = (int)info->orb_count;
|
||||
int gems = (int)info->gem_count;
|
||||
u32 deaths = info->death_count;
|
||||
// convert encodings
|
||||
std::string status = get_font_bank(GameTextVersion::JAK2)
|
||||
->convert_game_to_utf8(Ptr<String>(info->status).c()->data());
|
||||
|
||||
// get rid of special encodings like <COLOR_WHITE>
|
||||
std::regex r("<.*?>");
|
||||
while (std::regex_search(status, r)) {
|
||||
status = std::regex_replace(status, r, "");
|
||||
}
|
||||
|
||||
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 time = info->time_of_day;
|
||||
float percent_completed = info->percent_completed;
|
||||
std::bitset<32> focus_status;
|
||||
focus_status = info->focus_status;
|
||||
char* task = Ptr<String>(info->task).c()->data();
|
||||
|
||||
// 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());
|
||||
get_full_level_name(level_names, level_name_remap, 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);
|
||||
// if we have an active task, set the mission specific image for it
|
||||
// also small hack to prevent oracle image from showing up while inside levels
|
||||
// like hideout, onintent, etc.
|
||||
if (strcmp(task, "unknown") != 0 && strcmp(task, "city-oracle") != 0) {
|
||||
strcpy(large_image_key, task);
|
||||
} else {
|
||||
strcpy(large_image_key, level);
|
||||
// if we are in an outdoors level, use the picture for the corresponding time of day
|
||||
if (!indoors(indoor_levels, 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")) {
|
||||
@@ -548,20 +569,42 @@ void update_discord_rpc(u32 discord_info) {
|
||||
rpc.largeImageKey = large_image_key;
|
||||
if (cutscene != offset_of_s7()) {
|
||||
strcpy(state, "Watching a cutscene");
|
||||
// temporarily move these counters to the large image tooltip during a cutscene
|
||||
strcat(large_image_text,
|
||||
fmt::format(" | {:.0f}% | Orbs: {} | Gems: {} | {}", percent_completed,
|
||||
std::to_string(orbs), std::to_string(gems), get_time_of_day(time))
|
||||
.c_str());
|
||||
} else {
|
||||
strcpy(state, fmt::format("{:.0f}% | Orbs: {} | Gems: {}", percent_completed,
|
||||
std::to_string(orbs), std::to_string(gems))
|
||||
strcpy(state, fmt::format("{:.0f}% | Orbs: {} | Gems: {} | {}", percent_completed,
|
||||
std::to_string(orbs), std::to_string(gems), get_time_of_day(time))
|
||||
.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());
|
||||
// check for any special conditions to display for the small image
|
||||
if (FOCUS_TEST(focus_status, FocusStatus::Board)) {
|
||||
strcpy(small_image_key, "focus-status-board");
|
||||
strcpy(small_image_text, "On the JET-Board");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Mech)) {
|
||||
strcpy(small_image_key, "focus-status-mech");
|
||||
strcpy(small_image_text, "In the Titan Suit");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Pilot)) {
|
||||
strcpy(small_image_key, "focus-status-pilot");
|
||||
strcpy(small_image_text, "Driving a Zoomer");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Indax)) {
|
||||
strcpy(small_image_key, "focus-status-indax");
|
||||
strcpy(small_image_text, "Playing as Daxter");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Dark)) {
|
||||
strcpy(small_image_key, "focus-status-dark");
|
||||
strcpy(small_image_text, "Dark Jak");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Disable) &&
|
||||
FOCUS_TEST(focus_status, FocusStatus::Grabbed)) {
|
||||
// being in a turret sets disable and grabbed flags
|
||||
strcpy(small_image_key, "focus-status-turret");
|
||||
strcpy(small_image_text, "In a Gunpod");
|
||||
} else if (FOCUS_TEST(focus_status, FocusStatus::Gun)) {
|
||||
strcpy(small_image_key, "focus-status-gun");
|
||||
strcpy(small_image_text, "Using a Gun");
|
||||
} else {
|
||||
strcpy(small_image_key, "");
|
||||
strcpy(small_image_text, "");
|
||||
@@ -569,7 +612,7 @@ void update_discord_rpc(u32 discord_info) {
|
||||
rpc.smallImageKey = small_image_key;
|
||||
rpc.smallImageText = small_image_text;
|
||||
rpc.startTimestamp = gStartTime;
|
||||
rpc.details = status;
|
||||
rpc.details = status.c_str();
|
||||
rpc.partySize = 0;
|
||||
rpc.partyMax = 0;
|
||||
Discord_UpdatePresence(&rpc);
|
||||
|
||||
@@ -53,15 +53,55 @@ struct MouseInfo {
|
||||
// (speedy float :offset 108)
|
||||
};
|
||||
|
||||
enum class FocusStatus : u32 {
|
||||
Disable = 0,
|
||||
Dead = 1,
|
||||
Ignore = 2,
|
||||
Inactive = 3,
|
||||
Dangerous = 4,
|
||||
InAir = 5,
|
||||
Hit = 6,
|
||||
Grabbed = 7,
|
||||
InHead = 8,
|
||||
TouchWater = 9,
|
||||
OnWater = 10,
|
||||
UnderWater = 11,
|
||||
EdgeGrab = 12,
|
||||
Pole = 13,
|
||||
PilotRiding = 14,
|
||||
Flut = 15,
|
||||
Tube = 16,
|
||||
Ice = 17,
|
||||
Board = 18,
|
||||
Gun = 19,
|
||||
Pilot = 20,
|
||||
Mech = 21,
|
||||
Dark = 22,
|
||||
Rail = 23,
|
||||
Halfpipe = 24,
|
||||
Carry = 25,
|
||||
Super = 26,
|
||||
Shooting = 27,
|
||||
Indax = 28,
|
||||
Arrestable = 29,
|
||||
Teleporting = 30,
|
||||
FS31 = 31,
|
||||
Max = 32
|
||||
};
|
||||
|
||||
#define FOCUS_TEST(status, foc) (status.test(static_cast<size_t>(foc)))
|
||||
|
||||
struct DiscordInfo {
|
||||
u32 orb_count; // (pointer float)
|
||||
u32 gem_count; // (pointer float)
|
||||
u32 death_count; // (pointer int32)
|
||||
float orb_count; // float
|
||||
float gem_count; // float
|
||||
u32 death_count; // int32
|
||||
u32 status; // string
|
||||
u32 level; // string
|
||||
u32 cutscene; // symbol - bool
|
||||
u32 time_of_day; // (pointer float
|
||||
float time_of_day; // float
|
||||
float percent_completed; // float
|
||||
u32 focus_status; // uint32
|
||||
u32 task; // string
|
||||
};
|
||||
// To speedup finding the auto-splitter block in GOAL memory
|
||||
// all this has is a marker for LiveSplit to find, and then the pointer
|
||||
|
||||
@@ -700,6 +700,96 @@
|
||||
obj
|
||||
)
|
||||
|
||||
(defun get-active-mission-description ((info discord-info))
|
||||
"Added in PC port to retrieve a string for the currently active mission to display in Discord."
|
||||
(let* ((nodes (-> *game-info* sub-task-list))
|
||||
(level (level-get-target-inside *level*))
|
||||
(task-level-idx (if level (-> level info task-level) 0))
|
||||
(side-missions-start (the int (game-task-node city-burning-bush-ring-1-introduction)))
|
||||
(side-missions-end (the int (game-task-node stadium-burning-bush-race-class1-r-resolution)))
|
||||
(gg-node? (lambda ((node game-task-node-info))
|
||||
(or (= (-> node task) (game-task city-red-gun-training))
|
||||
(= (-> node task) (game-task city-yellow-gun-training))
|
||||
(= (-> node task) (game-task city-blue-gun-training))
|
||||
(= (-> node task) (game-task city-dark-gun-training))))))
|
||||
(dotimes (i (-> nodes length))
|
||||
(when (nonzero? i)
|
||||
(let* ((node (-> nodes i))
|
||||
(mgr-info (-> node info))
|
||||
(mgr-handle (if mgr-info (-> mgr-info manager) (the-as handle #f))))
|
||||
;; if we are not in a valid task level, just return.
|
||||
(when (= task-level-idx 0)
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-not-in-mission) #f))
|
||||
)
|
||||
;; check for onin-game
|
||||
(when (and (= (-> level name) 'onintent)
|
||||
(or (= (the game-task-node i) (game-task-node city-play-onin-game-post-game))
|
||||
(= (the game-task-node i) (game-task-node city-play-onin-game-skill)))
|
||||
(task-node-open? (the game-task-node i))
|
||||
(process-by-name "onin-game" *active-pool*))
|
||||
(set! (-> info task) "onin-game")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-onin-game) #f))
|
||||
)
|
||||
;; check for whack-a-metal
|
||||
(when (and (= (-> level name) 'hiphog)
|
||||
(= (the game-task-node i) (game-task-node city-whack-post-game))
|
||||
(task-node-open? (the game-task-node i))
|
||||
(process-by-name "whack-a-metal" *active-pool*))
|
||||
(set! (-> info task) "city-whack")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-whack) #f))
|
||||
)
|
||||
;; check for gungame (there is probably a better way to do this)
|
||||
(when (and (= (-> level name) 'gungame)
|
||||
(gg-node? node)
|
||||
(task-node-open? (the game-task-node i)))
|
||||
(awhen (process-by-ename "training-manager-2")
|
||||
(if (= (-> it next-state name) 'course)
|
||||
(let ((feat (-> *setting-control* user-current features)))
|
||||
(cond
|
||||
((not (logtest? feat (game-feature gun-yellow gun-blue gun-dark)))
|
||||
(set! (-> info task) "city-red-gun-training")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-red-gun-training) #f))
|
||||
)
|
||||
((not (logtest? feat (game-feature gun-red gun-blue gun-dark)))
|
||||
(set! (-> info task) "city-yellow-gun-training")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-yellow-gun-training) #f))
|
||||
)
|
||||
((not (logtest? feat (game-feature gun-red gun-yellow gun-dark)))
|
||||
(set! (-> info task) "city-blue-gun-training")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-blue-gun-training) #f))
|
||||
)
|
||||
(else
|
||||
(set! (-> info task) "city-dark-gun-training")
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-dark-gun-training) #f))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
;; check for side missions
|
||||
(when (and (>= i side-missions-start)
|
||||
(<= i side-missions-end)
|
||||
(task-node-open? (the game-task-node i))
|
||||
mgr-handle)
|
||||
(set! (-> info task) (game-task->string (-> node task)))
|
||||
(return (lookup-text! *common-text* (text-id discord-rpc-in-side-mission) #f))
|
||||
)
|
||||
;; check for any other tasks
|
||||
(when (and (not (gg-node? node))
|
||||
(task-node-open? (the game-task-node i))
|
||||
(!= (-> node description) (the text-id 0))
|
||||
(= (-> *task-level* task-level-idx) (-> node level)))
|
||||
(set! (-> info task) (game-task->string (-> node task)))
|
||||
(return (lookup-text! *common-text* (-> node description) #f))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(lookup-text! *common-text* (text-id discord-rpc-not-in-mission) #f)
|
||||
)
|
||||
)
|
||||
|
||||
(defmethod close! game-task-node-info ((obj game-task-node-info) (arg0 symbol))
|
||||
(when (not (logtest? (-> obj flags) (game-task-node-flag closed)))
|
||||
(let ((task-node-close-func
|
||||
|
||||
@@ -560,6 +560,16 @@
|
||||
(scene-subtitles-disabled #x30e)
|
||||
(board-name #x30f)
|
||||
(progress-unknown-continue #x310)
|
||||
|
||||
;; PC port IDs START
|
||||
(discord-rpc-not-in-mission #x1100)
|
||||
(discord-rpc-in-side-mission #x1101)
|
||||
(discord-rpc-red-gun-training #x1102)
|
||||
(discord-rpc-yellow-gun-training #x1103)
|
||||
(discord-rpc-blue-gun-training #x1104)
|
||||
(discord-rpc-dark-gun-training #x1105)
|
||||
(discord-rpc-onin-game #x1106)
|
||||
(discord-rpc-whack #x1107)
|
||||
)
|
||||
;; ---text-id
|
||||
|
||||
|
||||
@@ -68,18 +68,19 @@
|
||||
|
||||
;; jak 2 discord rpc structure
|
||||
(deftype discord-info (structure)
|
||||
((orb-count (pointer float))
|
||||
(gem-count (pointer float))
|
||||
(death-count (pointer int32))
|
||||
((orb-count float)
|
||||
(gem-count float)
|
||||
(death-count int32)
|
||||
(status string)
|
||||
(level string)
|
||||
(cutscene? symbol)
|
||||
(time-of-day (pointer float))
|
||||
(time-of-day float)
|
||||
(percent-complete float)
|
||||
(focus-status uint32)
|
||||
(task string) ;; currenly active game-task used for mission specific images
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; resets
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
|#
|
||||
|
||||
(define-extern get-active-mission-description (function discord-info string))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;; methods
|
||||
@@ -62,19 +63,29 @@
|
||||
(defmethod update-discord-rpc pc-settings-jak2 ((obj pc-settings-jak2))
|
||||
"update discord rpc module"
|
||||
(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 II™")
|
||||
(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 task) "unknown")
|
||||
(set! (-> info status) (get-active-mission-description info))
|
||||
;; 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))
|
||||
(cond
|
||||
((or (aif (level-get *level* 'title) (= (-> it status) 'active))
|
||||
(and *progress-process*
|
||||
(= 'title (-> *progress-process* 0 state-stack 0))))
|
||||
;; in title screen.
|
||||
(set! (-> info level) (symbol->string 'title))
|
||||
(set! (-> info status) "In title screen"))
|
||||
(else
|
||||
(set! (-> info level) (aif (-> *load-state* vis-nick) (symbol->string it) "unknown")))
|
||||
)
|
||||
(set! (-> info cutscene?) (-> *pc-settings* movie?))
|
||||
(set! (-> info time-of-day) (-> *time-of-day-context* time))
|
||||
(set! (-> info percent-complete) (calculate-percentage *game-info*))
|
||||
(set! (-> info focus-status) (if *target* (-> *target* focus-status) 0))
|
||||
;; TODO - wrapping in `with-profiler` causes an error, fix it
|
||||
(pc-discord-rpc-update info))
|
||||
(pc-discord-rpc-update info)
|
||||
)
|
||||
(none))
|
||||
|
||||
(defmethod update-speedrun pc-settings-jak2 ((obj pc-settings-jak2))
|
||||
|
||||
Reference in New Issue
Block a user