From 56d495bc9f25944bb1d1b4e9cffe42ce7cd90a8e Mon Sep 17 00:00:00 2001 From: Matt Dallmeyer Date: Sun, 8 Jun 2025 16:35:25 -0700 Subject: [PATCH] [jak2/3] Support yakows in custom levels (#3951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I discovered that `yakow`s are kinda broken if you try to use them in custom levels in jak 2/3. It's due to the missing `yakow-lod0` texture and associated fix, replacing it with `yak-medfur-end`. This solution works fine for the decompiler on vanilla levels. But for building custom levels, the requested art-groups were being handled before the textures were, and so it was impossible to have `yak-medfur-end` on hand to do the replacement. You'd hit an exception here because `idx_in_level_texture` would still be `INT32_MAX`: https://github.com/open-goal/jak-project/blob/c08118509b84feba002bd9e208f49162b4218556/decompiler/level_extractor/extract_merc.cpp#L806 My fix was just to swap the order when building custom levels, and handle the textures first. I only made the changes for jak2/3, because I see @Hat-Kid has a slightly different implementation for jak1. There's one other small change relating to the `combo_id` / `pc_combo_tex_id` short-circtuiting - I think `pc_combo_tex_id` is always 0 for vanilla textures? So initially `yakow-lod0` actually ended up matching the `combo_id` of the checkerboard texture from the test-zone GLB. I just added another sanity check here that the texture names match too. (I also added yakows in the test-zone.jsonc files 🐄) --- .vs/launch.vs.json | 11 +++++ .../jak2/levels/test-zone/test-zone.jsonc | 14 +++--- .../jak2/levels/test-zone/testzone.gd | 3 +- .../jak3/levels/test-zone/test-zone.jsonc | 17 ++++++- .../jak3/levels/test-zone/testzone.gd | 1 + decompiler/level_extractor/extract_merc.cpp | 3 +- goalc/build_level/jak2/build_level.cpp | 46 +++++++++---------- goalc/build_level/jak3/build_level.cpp | 46 +++++++++---------- 8 files changed, 84 insertions(+), 57 deletions(-) diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index b88abb0e5c..48ec57dbf4 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -246,6 +246,17 @@ "jak2" ] }, + { + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc.exe (bin\\goalc.exe)", + "name": "REPL - Jak 3", + "args": [ + "--user-auto", + "--game", + "jak3" + ] + }, { "type": "default", "project": "CMakeLists.txt", diff --git a/custom_assets/jak2/levels/test-zone/test-zone.jsonc b/custom_assets/jak2/levels/test-zone/test-zone.jsonc index 3e5f14f46a..78dce59c8f 100644 --- a/custom_assets/jak2/levels/test-zone/test-zone.jsonc +++ b/custom_assets/jak2/levels/test-zone/test-zone.jsonc @@ -53,8 +53,8 @@ "base_id": 100, // All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file. - // Removed so that the release builds don't have to double-decompile the game - // "art_groups": ["prsn-torture-ag"], + // Commented out so that the release builds don't have to double-decompile the game + // "art_groups": ["yakow-ag"], // If you have any custom models in the "custom_assets/jak2/models/custom_levels" folder that you want to use in your level, add them to this list. // Note: Like with art groups, these should also be added to your level's .gd file. @@ -66,7 +66,7 @@ // by setting "save_texture_pngs" to true in the decompiler config. // The format is ["tpage-name", "texture-name1", "texture-name2", ...]. // If you want all textures from a tpage, you can just do ["tpage-name"]. - "textures": [], + "textures": ["yak-medfur-end"], // for yakow texture fix "actors": [ { @@ -95,13 +95,13 @@ }, { - "trans": [-7.41, 13.5, 28.42], // translation - "etype": "prsn-torture", // actor type + "trans": [-7.41, 1.04, 28.42], // translation + "etype": "yakow", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-7.41, 13.5, 28.42, 10], // bounding sphere + "bsphere": [-7.41, 1.04, 28.42, 10], // bounding sphere "lump": { - "name": "test-torture" + "name": "test-yakow" } }, diff --git a/custom_assets/jak2/levels/test-zone/testzone.gd b/custom_assets/jak2/levels/test-zone/testzone.gd index 9527b8f7b4..8cd40088b2 100644 --- a/custom_assets/jak2/levels/test-zone/testzone.gd +++ b/custom_assets/jak2/levels/test-zone/testzone.gd @@ -4,7 +4,8 @@ ;; the actual file name still needs to be 8.3 ("TSZ.DGO" ( - "prison-obs.o" + ;; "yakow.o" ;; leave this out, so it will spawn dummy viewer process (otherwise yakow needs navmesh) + "yakow-ag.go" "test-zone-obs.o" "test-actor-ag.go" "test-zone.go" diff --git a/custom_assets/jak3/levels/test-zone/test-zone.jsonc b/custom_assets/jak3/levels/test-zone/test-zone.jsonc index 29863094b1..cd852780df 100644 --- a/custom_assets/jak3/levels/test-zone/test-zone.jsonc +++ b/custom_assets/jak3/levels/test-zone/test-zone.jsonc @@ -53,7 +53,8 @@ "base_id": 100, // All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file. - // "art_groups": [], + // Commented out so that the release builds don't have to double-decompile the game + // "art_groups": ["yakow-ag"], // If you have any custom models in the "custom_assets/jak3/models/custom_levels" folder that you want to use in your level, add them to this list. // Note: Like with art groups, these should also be added to your level's .gd file. @@ -65,7 +66,7 @@ // by setting "save_texture_pngs" to true in the decompiler config. // The format is ["tpage-name", "texture-name1", "texture-name2", ...]. // If you want all textures from a tpage, you can just do ["tpage-name"]. - "textures": [], + "textures": ["yak-medfur-end"], // for yakow texture fix "actors": [ { @@ -93,6 +94,18 @@ } }, + { + "trans": [-7.75, 1.04, 27.136], // translation + "etype": "yakow", // actor type + "game_task": "(game-task none)", // associated game task (for powercells, etc) + "kill_mask": 0, + "quat": [0, 0, 0, 1], // quaternion + "bsphere": [-7.75, 1.04, 27.136, 10], // bounding sphere + "lump": { + "name": "test-yakow" + } + }, + { "trans": [5.41, 3.5, 28.42], // translation "etype": "test-actor", // actor type diff --git a/custom_assets/jak3/levels/test-zone/testzone.gd b/custom_assets/jak3/levels/test-zone/testzone.gd index 18e15a2c1e..61e0979ba5 100644 --- a/custom_assets/jak3/levels/test-zone/testzone.gd +++ b/custom_assets/jak3/levels/test-zone/testzone.gd @@ -4,6 +4,7 @@ ;; the actual file name still needs to be 8.3 ("TSZ.DGO" ( + "yakow-ag.go" ;; no code page for this in jak3, so it will spawn dummy viewer process "test-zone-obs.o" "test-actor-ag.go" "test-zone.go" diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index 5e08e1d9a8..dbba525986 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -729,7 +729,7 @@ s32 find_or_add_texture_to_level(tfrag3::Level& out, GameVersion version) { s32 idx_in_level_texture = INT32_MAX; for (s32 i = 0; i < (int)out.textures.size(); i++) { - if (out.textures[i].combo_id == pc_combo_tex_id) { + if (out.textures[i].combo_id == pc_combo_tex_id && out.textures[i].debug_name == debug_name) { idx_in_level_texture = i; break; } @@ -746,6 +746,7 @@ s32 find_or_add_texture_to_level(tfrag3::Level& out, for (size_t i = 0; i < out.textures.size(); i++) { auto& existing = out.textures[i]; if (existing.debug_name == "yak-medfur-end") { + lg::info("found yak-medfur-end to replace missing yakow-lod0"); idx_in_level_texture = i; break; } diff --git a/goalc/build_level/jak2/build_level.cpp b/goalc/build_level/jak2/build_level.cpp index e226837583..7a65489e8c 100644 --- a/goalc/build_level/jak2/build_level.cpp +++ b/goalc/build_level/jak2/build_level.cpp @@ -148,29 +148,6 @@ bool run_build_level(const std::string& input_file, tex_db.replace_textures(replacements_path); } - // find all art groups used by the custom level in other dgos - if (gen_fr3 && level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { - for (auto& dgo : config.dgo_names) { - std::vector processed_art_groups; - // remove "DGO/" prefix - const auto& dgo_name = dgo.substr(4); - const auto& files = db.obj_files_by_dgo.at(dgo_name); - auto art_groups = - find_art_groups(processed_art_groups, - level_json.at("art_groups").get>(), files); - auto tex_remap = decompiler::extract_tex_remap(db, dgo_name); - for (const auto& ag : art_groups) { - if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { - const auto& ag_file = db.lookup_record(ag); - lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); - decompiler::MercSwapInfo info; - decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, - db.version(), info); - } - } - } - } - // add textures if (level_json.contains("textures") && !level_json.at("textures").empty()) { std::vector processed_textures; @@ -201,6 +178,29 @@ bool run_build_level(const std::string& input_file, } } } + + // find all art groups used by the custom level in other dgos + if (gen_fr3 && level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { + for (auto& dgo : config.dgo_names) { + std::vector processed_art_groups; + // remove "DGO/" prefix + const auto& dgo_name = dgo.substr(4); + const auto& files = db.obj_files_by_dgo.at(dgo_name); + auto art_groups = + find_art_groups(processed_art_groups, + level_json.at("art_groups").get>(), files); + auto tex_remap = decompiler::extract_tex_remap(db, dgo_name); + for (const auto& ag : art_groups) { + if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { + const auto& ag_file = db.lookup_record(ag); + lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); + decompiler::MercSwapInfo info; + decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, + db.version(), info); + } + } + } + } } // add custom models to fr3 diff --git a/goalc/build_level/jak3/build_level.cpp b/goalc/build_level/jak3/build_level.cpp index 615bfa8d97..e83e220760 100644 --- a/goalc/build_level/jak3/build_level.cpp +++ b/goalc/build_level/jak3/build_level.cpp @@ -146,29 +146,6 @@ bool run_build_level(const std::string& input_file, tex_db.replace_textures(replacements_path); } - // find all art groups used by the custom level in other dgos - if (gen_fr3 && level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { - for (auto& dgo : config.dgo_names) { - std::vector processed_art_groups; - // remove "DGO/" prefix - const auto& dgo_name = dgo.substr(4); - const auto& files = db.obj_files_by_dgo.at(dgo_name); - auto art_groups = - find_art_groups(processed_art_groups, - level_json.at("art_groups").get>(), files); - auto tex_remap = decompiler::extract_tex_remap(db, dgo_name); - for (const auto& ag : art_groups) { - if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { - const auto& ag_file = db.lookup_record(ag); - lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); - decompiler::MercSwapInfo info; - decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, - db.version(), info); - } - } - } - } - // add textures if (level_json.contains("textures") && !level_json.at("textures").empty()) { std::vector processed_textures; @@ -199,6 +176,29 @@ bool run_build_level(const std::string& input_file, } } } + + // find all art groups used by the custom level in other dgos + if (gen_fr3 && level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { + for (auto& dgo : config.dgo_names) { + std::vector processed_art_groups; + // remove "DGO/" prefix + const auto& dgo_name = dgo.substr(4); + const auto& files = db.obj_files_by_dgo.at(dgo_name); + auto art_groups = + find_art_groups(processed_art_groups, + level_json.at("art_groups").get>(), files); + auto tex_remap = decompiler::extract_tex_remap(db, dgo_name); + for (const auto& ag : art_groups) { + if (ag.name.length() > 3 && !ag.name.compare(ag.name.length() - 3, 3, "-ag")) { + const auto& ag_file = db.lookup_record(ag); + lg::print("custom level: extracting art group {}\n", ag_file.name_in_dgo); + decompiler::MercSwapInfo info; + decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, + db.version(), info); + } + } + } + } } // add custom models to fr3