mirror of
https://github.com/open-goal/jak-project
synced 2026-07-10 23:22:17 -04:00
jak3: speedrunner mode (#3761)
Base implementation of the popup menu and speedrunner mode in Jak 3. Autosplitter is untested because I'm on Linux. Also a couple of other misc changes: - Model replacements can now have custom bone weights. Needs the "Use Custom Bone Weights" property (provided by the OpenGOAL Blender plugin) enabled in Blender. - Better error message for lump syntax errors in custom level JSON files.
This commit is contained in:
@@ -11,8 +11,6 @@ void pc_set_levels(u32 lev_list);
|
||||
void pc_set_active_levels(u32 lev_list);
|
||||
u32 alloc_vagdir_names(u32 heap_sym);
|
||||
inline u64 bool_to_symbol(const bool val);
|
||||
// TODO - move to common
|
||||
void encode_utf8_string(u32 src_str_ptr, u32 str_dest_ptr);
|
||||
void init_autosplit_struct();
|
||||
void callback_fetch_external_speedrun_times(bool success,
|
||||
const std::string& cache_id,
|
||||
|
||||
@@ -369,7 +369,8 @@ void InitMachine_PCPort() {
|
||||
make_function_symbol_from_c("__pc-set-active-levels",
|
||||
(void*)kmachine_extras::pc_set_active_levels);
|
||||
make_function_symbol_from_c("__pc-get-tex-remap", (void*)lookup_jak3_texture_dest_offset);
|
||||
// make_function_symbol_from_c("pc-init-autosplitter-struct", (void*)init_autosplit_struct);
|
||||
make_function_symbol_from_c("pc-init-autosplitter-struct",
|
||||
(void*)kmachine_extras::init_autosplit_struct);
|
||||
|
||||
// discord rich presence
|
||||
make_function_symbol_from_c("pc-discord-rpc-update", (void*)kmachine_extras::update_discord_rpc);
|
||||
@@ -395,6 +396,45 @@ void InitMachine_PCPort() {
|
||||
(void*)pc_get_num_external_highscores);
|
||||
*/
|
||||
|
||||
// speedrunning stuff
|
||||
make_function_symbol_from_c("pc-sr-mode-get-practice-entries-amount",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entries_amount);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-practice-entry-name",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_name);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-practice-entry-continue-point",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_continue_point);
|
||||
make_function_symbol_from_c(
|
||||
"pc-sr-mode-get-practice-entry-history-success",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_history_success);
|
||||
make_function_symbol_from_c(
|
||||
"pc-sr-mode-get-practice-entry-history-attempts",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_history_attempts);
|
||||
make_function_symbol_from_c(
|
||||
"pc-sr-mode-get-practice-entry-session-success",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_session_success);
|
||||
make_function_symbol_from_c(
|
||||
"pc-sr-mode-get-practice-entry-session-attempts",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_session_attempts);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-practice-entry-avg-time",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_avg_time);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-practice-entry-fastest-time",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_practice_entry_fastest_time);
|
||||
make_function_symbol_from_c("pc-sr-mode-record-practice-entry-attempt!",
|
||||
(void*)kmachine_extras::pc_sr_mode_record_practice_entry_attempt);
|
||||
make_function_symbol_from_c("pc-sr-mode-init-practice-info!",
|
||||
(void*)kmachine_extras::pc_sr_mode_init_practice_info);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-custom-category-amount",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_custom_category_amount);
|
||||
make_function_symbol_from_c("pc-sr-mode-get-custom-category-name",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_custom_category_name);
|
||||
make_function_symbol_from_c(
|
||||
"pc-sr-mode-get-custom-category-continue-point",
|
||||
(void*)kmachine_extras::pc_sr_mode_get_custom_category_continue_point);
|
||||
make_function_symbol_from_c("pc-sr-mode-init-custom-category-info!",
|
||||
(void*)kmachine_extras::pc_sr_mode_init_custom_category_info);
|
||||
make_function_symbol_from_c("pc-sr-mode-dump-new-custom-category",
|
||||
(void*)kmachine_extras::pc_sr_mode_dump_new_custom_category);
|
||||
|
||||
// setup string constants
|
||||
auto user_dir_path = file_util::get_user_config_dir();
|
||||
intern_from_c(-1, 0, "*pc-user-dir-base-path*")->value() =
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace jak3 {
|
||||
namespace kmachine_extras {
|
||||
using namespace jak3;
|
||||
AutoSplitterBlock g_auto_splitter_block_jak3;
|
||||
|
||||
void update_discord_rpc(u32 discord_info) {
|
||||
if (gDiscordRpcEnabled) {
|
||||
@@ -225,5 +225,737 @@ inline bool symbol_to_bool(const u32 symptr) {
|
||||
return symptr != s7.offset;
|
||||
}
|
||||
|
||||
void init_autosplit_struct() {
|
||||
g_auto_splitter_block_jak3.pointer_to_symbol =
|
||||
(u64)g_ee_main_mem + (u64)intern_from_c(-1, 0, "*autosplit-info-jak3*")->value();
|
||||
}
|
||||
|
||||
// TODO - currently using a single mutex for all background task synchronization
|
||||
std::mutex background_task_lock;
|
||||
|
||||
std::string last_rpc_error;
|
||||
|
||||
// TODO - add a TTL to this
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, float>>>
|
||||
external_speedrun_time_cache = {};
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, float>>>
|
||||
external_race_time_cache = {};
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, float>>>
|
||||
external_highscores_cache = {};
|
||||
|
||||
// clang-format off
|
||||
// TODO - eventually don't depend on SRC
|
||||
const std::unordered_map<std::string, std::string> external_speedrun_lookup_urls = {
|
||||
{"any", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/9d8p1qkn?embed=players&max=200"},
|
||||
{"nooob", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/5dwj0n0k?embed=players&max=200"},
|
||||
{"allmissions", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/xd1r98k8?embed=players&max=200"},
|
||||
{"100", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/zd30nndn?embed=players&max=200"},
|
||||
{"anyorbs", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/jdzw79vd?embed=players&max=200"},
|
||||
{"anyhero", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/category/9kvp50kg?embed=players&max=200"}};
|
||||
const std::unordered_map<std::string, std::string> external_race_lookup_urls = {
|
||||
{"time-trial", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/level/kwjvyzwg/jdr8onk6?embed=players&max=200"},
|
||||
{"rally", "https://www.speedrun.com/api/v1/leaderboards/nj1nww1p/level/owo3kyw6/jdr8onk6?embed=players&max=200"}};
|
||||
const std::unordered_map<std::string, std::string> external_highscores_lookup_urls = {
|
||||
{"was-pre-game", "https://api.jakspeedruns.workers.dev/v1/highscores/9"},
|
||||
{"air-time", "https://api.jakspeedruns.workers.dev/v1/highscores/10"},
|
||||
{"total-air-time", "https://api.jakspeedruns.workers.dev/v1/highscores/11"},
|
||||
{"jump-distance", "https://api.jakspeedruns.workers.dev/v1/highscores/12"},
|
||||
{"total-jump-distance", "https://api.jakspeedruns.workers.dev/v1/highscores/13"},
|
||||
{"roll-count", "https://api.jakspeedruns.workers.dev/v1/highscores/14"},
|
||||
{"wascity-gungame", "https://api.jakspeedruns.workers.dev/v1/highscores/15"},
|
||||
{"jetboard", "https://api.jakspeedruns.workers.dev/v1/highscores/16"},
|
||||
{"gungame-yellow-2", "https://api.jakspeedruns.workers.dev/v1/highscores/17"},
|
||||
{"gungame-red-2", "https://api.jakspeedruns.workers.dev/v1/highscores/18"},
|
||||
{"gungame-ratchet", "https://api.jakspeedruns.workers.dev/v1/highscores/19"},
|
||||
{"gungame-clank", "https://api.jakspeedruns.workers.dev/v1/highscores/20"},
|
||||
{"power-game", "https://api.jakspeedruns.workers.dev/v1/highscores/21"},
|
||||
{"destroy-interceptors", "https://api.jakspeedruns.workers.dev/v1/highscores/22"}};
|
||||
// clang-format on
|
||||
|
||||
void callback_fetch_external_speedrun_times(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
|
||||
if (!success) {
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(true);
|
||||
if (result) {
|
||||
last_rpc_error = result.value();
|
||||
} else {
|
||||
last_rpc_error = "Unexpected Error Occurred";
|
||||
}
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - might be nice to have an error if we get an unexpected payload
|
||||
if (!result) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
const auto data = safe_parse_json(result.value());
|
||||
if (!data || !data->contains("data") || !data->at("data").contains("players") ||
|
||||
!data->at("data").at("players").contains("data") || !data->at("data").contains("runs")) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& players = data->at("data").at("players").at("data");
|
||||
auto& runs = data->at("data").at("runs");
|
||||
std::vector<std::pair<std::string, float>> times = {};
|
||||
for (const auto& run_info : runs) {
|
||||
std::pair<std::string, float> time_info;
|
||||
if (players.size() > times.size() && players.at(times.size()).contains("names") &&
|
||||
players.at(times.size()).at("names").contains("international")) {
|
||||
time_info.first = players.at(times.size()).at("names").at("international");
|
||||
} else if (players.size() > times.size() && players.at(times.size()).contains("name")) {
|
||||
time_info.first = players.at(times.size()).at("name");
|
||||
} else {
|
||||
time_info.first = "Unknown";
|
||||
}
|
||||
if (run_info.contains("run") && run_info.at("run").contains("times") &&
|
||||
run_info.at("run").at("times").contains("primary_t")) {
|
||||
time_info.second = run_info.at("run").at("times").at("primary_t");
|
||||
times.push_back(time_info);
|
||||
}
|
||||
}
|
||||
external_speedrun_time_cache[cache_id] = times;
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
}
|
||||
|
||||
// TODO - duplicate code, put it in a function
|
||||
void callback_fetch_external_race_times(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
|
||||
if (!success) {
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(true);
|
||||
if (result) {
|
||||
last_rpc_error = result.value();
|
||||
} else {
|
||||
last_rpc_error = "Unexpected Error Occurred";
|
||||
}
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - might be nice to have an error if we get an unexpected payload
|
||||
if (!result) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
const auto data = safe_parse_json(result.value());
|
||||
if (!data || !data->contains("data") || !data->at("data").contains("players") ||
|
||||
!data->at("data").at("players").contains("data") || !data->at("data").contains("runs")) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& players = data->at("data").at("players").at("data");
|
||||
auto& runs = data->at("data").at("runs");
|
||||
std::vector<std::pair<std::string, float>> times = {};
|
||||
for (const auto& run_info : runs) {
|
||||
std::pair<std::string, float> time_info;
|
||||
if (players.size() > times.size() && players.at(times.size()).contains("names") &&
|
||||
players.at(times.size()).at("names").contains("international")) {
|
||||
time_info.first = players.at(times.size()).at("names").at("international");
|
||||
} else if (players.size() > times.size() && players.at(times.size()).contains("name")) {
|
||||
time_info.first = players.at(times.size()).at("name");
|
||||
} else {
|
||||
time_info.first = "Unknown";
|
||||
}
|
||||
if (run_info.contains("run") && run_info.at("run").contains("times") &&
|
||||
run_info.at("run").at("times").contains("primary_t")) {
|
||||
time_info.second = run_info.at("run").at("times").at("primary_t");
|
||||
times.push_back(time_info);
|
||||
}
|
||||
}
|
||||
external_race_time_cache[cache_id] = times;
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
}
|
||||
|
||||
// TODO - duplicate code, put it in a function
|
||||
void callback_fetch_external_highscores(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
|
||||
if (!success) {
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(true);
|
||||
if (result) {
|
||||
last_rpc_error = result.value();
|
||||
} else {
|
||||
last_rpc_error = "Unexpected Error Occurred";
|
||||
}
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO - might be nice to have an error if we get an unexpected payload
|
||||
if (!result) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
const auto data = safe_parse_json(result.value());
|
||||
std::vector<std::pair<std::string, float>> times = {};
|
||||
for (const auto& highscore_info : data.value()) {
|
||||
if (highscore_info.contains("playerName") && highscore_info.contains("score")) {
|
||||
std::pair<std::string, float> time_info;
|
||||
time_info.first = highscore_info.at("playerName");
|
||||
time_info.second = highscore_info.at("score");
|
||||
times.push_back(time_info);
|
||||
}
|
||||
}
|
||||
external_highscores_cache[cache_id] = times;
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(false);
|
||||
}
|
||||
|
||||
void pc_fetch_external_speedrun_times(u32 speedrun_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto speedrun_id = std::string(Ptr<String>(speedrun_id_ptr).c()->data());
|
||||
if (external_speedrun_lookup_urls.find(speedrun_id) == external_speedrun_lookup_urls.end()) {
|
||||
lg::error("No URL for speedrun_id: '{}'", speedrun_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// First check to see if we've already retrieved this info
|
||||
if (external_speedrun_time_cache.find(speedrun_id) == external_speedrun_time_cache.end()) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(true);
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(false);
|
||||
// otherwise, hit the URL
|
||||
WebRequestJobPayload req;
|
||||
req.callback = callback_fetch_external_speedrun_times;
|
||||
req.url = external_speedrun_lookup_urls.at(speedrun_id);
|
||||
req.cache_id = speedrun_id;
|
||||
g_background_worker.enqueue_webrequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
void pc_fetch_external_race_times(u32 race_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto race_id = std::string(Ptr<String>(race_id_ptr).c()->data());
|
||||
if (external_race_lookup_urls.find(race_id) == external_race_lookup_urls.end()) {
|
||||
lg::error("No URL for race_id: '{}'", race_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// First check to see if we've already retrieved this info
|
||||
if (external_race_time_cache.find(race_id) == external_race_time_cache.end()) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(true);
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(false);
|
||||
// otherwise, hit the URL
|
||||
WebRequestJobPayload req;
|
||||
req.callback = callback_fetch_external_race_times;
|
||||
req.url = external_race_lookup_urls.at(race_id);
|
||||
req.cache_id = race_id;
|
||||
g_background_worker.enqueue_webrequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
void pc_fetch_external_highscores(u32 highscore_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto highscore_id = std::string(Ptr<String>(highscore_id_ptr).c()->data());
|
||||
if (external_highscores_lookup_urls.find(highscore_id) == external_highscores_lookup_urls.end()) {
|
||||
lg::error("No URL for highscore_id: '{}'", highscore_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// First check to see if we've already retrieved this info
|
||||
if (external_highscores_cache.find(highscore_id) == external_highscores_cache.end()) {
|
||||
intern_from_c(-1, 0, "*pc-waiting-on-rpc?*")->value() = bool_to_symbol(true);
|
||||
intern_from_c(-1, 0, "*pc-rpc-error?*")->value() = bool_to_symbol(false);
|
||||
// otherwise, hit the URL
|
||||
WebRequestJobPayload req;
|
||||
req.callback = callback_fetch_external_highscores;
|
||||
req.url = external_highscores_lookup_urls.at(highscore_id);
|
||||
req.cache_id = highscore_id;
|
||||
g_background_worker.enqueue_webrequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
void pc_get_external_speedrun_time(u32 speedrun_id_ptr,
|
||||
s32 index,
|
||||
u32 name_dest_ptr,
|
||||
u32 time_dest_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto speedrun_id = std::string(Ptr<String>(speedrun_id_ptr).c()->data());
|
||||
if (external_speedrun_time_cache.find(speedrun_id) != external_speedrun_time_cache.end()) {
|
||||
const auto& runs = external_speedrun_time_cache.at(speedrun_id);
|
||||
if (index < (int)runs.size()) {
|
||||
const auto& run_info = external_speedrun_time_cache.at(speedrun_id).at(index);
|
||||
std::string converted =
|
||||
get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(run_info.first);
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = run_info.second;
|
||||
} else {
|
||||
std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game("");
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = -1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pc_get_external_race_time(u32 race_id_ptr, s32 index, u32 name_dest_ptr, u32 time_dest_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto race_id = std::string(Ptr<String>(race_id_ptr).c()->data());
|
||||
if (external_race_time_cache.find(race_id) != external_race_time_cache.end()) {
|
||||
const auto& runs = external_race_time_cache.at(race_id);
|
||||
if (index < (int)runs.size()) {
|
||||
const auto& run_info = external_race_time_cache.at(race_id).at(index);
|
||||
std::string converted =
|
||||
get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(run_info.first);
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = run_info.second;
|
||||
} else {
|
||||
std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game("");
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = -1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pc_get_external_highscore(u32 highscore_id_ptr,
|
||||
s32 index,
|
||||
u32 name_dest_ptr,
|
||||
u32 time_dest_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto highscore_id = std::string(Ptr<String>(highscore_id_ptr).c()->data());
|
||||
if (external_highscores_cache.find(highscore_id) != external_highscores_cache.end()) {
|
||||
const auto& runs = external_highscores_cache.at(highscore_id);
|
||||
if (index < (int)runs.size()) {
|
||||
const auto& run_info = external_highscores_cache.at(highscore_id).at(index);
|
||||
std::string converted =
|
||||
get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(run_info.first);
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = run_info.second;
|
||||
} else {
|
||||
std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game("");
|
||||
strcpy(Ptr<String>(name_dest_ptr).c()->data(), converted.c_str());
|
||||
*(Ptr<float>(time_dest_ptr).c()) = -1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s32 pc_get_num_external_speedrun_times(u32 speedrun_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto speedrun_id = std::string(Ptr<String>(speedrun_id_ptr).c()->data());
|
||||
if (external_speedrun_time_cache.find(speedrun_id) != external_speedrun_time_cache.end()) {
|
||||
return external_speedrun_time_cache.at(speedrun_id).size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 pc_get_num_external_race_times(u32 race_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto race_id = std::string(Ptr<String>(race_id_ptr).c()->data());
|
||||
if (external_race_time_cache.find(race_id) != external_race_time_cache.end()) {
|
||||
return external_race_time_cache.at(race_id).size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 pc_get_num_external_highscores(u32 highscore_id_ptr) {
|
||||
std::scoped_lock lock{background_task_lock};
|
||||
auto highscore_id = std::string(Ptr<String>(highscore_id_ptr).c()->data());
|
||||
if (external_highscores_cache.find(highscore_id) != external_highscores_cache.end()) {
|
||||
return external_highscores_cache.at(highscore_id).size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void to_json(json& j, const SpeedrunPracticeEntryHistoryAttempt& obj) {
|
||||
if (obj.time) {
|
||||
j["time"] = obj.time.value();
|
||||
} else {
|
||||
j["time"] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const json& j, SpeedrunPracticeEntryHistoryAttempt& obj) {
|
||||
if (j["time"].is_null()) {
|
||||
obj.time = {};
|
||||
} else {
|
||||
obj.time = j["time"];
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const SpeedrunPracticeEntry& obj) {
|
||||
json_serialize(name);
|
||||
json_serialize(continue_point_name);
|
||||
json_serialize(flags);
|
||||
json_serialize(completed_task);
|
||||
json_serialize(features);
|
||||
json_serialize(secrets);
|
||||
json_serialize(vehicles);
|
||||
json_serialize(starting_position);
|
||||
json_serialize(starting_rotation);
|
||||
json_serialize(starting_camera_position);
|
||||
json_serialize(starting_camera_rotation);
|
||||
json_serialize(start_zone_v1);
|
||||
json_serialize(start_zone_v2);
|
||||
json_serialize_optional(end_zone_v1);
|
||||
json_serialize_optional(end_zone_v2);
|
||||
json_serialize_optional(end_task);
|
||||
json_serialize(history);
|
||||
}
|
||||
|
||||
void from_json(const json& j, SpeedrunPracticeEntry& obj) {
|
||||
json_deserialize_if_exists(name);
|
||||
json_deserialize_if_exists(continue_point_name);
|
||||
json_deserialize_if_exists(flags);
|
||||
json_deserialize_if_exists(completed_task);
|
||||
json_deserialize_if_exists(features);
|
||||
json_deserialize_if_exists(secrets);
|
||||
json_deserialize_if_exists(vehicles);
|
||||
json_deserialize_if_exists(starting_position);
|
||||
json_deserialize_if_exists(starting_rotation);
|
||||
json_deserialize_if_exists(starting_camera_position);
|
||||
json_deserialize_if_exists(starting_camera_rotation);
|
||||
json_deserialize_if_exists(start_zone_v1);
|
||||
json_deserialize_if_exists(start_zone_v2);
|
||||
json_deserialize_optional_if_exists(end_zone_v1);
|
||||
json_deserialize_optional_if_exists(end_zone_v2);
|
||||
json_deserialize_optional_if_exists(end_task);
|
||||
json_deserialize_if_exists(history);
|
||||
}
|
||||
|
||||
void to_json(json& j, const SpeedrunCustomCategoryEntry& obj) {
|
||||
json_serialize(name);
|
||||
json_serialize(secrets);
|
||||
json_serialize(features);
|
||||
json_serialize(vehicles);
|
||||
json_serialize(forbidden_features);
|
||||
json_serialize(cheats);
|
||||
json_serialize(continue_point_name);
|
||||
json_serialize(completed_task);
|
||||
}
|
||||
|
||||
void from_json(const json& j, SpeedrunCustomCategoryEntry& obj) {
|
||||
json_deserialize_if_exists(name);
|
||||
json_deserialize_if_exists(secrets);
|
||||
json_deserialize_if_exists(features);
|
||||
json_deserialize_if_exists(vehicles);
|
||||
json_deserialize_if_exists(forbidden_features);
|
||||
json_deserialize_if_exists(cheats);
|
||||
json_deserialize_if_exists(continue_point_name);
|
||||
json_deserialize_if_exists(completed_task);
|
||||
}
|
||||
|
||||
std::vector<SpeedrunPracticeEntry> g_speedrun_practice_entries;
|
||||
std::unordered_map<int, SpeedrunPracticeState> g_speedrun_practice_state;
|
||||
|
||||
s32 pc_sr_mode_get_practice_entries_amount() {
|
||||
// load practice entries from the file
|
||||
const auto file_path =
|
||||
file_util::get_user_features_dir(g_game_version) / "speedrun-practice.json";
|
||||
if (!file_util::file_exists(file_path.string())) {
|
||||
lg::info("speedrun-practice.json not found, no entries to return!");
|
||||
return 0;
|
||||
}
|
||||
const auto file_contents = safe_parse_json(file_util::read_text_file(file_path));
|
||||
if (!file_contents) {
|
||||
lg::error("speedrun-practice.json could not be parsed!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_speedrun_practice_entries = *file_contents;
|
||||
|
||||
for (size_t i = 0; i < g_speedrun_practice_entries.size(); i++) {
|
||||
const auto& entry = g_speedrun_practice_entries.at(i);
|
||||
s32 last_session_id = -1;
|
||||
s32 total_attempts = 0;
|
||||
s32 total_successes = 0;
|
||||
s32 session_attempts = 0;
|
||||
s32 session_successes = 0;
|
||||
double total_time = 0;
|
||||
float average_time = 0;
|
||||
float fastest_time = 0;
|
||||
for (const auto& [history_session, times] : entry.history) {
|
||||
s32 session_id = stoi(history_session);
|
||||
if (session_id > last_session_id) {
|
||||
last_session_id = session_id;
|
||||
}
|
||||
for (const auto& time : times) {
|
||||
total_attempts++;
|
||||
if (time.time) {
|
||||
total_successes++;
|
||||
total_time += *time.time;
|
||||
if (fastest_time == 0 || *time.time < fastest_time) {
|
||||
fastest_time = *time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (total_successes != 0) {
|
||||
average_time = total_time / total_successes;
|
||||
}
|
||||
g_speedrun_practice_state[i] = {last_session_id + 1, total_attempts, total_successes,
|
||||
session_attempts, session_successes, total_time,
|
||||
average_time, fastest_time};
|
||||
}
|
||||
|
||||
return g_speedrun_practice_entries.size();
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_practice_entry_name(s32 entry_index, u32 name_str_ptr) {
|
||||
std::string name;
|
||||
if (entry_index < (int)g_speedrun_practice_entries.size()) {
|
||||
name = g_speedrun_practice_entries.at(entry_index).name;
|
||||
}
|
||||
strcpy(Ptr<String>(name_str_ptr).c()->data(), name.c_str());
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_practice_entry_continue_point(s32 entry_index, u32 name_str_ptr) {
|
||||
std::string name;
|
||||
if (entry_index < (int)g_speedrun_practice_entries.size()) {
|
||||
name = g_speedrun_practice_entries.at(entry_index).continue_point_name;
|
||||
}
|
||||
strcpy(Ptr<String>(name_str_ptr).c()->data(), name.c_str());
|
||||
}
|
||||
|
||||
s32 pc_sr_mode_get_practice_entry_history_success(s32 entry_index) {
|
||||
return g_speedrun_practice_state.at(entry_index).total_successes;
|
||||
}
|
||||
|
||||
s32 pc_sr_mode_get_practice_entry_history_attempts(s32 entry_index) {
|
||||
return g_speedrun_practice_state.at(entry_index).total_attempts;
|
||||
}
|
||||
|
||||
s32 pc_sr_mode_get_practice_entry_session_success(s32 entry_index) {
|
||||
return g_speedrun_practice_state.at(entry_index).session_successes;
|
||||
}
|
||||
|
||||
s32 pc_sr_mode_get_practice_entry_session_attempts(s32 entry_index) {
|
||||
return g_speedrun_practice_state.at(entry_index).session_attempts;
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_practice_entry_avg_time(s32 entry_index, u32 time_str_ptr) {
|
||||
const auto time = fmt::format("{:.2f}", g_speedrun_practice_state.at(entry_index).average_time);
|
||||
strcpy(Ptr<String>(time_str_ptr).c()->data(), time.c_str());
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_practice_entry_fastest_time(s32 entry_index, u32 time_str_ptr) {
|
||||
const auto time = fmt::format("{:.2f}", g_speedrun_practice_state.at(entry_index).fastest_time);
|
||||
strcpy(Ptr<String>(time_str_ptr).c()->data(), time.c_str());
|
||||
}
|
||||
|
||||
u64 pc_sr_mode_record_practice_entry_attempt(s32 entry_index, u32 success_bool, u32 time_ptr) {
|
||||
auto& state = g_speedrun_practice_state.at(entry_index);
|
||||
const auto was_successful = symbol_to_bool(success_bool);
|
||||
state.total_attempts++;
|
||||
state.session_attempts++;
|
||||
bool ret = false;
|
||||
SpeedrunPracticeEntryHistoryAttempt new_history_entry;
|
||||
if (was_successful) {
|
||||
auto time = Ptr<float>(time_ptr).c();
|
||||
new_history_entry.time = *time;
|
||||
state.total_successes++;
|
||||
state.session_successes++;
|
||||
state.total_time += *time;
|
||||
state.average_time = state.total_time / state.total_successes;
|
||||
if (*time < state.fastest_time) {
|
||||
state.fastest_time = *time;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
// persist to file
|
||||
const auto file_path =
|
||||
file_util::get_user_features_dir(g_game_version) / "speedrun-practice.json";
|
||||
if (!file_util::file_exists(file_path.string())) {
|
||||
lg::info("speedrun-practice.json not found, not persisting!");
|
||||
} else {
|
||||
auto& history = g_speedrun_practice_entries.at(entry_index).history;
|
||||
if (history.find(fmt::format("{}", state.current_session_id)) == history.end()) {
|
||||
history[fmt::format("{}", state.current_session_id)] = {};
|
||||
}
|
||||
history[fmt::format("{}", state.current_session_id)].push_back(new_history_entry);
|
||||
json data = g_speedrun_practice_entries;
|
||||
file_util::write_text_file(file_path, data.dump(2));
|
||||
}
|
||||
// return
|
||||
return bool_to_symbol(ret);
|
||||
}
|
||||
|
||||
void pc_sr_mode_init_practice_info(s32 entry_index, u32 speedrun_practice_obj_ptr) {
|
||||
if (entry_index >= (int)g_speedrun_practice_entries.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto objective = speedrun_practice_obj_ptr
|
||||
? Ptr<SpeedrunPracticeObjective>(speedrun_practice_obj_ptr).c()
|
||||
: NULL;
|
||||
if (objective) {
|
||||
const auto& json_info = g_speedrun_practice_entries.at(entry_index);
|
||||
|
||||
objective->index = entry_index;
|
||||
objective->flags = json_info.flags;
|
||||
objective->completed_task = json_info.completed_task;
|
||||
objective->features = json_info.features;
|
||||
objective->vehicles = json_info.vehicles;
|
||||
objective->secrets = json_info.secrets;
|
||||
auto starting_position =
|
||||
objective->starting_position ? Ptr<Vector>(objective->starting_position).c() : NULL;
|
||||
if (starting_position) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
starting_position->data[i] = json_info.starting_position.at(i) * METER_LENGTH;
|
||||
}
|
||||
}
|
||||
auto starting_rotation =
|
||||
objective->starting_rotation ? Ptr<Vector>(objective->starting_rotation).c() : NULL;
|
||||
if (starting_rotation) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
starting_rotation->data[i] = json_info.starting_rotation.at(i);
|
||||
}
|
||||
}
|
||||
auto starting_camera_position = objective->starting_camera_position
|
||||
? Ptr<Vector>(objective->starting_camera_position).c()
|
||||
: NULL;
|
||||
if (starting_camera_position) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
starting_camera_position->data[i] = json_info.starting_camera_position.at(i) * 4096.0;
|
||||
}
|
||||
}
|
||||
auto starting_camera_rotation = objective->starting_camera_rotation
|
||||
? Ptr<Vector>(objective->starting_camera_rotation).c()
|
||||
: NULL;
|
||||
if (starting_camera_rotation) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
starting_camera_rotation->data[i] = json_info.starting_camera_rotation.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_info.end_task) {
|
||||
objective->end_task = *json_info.end_task;
|
||||
} else {
|
||||
objective->end_task = 0;
|
||||
}
|
||||
|
||||
auto starting_zone = objective->start_zone_init_params
|
||||
? Ptr<ObjectiveZoneInitParams>(objective->start_zone_init_params).c()
|
||||
: NULL;
|
||||
if (starting_zone) {
|
||||
starting_zone->v1[0] = json_info.start_zone_v1.at(0) * METER_LENGTH;
|
||||
starting_zone->v1[1] = json_info.start_zone_v1.at(1) * METER_LENGTH;
|
||||
starting_zone->v1[2] = json_info.start_zone_v1.at(2) * METER_LENGTH;
|
||||
starting_zone->v1[3] = json_info.start_zone_v1.at(3) * METER_LENGTH;
|
||||
starting_zone->v2[0] = json_info.start_zone_v2.at(0) * METER_LENGTH;
|
||||
starting_zone->v2[1] = json_info.start_zone_v2.at(1) * METER_LENGTH;
|
||||
starting_zone->v2[2] = json_info.start_zone_v2.at(2) * METER_LENGTH;
|
||||
starting_zone->v2[3] = json_info.start_zone_v2.at(3) * METER_LENGTH;
|
||||
}
|
||||
|
||||
if (json_info.end_zone_v1 && json_info.end_zone_v2) {
|
||||
auto ending_zone = objective->end_zone_init_params
|
||||
? Ptr<ObjectiveZoneInitParams>(objective->end_zone_init_params).c()
|
||||
: NULL;
|
||||
if (ending_zone) {
|
||||
ending_zone->v1[0] = json_info.end_zone_v1->at(0) * METER_LENGTH;
|
||||
ending_zone->v1[1] = json_info.end_zone_v1->at(1) * METER_LENGTH;
|
||||
ending_zone->v1[2] = json_info.end_zone_v1->at(2) * METER_LENGTH;
|
||||
ending_zone->v1[3] = json_info.end_zone_v1->at(3) * METER_LENGTH;
|
||||
ending_zone->v2[0] = json_info.end_zone_v2->at(0) * METER_LENGTH;
|
||||
ending_zone->v2[1] = json_info.end_zone_v2->at(1) * METER_LENGTH;
|
||||
ending_zone->v2[2] = json_info.end_zone_v2->at(2) * METER_LENGTH;
|
||||
ending_zone->v2[3] = json_info.end_zone_v2->at(3) * METER_LENGTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpeedrunCustomCategoryEntry> g_speedrun_custom_categories;
|
||||
|
||||
s32 pc_sr_mode_get_custom_category_amount() {
|
||||
// load practice entries from the file
|
||||
const auto file_path =
|
||||
file_util::get_user_features_dir(g_game_version) / "speedrun-categories.json";
|
||||
if (!file_util::file_exists(file_path.string())) {
|
||||
lg::info("speedrun-categories.json not found, no entries to return!");
|
||||
return 0;
|
||||
}
|
||||
const auto file_contents = safe_parse_json(file_util::read_text_file(file_path));
|
||||
if (!file_contents) {
|
||||
lg::error("speedrun-categories.json could not be parsed!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_speedrun_custom_categories = *file_contents;
|
||||
|
||||
return g_speedrun_custom_categories.size();
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_custom_category_name(s32 entry_index, u32 name_str_ptr) {
|
||||
std::string name;
|
||||
if (entry_index < (int)g_speedrun_custom_categories.size()) {
|
||||
name = g_speedrun_custom_categories.at(entry_index).name;
|
||||
}
|
||||
strcpy(Ptr<String>(name_str_ptr).c()->data(), name.c_str());
|
||||
}
|
||||
|
||||
void pc_sr_mode_get_custom_category_continue_point(s32 entry_index, u32 name_str_ptr) {
|
||||
std::string name;
|
||||
if (entry_index < (int)g_speedrun_custom_categories.size()) {
|
||||
name = g_speedrun_custom_categories.at(entry_index).continue_point_name;
|
||||
}
|
||||
strcpy(Ptr<String>(name_str_ptr).c()->data(), name.c_str());
|
||||
}
|
||||
|
||||
void pc_sr_mode_init_custom_category_info(s32 entry_index, u32 speedrun_custom_category_ptr) {
|
||||
if (entry_index >= (int)g_speedrun_custom_categories.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto category = speedrun_custom_category_ptr
|
||||
? Ptr<SpeedrunCustomCategory>(speedrun_custom_category_ptr).c()
|
||||
: NULL;
|
||||
if (category) {
|
||||
const auto& json_info = g_speedrun_custom_categories.at(entry_index);
|
||||
category->index = entry_index;
|
||||
category->secrets = json_info.secrets;
|
||||
category->features = json_info.features;
|
||||
category->vehicles = json_info.vehicles;
|
||||
category->forbidden_features = json_info.forbidden_features;
|
||||
category->cheats = json_info.cheats;
|
||||
category->completed_task = json_info.completed_task;
|
||||
}
|
||||
}
|
||||
|
||||
void pc_sr_mode_dump_new_custom_category(u32 speedrun_custom_category_ptr) {
|
||||
const auto file_path =
|
||||
file_util::get_user_features_dir(g_game_version) / "speedrun-categories.json";
|
||||
if (file_util::file_exists(file_path.string())) {
|
||||
// read current categories from file
|
||||
const auto file_contents = safe_parse_json(file_util::read_text_file(file_path));
|
||||
if (file_contents) {
|
||||
g_speedrun_custom_categories = *file_contents;
|
||||
}
|
||||
}
|
||||
|
||||
auto category = speedrun_custom_category_ptr
|
||||
? Ptr<SpeedrunCustomCategory>(speedrun_custom_category_ptr).c()
|
||||
: NULL;
|
||||
if (category) {
|
||||
SpeedrunCustomCategoryEntry new_category;
|
||||
new_category.name = fmt::format("custom-category-{}", g_speedrun_custom_categories.size());
|
||||
new_category.secrets = category->secrets;
|
||||
new_category.features = category->features;
|
||||
new_category.vehicles = category->vehicles;
|
||||
new_category.forbidden_features = category->forbidden_features;
|
||||
new_category.cheats = category->cheats;
|
||||
new_category.completed_task = category->completed_task;
|
||||
new_category.continue_point_name = "";
|
||||
g_speedrun_custom_categories.push_back(new_category);
|
||||
// convert to json and write file
|
||||
json data = g_speedrun_custom_categories;
|
||||
file_util::write_text_file(file_path, data.dump(2));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace kmachine_extras
|
||||
} // namespace jak3
|
||||
|
||||
@@ -12,8 +12,47 @@ void pc_set_levels(u32 lev_list);
|
||||
void pc_set_active_levels(u32 lev_list);
|
||||
u32 alloc_vagdir_names(u32 heap_sym);
|
||||
inline u64 bool_to_symbol(const bool val);
|
||||
// TODO - move to common
|
||||
void encode_utf8_string(u32 src_str_ptr, u32 str_dest_ptr);
|
||||
void init_autosplit_struct();
|
||||
void callback_fetch_external_speedrun_times(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result);
|
||||
void callback_fetch_external_race_times(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result);
|
||||
void callback_fetch_external_highscores(bool success,
|
||||
const std::string& cache_id,
|
||||
std::optional<std::string> result);
|
||||
void pc_fetch_external_speedrun_times(u32 speedrun_id_ptr);
|
||||
void pc_fetch_external_race_times(u32 race_id_ptr);
|
||||
void pc_fetch_external_highscores(u32 highscore_id_ptr);
|
||||
void pc_get_external_speedrun_time(u32 speedrun_id_ptr,
|
||||
s32 index,
|
||||
u32 name_dest_ptr,
|
||||
u32 time_dest_ptr);
|
||||
void pc_get_external_race_time(u32 race_id_ptr, s32 index, u32 name_dest_ptr, u32 time_dest_ptr);
|
||||
void pc_get_external_highscore(u32 highscore_id_ptr,
|
||||
s32 index,
|
||||
u32 name_dest_ptr,
|
||||
u32 time_dest_ptr);
|
||||
s32 pc_get_num_external_speedrun_times(u32 speedrun_id_ptr);
|
||||
s32 pc_get_num_external_race_times(u32 race_id_ptr);
|
||||
s32 pc_get_num_external_highscores(u32 highscore_id_ptr);
|
||||
s32 pc_sr_mode_get_practice_entries_amount();
|
||||
void pc_sr_mode_get_practice_entry_name(s32 entry_index, u32 name_str_ptr);
|
||||
void pc_sr_mode_get_practice_entry_continue_point(s32 entry_index, u32 name_str_ptr);
|
||||
s32 pc_sr_mode_get_practice_entry_history_success(s32 entry_index);
|
||||
s32 pc_sr_mode_get_practice_entry_history_attempts(s32 entry_index);
|
||||
s32 pc_sr_mode_get_practice_entry_session_success(s32 entry_index);
|
||||
s32 pc_sr_mode_get_practice_entry_session_attempts(s32 entry_index);
|
||||
void pc_sr_mode_get_practice_entry_avg_time(s32 entry_index, u32 time_str_ptr);
|
||||
void pc_sr_mode_get_practice_entry_fastest_time(s32 entry_index, u32 time_str_ptr);
|
||||
u64 pc_sr_mode_record_practice_entry_attempt(s32 entry_index, u32 success_bool, u32 time);
|
||||
void pc_sr_mode_init_practice_info(s32 entry_index, u32 speedrun_practice_obj_ptr);
|
||||
s32 pc_sr_mode_get_custom_category_amount();
|
||||
void pc_sr_mode_get_custom_category_name(s32 entry_index, u32 name_str_ptr);
|
||||
void pc_sr_mode_get_custom_category_continue_point(s32 entry_index, u32 name_str_ptr);
|
||||
void pc_sr_mode_init_custom_category_info(s32 entry_index, u32 speedrun_custom_category_ptr);
|
||||
void pc_sr_mode_dump_new_custom_category(u32 speedrun_custom_category_ptr);
|
||||
|
||||
struct DiscordInfo {
|
||||
float orb_count; // float
|
||||
@@ -113,5 +152,110 @@ enum class FocusStatus : u64 {
|
||||
|
||||
#define FOCUS_TEST(status, foc) (status.test(static_cast<size_t>(foc)))
|
||||
|
||||
// To speed up finding the auto-splitter block in GOAL memory
|
||||
// all this has is a marker for LiveSplit to find, and then the pointer
|
||||
// to the symbol
|
||||
struct AutoSplitterBlock {
|
||||
const char marker[20] = "UnLiStEdStRaTs_JaK3";
|
||||
u64 pointer_to_symbol = 0;
|
||||
};
|
||||
|
||||
extern AutoSplitterBlock g_auto_splitter_block_jak3;
|
||||
|
||||
struct SpeedrunPracticeEntryHistoryAttempt {
|
||||
std::optional<float> time;
|
||||
};
|
||||
void to_json(json& j, const SpeedrunPracticeEntryHistoryAttempt& obj);
|
||||
void from_json(const json& j, SpeedrunPracticeEntryHistoryAttempt& obj);
|
||||
|
||||
struct SpeedrunPracticeEntry {
|
||||
std::string name;
|
||||
std::string continue_point_name;
|
||||
u64 flags;
|
||||
u64 completed_task;
|
||||
u64 features;
|
||||
u64 secrets;
|
||||
u64 vehicles;
|
||||
std::vector<float> starting_position;
|
||||
std::vector<float> starting_rotation;
|
||||
std::vector<float> starting_camera_position;
|
||||
std::vector<float> starting_camera_rotation;
|
||||
std::vector<float> start_zone_v1;
|
||||
std::vector<float> start_zone_v2;
|
||||
std::optional<std::vector<float>> end_zone_v1;
|
||||
std::optional<std::vector<float>> end_zone_v2;
|
||||
std::optional<u64> end_task;
|
||||
std::map<std::string, std::vector<SpeedrunPracticeEntryHistoryAttempt>> history;
|
||||
};
|
||||
void to_json(json& j, const SpeedrunPracticeEntry& obj);
|
||||
void from_json(const json& j, SpeedrunPracticeEntry& obj);
|
||||
|
||||
struct SpeedrunPracticeState {
|
||||
s32 current_session_id;
|
||||
s32 total_attempts;
|
||||
s32 total_successes;
|
||||
s32 session_attempts;
|
||||
s32 session_successes;
|
||||
double total_time;
|
||||
float average_time;
|
||||
float fastest_time;
|
||||
};
|
||||
|
||||
struct ObjectiveZoneInitParams {
|
||||
float v1[4];
|
||||
float v2[4];
|
||||
};
|
||||
|
||||
struct Vector {
|
||||
float data[4];
|
||||
};
|
||||
|
||||
struct Matrix {
|
||||
float data[16];
|
||||
};
|
||||
|
||||
struct SpeedrunPracticeObjective {
|
||||
s32 index;
|
||||
u8 pad1[4];
|
||||
u64 flags;
|
||||
u8 completed_task;
|
||||
u8 pad2[7];
|
||||
u64 features;
|
||||
u64 secrets;
|
||||
u64 vehicles;
|
||||
u32 starting_position; // Vector
|
||||
u32 starting_rotation; // Vector
|
||||
u32 starting_camera_position; // Vector
|
||||
u32 starting_camera_rotation; // Matrix
|
||||
u8 end_task;
|
||||
u32 start_zone_init_params; // ObjectiveZoneInitParams
|
||||
u32 start_zone; // irrelevant for cpp
|
||||
u32 end_zone_init_params; // ObjectiveZoneInitParams
|
||||
u32 end_zone; // irrelevant for cpp
|
||||
};
|
||||
|
||||
struct SpeedrunCustomCategoryEntry {
|
||||
std::string name;
|
||||
u64 secrets;
|
||||
u64 features;
|
||||
u64 vehicles;
|
||||
u64 forbidden_features;
|
||||
u64 cheats;
|
||||
std::string continue_point_name;
|
||||
u64 completed_task;
|
||||
};
|
||||
void to_json(json& j, const SpeedrunCustomCategoryEntry& obj);
|
||||
void from_json(const json& j, SpeedrunCustomCategoryEntry& obj);
|
||||
|
||||
struct SpeedrunCustomCategory {
|
||||
s32 index;
|
||||
u64 secrets;
|
||||
u64 features;
|
||||
u64 vehicles;
|
||||
u64 forbidden_features;
|
||||
u64 cheats;
|
||||
u8 completed_task;
|
||||
};
|
||||
|
||||
} // namespace kmachine_extras
|
||||
} // namespace jak3
|
||||
|
||||
Reference in New Issue
Block a user