From 7543acfb8a63cd6558ca9867747855dc8a436e67 Mon Sep 17 00:00:00 2001 From: Hat Kid <6624576+Hat-Kid@users.noreply.github.com> Date: Sun, 17 Nov 2024 06:45:34 +0100 Subject: [PATCH] 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. --- custom_assets/blender_plugins/opengoal.py | 2 + .../level_extractor/merc_replacement.cpp | 62 +- decompiler/level_extractor/merc_replacement.h | 2 +- game/kernel/jak2/kmachine_extras.h | 2 - game/kernel/jak3/kmachine.cpp | 42 +- game/kernel/jak3/kmachine_extras.cpp | 734 ++++++++++++++++- game/kernel/jak3/kmachine_extras.h | 148 +++- .../tools/subtitle_editor/subtitle_editor.cpp | 8 +- goal_src/jak3/dgos/game.gd | 6 + goal_src/jak3/engine/game/main.gc | 14 +- goal_src/jak3/engine/gfx/mood/time-of-day.gc | 4 +- .../engine/gfx/texture/texture-anim-tables.gc | 609 ++++++++++++++ .../jak3/engine/gfx/texture/texture-anim.gc | 42 + .../jak3/engine/gfx/texture/texture-finish.gc | 2 + goal_src/jak3/engine/ui/text-h.gc | 15 + goal_src/jak3/engine/ui/text.gc | 23 +- goal_src/jak3/kernel-defs.gc | 22 + goal_src/jak3/kernel/gstring.gc | 10 + .../levels/desert/boss/terraformer-head.gc | 3 + goal_src/jak3/pc/features/autosplit-h.gc | 164 ++++ goal_src/jak3/pc/features/autosplit.gc | 257 ++++++ goal_src/jak3/pc/features/speedruns-h.gc | 134 +++ goal_src/jak3/pc/features/speedruns.gc | 766 ++++++++++++++++++ goal_src/jak3/pc/pckernel-impl.gc | 6 +- goal_src/jak3/pc/pckernel.gc | 9 +- goal_src/jak3/pc/util/popup-menu-h.gc | 59 ++ goal_src/jak3/pc/util/popup-menu.gc | 289 +++++++ goalc/build_level/common/Entity.cpp | 15 +- 28 files changed, 3400 insertions(+), 49 deletions(-) create mode 100644 goal_src/jak3/pc/features/autosplit-h.gc create mode 100644 goal_src/jak3/pc/features/autosplit.gc create mode 100644 goal_src/jak3/pc/features/speedruns-h.gc create mode 100644 goal_src/jak3/pc/features/speedruns.gc create mode 100644 goal_src/jak3/pc/util/popup-menu-h.gc create mode 100644 goal_src/jak3/pc/util/popup-menu.gc diff --git a/custom_assets/blender_plugins/opengoal.py b/custom_assets/blender_plugins/opengoal.py index e875ad36ee7..4452ddc4323 100644 --- a/custom_assets/blender_plugins/opengoal.py +++ b/custom_assets/blender_plugins/opengoal.py @@ -90,6 +90,7 @@ def draw_func_ob(self, context): layout = self.layout ob = context.object layout.prop(ob, "set_invisible") + layout.prop(ob, "enable_custom_weights") layout.prop(ob, "set_collision") if (ob.set_collision): layout.prop(ob, "ignore") @@ -116,6 +117,7 @@ def register(): bpy.types.Object.set_invisible = bpy.props.BoolProperty(name="Invisible") bpy.types.Object.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties") + bpy.types.Object.enable_custom_weights = bpy.props.BoolProperty(name="Use Custom Bone Weights") bpy.types.Object.ignore = bpy.props.BoolProperty(name="ignore") bpy.types.Object.noedge = bpy.props.BoolProperty(name="No-Edge") bpy.types.Object.noentity = bpy.props.BoolProperty(name="No-Entity") diff --git a/decompiler/level_extractor/merc_replacement.cpp b/decompiler/level_extractor/merc_replacement.cpp index 755c1e4e68d..ee8129d9ec2 100644 --- a/decompiler/level_extractor/merc_replacement.cpp +++ b/decompiler/level_extractor/merc_replacement.cpp @@ -9,7 +9,8 @@ void extract(const std::string& name, const std::vector& all_nodes, u32 index_offset, u32 vertex_offset, - u32 tex_offset) { + u32 tex_offset, + bool& has_custom_weights) { ASSERT(out.new_vertices.empty()); std::map draw_by_material; @@ -24,6 +25,8 @@ void extract(const std::string& name, if (node.mesh >= 0) { const auto& mesh = model.meshes[node.mesh]; mesh_count++; + has_custom_weights = node.extras.Has("enable_custom_weights") && + node.extras.Get("enable_custom_weights").Get(); for (const auto& prim : mesh.primitives) { prim_count++; // extract index buffer @@ -39,6 +42,21 @@ void extract(const std::string& name, out.normals.insert(out.normals.end(), verts.normals.begin(), verts.normals.end()); ASSERT(out.new_colors.size() == out.new_vertices.size()); + if (prim.attributes.count("JOINTS_0") && prim.attributes.count("WEIGHTS_0")) { + auto joints_and_weights = gltf_util::extract_and_flatten_joints_and_weights(model, prim); + ASSERT(joints_and_weights.size() == verts.vtx.size()); + out.joints_and_weights.insert(out.joints_and_weights.end(), joints_and_weights.begin(), + joints_and_weights.end()); + } else { + // add fake data for vertices without this data + gltf_util::JointsAndWeights dummy; + dummy.joints[0] = 3; + dummy.weights[0] = 1.f; + for (size_t i = 0; i < out.new_vertices.size(); i++) { + out.joints_and_weights.push_back(dummy); + } + } + // TODO: just putting it all in one material auto& draw = draw_by_material[prim.material]; draw.mode = gltf_util::make_default_draw_mode(); // todo rm @@ -119,7 +137,8 @@ const tfrag3::MercVertex& find_closest(const std::vector& ol void merc_convert_replacement(MercSwapData& out, const MercExtractData& in, - const std::vector& old_verts) { + const std::vector& old_verts, + bool use_custom_weights) { out.new_model = in.new_model; out.new_indices = in.new_indices; out.new_textures = in.tex_pool.textures_by_idx; @@ -127,6 +146,7 @@ void merc_convert_replacement(MercSwapData& out, // convert vertices for (size_t i = 0; i < in.new_vertices.size(); i++) { const auto& y = in.new_vertices[i]; + const auto& copy_from = find_closest(old_verts, y.x, y.y, y.z); auto& x = out.new_vertices.emplace_back(); x.pos[0] = y.x; @@ -135,18 +155,27 @@ void merc_convert_replacement(MercSwapData& out, x.normal[0] = in.normals.at(i).x(); x.normal[1] = in.normals.at(i).y(); x.normal[2] = in.normals.at(i).z(); - x.weights[0] = copy_from.weights[0]; - x.weights[1] = copy_from.weights[1]; - x.weights[2] = copy_from.weights[2]; + if (use_custom_weights) { + x.weights[0] = in.joints_and_weights.at(i).weights[0]; + x.weights[1] = in.joints_and_weights.at(i).weights[1]; + x.weights[2] = in.joints_and_weights.at(i).weights[2]; + x.mats[0] = in.joints_and_weights.at(i).joints[0]; + x.mats[1] = in.joints_and_weights.at(i).joints[1]; + x.mats[2] = in.joints_and_weights.at(i).joints[2]; + } else { + x.weights[0] = copy_from.weights[0]; + x.weights[1] = copy_from.weights[1]; + x.weights[2] = copy_from.weights[2]; + x.mats[0] = copy_from.mats[0]; + x.mats[1] = copy_from.mats[1]; + x.mats[2] = copy_from.mats[2]; + } x.st[0] = y.s; x.st[1] = y.t; x.rgba[0] = in.new_colors[i][0]; x.rgba[1] = in.new_colors[i][1]; x.rgba[2] = in.new_colors[i][2]; x.rgba[3] = in.new_colors[i][3]; - x.mats[0] = copy_from.mats[0]; - x.mats[1] = copy_from.mats[1]; - x.mats[2] = copy_from.mats[2]; } } @@ -165,18 +194,18 @@ void merc_convert_custom(MercSwapData& out, const MercExtractData& in) { x.normal[0] = in.normals.at(i).x(); x.normal[1] = in.normals.at(i).y(); x.normal[2] = in.normals.at(i).z(); - x.weights[0] = 1.0f; - x.weights[1] = 0.0f; - x.weights[2] = 0.0f; + x.weights[0] = in.joints_and_weights.at(i).weights[0]; + x.weights[1] = in.joints_and_weights.at(i).weights[1]; + x.weights[2] = in.joints_and_weights.at(i).weights[2]; x.st[0] = y.s; x.st[1] = y.t; x.rgba[0] = in.new_colors[i][0]; x.rgba[1] = in.new_colors[i][1]; x.rgba[2] = in.new_colors[i][2]; x.rgba[3] = in.new_colors[i][3]; - x.mats[0] = 3; - x.mats[1] = 0; - x.mats[2] = 0; + x.mats[0] = in.joints_and_weights.at(i).joints[0]; + x.mats[1] = in.joints_and_weights.at(i).joints[1]; + x.mats[2] = in.joints_and_weights.at(i).joints[2]; } } @@ -199,12 +228,13 @@ MercSwapData load_replacement_merc_model(const std::string& name, auto all_nodes = flatten_nodes_from_all_scenes(model); MercExtractData extract_data; + auto has_custom_weights = false; extract(name, extract_data, model, all_nodes, current_idx_count, current_vtx_count, - current_tex_count); + current_tex_count, has_custom_weights); if (custom_mdl) { merc_convert_custom(result, extract_data); } else { - merc_convert_replacement(result, extract_data, old_verts); + merc_convert_replacement(result, extract_data, old_verts, has_custom_weights); } return result; diff --git a/decompiler/level_extractor/merc_replacement.h b/decompiler/level_extractor/merc_replacement.h index 13b9e10ec9a..c6f33483a35 100644 --- a/decompiler/level_extractor/merc_replacement.h +++ b/decompiler/level_extractor/merc_replacement.h @@ -10,7 +10,7 @@ struct MercExtractData { std::vector new_vertices; std::vector> new_colors; std::vector normals; - + std::vector joints_and_weights; tfrag3::MercModel new_model; }; diff --git a/game/kernel/jak2/kmachine_extras.h b/game/kernel/jak2/kmachine_extras.h index f10c13d5a6c..71893d3d068 100644 --- a/game/kernel/jak2/kmachine_extras.h +++ b/game/kernel/jak2/kmachine_extras.h @@ -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, diff --git a/game/kernel/jak3/kmachine.cpp b/game/kernel/jak3/kmachine.cpp index 4518e7b9fbc..3c185bb0614 100644 --- a/game/kernel/jak3/kmachine.cpp +++ b/game/kernel/jak3/kmachine.cpp @@ -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() = diff --git a/game/kernel/jak3/kmachine_extras.cpp b/game/kernel/jak3/kmachine_extras.cpp index 83fc90307db..55a4e32b203 100644 --- a/game/kernel/jak3/kmachine_extras.cpp +++ b/game/kernel/jak3/kmachine_extras.cpp @@ -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>> + external_speedrun_time_cache = {}; +std::unordered_map>> + external_race_time_cache = {}; +std::unordered_map>> + external_highscores_cache = {}; + +// clang-format off +// TODO - eventually don't depend on SRC +const std::unordered_map 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 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 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 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> times = {}; + for (const auto& run_info : runs) { + std::pair 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 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> times = {}; + for (const auto& run_info : runs) { + std::pair 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 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> times = {}; + for (const auto& highscore_info : data.value()) { + if (highscore_info.contains("playerName") && highscore_info.contains("score")) { + std::pair 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(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(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(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(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(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(time_dest_ptr).c()) = run_info.second; + } else { + std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(""); + strcpy(Ptr(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(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(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(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(time_dest_ptr).c()) = run_info.second; + } else { + std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(""); + strcpy(Ptr(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(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(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(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(time_dest_ptr).c()) = run_info.second; + } else { + std::string converted = get_font_bank(GameTextVersion::JAK3)->convert_utf8_to_game(""); + strcpy(Ptr(name_dest_ptr).c()->data(), converted.c_str()); + *(Ptr(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(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(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(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 g_speedrun_practice_entries; +std::unordered_map 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(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(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(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(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(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(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(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(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(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(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(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(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 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(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(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(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(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 diff --git a/game/kernel/jak3/kmachine_extras.h b/game/kernel/jak3/kmachine_extras.h index 71a84cf904a..ebbbc9ce363 100644 --- a/game/kernel/jak3/kmachine_extras.h +++ b/game/kernel/jak3/kmachine_extras.h @@ -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 result); +void callback_fetch_external_race_times(bool success, + const std::string& cache_id, + std::optional result); +void callback_fetch_external_highscores(bool success, + const std::string& cache_id, + std::optional 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(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 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 starting_position; + std::vector starting_rotation; + std::vector starting_camera_position; + std::vector starting_camera_rotation; + std::vector start_zone_v1; + std::vector start_zone_v2; + std::optional> end_zone_v1; + std::optional> end_zone_v2; + std::optional end_task; + std::map> 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 diff --git a/game/tools/subtitle_editor/subtitle_editor.cpp b/game/tools/subtitle_editor/subtitle_editor.cpp index dfa27c108af..c500c28be11 100644 --- a/game/tools/subtitle_editor/subtitle_editor.cpp +++ b/game/tools/subtitle_editor/subtitle_editor.cpp @@ -414,11 +414,11 @@ void SubtitleEditor::draw_subtitle_options(GameSubtitleSceneInfo& scene, bool cu play = true; save_and_reload_text = true; } + if (save_and_reload_text) { + m_subtitle_db.write_subtitle_db_to_files(g_game_version); + m_repl.rebuild_text(); + } if (play) { - if (save_and_reload_text) { - m_subtitle_db.write_subtitle_db_to_files(g_game_version); - m_repl.rebuild_text(); - } if (g_game_version == GameVersion::Jak1) { m_jak1_editor_db.update(); if (scene.is_cutscene) { diff --git a/goal_src/jak3/dgos/game.gd b/goal_src/jak3/dgos/game.gd index 8bf4ebf8667..fcf0fa02330 100644 --- a/goal_src/jak3/dgos/game.gd +++ b/goal_src/jak3/dgos/game.gd @@ -240,6 +240,10 @@ "game-task.o" "game-save.o" "settings.o" + "autosplit-h.o" ;; added + "autosplit.o" ;; added + "popup-menu-h.o" ;; added + "speedruns-h.o" ;; added "mood-tables.o" "mood-tables2.o" "mood.o" @@ -342,6 +346,8 @@ "board-states.o" "mech-h.o" "menu.o" + "popup-menu.o" ;; added + "speedruns.o" ;; added "drawable.o" "drawable-group.o" "drawable-inline-array.o" diff --git a/goal_src/jak3/engine/game/main.gc b/goal_src/jak3/engine/game/main.gc index 2cc1812227f..4d6aa420b53 100644 --- a/goal_src/jak3/engine/game/main.gc +++ b/goal_src/jak3/engine/game/main.gc @@ -260,7 +260,9 @@ ) ) (('menu) - (set-master-mode (cond + ;; og:preserve-this Let the popup menu code handle inputs instead of the code written for the original debug menu + (when (not *popup-menu-open*) + (set-master-mode (cond ((and *debug-segment* (cpad-hold? 0 l3) (cpad-pressed? 0 select start)) 'menu ) @@ -280,7 +282,7 @@ *master-mode* ) ) - ) + )) (set! *pause-lock* #f) ) (('pause) @@ -1508,10 +1510,12 @@ ; (draw-color-bars *blit-displays-work*) ; ) - ;; run debug menu - (*menu-hook*) + ;; draw and update menus + ;; og:preserve-this Let the popup menu code handle inputs instead of the code written for the original debug menu + (when (not *popup-menu-open*) + (*menu-hook*)) + ; load the right language file. - ;; disabled for now: seems to load 255COMMON.TXT. (load-level-text-files -1) ;; draw screen filter diff --git a/goal_src/jak3/engine/gfx/mood/time-of-day.gc b/goal_src/jak3/engine/gfx/mood/time-of-day.gc index b0d98987035..31bb86a1aa5 100644 --- a/goal_src/jak3/engine/gfx/mood/time-of-day.gc +++ b/goal_src/jak3/engine/gfx/mood/time-of-day.gc @@ -549,7 +549,9 @@ ) ) (if (-> arg0 sky) - (set! (-> (&-> *level* level-default texture-anim-array 9) 0) *sky-texture-anim-array*) + ;; og:preserve-this + (set! (-> (&-> *level* level-default texture-anim-array 9) 0) (#if PC_PORT (if *hires-sky* *sky-hires-texture-anim-array* *sky-texture-anim-array*) + *sky-texture-anim-array*)) (set! (-> (&-> *level* level-default texture-anim-array 9) 0) #f) ) (let ((s5-2 (level-get-target-inside *level*))) diff --git a/goal_src/jak3/engine/gfx/texture/texture-anim-tables.gc b/goal_src/jak3/engine/gfx/texture/texture-anim-tables.gc index d74ed8ded9a..af81ef3d263 100644 --- a/goal_src/jak3/engine/gfx/texture/texture-anim-tables.gc +++ b/goal_src/jak3/engine/gfx/texture/texture-anim-tables.gc @@ -419,9 +419,614 @@ ) ) +;; og:preserve-this +(#when PC_PORT +(define *sky-hires-texture-anim-array* + (the (texture-anim-array texture-anim) + (new 'static 'texture-anim-array :type texture-anim + (new 'static 'texture-anim + :func-id 'texture-anim-alpha-ramp-clut-upload + :init-func-id 'texture-anim-alpha-ramp-clut-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 24.0) + :color (new 'static 'rgba :a #x80) + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 0) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 16.0 :y 4.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 9600.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 16.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 9600.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 16.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 9600.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 32.0 :y 5.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 4800.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 32.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 4800.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 32.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 4800.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 64.0 :y 6.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 2400.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 64.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 2400.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 64.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 2400.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 128.0 :y 8.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 1200.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 128.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 1200.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 128.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 1200.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 256.0 :y 8.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 600.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 256.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 600.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 256.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 600.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x2 + :func-id 'cloud-texture-anim-func + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 512.0 :y 8.0) + :color (new 'static 'rgba :a #x80) + :frame-delta 300.0 + :frame-mod 450.0 + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 512.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 450.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 512.0 :y 16.0 :z 24.0) + :func-id 'cloud-texture-anim-layer-func + :init-func-id 'noise-texture-init + :tex #f + :end-time 450.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers 6 + :func #f + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 512.0 :y 16.0) + :color (new 'static 'rgba :a #x80) + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 6 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 16.0 :y 4.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.49) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 32.0 :y 5.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.19) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 64.0 :y 6.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.145) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 128.0 :y 8.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.015) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 256.0 :y 8.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.01) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 512.0 :y 8.0) + :func-id 'default-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x3 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x2 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 0.0075) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :num-layers #x1 + :func #f + :init-func-id 'dest-texture-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 512.0 :y 8.0) + :color (new 'static 'rgba :a #x80) + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2 + (new 'static 'texture-anim-layer + :extra (new 'static 'vector :x 512.0 :y 16.0) + :func-id 'move-rg-to-ba-texture-anim-layer-func + :init-func-id 'src-texture-init + :tex #f + :end-time 300.0 + :tex-name #f + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :start-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :start-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :start-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :start-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-color (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + :end-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-st-scale (new 'static 'vector2 :data (new 'static 'array float 2 1.0 1.0)) + :end-st-offset (new 'static 'vector2 :data (new 'static 'array float 2 0.5 0.5)) + :end-qs (new 'static 'vector :x 1.0 :y 1.0 :z 1.0 :w 1.0) + ) + ) + ) + (new 'static 'texture-anim + :func-id 'texture-anim-cloud-clut-upload + :init-func-id 'texture-anim-cloud-clut-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 24.0 :y 0.5 :z 1.0) + :color (new 'static 'rgba :a #x80) + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2) + ) + (new 'static 'texture-anim + :func-id 'fog-texture-anim-func + :init-func-id 'fog-texture-anim-init + :tex #f + :tex-name #f + :extra (new 'static 'vector :x 4.0 :y 6.0 :z 122880.0) + :color (new 'static 'rgba :a #x80) + :test (new 'static 'gs-test :ate #x1 :afail #x1 :zte #x1 :ztst (gs-ztest always)) + :alpha (new 'static 'gs-alpha :b #x1 :d #x1) + :data (new 'static 'array texture-anim-layer 2) + ) + ) + ) + ) + +(defun set-layer-scale! ((n int) (val float)) + "set the scale of noise layer `n` in the hires sky anim" + (set! (-> *sky-hires-texture-anim-array* array-data 7 data n start-color w) val) + ) +(defun set-layer-update-time! ((n int) (val float)) + "set the update time of noise layer `n` in the hires sky anim" + (set! (-> *sky-hires-texture-anim-array* array-data (1+ n) frame-mod) val) + (set! (-> *sky-hires-texture-anim-array* array-data (1+ n) data 0 end-time) val) + (set! (-> *sky-hires-texture-anim-array* array-data (1+ n) data 1 end-time) val) + ) + +(set-layer-scale! 0 0.49) +(set-layer-scale! 1 0.19) +(set-layer-scale! 2 0.145) +(set-layer-scale! 3 0.015) +(set-layer-scale! 4 0.01) +(set-layer-scale! 5 0.0075) +(set-layer-update-time! 0 (fsec 16)) +(set-layer-update-time! 1 (fsec 8)) +(set-layer-update-time! 2 (fsec 6)) +(set-layer-update-time! 3 (fsec 4)) +(set-layer-update-time! 4 (fsec 3)) +(set-layer-update-time! 5 (fsec 2)) + +) + ;; WARN: Return type mismatch float vs none. (defun set-fog-height! ((arg0 float)) (set! (-> *sky-texture-anim-array* array-data 8 extra z) arg0) + ;; og:preserve-this + (#when PC_PORT + (set! (-> (the (array texture-anim) *sky-hires-texture-anim-array*) 10 extra z) arg0)) (none) ) @@ -429,6 +1034,10 @@ (defun set-cloud-minmax! ((arg0 float) (arg1 float)) (set! (-> *sky-texture-anim-array* array-data 7 extra y) arg0) (set! (-> *sky-texture-anim-array* array-data 7 extra z) arg1) + ;; og:preserve-this + (#when PC_PORT + (set! (-> *sky-hires-texture-anim-array* array-data 9 extra y) arg0) + (set! (-> *sky-hires-texture-anim-array* array-data 9 extra z) arg1)) (none) ) diff --git a/goal_src/jak3/engine/gfx/texture/texture-anim.gc b/goal_src/jak3/engine/gfx/texture/texture-anim.gc index 63ac9d4792a..e5a3f6cad8e 100644 --- a/goal_src/jak3/engine/gfx/texture/texture-anim.gc +++ b/goal_src/jak3/engine/gfx/texture/texture-anim.gc @@ -361,6 +361,9 @@ ) (define-extern *sky-texture-anim-array* (texture-anim-array texture-anim)) +;; og:preserve-this +(#when PC_PORT + (define-extern *sky-hires-texture-anim-array* (texture-anim-array texture-anim))) (define-extern *darkjak-texture-anim-array* (texture-anim-array texture-anim)) (define-extern *darkjak-highres-texture-anim-array* (texture-anim-array texture-anim)) (define-extern *skull-gem-texture-anim-array* (texture-anim-array texture-anim)) @@ -430,6 +433,22 @@ ) ) +(defun make-sky-hires-input ((si sky-input)) + (set! (-> si fog-height) (-> (the (array texture-anim) *sky-hires-texture-anim-array*) 10 extra z)) + (set! (-> si cloud-min) (-> *sky-hires-texture-anim-array* array-data 9 extra y)) + (set! (-> si cloud-max) (-> *sky-hires-texture-anim-array* array-data 9 extra z)) + (set! (-> si cloud-dest) (the int (-> *sky-hires-texture-anim-array* array-data 8 tex dest 0))) + (dotimes (i (-> *sky-hires-texture-anim-array* array-data 7 num-layers)) + (set! (-> si scales i) (-> *sky-hires-texture-anim-array* array-data 7 data i start-color w)) + (set! (-> si max-times i) (-> *sky-hires-texture-anim-array* array-data (1+ i) frame-mod)) + ) + (dotimes (i 11) + (set! (-> si times i) + (-> *sky-hires-texture-anim-array* array-data i frame-time) + ) + ) + ) + (defmacro print-anim (&rest args) ;`(format 0 ,@args) 0 @@ -499,6 +518,29 @@ (set! anim-idx 8) ;; fog ;; (return #f) ) + ((*sky-hires-texture-anim-array*) + (when (= bucket (bucket-id tex-lcom-sky-post)) + ;; skip. I believe this is only used to generate the envmap texture for the ocean. + ;; it generates the exact same thing, so if we want this on PC one day, we can just + ;; steal if from the beginning of the frame. + (return #f) + ) + (with-dma-buffer-add-bucket ((dma-buf (-> *display* frames (-> *display* on-screen) global-buf)) + bucket + ) + (pc-texture-anim-flag start-anim-array dma-buf) + (pc-texture-anim-flag clouds-hires dma-buf :qwc (/ (psize-of sky-input) 16)) + (make-sky-hires-input (the sky-input (-> dma-buf base))) + (&+! (-> dma-buf base) (psize-of sky-input)) + (pc-texture-anim-flag finish-anim-array dma-buf) + (dotimes (i 10) ;; intentially skipping fog here!! + (pc-update-anim-frame-time (-> *sky-hires-texture-anim-array* array-data i)) + ) + ) + ;; falling through on purpose + (set! anim-idx 10) ;; fog + ; (return #f) + ) ((*darkjak-texture-anim-array*) (pc-clut-blender bucket (texture-anim-pc darkjak) anim-array) (return #f) diff --git a/goal_src/jak3/engine/gfx/texture/texture-finish.gc b/goal_src/jak3/engine/gfx/texture/texture-finish.gc index 474a6d12f87..f7f7faa342c 100644 --- a/goal_src/jak3/engine/gfx/texture/texture-finish.gc +++ b/goal_src/jak3/engine/gfx/texture/texture-finish.gc @@ -18,6 +18,8 @@ ) (init! *sky-texture-anim-array*) +;; og:preserve-this +(init! *sky-hires-texture-anim-array*) (init! *darkjak-texture-anim-array*) (init! *skull-gem-texture-anim-array*) (init! *default-water-texture-anim-array*) diff --git a/goal_src/jak3/engine/ui/text-h.gc b/goal_src/jak3/engine/ui/text-h.gc index 8e801887fca..0fd42bdf54e 100644 --- a/goal_src/jak3/engine/ui/text-h.gc +++ b/goal_src/jak3/engine/ui/text-h.gc @@ -1383,3 +1383,18 @@ (define *common-text-heap* (new 'global 'kheap)) (define *common-text* (the-as game-text-info #f)) + +;; og:preserve-this +;; NOTE - PC PORT difference +;; Partial translations are a thing that we should support. Parts of translating the game are intentionally made +;; difficult for normal translators due to not wanting to make all the strings public (or in the case of subtitles, +;; we straight up didn't have them yet) +;; +;; So to remedy this, we always load the english text as a fallback so that if there is only a partial translation +;; the UX won't be horrible with everything displaying as UNKNOWN. +;; +;; One of the reasons we didn't do this is because it makes it obvious which strings are remaining, +;; but there are better ways to keep track or check if strings are missing. +(#when PC_PORT + (kheap-alloc (define *fallback-text-heap* (new 'global 'kheap)) (* 80 1024)) + (define *fallback-text* (the game-text-info #f))) \ No newline at end of file diff --git a/goal_src/jak3/engine/ui/text.gc b/goal_src/jak3/engine/ui/text.gc index ef9cdc04640..34867435416 100644 --- a/goal_src/jak3/engine/ui/text.gc +++ b/goal_src/jak3/engine/ui/text.gc @@ -194,8 +194,13 @@ (the-as string #f) ) (else - (format (clear *temp-string*) "UNKNOWN ID ~X" arg0) - *temp-string* + ;; og:preserve-this Added fallback to English if string is not found. + (#if PC_PORT + (if *fallback-text-lookup?* + (aif (lookup-text! *fallback-text* arg0 #t) + it + (string-format "UNKNOWN ID ~D" arg0))) + (string-format "UNKNOWN ID ~D" arg0)) ) ) ) @@ -301,12 +306,14 @@ ) (defun load-level-text-files ((arg0 int)) - (if (or *level-text-file-load-flag* (>= arg0 0)) - (load-game-text-info "common" (&-> '*common-text* value) *common-text-heap*) - ) - 0 - (none) - ) + ;; og:preserve-this Load in English file to use as a fallback + (when (or *level-text-file-load-flag* (>= arg0 0)) + (load-game-text-info "common" (&-> '*common-text* value) *common-text-heap*) + (#when PC_PORT + (protect ((-> *setting-control* user-current language)) + (set! (-> *setting-control* user-current language) (language-enum english)) + (load-game-text-info "common" (&-> '*fallback-text* value) *fallback-text-heap*)))) + (none)) (defun draw-debug-text-box ((arg0 font-context)) (when *cheat-mode* diff --git a/goal_src/jak3/kernel-defs.gc b/goal_src/jak3/kernel-defs.gc index 852d4915503..5d65d70f5d7 100644 --- a/goal_src/jak3/kernel-defs.gc +++ b/goal_src/jak3/kernel-defs.gc @@ -249,6 +249,7 @@ ;; Jak 3 Specific Kernel Definitions (define *pc-waiting-on-rpc?* symbol) (define *pc-rpc-error?* symbol) + (define-extern pc-get-last-rpc-error (function string none)) (define-extern pc-fetch-external-race-times (function string none)) (define-extern pc-fetch-external-speedrun-times (function string none)) @@ -260,6 +261,27 @@ (define-extern pc-get-num-external-speedrun-times (function string int)) (define-extern pc-get-num-external-highscores (function string int)) +;; Speedrunner Mode Stuff +(define-extern pc-sr-mode-get-practice-entries-amount (function int)) +(define-extern pc-sr-mode-get-practice-entry-name (function int string none)) +(define-extern pc-sr-mode-get-practice-entry-continue-point (function int string none)) +(define-extern pc-sr-mode-get-practice-entry-history-success (function int int)) +(define-extern pc-sr-mode-get-practice-entry-history-attempts (function int int)) +(define-extern pc-sr-mode-get-practice-entry-session-success (function int int)) +(define-extern pc-sr-mode-get-practice-entry-session-attempts (function int int)) +(define-extern pc-sr-mode-get-practice-entry-avg-time (function int string none)) +(define-extern pc-sr-mode-get-practice-entry-fastest-time (function int string none)) +(define-extern pc-sr-mode-record-practice-entry-attempt! (function int symbol (pointer float) symbol)) +(declare-type speedrun-practice-objective structure) +(define-extern pc-sr-mode-init-practice-info! (function int speedrun-practice-objective none)) +;; TODO - a menu to dump out the 3 numbers with a pre-generated name to the file +(define-extern pc-sr-mode-get-custom-category-amount (function int)) +(define-extern pc-sr-mode-get-custom-category-name (function int string none)) +(define-extern pc-sr-mode-get-custom-category-continue-point (function int string none)) +(declare-type speedrun-custom-category structure) +(define-extern pc-sr-mode-init-custom-category-info! (function int speedrun-custom-category none)) +(define-extern pc-sr-mode-dump-new-custom-category (function speedrun-custom-category none)) + (define-extern file-stream-open (function file-stream string symbol file-stream)) (define-extern file-stream-close (function file-stream file-stream)) (define-extern file-stream-length (function file-stream int)) diff --git a/goal_src/jak3/kernel/gstring.gc b/goal_src/jak3/kernel/gstring.gc index af70c6fba2b..bf352582fe0 100644 --- a/goal_src/jak3/kernel/gstring.gc +++ b/goal_src/jak3/kernel/gstring.gc @@ -815,8 +815,18 @@ (define *temp-string* (new 'global 'string 2048 (the-as string #f))) +;; og:preserve-this +(define *temp-string2* (new 'global 'string 2048 (the string #f))) + +(defconstant EMPTY_STRING "") + (#when PC_PORT (define *pc-encoded-temp-string* (new 'global 'string 2048 (the-as string #f)))) +(#when PC_PORT + (define *pc-cpp-temp-string* + "A convenient place to retrieve a string from C++" + (new 'global 'string 2048 (the string #f)))) + (kmemclose) (defmacro string-format (&rest args) diff --git a/goal_src/jak3/levels/desert/boss/terraformer-head.gc b/goal_src/jak3/levels/desert/boss/terraformer-head.gc index cbefa1bd82b..c1cf64726e5 100644 --- a/goal_src/jak3/levels/desert/boss/terraformer-head.gc +++ b/goal_src/jak3/levels/desert/boss/terraformer-head.gc @@ -2603,6 +2603,9 @@ :trans (behavior () (cond ((= (-> self hit-points) 0.0) + ;; og:preserve-this + (#when PC_PORT + (set! (-> *autosplit-info-jak3* errol-dead?) 1)) (let ((a1-0 (new 'stack-no-clear 'event-message-block))) (set! (-> a1-0 from) (process->ppointer self)) (set! (-> a1-0 num-params) 0) diff --git a/goal_src/jak3/pc/features/autosplit-h.gc b/goal_src/jak3/pc/features/autosplit-h.gc new file mode 100644 index 00000000000..70f76cfc032 --- /dev/null +++ b/goal_src/jak3/pc/features/autosplit-h.gc @@ -0,0 +1,164 @@ +;;-*-Lisp-*- +(in-package goal) + +;; LiveSplit ASL requires all settings to initalized _before_ you connect the process +;; Therefore everything has to be laid out in a predictable fashion before hand +;; So this is a lot of hard-coding, but not too bad when just copied from the debug menu code +;; +;; DO NOT change the order, appending to the end is safe! + +(deftype autosplit-info (structure) + (;; Version Info + (version-major uint16) + (version-minor uint16) + ;; General stats + (num-orbs uint32) + (num-skullgems uint32) + (errol-dead? uint8) + (all-collectables-acquired? uint8) + (padding-stats uint8 198) ;; padding for future growth + ;; loading/cutscene/control related info + (game-hash uint32) + (in-cutscene? uint8) + (is-loading? uint8) + (padding-controls uint8 200) ;; padding for future growth + ;; need-resolution tasks + (res-arena-training-1 uint8) + (res-arena-fight-1 uint8) + (res-wascity-chase uint8) + (res-wascity-pre-game uint8) + (res-desert-turtle-training uint8) + (res-desert-course-race uint8) + (res-desert-artifact-race-1 uint8) + (res-wascity-leaper-race uint8) + (res-desert-hover uint8) + (res-arena-fight-2 uint8) + (res-desert-catch-lizards uint8) + (res-desert-rescue uint8) + (res-wascity-gungame uint8) + (res-arena-fight-3 uint8) + (res-nest-eggs uint8) + (res-temple-climb uint8) + (res-desert-glide uint8) + (res-volcano-darkeco uint8) + (res-temple-oracle uint8) + (res-desert-oasis-defense uint8) + (res-temple-tests uint8) + (res-comb-travel uint8) + (res-mine-explore uint8) + (res-mine-blow uint8) + (res-mine-boss uint8) + (res-sewer-met-hum uint8) + (res-city-vehicle-training uint8) + (res-city-port-fight uint8) + (res-city-port-attack uint8) + (res-city-gun-course-1 uint8) + (res-city-sniper-fight uint8) + (res-sewer-kg-met uint8) + (res-city-destroy-darkeco uint8) + (res-forest-kill-plants uint8) + (res-city-destroy-grid uint8) + (res-city-hijack-vehicle uint8) + (res-city-port-assault uint8) + (res-city-gun-course-2 uint8) + (res-city-blow-barricade uint8) + (res-city-protect-hq uint8) + (res-sewer-hum-kg uint8) + (res-city-power-game uint8) + (res-desert-artifact-race-2 uint8) + (res-nest-hunt uint8) + (res-desert-beast-battle uint8) + (res-desert-jump-mission uint8) + (res-desert-chase-marauders uint8) + (res-forest-ring-chase uint8) + (res-factory-sky-battle uint8) + (res-factory-assault uint8) + (res-factory-boss uint8) + (res-temple-defend uint8) + (res-wascity-defend uint8) + (res-forest-turn-on-machine uint8) + (res-precursor-tour uint8) + (res-city-blow-tower uint8) + (res-tower-destroy uint8) + (res-palace-ruins-patrol uint8) + (res-palace-ruins-attack uint8) + (res-comb-wild-ride uint8) + (res-precursor-destroy-ship uint8) + (res-desert-final-boss uint8) + (res-city-win uint8) + (res-desert-bbush-get-to-1 uint8) + (res-desert-bbush-get-to-2 uint8) + (res-desert-bbush-get-to-3 uint8) + (res-desert-bbush-get-to-4 uint8) + (res-desert-bbush-get-to-5 uint8) + (res-desert-bbush-get-to-6 uint8) + (res-desert-bbush-get-to-7 uint8) + (res-desert-bbush-get-to-8 uint8) + (res-desert-bbush-get-to-9 uint8) + (res-desert-bbush-get-to-11 uint8) + (res-desert-bbush-get-to-12 uint8) + (res-desert-bbush-get-to-14 uint8) + (res-desert-bbush-get-to-16 uint8) + (res-desert-bbush-get-to-17 uint8) + (res-wascity-bbush-get-to-18 uint8) + (res-desert-bbush-get-to-19 uint8) + (res-wascity-bbush-get-to-20 uint8) + (res-wascity-bbush-get-to-21 uint8) + (res-wascity-bbush-get-to-22 uint8) + (res-wascity-bbush-get-to-23 uint8) + (res-wascity-bbush-get-to-24 uint8) + (res-wascity-bbush-get-to-25 uint8) + (res-city-bbush-get-to-26 uint8) + (res-city-bbush-get-to-27 uint8) + (res-city-bbush-get-to-28 uint8) + (res-city-bbush-get-to-29 uint8) + (res-city-bbush-get-to-30 uint8) + (res-city-bbush-get-to-31 uint8) + (res-city-bbush-get-to-32 uint8) + (res-city-bbush-get-to-33 uint8) + (res-city-bbush-get-to-34 uint8) + (res-city-bbush-get-to-35 uint8) + (res-city-bbush-get-to-36 uint8) + (res-city-bbush-get-to-37 uint8) + (res-city-bbush-get-to-38 uint8) + (res-city-bbush-get-to-39 uint8) + (res-city-bbush-get-to-40 uint8) + (res-city-bbush-get-to-41 uint8) + (res-city-bbush-get-to-42 uint8) + (res-city-bbush-get-to-43 uint8) + (res-city-bbush-get-to-44 uint8) + (res-desert-bbush-ring-1 uint8) + (res-desert-bbush-ring-2 uint8) + (res-wascity-bbush-ring-3 uint8) + (res-wascity-bbush-ring-4 uint8) + (res-city-bbush-ring-5 uint8) + (res-city-bbush-ring-6 uint8) + (res-desert-bbush-egg-spider-1 uint8) + (res-desert-bbush-spirit-chase-1 uint8) + (res-wascity-bbush-spirit-chase-2 uint8) + (res-city-bbush-spirit-chase-3 uint8) + (res-desert-bbush-timer-chase-1 uint8) + (res-wascity-bbush-timer-chase-2 uint8) + (res-desert-bbush-air-time uint8) + (res-desert-bbush-total-air-time uint8) + (res-desert-bbush-jump-distance uint8) + (res-desert-bbush-total-jump-distance uint8) + (res-desert-bbush-roll-count uint8) + (res-desert-bbush-time-trial-1 uint8) + (res-desert-bbush-rally uint8) + (res-city-bbush-port-attack uint8) + (res-desert-rescue-bbush uint8) + (res-city-gun-course-play-for-fun uint8) + (res-city-jetboard-bbush uint8) + (res-desert-bbush-destroy-interceptors uint8) + ;; TODO misc other task-nodes + (arena-fight-1-throne uint8) ;; after arena 1 cutscene + ;; TODO - orbs in level X + ;; end marker just to make things look nice in a memory view + (end-marker uint8 4)) + (:methods + (reset! (_type_) object) + (update! (_type_) object) + (debug-draw (_type_) object))) + +(define-extern *autosplit-info-jak3* autosplit-info) diff --git a/goal_src/jak3/pc/features/autosplit.gc b/goal_src/jak3/pc/features/autosplit.gc new file mode 100644 index 00000000000..8d1109f41d1 --- /dev/null +++ b/goal_src/jak3/pc/features/autosplit.gc @@ -0,0 +1,257 @@ +;;-*-Lisp-*- +(in-package goal) +(define *autosplit-info-jak3* (new 'static 'autosplit-info)) + +(pc-init-autosplitter-struct) + +;; Setup Version +(set! (-> *autosplit-info-jak3* version-major) 0) + +(set! (-> *autosplit-info-jak3* version-minor) 1) + +;; Setup markers +(charp<-string (-> *autosplit-info-jak3* end-marker) "end") + +;; Setup Padding +(charp<-string (-> *autosplit-info-jak3* padding-stats) "padding-stats!") + +(charp<-string (-> *autosplit-info-jak3* padding-controls) "padding-controls!") + +(defconstant MAX_ORBS 600) + +(defconstant AUTOSPLITTER_DEBUG #f) + +(defmacro autosplit-flag-task-complete! (field-name task-name) + "Given a field name in the autosplitter struct, and a [[game-task]] name to check, sets either a 0 or a 1" + `(begin + (if (!= (-> this ,field-name) (if (task-complete? *game-info* (game-task ,task-name)) 1 0)) + (format 0 "AUTOSPLIT for ~A~%" (quote ,task-name))) + (set! (-> this ,field-name) (if (task-complete? *game-info* (game-task ,task-name)) 1 0)))) + +(defmacro autosplit-flag-task-node-closed! (field-name task-node-name) + "Given a field name in the autosplitter struct, and a [[game-task-node]] name to check, sets either a 0 or a 1" + `(begin + (if (!= (-> this ,field-name) (if (task-node-closed? (game-task-node ,task-node-name)) 1 0)) + (format 0 "AUTOSPLIT for ~A~%" (quote ,task-node-name))) + (set! (-> this ,field-name) (if (task-node-closed? (game-task-node ,task-node-name)) 1 0)))) + +(defmethod update! ((this autosplit-info)) + ;; general statistics + ;; when we are blacked out in loads the value of these are temporarily 0, and that messes with the auto splitter. + (let ((in-blackout? (>= (-> *game-info* blackout-time) (current-time)))) + (when (not in-blackout?) + (set! (-> this num-orbs) (the int (-> *game-info* skill-total))) + (set! (-> this num-skullgems) (the int (-> *game-info* gem-total))) + ;; ending conditions + ;; all collectables + ;; - check for all features + ;; - check for all vehicles + ;; - check for all inventory items + ;; - check for all orbs + (set! (-> this all-collectables-acquired?) + (if (and (logtesta? (-> *game-info* features) + (game-feature jakc + board + board-launch + board-zap + darkeco + darkjak + darkjak + darkjak-smack + darkjak-bomb0 + darkjak-bomb1 + lighteco + lightjak + lightjak-regen + lightjak-swoop + lightjak-freeze + lightjak-shield + gun + gun-red-1 + gun-yellow-1 + gun-blue-1 + gun-dark-1 + gun-red-2 + gun-yellow-2 + gun-blue-2 + gun-dark-2 + gun-red-3 + gun-yellow-3 + gun-blue-3 + gun-dark-3)) + (logtesta? (-> *game-info* vehicles) (game-vehicles v-turtle v-snake v-scorpion v-toad v-fox v-rhino v-mirage v-x-ride)) + (logtesta? (-> *game-info* items) + (game-items amulet0 + amulet1 + amulet2 + pass-front-gate + seal-of-mar + cypher-gliph + artifact-holocube + artifact-av-reflector + artifact-av-prism + artifact-av-generator + artifact-av-map + light-eco-crystal0 + light-eco-crystal1 + light-eco-crystal2 + light-eco-crystal3 + dark-eco-crystal0 + dark-eco-crystal1 + dark-eco-crystal2 + dark-eco-crystal3)) + (>= (-> this num-orbs) MAX_ORBS)) + 1 + 0)))) + ;; loading/cutscene related flags + (set! (-> this in-cutscene?) (if (movie?) 1 0)) + ;; need resolution flags + (autosplit-flag-task-complete! res-arena-training-1 arena-training-1) + (autosplit-flag-task-complete! res-arena-fight-1 arena-fight-1) + (autosplit-flag-task-complete! res-wascity-chase wascity-chase) + (autosplit-flag-task-complete! res-wascity-pre-game wascity-pre-game) + (autosplit-flag-task-complete! res-desert-turtle-training desert-turtle-training) + (autosplit-flag-task-complete! res-desert-course-race desert-course-race) + (autosplit-flag-task-complete! res-desert-artifact-race-1 desert-artifact-race-1) + (autosplit-flag-task-complete! res-wascity-leaper-race wascity-leaper-race) + (autosplit-flag-task-complete! res-desert-hover desert-hover) + (autosplit-flag-task-complete! res-arena-fight-2 arena-fight-2) + (autosplit-flag-task-complete! res-desert-catch-lizards desert-catch-lizards) + (autosplit-flag-task-complete! res-desert-rescue desert-rescue) + (autosplit-flag-task-complete! res-wascity-gungame wascity-gungame) + (autosplit-flag-task-complete! res-arena-fight-3 arena-fight-3) + (autosplit-flag-task-complete! res-nest-eggs nest-eggs) + (autosplit-flag-task-complete! res-temple-climb temple-climb) + (autosplit-flag-task-complete! res-desert-glide desert-glide) + (autosplit-flag-task-complete! res-volcano-darkeco volcano-darkeco) + (autosplit-flag-task-complete! res-temple-oracle temple-oracle) + (autosplit-flag-task-complete! res-desert-oasis-defense desert-oasis-defense) + (autosplit-flag-task-complete! res-temple-tests temple-tests) + (autosplit-flag-task-complete! res-comb-travel comb-travel) + (autosplit-flag-task-complete! res-mine-explore mine-explore) + (autosplit-flag-task-complete! res-mine-blow mine-blow) + (autosplit-flag-task-complete! res-mine-boss mine-boss) + (autosplit-flag-task-complete! res-sewer-met-hum sewer-met-hum) + (autosplit-flag-task-complete! res-city-vehicle-training city-vehicle-training) + (autosplit-flag-task-complete! res-city-port-fight city-port-fight) + (autosplit-flag-task-complete! res-city-port-attack city-port-attack) + (autosplit-flag-task-complete! res-city-gun-course-1 city-gun-course-1) + (autosplit-flag-task-complete! res-city-sniper-fight city-sniper-fight) + (autosplit-flag-task-complete! res-sewer-kg-met sewer-kg-met) + (autosplit-flag-task-complete! res-city-destroy-darkeco city-destroy-darkeco) + (autosplit-flag-task-complete! res-forest-kill-plants forest-kill-plants) + (autosplit-flag-task-complete! res-city-destroy-grid city-destroy-grid) + (autosplit-flag-task-complete! res-city-hijack-vehicle city-hijack-vehicle) + (autosplit-flag-task-complete! res-city-port-assault city-port-assault) + (autosplit-flag-task-complete! res-city-gun-course-2 city-gun-course-2) + (autosplit-flag-task-complete! res-city-blow-barricade city-blow-barricade) + (autosplit-flag-task-complete! res-city-protect-hq city-protect-hq) + (autosplit-flag-task-complete! res-sewer-hum-kg sewer-hum-kg) + (autosplit-flag-task-complete! res-city-power-game city-power-game) + (autosplit-flag-task-complete! res-desert-artifact-race-2 desert-artifact-race-2) + (autosplit-flag-task-complete! res-nest-hunt nest-hunt) + (autosplit-flag-task-complete! res-desert-beast-battle desert-beast-battle) + (autosplit-flag-task-complete! res-desert-jump-mission desert-jump-mission) + (autosplit-flag-task-complete! res-desert-chase-marauders desert-chase-marauders) + (autosplit-flag-task-complete! res-forest-ring-chase forest-ring-chase) + (autosplit-flag-task-complete! res-factory-sky-battle factory-sky-battle) + (autosplit-flag-task-complete! res-factory-assault factory-assault) + (autosplit-flag-task-complete! res-factory-boss factory-boss) + (autosplit-flag-task-complete! res-temple-defend temple-defend) + (autosplit-flag-task-complete! res-wascity-defend wascity-defend) + (autosplit-flag-task-complete! res-forest-turn-on-machine forest-turn-on-machine) + (autosplit-flag-task-complete! res-precursor-tour precursor-tour) + (autosplit-flag-task-complete! res-city-blow-tower city-blow-tower) + (autosplit-flag-task-complete! res-tower-destroy tower-destroy) + (autosplit-flag-task-complete! res-palace-ruins-patrol palace-ruins-patrol) + (autosplit-flag-task-complete! res-palace-ruins-attack palace-ruins-attack) + (autosplit-flag-task-complete! res-comb-wild-ride comb-wild-ride) + (autosplit-flag-task-complete! res-precursor-destroy-ship precursor-destroy-ship) + (autosplit-flag-task-complete! res-desert-final-boss desert-final-boss) + (autosplit-flag-task-complete! res-city-win city-win) + (autosplit-flag-task-complete! res-desert-bbush-get-to-1 desert-bbush-get-to-1) + (autosplit-flag-task-complete! res-desert-bbush-get-to-2 desert-bbush-get-to-2) + (autosplit-flag-task-complete! res-desert-bbush-get-to-3 desert-bbush-get-to-3) + (autosplit-flag-task-complete! res-desert-bbush-get-to-4 desert-bbush-get-to-4) + (autosplit-flag-task-complete! res-desert-bbush-get-to-5 desert-bbush-get-to-5) + (autosplit-flag-task-complete! res-desert-bbush-get-to-6 desert-bbush-get-to-6) + (autosplit-flag-task-complete! res-desert-bbush-get-to-7 desert-bbush-get-to-7) + (autosplit-flag-task-complete! res-desert-bbush-get-to-8 desert-bbush-get-to-8) + (autosplit-flag-task-complete! res-desert-bbush-get-to-9 desert-bbush-get-to-9) + (autosplit-flag-task-complete! res-desert-bbush-get-to-11 desert-bbush-get-to-11) + (autosplit-flag-task-complete! res-desert-bbush-get-to-12 desert-bbush-get-to-12) + (autosplit-flag-task-complete! res-desert-bbush-get-to-14 desert-bbush-get-to-14) + (autosplit-flag-task-complete! res-desert-bbush-get-to-16 desert-bbush-get-to-16) + (autosplit-flag-task-complete! res-desert-bbush-get-to-17 desert-bbush-get-to-17) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-18 wascity-bbush-get-to-18) + (autosplit-flag-task-complete! res-desert-bbush-get-to-19 desert-bbush-get-to-19) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-20 wascity-bbush-get-to-20) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-21 wascity-bbush-get-to-21) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-22 wascity-bbush-get-to-22) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-23 wascity-bbush-get-to-23) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-24 wascity-bbush-get-to-24) + (autosplit-flag-task-complete! res-wascity-bbush-get-to-25 wascity-bbush-get-to-25) + (autosplit-flag-task-complete! res-city-bbush-get-to-26 city-bbush-get-to-26) + (autosplit-flag-task-complete! res-city-bbush-get-to-27 city-bbush-get-to-27) + (autosplit-flag-task-complete! res-city-bbush-get-to-28 city-bbush-get-to-28) + (autosplit-flag-task-complete! res-city-bbush-get-to-29 city-bbush-get-to-29) + (autosplit-flag-task-complete! res-city-bbush-get-to-30 city-bbush-get-to-30) + (autosplit-flag-task-complete! res-city-bbush-get-to-31 city-bbush-get-to-31) + (autosplit-flag-task-complete! res-city-bbush-get-to-32 city-bbush-get-to-32) + (autosplit-flag-task-complete! res-city-bbush-get-to-33 city-bbush-get-to-33) + (autosplit-flag-task-complete! res-city-bbush-get-to-34 city-bbush-get-to-34) + (autosplit-flag-task-complete! res-city-bbush-get-to-35 city-bbush-get-to-35) + (autosplit-flag-task-complete! res-city-bbush-get-to-36 city-bbush-get-to-36) + (autosplit-flag-task-complete! res-city-bbush-get-to-37 city-bbush-get-to-37) + (autosplit-flag-task-complete! res-city-bbush-get-to-38 city-bbush-get-to-38) + (autosplit-flag-task-complete! res-city-bbush-get-to-39 city-bbush-get-to-39) + (autosplit-flag-task-complete! res-city-bbush-get-to-40 city-bbush-get-to-40) + (autosplit-flag-task-complete! res-city-bbush-get-to-41 city-bbush-get-to-41) + (autosplit-flag-task-complete! res-city-bbush-get-to-42 city-bbush-get-to-42) + (autosplit-flag-task-complete! res-city-bbush-get-to-43 city-bbush-get-to-43) + (autosplit-flag-task-complete! res-city-bbush-get-to-44 city-bbush-get-to-44) + (autosplit-flag-task-complete! res-desert-bbush-ring-1 desert-bbush-ring-1) + (autosplit-flag-task-complete! res-desert-bbush-ring-2 desert-bbush-ring-2) + (autosplit-flag-task-complete! res-wascity-bbush-ring-3 wascity-bbush-ring-3) + (autosplit-flag-task-complete! res-wascity-bbush-ring-4 wascity-bbush-ring-4) + (autosplit-flag-task-complete! res-city-bbush-ring-5 city-bbush-ring-5) + (autosplit-flag-task-complete! res-city-bbush-ring-6 city-bbush-ring-6) + (autosplit-flag-task-complete! res-desert-bbush-egg-spider-1 desert-bbush-egg-spider-1) + (autosplit-flag-task-complete! res-desert-bbush-spirit-chase-1 desert-bbush-spirit-chase-1) + (autosplit-flag-task-complete! res-wascity-bbush-spirit-chase-2 wascity-bbush-spirit-chase-2) + (autosplit-flag-task-complete! res-city-bbush-spirit-chase-3 city-bbush-spirit-chase-3) + (autosplit-flag-task-complete! res-desert-bbush-timer-chase-1 desert-bbush-timer-chase-1) + (autosplit-flag-task-complete! res-wascity-bbush-timer-chase-2 wascity-bbush-timer-chase-2) + (autosplit-flag-task-complete! res-desert-bbush-air-time desert-bbush-air-time) + (autosplit-flag-task-complete! res-desert-bbush-total-air-time desert-bbush-total-air-time) + (autosplit-flag-task-complete! res-desert-bbush-jump-distance desert-bbush-jump-distance) + (autosplit-flag-task-complete! res-desert-bbush-total-jump-distance desert-bbush-total-jump-distance) + (autosplit-flag-task-complete! res-desert-bbush-roll-count desert-bbush-roll-count) + (autosplit-flag-task-complete! res-desert-bbush-time-trial-1 desert-bbush-time-trial-1) + (autosplit-flag-task-complete! res-desert-bbush-rally desert-bbush-rally) + (autosplit-flag-task-complete! res-city-bbush-port-attack city-bbush-port-attack) + (autosplit-flag-task-complete! res-desert-rescue-bbush desert-rescue-bbush) + (autosplit-flag-task-complete! res-city-gun-course-play-for-fun city-gun-course-play-for-fun) + (autosplit-flag-task-complete! res-city-jetboard-bbush city-jetboard-bbush) + (autosplit-flag-task-complete! res-desert-bbush-destroy-interceptors desert-bbush-destroy-interceptors) + ;; misc other tasks + (autosplit-flag-task-node-closed! arena-fight-1-throne arena-fight-1-throne) ;; after arena 1 cutscene + ;; debug only, draw stuff to the screen so i don't have to stare at a memory editor + (if AUTOSPLITTER_DEBUG (debug-draw this))) + +(defmethod reset! ((this autosplit-info)) + (set! (-> this game-hash) (pc-get-unix-timestamp)) + (set! (-> this errol-dead?) 0)) + +(defmethod debug-draw ((this autosplit-info)) + (format (clear *temp-string*) "errol-dead?: ~D~%" (-> this errol-dead?)) + (format *temp-string* "all-collectables-acquired?: ~D~%" (-> this all-collectables-acquired?)) + (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf1)) + ;; reset bucket settings prior to drawing - font won't do this for us, and + ;; draw-raw-image can sometimes mess them up. (intro sequence) + (dma-buffer-add-gs-set-flusha buf + (alpha-1 (new 'static 'gs-alpha :b #x1 :d #x1)) + (tex1-1 (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))) + (let ((font-ctx (new 'stack 'font-context *font-default-matrix* 10 50 0.0 (font-color default) (font-flags shadow kerning large)))) + (set! (-> font-ctx scale) 0.325) + (draw-string-adv *temp-string* buf font-ctx)))) diff --git a/goal_src/jak3/pc/features/speedruns-h.gc b/goal_src/jak3/pc/features/speedruns-h.gc new file mode 100644 index 00000000000..3d5bede3235 --- /dev/null +++ b/goal_src/jak3/pc/features/speedruns-h.gc @@ -0,0 +1,134 @@ +;;-*-Lisp-*- +(in-package goal) + +;; TEST - safe with malformed entries + +(deftype speedrun-timer (process) + ((draw? symbol) + (started? symbol) + (stopped? symbol) + (start-time time-frame) + (end-time time-frame) + (recorded-time float)) + (:methods + (draw-timer (_type_) object) + (start! (_type_) object) + (reset! (_type_) object) + (stop! (_type_) float)) + (:state-methods + idle)) + +(defbehavior speedrun-timer-init-by-other speedrun-timer () + (false! (-> self draw?)) + (false! (-> self started?)) + (set! (-> self start-time) 0) + (set! (-> self end-time) 0) + (set! (-> self recorded-time) 0.0) + (go-virtual idle)) + +(defstate idle (speedrun-timer) + :virtual #t + :code + (behavior () + (loop + (when (-> self draw?) + (draw-timer self)) + (suspend)))) + +;; TODO - put in util +(deftype objective-zone (process) + ((start? symbol) + (v1 vector :inline) + (v2 vector :inline) + (on-enter (function none)) + (on-exit (function none))) + (:methods + (draw-zone (_type_) object)) + (:state-methods + waiting-for-player + player-inside)) + +(deftype objective-zone-init-params (structure) + ((v1 vector :inline) + (v2 vector :inline))) + +(defenum speedrun-practice-flags + (none)) + +;; reset method +(deftype speedrun-practice-objective (structure) + ((index int32) + (flags speedrun-practice-flags) + (completed-task game-task) + (features game-feature) + (secrets game-secrets) + (vehicles game-vehicles) + (starting-position vector) + (starting-rotation vector) + (starting-camera-position vector) + (starting-camera-rotation matrix) + (end-task game-task) + (start-zone-init-params objective-zone-init-params) + (start-zone (pointer objective-zone)) + (end-zone-init-params objective-zone-init-params) + (end-zone (pointer objective-zone))) + (:methods + (draw-info (_type_) object) + (reset! (_type_) object))) + +(defenum speedrun-category + :type uint32 + ;; Main Categories + (newgame-normal 0) + (newgame-heromode 1) + ;; TODO - add ILs and such later + ;; there's no point in adding categories that just start from a new-game and have later restrictions + ;; because we aren't going to modify the code to make that possible + ;; ie. removing mars tomb skip if you pick "all missions" + ;; Random one for experimentation + (all-cheats-allowed 999) + (custom 9999)) + +(deftype speedrun-custom-category (structure) + ((index int32) + (secrets game-secrets) + (features game-feature) + (vehicles game-vehicles) + (forbidden-features game-feature) + (pc-cheats pc-cheats) + (completed-task game-task))) + +(deftype speedrun-info (structure) + ((category speedrun-category) + (active-custom-category speedrun-custom-category) + (dump-custom-category speedrun-custom-category) + (display-run-info? symbol) + (practicing? symbol) + (active-practice-objective speedrun-practice-objective) + (waiting-to-record-practice-attempt? symbol) + (run-started-at time-frame)) + (:methods + (set-category! (_type_ speedrun-category) object) + (start-run! (_type_) object) + (enforce-settings! (_type_) object) + (update! (_type_) object) + (draw-run-info (_type_) object))) + +(define-extern *speedrun-info* speedrun-info) + +(defenum speedrun-menu-command + :type uint32 + (reset 0) + (exit 1)) + +(deftype speedrun-manager (process) + ((popup-menu (pointer popup-menu)) + (ignore-menu-toggle? symbol) + (opened-with-start? symbol) + (timer (pointer speedrun-timer))) + (:methods + (draw-menu (_type_) object)) + (:state-methods + idle)) + +(define-extern *speedrun-manager* (pointer speedrun-manager)) diff --git a/goal_src/jak3/pc/features/speedruns.gc b/goal_src/jak3/pc/features/speedruns.gc new file mode 100644 index 00000000000..9b88ceb22d9 --- /dev/null +++ b/goal_src/jak3/pc/features/speedruns.gc @@ -0,0 +1,766 @@ +;;-*-Lisp-*- +(in-package goal) + +;; TODO later - customize menu open keybind + +(define-extern task-close! (function string symbol)) + +(define-extern *pc-dead-pool* dead-pool) + +(define *speedrun-info* (new 'static 'speedrun-info)) + +(set! (-> *speedrun-info* active-custom-category) (new 'static 'speedrun-custom-category)) + +(set! (-> *speedrun-info* dump-custom-category) (new 'static 'speedrun-custom-category)) + +(set! (-> *speedrun-info* active-practice-objective) (new 'static 'speedrun-practice-objective)) + +(set! (-> *speedrun-info* active-practice-objective starting-position) (new 'static 'vector)) + +(set! (-> *speedrun-info* active-practice-objective starting-rotation) (new 'static 'vector)) + +(set! (-> *speedrun-info* active-practice-objective starting-camera-position) (new 'static 'vector)) + +(set! (-> *speedrun-info* active-practice-objective starting-camera-rotation) (new 'static 'matrix)) + +(set! (-> *speedrun-info* active-practice-objective start-zone-init-params) (new 'static 'objective-zone-init-params)) + +(set! (-> *speedrun-info* active-practice-objective end-zone-init-params) (new 'static 'objective-zone-init-params)) + +(defmethod draw-timer ((this speedrun-timer)) + (clear *temp-string*) + (clear *pc-encoded-temp-string*) + (cond + ((-> this started?) + (format *temp-string* "~,,2fs~%" (* (the float (- (current-time) (-> this start-time))) 0.0033333334))) + ((and (!= 0 (-> this end-time))) + (format *temp-string* "~,,2fs~%" (* (the float (- (-> this end-time) (-> this start-time))) 0.0033333334))) + (else (format *temp-string* "0.0s~%"))) + (when *target* + (format *temp-string* "~,,2M~%" (-> *target* control ctrl-xz-vel))) + (pc-encode-utf8-string *temp-string* *pc-encoded-temp-string*) + (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf1)) + ;; reset bucket settings prior to drawing - font won't do this for us, and + ;; draw-raw-image can sometimes mess them up. (intro sequence) + (dma-buffer-add-gs-set-flusha buf + (alpha-1 (new 'static 'gs-alpha :b #x1 :d #x1)) + (tex1-1 (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))) + (let ((font-ctx (new 'stack 'font-context *font-default-matrix* 256 350 0.0 (font-color default) (font-flags middle shadow kerning large)))) + (set! (-> font-ctx scale) 0.325) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx)))) + +(defmethod start! ((this speedrun-timer)) + (true! (-> this started?)) + (false! (-> this stopped?)) + (set-time! (-> this start-time)) + (set! (-> this end-time) 0)) + +(defmethod reset! ((this speedrun-timer)) + (false! (-> this started?)) + (false! (-> this stopped?)) + (set! (-> this start-time) 0) + (set! (-> this end-time) 0)) + +(defmethod stop! ((this speedrun-timer)) + (when (not (-> this stopped?)) + (false! (-> this started?)) + (true! (-> this stopped?)) + (set-time! (-> this end-time)) + (set! (-> this recorded-time) (* (the float (- (-> this end-time) (-> this start-time))) 0.0033333334))) + (-> this recorded-time)) + +(defmethod set-category! ((this speedrun-info) (category speedrun-category)) + (set! (-> this category) category)) + +(defconstant HERO_MODE_SECRETS + (game-secrets hero-mode + endless-ammo + invulnerable + endless-dark + endless-light + unlimited-turbos + vehicle-hit-points + board-fast + vehicle-fox + vehicle-mirage + vehicle-x-ride + darkjak-tracking + button-invis + gun-upgrade-red-1 + gun-upgrade-red-2 + gun-upgrade-red-3 + gun-upgrade-yellow-1 + gun-upgrade-yellow-2 + gun-upgrade-yellow-3 + gun-upgrade-blue-1 + gun-upgrade-blue-2 + gun-upgrade-blue-3 + gun-upgrade-dark-1 + gun-upgrade-dark-2 + gun-upgrade-dark-3 + gun-upgrade-ammo-red + gun-upgrade-ammo-yellow + gun-upgrade-ammo-blue + gun-upgrade-ammo-dark)) + +(defconstant HERO_MODE_FEATURES + (game-feature gun + gun-red-1 + gun-red-2 + gun-red-3 + gun-yellow-1 + gun-yellow-2 + gun-yellow-3 + gun-blue-1 + gun-blue-2 + gun-blue-3 + gun-dark-1 + gun-dark-2 + gun-dark-3 + board + gun-upgrade-yellow-ammo-1 + gun-upgrade-yellow-ammo-2 + gun-upgrade-red-ammo-1 + gun-upgrade-red-ammo-2 + gun-upgrade-blue-ammo-1 + gun-upgrade-blue-ammo-2 + gun-upgrade-dark-ammo-1 + gun-upgrade-dark-ammo-2 + board-launch + board-zap + darkjak + darkjak-bomb0 + darkjak-bomb1 + lightjak + lightjak-regen + lightjak-freeze + lightjak-shield + armor0 + armor1 + armor2 + armor3 + lighteco + darkeco)) + +(defmethod start-run! ((this speedrun-info)) + ;; randomize game id so the autosplitter knows to restart + (reset! *autosplit-info-jak3*) + ;; turn on speedrun verification display + (true! (-> this display-run-info?)) + (send-event (ppointer->process *speedrun-manager*) 'start-run) + ;; ensure any required settings are enabled + (enforce-settings! this) + ;; finalize any category specific setup code + (case (-> this category) + (((speedrun-category newgame-normal)) + (set! (-> *game-info* mode) 'debug) + (initialize! *game-info* 'game (the game-save #f) (the string #f) (the resetter-spec #f)) + (set! (-> *game-info* mode) 'play) + (start 'play (get-continue-by-name *game-info* "wasstada-jump-training")) + (play-task (game-task arena-training-1) 'debug #f)) + (((speedrun-category newgame-heromode)) + (process-spawn-function process + (lambda :behavior process () + (set! (-> *game-info* mode) 'debug) + (initialize! *game-info* 'game (the game-save #f) (the string #f) (the resetter-spec #f)) + (set! (-> *game-info* mode) 'play) + (logior! (-> *game-info* secrets) (game-secrets hero-mode)) + (logior! (-> *game-info* purchase-secrets) (game-secrets hero-mode)) + (start 'play (get-continue-by-name *game-info* "wasstada-jump-training")) + (play-task (game-task arena-training-1) 'debug #f) + (until (and *target* (= (-> *target* next-state name) 'target-stance)) + (suspend)) + (set! (-> *game-info* secrets) HERO_MODE_SECRETS) + (set! (-> *game-info* purchase-secrets) HERO_MODE_SECRETS) + (set! (-> *game-info* features) HERO_MODE_FEATURES)))) + (((speedrun-category all-cheats-allowed)) + (process-spawn-function process + (lambda :behavior process () + (set! (-> *game-info* mode) 'debug) + (initialize! *game-info* 'game (the game-save #f) (the string #f) (the resetter-spec #f)) + (set! (-> *game-info* mode) 'play) + (start 'play (get-continue-by-name *game-info* "wasstada-jump-training")) + (play-task (game-task arena-training-1) 'debug #f)))) + (((speedrun-category custom)) + (process-spawn-function process + (lambda :behavior process () + (clear *temp-string*) + (pc-sr-mode-get-custom-category-continue-point (-> *speedrun-info* active-custom-category index) *temp-string*) + (if (string= *temp-string* EMPTY_STRING) + (initialize! *game-info* 'game (the game-save #f) "intro-start" (the resetter-spec #f)) + (initialize! *game-info* 'game (the game-save #f) *temp-string* (the resetter-spec #f))) + (until (and *target* (= (-> *target* next-state name) 'target-stance)) + (suspend)) + (when (nonzero? (-> *speedrun-info* active-custom-category completed-task)) + (task-resolution-close! (-> *speedrun-info* active-custom-category completed-task))))))) + (if (!= -1 (-> *game-info* auto-save-which)) (set! (-> *setting-control* user-default auto-save) #t))) + +(defmethod enforce-settings! ((this speedrun-info)) + (true! (-> *pc-settings* ps2-actor-vis?)) ;; force PS2 actor visibility + (set-frame-rate! *pc-settings* 60 #t) ;; force FPS to `60` + ;; For posterity, the main reason why changing the cheats is useful is for two main reasons: + ;; - If you are playing a category that requires cheats (ie. a turbo jetboard one) you'd + ;; probably like the game to automatically set the appropriate ones for you + ;; - If you are playing a category that forbids cheats, you wouldn't want your run invalidated because you forgot + (case (-> this category) + (((speedrun-category newgame-normal) (speedrun-category newgame-heromode)) + ;; disable any active cheats + (set! (-> *pc-settings* cheats) (pc-cheats))) + (((speedrun-category custom)) + (set! (-> *game-info* purchase-secrets) (-> *speedrun-info* active-custom-category secrets)) + (set! (-> *game-info* secrets) (-> *speedrun-info* active-custom-category secrets)) + (logior! (-> *game-info* features) (-> *speedrun-info* active-custom-category features)) + (logclear! (-> *game-info* features) (-> *speedrun-info* active-custom-category forbidden-features)) + (logior! (-> *game-info* vehicles) (-> *speedrun-info* active-custom-category vehicles)) + (set! (-> *pc-settings* cheats) (-> *speedrun-info* active-custom-category pc-cheats))))) + +(defmethod draw-zone ((this objective-zone)) + (add-debug-box #t + (bucket-id debug) + (-> this v1) + (-> this v2) + (if (-> this start?) (static-rgba #xff #xff #x00 #x80) (static-rgba #xff #x00 #xff #x80)))) + +(defstate waiting-for-player (objective-zone) + :virtual #t + :event + (behavior ((proc process) (argc int) (message symbol) (block event-message-block)) + #t) + :trans + (behavior () + ;; Check to see if we have entered the zone + (let ((min-point-x (fmin (-> self v1 x) (-> self v2 x))) + (min-point-y (fmin (-> self v1 y) (-> self v2 y))) + (min-point-z (fmin (-> self v1 z) (-> self v2 z))) + (max-point-x (fmax (-> self v1 x) (-> self v2 x))) + (max-point-y (fmax (-> self v1 y) (-> self v2 y))) + (max-point-z (fmax (-> self v1 z) (-> self v2 z))) + (pos (target-pos 0))) + (when (and (and (<= min-point-x (-> pos x)) (<= (-> pos x) max-point-x)) + (and (<= min-point-y (-> pos y)) (<= (-> pos y) max-point-y)) + (and (<= min-point-z (-> pos z)) (<= (-> pos z) max-point-z))) + (when (nonzero? (-> self on-enter)) + ((-> self on-enter))) + (go-virtual player-inside)))) + :code + (behavior () + (loop + (draw-zone self) + (suspend)))) + +(defstate player-inside (objective-zone) + :virtual #t + :trans + (behavior () + ;; Check to see if we have entered the zone + (let ((min-point-x (fmin (-> self v1 x) (-> self v2 x))) + (min-point-y (fmin (-> self v1 y) (-> self v2 y))) + (min-point-z (fmin (-> self v1 z) (-> self v2 z))) + (max-point-x (fmax (-> self v1 x) (-> self v2 x))) + (max-point-y (fmax (-> self v1 y) (-> self v2 y))) + (max-point-z (fmax (-> self v1 z) (-> self v2 z))) + (pos (target-pos 0))) + (when (not (and (and (<= min-point-x (-> pos x)) (<= (-> pos x) max-point-x)) + (and (<= min-point-y (-> pos y)) (<= (-> pos y) max-point-y)) + (and (<= min-point-z (-> pos z)) (<= (-> pos z) max-point-z)))) + (when (nonzero? (-> self on-exit)) + ((-> self on-exit))) + (go-virtual waiting-for-player)))) + :code + (behavior () + (loop + (draw-zone self) + (suspend)))) + +(defbehavior objective-zone-init-by-other objective-zone ((start? symbol) (params objective-zone-init-params)) + (set! (-> self start?) start?) + (vector-copy! (-> self v1) (-> params v1)) + (vector-copy! (-> self v2) (-> params v2)) + (go-virtual waiting-for-player)) + +(defmethod draw-info ((this speedrun-practice-objective)) + (clear *temp-string*) + (clear *pc-encoded-temp-string*) + (pc-sr-mode-get-practice-entry-name (-> this index) *pc-encoded-temp-string*) + (format *temp-string* "Practicing: ~S~%" *pc-encoded-temp-string*) + (if (> (pc-sr-mode-get-practice-entry-history-attempts (-> this index)) 0) + (format *temp-string* + "History: ~D/~D (~,,2f%)~%" + (pc-sr-mode-get-practice-entry-history-success (-> this index)) + (pc-sr-mode-get-practice-entry-history-attempts (-> this index)) + (* 100.0 + (/ (the float (pc-sr-mode-get-practice-entry-history-success (-> this index))) + (the float (pc-sr-mode-get-practice-entry-history-attempts (-> this index)))))) + (format *temp-string* "History: --~%")) + (if (> (pc-sr-mode-get-practice-entry-session-attempts (-> this index)) 0) + (format *temp-string* + "Session: ~D/~D (~,,2f%)~%" + (pc-sr-mode-get-practice-entry-session-success (-> this index)) + (pc-sr-mode-get-practice-entry-session-attempts (-> this index)) + (* 100.0 + (/ (the float (pc-sr-mode-get-practice-entry-session-success (-> this index))) + (the float (pc-sr-mode-get-practice-entry-session-attempts (-> this index)))))) + (format *temp-string* "Session: --~%")) + (pc-sr-mode-get-practice-entry-avg-time (-> this index) *pc-encoded-temp-string*) + (format *temp-string* "Average Time: ~Ss~%" *pc-encoded-temp-string*) + (pc-sr-mode-get-practice-entry-fastest-time (-> this index) *pc-encoded-temp-string*) + (format *temp-string* "Fastest Time: ~Ss~%" *pc-encoded-temp-string*) + (format *temp-string* "\c91 L3: Reset~%") + (pc-encode-utf8-string *temp-string* *pc-encoded-temp-string*) + (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf2)) + ;; reset bucket settings prior to drawing - font won't do this for us, and + ;; draw-raw-image can sometimes mess them up. (intro sequence) + (dma-buffer-add-gs-set-flusha buf + (alpha-1 (new 'static 'gs-alpha :b #x1 :d #x1)) + (tex1-1 (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))) + (let ((font-ctx (new 'stack 'font-context *font-default-matrix* 510 20 0.0 (font-color default) (font-flags right shadow kerning large)))) + (set! (-> font-ctx scale) 0.325) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx)))) + +(defmethod reset! ((this speedrun-practice-objective)) + ;; record attempt if attempt was started + (when (-> *speedrun-info* waiting-to-record-practice-attempt?) + (pc-sr-mode-record-practice-entry-attempt! (-> this index) + #f + (&-> (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer))) recorded-time))) + ;; TODO - load checkpoint if not already in that checkpoint + ;; TODO - set features / cheats / completed-task / etc + ;; Update player position + (vector-copy! (-> *target* root trans) (-> this starting-position)) + (vector-copy! (-> *target* root quat) (-> this starting-rotation)) + ;; - get off jetboard and reset speed + (vector-copy! (-> *target* control transv) *zero-vector*) + (send-event *target* 'change-mode 'normal) + ;; Update camera position and rotation + (vector-copy! (-> *camera-combiner* trans) (-> this starting-camera-position)) + (matrix-identity! (-> *camera-combiner* inv-camera-rot)) + (matrix-copy! (-> *camera-combiner* inv-camera-rot) (-> this starting-camera-rotation)) + (process-spawn-function process + (lambda :behavior process () + (suspend) + (send-event *camera* 'teleport) + (deactivate self))) + (cam-master-activate-slave #f)) + +(define *speedrun-popup-menu-entries* + (new 'static + 'boxed-array + :type + popup-menu-entry + (new 'static + 'popup-menu-button + :label "Reset" + :on-confirm + (lambda () + (send-event (ppointer->process *speedrun-manager*) 'invoke (speedrun-menu-command reset)) + (send-event (-> *speedrun-manager* 0 popup-menu 0) 'close-menu))) + (new 'static + 'popup-menu-submenu + :label "Built-in Category Select" + :entries + (new 'static + 'boxed-array + :type + popup-menu-entry + (new 'static + 'popup-menu-flag + :label "Normal" + :on-confirm + (lambda () + (set-category! *speedrun-info* (speedrun-category newgame-normal))) + :is-toggled? + (lambda () + (= (-> *speedrun-info* category) (speedrun-category newgame-normal)))) + (new 'static + 'popup-menu-flag + :label "Hero Mode" + :on-confirm + (lambda () + (set-category! *speedrun-info* (speedrun-category newgame-heromode))) + :is-toggled? + (lambda () + (= (-> *speedrun-info* category) (speedrun-category newgame-heromode)))) + (new 'static + 'popup-menu-flag + :label "All Cheats Allowed" + :on-confirm + (lambda () + (set-category! *speedrun-info* (speedrun-category all-cheats-allowed))) + :is-toggled? + (lambda () + (= (-> *speedrun-info* category) (speedrun-category all-cheats-allowed)))))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Custom Category Select" + :get-length + (lambda () + (pc-sr-mode-get-custom-category-amount)) + :get-entry-label + (lambda ((index int) (str-dest string)) + (pc-sr-mode-get-custom-category-name index str-dest)) + :on-entry-confirm + (lambda ((index int)) + ;; hydrate from cpp + (pc-sr-mode-init-custom-category-info! index (-> *speedrun-info* active-custom-category)) + (set-category! *speedrun-info* (speedrun-category custom))) + :entry-selected? + (lambda ((index int)) + (and (= (-> *speedrun-info* category) (speedrun-category custom)) + (= index (-> *speedrun-info* active-custom-category index))))) + ;; TODO - disabled until finalized + ; (new 'static + ; 'popup-menu-dynamic-submenu + ; :label "Practice select" + ; :entry-disabled? + ; (lambda () + ; (not (-> *speedrun-info* practicing?))) + ; :get-length + ; (lambda () + ; (pc-sr-mode-get-practice-entries-amount)) + ; :get-entry-label + ; (lambda ((index int) (str-dest string)) + ; (pc-sr-mode-get-practice-entry-name index str-dest)) + ; :on-entry-confirm + ; (lambda ((index int)) + ; ;; turn on timer + ; (set! (-> (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer))) draw?) #t) + ; ;; tear down old processes + ; (when (nonzero? (-> *speedrun-info* active-practice-objective start-zone)) + ; (deactivate (-> *speedrun-info* active-practice-objective start-zone 0))) + ; (when (nonzero? (-> *speedrun-info* active-practice-objective end-zone)) + ; (deactivate (-> *speedrun-info* active-practice-objective end-zone 0))) + ; ;; init from cpp + ; (pc-sr-mode-init-practice-info! index (-> *speedrun-info* active-practice-objective)) + ; ;; startup new processes + ; (set! (-> *speedrun-info* active-practice-objective start-zone) + ; (the (pointer objective-zone) + ; (process-spawn objective-zone #t (-> *speedrun-info* active-practice-objective start-zone-init-params)))) + ; (set! (-> *speedrun-info* active-practice-objective start-zone 0 on-exit) + ; (lambda () + ; (start! (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer)))) + ; (set! (-> *speedrun-info* waiting-to-record-practice-attempt?) #t) + ; (none))) + ; (set! (-> *speedrun-info* active-practice-objective start-zone 0 on-enter) + ; (lambda () + ; (when (and *target* (>= (-> *target* control ctrl-xz-vel) (meters 30.0))) + ; (vector-copy! (-> *target* control transv) *zero-vector*)) + ; (set! (-> *speedrun-info* waiting-to-record-practice-attempt?) #f) + ; (reset! (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer)))) + ; (none))) + ; (when (= 0 (-> *speedrun-info* active-practice-objective end-task)) + ; (set! (-> *speedrun-info* active-practice-objective end-zone) + ; (the (pointer objective-zone) + ; (process-spawn objective-zone #f (-> *speedrun-info* active-practice-objective end-zone-init-params)))) + ; (set! (-> *speedrun-info* active-practice-objective end-zone 0 on-enter) + ; (lambda () + ; (when (-> *speedrun-info* waiting-to-record-practice-attempt?) + ; (stop! (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer)))) + ; (if (pc-sr-mode-record-practice-entry-attempt! (-> *speedrun-info* active-practice-objective index) + ; #t + ; (&-> (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer))) recorded-time)) + ; (sound-play "skill-pickup") + ; (sound-play "menu-pick")) + ; (set! (-> *speedrun-info* waiting-to-record-practice-attempt?) #f)) + ; (none)))) + ; (set! (-> *speedrun-info* practicing?) #t) + ; (reset! (-> *speedrun-info* active-practice-objective)) + ; (set-master-mode 'game) + ; (send-event (ppointer->process (-> *speedrun-manager* 0 popup-menu)) 'close-menu)) + ; :entry-selected? + ; (lambda ((index int)) + ; (and (-> *speedrun-info* practicing?) (= index (-> *speedrun-info* active-practice-objective index))))) + ; (new 'static + ; 'popup-menu-button + ; :label "Stop practicing" + ; :entry-disabled? + ; (lambda () + ; (not (-> *speedrun-info* practicing?))) + ; :on-confirm + ; (lambda () + ; (when (-> *speedrun-info* practicing?) + ; (when (nonzero? (-> *speedrun-info* active-practice-objective start-zone)) + ; (deactivate (-> *speedrun-info* active-practice-objective start-zone 0))) + ; (when (nonzero? (-> *speedrun-info* active-practice-objective end-zone)) + ; (deactivate (-> *speedrun-info* active-practice-objective end-zone 0)))) + ; (set! (-> *speedrun-info* practicing?) #f) + ; (set! (-> (the speedrun-timer (ppointer->process (-> *speedrun-manager* 0 timer))) draw?) #f))) + (new 'static + 'popup-menu-submenu + :label "Tools" + :entries + (new 'static + 'boxed-array + :type + popup-menu-entry + (new 'static + 'popup-menu-submenu + :label "Create custom category" + :entries + (new 'static + 'boxed-array + :type + popup-menu-entry + (new 'static + 'popup-menu-dynamic-submenu + :label "Select secrets" + :get-length + (lambda () + 58) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (bitfield->string game-secrets index))) + :on-entry-confirm + (lambda ((index int)) + (logxor! (-> *speedrun-info* dump-custom-category secrets) (shl 1 index))) + :entry-selected? + (lambda ((index int)) + (logtest? (-> *speedrun-info* dump-custom-category secrets) (shl 1 index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category secrets) (game-secrets)))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Select features" + :get-length + (lambda () + 58) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (bitfield->string game-feature index))) + :on-entry-confirm + (lambda ((index int)) + (logxor! (-> *speedrun-info* dump-custom-category features) (shl 1 index))) + :entry-selected? + (lambda ((index int)) + (logtest? (-> *speedrun-info* dump-custom-category features) (shl 1 index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category features) (game-feature)))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Select vehicles" + :get-length + (lambda () + 8) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (bitfield->string game-vehicles index))) + :on-entry-confirm + (lambda ((index int)) + (logxor! (-> *speedrun-info* dump-custom-category vehicles) (shl 1 index))) + :entry-selected? + (lambda ((index int)) + (logtest? (-> *speedrun-info* dump-custom-category vehicles) (shl 1 index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category vehicles) (game-vehicles)))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Forbid features" + :get-length + (lambda () + 58) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (bitfield->string game-feature index))) + :on-entry-confirm + (lambda ((index int)) + (logxor! (-> *speedrun-info* dump-custom-category forbidden-features) (shl 1 index))) + :entry-selected? + (lambda ((index int)) + (logtest? (-> *speedrun-info* dump-custom-category forbidden-features) (shl 1 index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category forbidden-features) (game-feature)))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Select cheats" + :get-length + (lambda () + 17) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (bitfield->string pc-cheats index))) + :on-entry-confirm + (lambda ((index int)) + (logxor! (-> *speedrun-info* dump-custom-category pc-cheats) (shl 1 index))) + :entry-selected? + (lambda ((index int)) + (logtest? (-> *speedrun-info* dump-custom-category pc-cheats) (shl 1 index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category pc-cheats) (pc-cheats)))) + (new 'static + 'popup-menu-dynamic-submenu + :label "Select completed task" + :get-length + (lambda () + (dec (the int (game-task max)))) + :get-entry-label + (lambda ((index int) (str-dest string)) + (copy-string<-string str-dest (enum->string game-task index))) + :on-entry-confirm + (lambda ((index int)) + (set! (-> *speedrun-info* dump-custom-category completed-task) (the game-task index))) + :entry-selected? + (lambda ((index int)) + (= (-> *speedrun-info* dump-custom-category completed-task) (the game-task index))) + :on-reset + (lambda () + (set! (-> *speedrun-info* dump-custom-category completed-task) (game-task none)))) + (new 'static + 'popup-menu-button + :label "Save new category to file" + :on-confirm + (lambda () + (pc-sr-mode-dump-new-custom-category (-> *speedrun-info* dump-custom-category)))))))) + (new 'static + 'popup-menu-button + :label "Exit" + :on-confirm + (lambda () + (send-event (ppointer->process *speedrun-manager*) 'invoke (speedrun-menu-command exit)))))) + +(define *speedrun-manager* (the (pointer speedrun-manager) #f)) + +(defbehavior speedrun-manager-init-by-other speedrun-manager () + (process-mask-clear! (-> self mask) menu pause) + (set! *speedrun-manager* (the (pointer speedrun-manager) (process->ppointer self))) + (set! (-> self popup-menu) (process-spawn popup-menu "Speedrun Menu" *speedrun-popup-menu-entries* :to self)) + (set! (-> self timer) (process-spawn speedrun-timer :to self)) + (set! (-> self ignore-menu-toggle?) #f) + (set! (-> self opened-with-start?) #f) + (set! (-> *speedrun-info* practicing?) #f) + (set! (-> *speedrun-info* waiting-to-record-practice-attempt?) #f) + (go-virtual idle)) + +(defmethod update! ((this speedrun-info)) + "A per frame update for speedrunning related stuff" + ;; Ensure the speedrunner menu process is enabled or destroyed + (when (and (-> *pc-settings* speedrunner-mode?) (not *speedrun-manager*)) + (process-spawn speedrun-manager :from *pc-dead-pool* :to *pc-pool*)) + (when (and (not (-> *pc-settings* speedrunner-mode?)) *speedrun-manager*) + (deactivate (-> *speedrun-manager* 0))) + ;; do speedrunner mode things + (when (-> *pc-settings* speedrunner-mode?) + ;; Update auto-splitter struct + (update! *autosplit-info-jak3*) + ;; see if we should stop drawing the run info (when you finish arena training) + (when (and (!= (-> this category) (speedrun-category custom)) (task-complete? *game-info* (game-task arena-training-1))) + (false! (-> this display-run-info?))) + ;; Draw info to the screen + (when (and (not (-> *speedrun-info* practicing?)) (-> this display-run-info?)) + (draw-run-info this)) + ;; enforce settings even if they've changed them + (enforce-settings! this) + ;; draw objective info if practicing + (when (-> *speedrun-info* practicing?) + (draw-info (-> this active-practice-objective))))) + +(defmethod draw-run-info ((this speedrun-info)) + "Draw speedrun related settings in the bottom left corner" + (when (and (-> *pc-settings* speedrunner-mode?) (-> this display-run-info?)) + (clear *temp-string*) + (clear *pc-encoded-temp-string*) + (clear *pc-cpp-temp-string*) + (cond + ((= (-> this category) (speedrun-category custom)) + (pc-sr-mode-get-custom-category-name (-> this active-custom-category index) *pc-cpp-temp-string*) + (format *temp-string* + "Category: ~S~%Secrets: ~D~%Features: ~D~%Forbidden Features: ~D~%Cheats: ~D~%Version: ~S~%" + *pc-cpp-temp-string* + (-> this active-custom-category secrets) + (-> this active-custom-category features) + (-> this active-custom-category forbidden-features) + (-> this active-custom-category pc-cheats) + *pc-settings-built-sha*)) + (else + (format *temp-string* + "Category: ~S~%PC Cheats: ~S~%Frame Rate: ~D~%PS2 Actor Vis?: ~S~%Version: ~S~%" + (enum->string speedrun-category (-> this category)) + (if (= (-> *pc-settings* cheats) (pc-cheats)) "None" (pc-cheats->string (-> *pc-settings* cheats) *temp-string2*)) + (-> *pc-settings* target-fps) + (if (-> *pc-settings* ps2-actor-vis?) "true" "false") + *pc-settings-built-sha*))) + (pc-encode-utf8-string *temp-string* *pc-encoded-temp-string*) + (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf2)) + ;; reset bucket settings prior to drawing - font won't do this for us, and + ;; draw-raw-image can sometimes mess them up. (intro sequence) + (dma-buffer-add-gs-set-flusha buf + (alpha-1 (new 'static 'gs-alpha :b #x1 :d #x1)) + (tex1-1 (new 'static 'gs-tex1 :mmag #x1 :mmin #x1))) + (let ((font-ctx (new 'stack + 'font-context + *font-default-matrix* + 510 + (if (= (-> this category) (speedrun-category custom)) 355 365) + 0.0 + (font-color default) + (font-flags right shadow kerning large)))) + (set! (-> font-ctx scale) 0.325) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx))))) + +;; Speedrun Menu + +(defmethod deactivate ((this speedrun-manager)) + (set! *speedrun-manager* (the (pointer speedrun-manager) #f)) + (call-parent-method this)) + +(defmethod draw-menu ((this speedrun-manager)) + ;; don't allow the menu to open during blackouts, apparently causes bugs + (if (< (-> *game-info* blackout-time) (current-time)) + ;; handle opening and closing the menu + (cond + ((!= (-> *pc-settings* speedrunner-mode-custom-bind) 0) + ;; the user has let go of the keybind completely or partially, allow the bind to trigger again + (when (and (-> this ignore-menu-toggle?) + (!= (cpad-hold 0) (logior (cpad-hold 0) (-> *pc-settings* speedrunner-mode-custom-bind)))) + (false! (-> this ignore-menu-toggle?))) + ;; bind handler + (when (and (not (-> this ignore-menu-toggle?)) + (= (cpad-hold 0) (logior (cpad-hold 0) (-> *pc-settings* speedrunner-mode-custom-bind)))) + (send-event (ppointer->process (-> this popup-menu)) 'open-menu) + (logclear! (cpad-hold 0) (-> *pc-settings* speedrunner-mode-custom-bind)) + (logclear! (cpad-pressed 0) (-> *pc-settings* speedrunner-mode-custom-bind)) + (true! (-> this ignore-menu-toggle?)))) + (else + (when (and (-> this ignore-menu-toggle?) + (or (not (cpad-hold? 0 l1)) (not (cpad-hold? 0 r1))) + (or (and (-> this opened-with-start?) (not (cpad-hold? 0 start))) + (and (not (-> this opened-with-start?)) (not (cpad-hold? 0 select))))) + (set! (-> this ignore-menu-toggle?) #f)) + (when (and (cpad-hold? 0 l1) + (cpad-hold? 0 r1) + (or (cpad-hold? 0 select) (cpad-hold? 0 start)) + (not (-> this ignore-menu-toggle?))) + (send-event (ppointer->process (-> this popup-menu)) 'open-menu) + (cpad-clear! 0 l1 r1) + (cond + ((cpad-hold? 0 select) (cpad-clear! 0 select) (false! (-> this opened-with-start?))) + ((cpad-hold? 0 start) (cpad-clear! 0 start) (true! (-> this opened-with-start?)))) + (true! (-> this ignore-menu-toggle?))))))) + +(defstate idle (speedrun-manager) + :virtual #t + :event + (behavior ((proc process) (argc int) (message symbol) (block event-message-block)) + (case message + (('start-run) (set-time! (-> *speedrun-info* run-started-at))) + (('invoke) + (case (-> block param 0) + (((speedrun-menu-command reset)) (start-run! *speedrun-info*)) + (((speedrun-menu-command exit)) (set-master-mode 'game) (send-event (ppointer->process (-> self popup-menu)) 'close-menu)) + (else (format 0 "nyi: invoke ~D~%" (-> block param 0))))))) + :trans + (behavior () + (draw-menu self)) + :code + (behavior () + (until #f + (when (and (-> *speedrun-info* practicing?) (cpad-pressed? 0 l3)) + (reset! (-> *speedrun-info* active-practice-objective))) + (when (and (-> *speedrun-info* display-run-info?) + (= (-> *speedrun-info* category) (speedrun-category custom)) + (time-elapsed? (-> *speedrun-info* run-started-at) (seconds 15))) + (false! (-> *speedrun-info* display-run-info?))) + (suspend)))) diff --git a/goal_src/jak3/pc/pckernel-impl.gc b/goal_src/jak3/pc/pckernel-impl.gc index 524ca7d6e9f..74f835b68c4 100644 --- a/goal_src/jak3/pc/pckernel-impl.gc +++ b/goal_src/jak3/pc/pckernel-impl.gc @@ -42,6 +42,10 @@ (weather-good) ) +(defun pc-cheats->string ((cheats pc-cheats) (buf object)) + (bit-enum->string pc-cheats cheats buf) + buf) + ;; pc enum for languages. this is the game's languages + custom ones. (defenum pc-language :type uint16 @@ -72,7 +76,7 @@ (custom 999) ;; temp ) -;; The Jak 2 version of the pc-settings object. +;; The Jak 3 version of the pc-settings object. (deftype pc-settings-jak3 (pc-settings) (;; cheats (cheats pc-cheats) diff --git a/goal_src/jak3/pc/pckernel.gc b/goal_src/jak3/pc/pckernel.gc index 45d803f4416..c684f355b81 100644 --- a/goal_src/jak3/pc/pckernel.gc +++ b/goal_src/jak3/pc/pckernel.gc @@ -303,9 +303,9 @@ (defmethod update-speedrun ((obj pc-settings-jak3)) "update speedrun module" ;; TODO - update to new with-profiler syntax - ;; (with-profiler "speedrun-update" - ;(update! *speedrun-info*) - ;;) + ; (with-profiler "speedrun-update" + (update! *speedrun-info*) + ; ) (none)) (defmethod update-video-hacks ((obj pc-settings-jak3)) @@ -751,7 +751,8 @@ ;; the actor pool for PC processes! it has space for 4 processes, with 16K of space. (define *pc-dead-pool* (new 'global 'dead-pool 4 (* 16 1024) "*pc-dead-pool*")) - +(set! (-> *pc-pool* clock) (-> *display* base-clock)) +(+! (-> *display* base-clock ref-count) 1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/goal_src/jak3/pc/util/popup-menu-h.gc b/goal_src/jak3/pc/util/popup-menu-h.gc new file mode 100644 index 00000000000..187272a6726 --- /dev/null +++ b/goal_src/jak3/pc/util/popup-menu-h.gc @@ -0,0 +1,59 @@ +;;-*-Lisp-*- +(in-package goal) + +;; A debug-menu style popup menu, a lightweight way to make a context menu that doesn't involve the progress code +;; and isn't debug-only + +(define *popup-menu-open* #f) + +(deftype popup-menu-entry (basic) + ((label string) + (entry-disabled? (function symbol)) + (on-confirm (function none))) + (:methods + (draw-entry (_type_ font-context dma-buffer symbol) object))) + +;; (deftype popup-menu-label (popup-menu-entry) ()) + +(deftype popup-menu-button (popup-menu-entry) ()) + +(deftype popup-menu-flag (popup-menu-entry) + ((is-toggled? (function symbol)))) + +(deftype popup-menu-submenu (popup-menu-entry) + ((entries (array popup-menu-entry)))) + +(deftype popup-menu-dynamic-submenu (popup-menu-entry) + ((get-length (function int)) + (get-entry-label (function int string none)) + (on-entry-confirm (function int none)) + (entry-selected? (function int symbol)) + (on-reset (function none)))) + +(deftype popup-menu-state (structure) + ((title string) + (entries (array popup-menu-entry)) + (entry-index int32) + (dynamic-menu? symbol) + (get-dynamic-menu-length (function int)) + (get-dynamic-menu-entry-label (function int string none)) + (on-dynamic-menu-entry-confirm (function int none)) + (dynamic-menu-entry-selected? (function int symbol)) + (on-dynamic-menu-reset (function none)))) + +(deftype popup-menu (process) + ((title string) + (entries (array popup-menu-entry)) + (menu-states popup-menu-state 10 :inline) + (curr-state-index int32) + (draw? symbol)) + (:methods + (update-menu! (_type_) object) + (draw-menu (_type_) object) + (move-up! (_type_ int) object) + (move-down! (_type_ int) object) + (confirm! (_type_) object) + (reset! (_type_) object) + (back! (_type_) symbol)) + (:state-methods + idle)) diff --git a/goal_src/jak3/pc/util/popup-menu.gc b/goal_src/jak3/pc/util/popup-menu.gc new file mode 100644 index 00000000000..19b23223faa --- /dev/null +++ b/goal_src/jak3/pc/util/popup-menu.gc @@ -0,0 +1,289 @@ +;;-*-Lisp-*- +(in-package goal) + +(defun get-widest-entry ((entries (array popup-menu-entry)) (title string) (font-ctx font-context) (start-index int) (end-index int)) + (let ((max-len 0.0)) + (dotimes (i (- end-index start-index)) + (let ((label-len (-> (get-string-length (-> entries (+ start-index i) label) font-ctx) length))) + (when (> label-len max-len) + (set! max-len label-len)))) + (let ((title-len (-> (get-string-length title font-ctx) length))) + (when (> title-len max-len) + (set! max-len title-len))) + (the int max-len))) + +(defun get-widest-dynamic-entry ((get-entry-label (function int string none)) (title string) (font-ctx font-context) (start-index int) (end-index int)) + (let ((max-len 0.0)) + (dotimes (i (- end-index start-index)) + (get-entry-label (+ start-index i) *pc-encoded-temp-string*) + (let ((label-len (-> (get-string-length *pc-encoded-temp-string* font-ctx) length))) + (when (> label-len max-len) + (set! max-len label-len)))) + (let ((title-len (-> (get-string-length title font-ctx) length))) + (when (> title-len max-len) + (set! max-len title-len))) + (the int max-len))) + +(defmethod draw-entry ((this popup-menu-entry) (font-ctx font-context) (dma-buf dma-buffer) (hovering? symbol)) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y)) + (old-color (-> font-ctx color))) + (pc-encode-utf8-string (-> this label) *pc-encoded-temp-string*) + (when hovering? + (set! (-> font-ctx color) (font-color cyan))) + (when (and (nonzero? (-> this entry-disabled?)) ((-> this entry-disabled?))) + (set! (-> font-ctx color) (font-color menu-parent))) + (draw-string-adv *pc-encoded-temp-string* dma-buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y) + (set! (-> font-ctx color) old-color))) + +(defmethod draw-entry ((this popup-menu-flag) (font-ctx font-context) (dma-buf dma-buffer) (hovering? symbol)) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y)) + (old-color (-> font-ctx color))) + (when ((-> this is-toggled?)) + (set! (-> font-ctx color) (font-color green)) + (set! (-> font-ctx origin x) (- old-x 6.0)) + (draw-string-adv "\c86" dma-buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y) + (set! (-> font-ctx color) old-color)) + (pc-encode-utf8-string (-> this label) *pc-encoded-temp-string*) + (when hovering? + (set! (-> font-ctx color) (font-color cyan))) + (draw-string-adv *pc-encoded-temp-string* dma-buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y) + (set! (-> font-ctx color) old-color))) + +(defun draw-dynamic-entry ((entry-id int) (get-label (function int string none)) (entry-selected? (function int symbol)) (font-ctx font-context) (dma-buf dma-buffer) (hovering? symbol)) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y)) + (old-color (-> font-ctx color))) + (when (entry-selected? entry-id) + (set! (-> font-ctx color) (font-color green)) + (set! (-> font-ctx origin x) (- old-x 6.0)) + (draw-string-adv "\c86" dma-buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y) + (set! (-> font-ctx color) old-color)) + (clear *pc-encoded-temp-string*) + (get-label entry-id *pc-encoded-temp-string*) + (pc-encode-utf8-string *pc-encoded-temp-string* *pc-encoded-temp-string*) + (when hovering? + (set! (-> font-ctx color) (font-color cyan))) + (draw-string-adv *pc-encoded-temp-string* dma-buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y) + (set! (-> font-ctx color) old-color))) + +(defmethod draw-menu ((this popup-menu)) + (let ((font-ctx (new 'debug 'font-context *font-default-matrix* 0 0 0.0 (font-color default) (font-flags shadow kerning large))) + (page-title (-> this menu-states (-> this curr-state-index) title)) + (dynamic-menu? (-> this menu-states (-> this curr-state-index) dynamic-menu?)) + (can-reset? (and (nonzero? (-> this menu-states (-> this curr-state-index) on-dynamic-menu-reset)) + (-> this menu-states (-> this curr-state-index) on-dynamic-menu-reset)))) + (set! (-> font-ctx scale) 0.25) + (set! (-> font-ctx origin x) 15.0) + (set! (-> font-ctx origin y) 75.0) + (let* ((entry-count (if dynamic-menu? + ((-> this menu-states (-> this curr-state-index) get-dynamic-menu-length)) + (-> this menu-states (-> this curr-state-index) entries length))) + (current-index (-> this menu-states (-> this curr-state-index) entry-index)) + (start-index (* (/ current-index 15) 15)) + (end-index (min (+ start-index 15) entry-count)) + (entry-count-to-render (- end-index start-index)) + (menu-rows (if (< end-index entry-count) (inc entry-count-to-render) entry-count-to-render)) + (widest-entry (if dynamic-menu? + (get-widest-dynamic-entry (-> this menu-states (-> this curr-state-index) get-dynamic-menu-entry-label) page-title font-ctx start-index end-index) + (get-widest-entry (-> this menu-states (-> this curr-state-index) entries) page-title font-ctx start-index end-index)))) + (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf2)) + ;; background border + (draw-sprite2d-xy buf + 6 + 64 + (+ 17 widest-entry) ;; width + (+ 17 (* 15 (inc menu-rows))) ;; height + (static-rgba 255 255 255 75) + #x3fffff) + ;; background + (draw-sprite2d-xy buf + 7 + 65 + (+ 15 widest-entry) ;; width + (+ 15 (* 15 (inc menu-rows))) ;; height + (static-rgba 0 0 0 255) + #x3fffff) + ;; title + ;; TODO - function + (pc-encode-utf8-string page-title *pc-encoded-temp-string*) + (set! (-> font-ctx color) (font-color menu-parent)) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y))) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y)) + (set! (-> font-ctx color) (font-color default)) + (set! (-> font-ctx origin y) (+ 15.0 (-> font-ctx origin y))) + ;; menu contents + (dotimes (i entry-count-to-render) + (if dynamic-menu? + (draw-dynamic-entry (+ i start-index) + (-> this menu-states (-> this curr-state-index) get-dynamic-menu-entry-label) + (-> this menu-states (-> this curr-state-index) dynamic-menu-entry-selected?) + font-ctx + buf + (= (+ i start-index) current-index)) + (draw-entry (-> (-> this menu-states (-> this curr-state-index) entries) i) font-ctx buf (= (+ i start-index) current-index))) + (set! (-> font-ctx origin y) (+ 15.0 (-> font-ctx origin y)))) + (when (< end-index entry-count) + (clear *pc-encoded-temp-string*) + (format *pc-encoded-temp-string* "~D more..." (- entry-count end-index)) + (pc-encode-utf8-string *pc-encoded-temp-string* *pc-encoded-temp-string*) + (set! (-> font-ctx color) (font-color menu-parent)) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y))) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y)) + (set! (-> font-ctx color) (font-color default)) + (set! (-> font-ctx origin y) (+ 15.0 (-> font-ctx origin y)))) + ;; button prompts + (cond + ((= (-> this curr-state-index) 0) + (pc-encode-utf8-string " Exit" *pc-encoded-temp-string*) + ) + ((and dynamic-menu? can-reset?) + (pc-encode-utf8-string " Reset / Back" *pc-encoded-temp-string*)) + (else + (pc-encode-utf8-string " Back" *pc-encoded-temp-string*)) + ) + (set! (-> font-ctx origin x) (- 25.0 (-> font-ctx origin x))) + (set! (-> font-ctx origin y) (+ 10.0 (-> font-ctx origin y))) + (let ((old-x (-> font-ctx origin x)) + (old-y (-> font-ctx origin y))) + (draw-string-adv *pc-encoded-temp-string* buf font-ctx) + (set! (-> font-ctx origin x) old-x) + (set! (-> font-ctx origin y) old-y)))))) + +(defmethod move-up! ((this popup-menu) (amount int)) + (let* ((curr-state (-> this menu-states (-> this curr-state-index))) + (new-index (max 0 (-! (-> curr-state entry-index) amount)))) + ;; dynamic menus don't have options that are disabled (just dont include them) + (when (not (-> curr-state dynamic-menu?)) + (let ((entry (-> curr-state entries new-index))) + (when (and (nonzero? (-> entry entry-disabled?)) ((-> entry entry-disabled?))) + (set! new-index (max 0 (dec new-index)))))) + (set! (-> curr-state entry-index) new-index))) + +(defmethod move-down! ((this popup-menu) (amount int)) + (let* ((curr-state (-> this menu-states (-> this curr-state-index))) + (max-entries (if (-> curr-state dynamic-menu?) + ((-> curr-state get-dynamic-menu-length)) + (-> curr-state entries length))) + (new-index (min (dec max-entries) (+! (-> curr-state entry-index) amount)))) + ;; dynamic menus don't have options that are disabled (just dont include them) + (when (not (-> curr-state dynamic-menu?)) + (let ((entry (-> curr-state entries new-index))) + (when (and (nonzero? (-> entry entry-disabled?)) ((-> entry entry-disabled?))) + (set! new-index (min (dec max-entries) (inc new-index)))))) + (set! (-> curr-state entry-index) new-index))) + +(defmethod confirm! ((this popup-menu)) + (let* ((menu-state (-> this menu-states (-> this curr-state-index))) + (dynamic-menu? (-> menu-state dynamic-menu?))) + (if dynamic-menu? + ((-> menu-state on-dynamic-menu-entry-confirm) (-> menu-state entry-index)) + (let ((entry (-> menu-state entries (-> menu-state entry-index)))) + (cond + ((type? entry popup-menu-dynamic-submenu) + ;; TODO - dont allow more than 10 nested menus + (inc! (-> this curr-state-index)) + (set! (-> this menu-states (-> this curr-state-index) entry-index) 0) + (set! (-> this menu-states (-> this curr-state-index) title) (-> entry label)) + (true! (-> this menu-states (-> this curr-state-index) dynamic-menu?)) + (set! (-> this menu-states (-> this curr-state-index) get-dynamic-menu-length) (-> (the-as popup-menu-dynamic-submenu entry) get-length)) + (set! (-> this menu-states (-> this curr-state-index) get-dynamic-menu-entry-label) (-> (the-as popup-menu-dynamic-submenu entry) get-entry-label)) + (set! (-> this menu-states (-> this curr-state-index) on-dynamic-menu-entry-confirm) (-> (the-as popup-menu-dynamic-submenu entry) on-entry-confirm)) + (set! (-> this menu-states (-> this curr-state-index) dynamic-menu-entry-selected?) (-> (the-as popup-menu-dynamic-submenu entry) entry-selected?)) + (set! (-> this menu-states (-> this curr-state-index) on-dynamic-menu-reset) (-> (the-as popup-menu-dynamic-submenu entry) on-reset))) + ((type? entry popup-menu-submenu) + ;; TODO - dont allow more than 10 nested menus + (inc! (-> this curr-state-index)) + (set! (-> this menu-states (-> this curr-state-index) entry-index) 0) + (false! (-> this menu-states (-> this curr-state-index) dynamic-menu?)) + (set! (-> this menu-states (-> this curr-state-index) title) (-> entry label)) + (set! (-> this menu-states (-> this curr-state-index) entries) (-> (the-as popup-menu-submenu entry) entries))) + (else + ((-> entry on-confirm))))))) + (sound-play "menu-pick")) + +(defmethod reset! ((this popup-menu)) + (let* ((menu-state (-> this menu-states (-> this curr-state-index)))) + (when (and (-> menu-state dynamic-menu?) + (nonzero? (-> menu-state on-dynamic-menu-reset)) + (-> menu-state on-dynamic-menu-reset)) ;; dont call if theres no function defined + ((-> menu-state on-dynamic-menu-reset)) + (sound-play "menu-pick")))) + +(defmethod back! ((this popup-menu)) + (sound-play "menu-pick") + (cond + ((<= (-> this curr-state-index) 0) + #t) + (else + (dec! (-> this curr-state-index)) + #f))) + +(defbehavior popup-menu-init-by-other popup-menu ((title string) (entries (array popup-menu-entry))) + (process-mask-clear! (-> self mask) menu pause) + (set! (-> self curr-state-index) 0) + (set! (-> self menu-states 0 title) title) + (set! (-> self menu-states 0 entries) entries) + (set! (-> self menu-states 0 entry-index) 0) + (false! (-> self menu-states 0 dynamic-menu?)) + (false! (-> self draw?)) + (go-virtual idle)) + +(defbehavior popup-menu-event-handler popup-menu ((proc process) (argc int) (message symbol) (block event-message-block)) + (case message + (('open-menu) + (set-master-mode 'menu) + (true! (-> self draw?)) + (true! *popup-menu-open*) + (sound-play "menu-pick")) + (('close-menu) + (set-master-mode 'game) + (false! (-> self draw?)) + (false! *popup-menu-open*)))) + +(defmethod update-menu! ((this popup-menu)) + (when (-> this draw?) + ;; handle input + (cond + ((cpad-pressed? 0 select) + (send-event this 'close-menu)) + ((cpad-pressed? 0 up) + (move-up! this 1)) + ((cpad-pressed? 0 down) + (move-down! this 1)) + ((cpad-pressed? 0 left) + (move-up! this 5)) + ((cpad-pressed? 0 right) + (move-down! this 5)) + ((cpad-pressed? 0 x) + (confirm! this)) + ((cpad-pressed? 0 square) + (reset! this)) + ((cpad-pressed? 0 triangle circle) + (when (back! this) + (send-event this 'close-menu)))) + (draw-menu this))) + +(defstatehandler popup-menu :event popup-menu-event-handler) + +(defstate idle (popup-menu) + :virtual #t + :trans (behavior () (update-menu! self)) + :code sleep-code) diff --git a/goalc/build_level/common/Entity.cpp b/goalc/build_level/common/Entity.cpp index 0659c82a27c..53323a26d62 100644 --- a/goalc/build_level/common/Entity.cpp +++ b/goalc/build_level/common/Entity.cpp @@ -328,11 +328,20 @@ static std::unordered_map res_from_json_array(const std::string& name, const nlohmann::json& json_array, decompiler::DecompilerTypeSystem& dts) { - ASSERT(!json_array.empty()); - std::string array_type = json_array[0].get(); + if (json_array.empty()) { + throw std::runtime_error(fmt::format("json for {} lump was empty", name)); + } + auto& lump = json_array[0]; + if (lump.type() != nlohmann::detail::value_t::string) { + throw std::runtime_error( + fmt::format("first entry of lump \"{}\" has json type {}, but should be string", name, + lump.type_name())); + } + auto array_type = lump.get(); if (lump_map.find(array_type) != lump_map.end()) { return lump_map[array_type](name, json_array, dts); } else { - ASSERT_MSG(false, fmt::format("unsupported array type: {}\n", array_type)); + throw std::runtime_error( + fmt::format("unsupported array type for lump {}: {}\n", name, array_type)); } } \ No newline at end of file