diff --git a/custom_levels/test-zone/test-zone.jsonc b/custom_levels/test-zone/test-zone.jsonc index 634d437741..ed0a568943 100644 --- a/custom_levels/test-zone/test-zone.jsonc +++ b/custom_levels/test-zone/test-zone.jsonc @@ -34,7 +34,24 @@ // The base actor id for your custom level. If you have multiple levels this should be unique! "base_id": 100, - "ambients": [], + // All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file. + // Note: You will still have to add them to your level's .gd file. + "art_groups": ["plat-ag"], + + // Ambients you want to use in your custom level. Ambients can be used for various things like changing the music variation (flava), + // adding ambient sounds, level name hints, etc. + "ambients": [ + { + "trans": [-21.6238, 20.0496, 17.1191, 10.0], + "bsphere": [-21.6238, 20.0496, 17.1191, 15.0], + "lump": { + "name": "test-ambient", + "type": "'hint", + "text-id": ["int32", 557], // text id for the "POWER CELL" text + "play-mode": "'notice" + } + } + ], "actors" : [ { @@ -70,6 +87,16 @@ "lump": { "name":"test-eco" } + }, + { + "trans": [-7.41, 3.5, 28.42], // translation + "etype": "plat", // actor type + "game_task": 0, // associated game task (for powercells, etc) + "quat": [0, 0, 0, 1], // quaternion + "bsphere": [-7.41, 3.5, 28.42, 10], // bounding sphere + "lump": { + "name": "test-plat" + } } ] } \ No newline at end of file diff --git a/custom_levels/test-zone/testzone.gd b/custom_levels/test-zone/testzone.gd index f9b189743c..778b875e07 100644 --- a/custom_levels/test-zone/testzone.gd +++ b/custom_levels/test-zone/testzone.gd @@ -5,4 +5,5 @@ ("TSZ.DGO" ("static-screen.o" "test-zone.go" + "plat-ag.go" )) \ No newline at end of file diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index f94fba74dd..389d1dfd95 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -131,6 +131,27 @@ void extract_art_groups_from_level(const ObjectFileDB& db, } } +std::vector extract_tex_remap(const ObjectFileDB& db, + const std::string& dgo_name) { + auto bsp_rec = get_bsp_file(db.obj_files_by_dgo.at(dgo_name), dgo_name); + if (!bsp_rec) { + lg::warn("Skipping extract for {} because the BSP file was not found", dgo_name); + return {}; + } + std::string level_name = bsp_rec->name.substr(0, bsp_rec->name.length() - 4); + + lg::info("Processing level {} ({})", dgo_name, level_name); + const auto& bsp_file = db.lookup_record(*bsp_rec); + bool ok = is_valid_bsp(bsp_file.linked_data); + ASSERT(ok); + + level_tools::DrawStats draw_stats; + level_tools::BspHeader bsp_header; + bsp_header.read_from_file(bsp_file.linked_data, db.dts, &draw_stats, db.version()); + + return bsp_header.texture_remap_table; +} + std::vector extract_bsp_from_level(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index 629d4fdfa8..a28fbaafa9 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -2,6 +2,8 @@ #include +#include "common_formats.h" + #include "common/math/Vector.h" #include "decompiler/ObjectFile/ObjectFileDB.h" @@ -17,4 +19,13 @@ void extract_all_levels(const ObjectFileDB& db, bool debug_dump_level, bool extract_collision, const fs::path& path); +void add_all_textures_from_level(tfrag3::Level& lev, + const std::string& level_name, + const TextureDB& tex_db); +tfrag3::Texture make_texture(u32 id, + const TextureDB::TextureData& tex, + const std::string& tpage_name, + bool pool_load); +std::vector extract_tex_remap(const ObjectFileDB& db, + const std::string& dgo_name); } // namespace decompiler diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index ea54fc2d1a..48e09db03d 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -56,7 +56,7 @@ add_library(compiler regalloc/Allocator_v2.cpp compiler/docs/DocTypes.cpp) -target_link_libraries(compiler common Zydis tiny_gltf) +target_link_libraries(compiler common Zydis tiny_gltf decomp) if (WIN32) target_link_libraries(compiler mman) diff --git a/goalc/build_level/build_level.cpp b/goalc/build_level/build_level.cpp index dcf3c09f99..6b21ab8adf 100644 --- a/goalc/build_level/build_level.cpp +++ b/goalc/build_level/build_level.cpp @@ -1,3 +1,5 @@ +#include "build_level.h" + #include "common/custom_data/Tfrag3Data.h" #include "common/log/log.h" #include "common/util/FileUtil.h" @@ -5,6 +7,7 @@ #include "common/util/json_util.h" #include "common/util/string_util.h" +#include "decompiler/level_extractor/extract_merc.h" #include "goalc/build_level/Entity.h" #include "goalc/build_level/FileInfo.h" #include "goalc/build_level/LevelFile.h" @@ -36,6 +39,27 @@ std::vector get_build_level_deps(const std::string& input_file) { return {level_json.at("gltf_file").get()}; } +// Find all art groups the custom level needs in a list of object files, +// skipping any that we already found in other dgos before +std::vector find_art_groups( + std::vector& processed_ags, + const std::vector& custom_level_ag, + const std::vector& dgo_files) { + std::vector art_groups; + for (const auto& file : dgo_files) { + // skip any art groups we already added from other dgos + if (std::find(processed_ags.begin(), processed_ags.end(), file.name) != processed_ags.end()) { + continue; + } + if (std::find(custom_level_ag.begin(), custom_level_ag.end(), file.name) != + custom_level_ag.end()) { + art_groups.push_back(file); + processed_ags.push_back(file.name); + } + } + return art_groups; +} + bool run_build_level(const std::string& input_file, const std::string& bsp_output_file, const std::string& output_prefix) { @@ -117,6 +141,74 @@ bool run_build_level(const std::string& input_file, lg::print("Saving to {}\n", save_path.string()); file_util::write_binary_file(save_path, result.data(), result.size()); + // Add textures and models + // TODO remove hardcoded config settings + if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { + decompiler::Config config; + try { + config = decompiler::read_config_file( + file_util::get_jak_project_dir() / "decompiler/config/jak1/jak1_config.jsonc", "ntsc_v1", + R"({"decompile_code": false, "find_functions": false, "levels_extract": true, "allowed_objects": []})"); + } catch (const std::exception& e) { + lg::error("Failed to parse config: {}", e.what()); + return false; + } + + fs::path in_folder; + lg::info("Looking for ISO path..."); + for (const auto& entry : + fs::directory_iterator(file_util::get_jak_project_dir() / "iso_data")) { + if (entry.is_directory() && + entry.path().filename().string().find("jak1") != std::string::npos) { + lg::info("Found ISO path: {}", entry.path().string()); + in_folder = entry.path(); + } + } + if (!fs::exists(in_folder)) { + lg::error("Could not find ISO path!"); + return false; + } + std::vector dgos, objs; + for (const auto& dgo_name : config.dgo_names) { + dgos.push_back(in_folder / dgo_name); + } + + for (const auto& obj_name : config.object_file_names) { + objs.push_back(in_folder / obj_name); + } + + decompiler::ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, {}, + config); + + // need to process link data for tpages + db.process_link_data(config); + + decompiler::TextureDB tex_db; + auto textures_out = file_util::get_jak_project_dir() / "decompiler_out/jak1/textures"; + file_util::create_dir_if_needed(textures_out); + db.process_tpages(tex_db, textures_out, config); + + std::vector processed_art_groups; + + // find all art groups used by the custom level in other dgos + for (auto& dgo : config.dgo_names) { + // 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::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, + db.version()); + } + } + } + } + // Save the PC level save_pc_data(file.nickname, pc_level, file_util::get_jak_project_dir() / "out" / output_prefix / "fr3"); diff --git a/goalc/build_level/build_level.h b/goalc/build_level/build_level.h index 32e80a26a0..e5f1c689f6 100644 --- a/goalc/build_level/build_level.h +++ b/goalc/build_level/build_level.h @@ -3,6 +3,8 @@ #include #include +#include "decompiler/level_extractor/extract_level.h" + bool run_build_level(const std::string& input_file, const std::string& bsp_output_file, const std::string& output_prefix);