From 614c5a663cb9e5754ff0ef3628be87aa5474bd8e Mon Sep 17 00:00:00 2001 From: Hat Kid <6624576+Hat-Kid@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:18:28 +0200 Subject: [PATCH] custom levels: support vanilla skies and texture remapping tables (#3691) This adds some new JSON entries to custom levels so they can support vanilla sky textures and the texture remapping tables, allowing for proper textures on objects that use `generic`, like dark eco pools or dying enemies. The comments explain it in more detail, but the gist is: For skies: - `sky` needs to be a vanilla level that has sky textures. - The alpha tpage (fourth entry in `tpages`) needs to be that vanilla level's alpha tpage (if `tex_remap` is the same level as `sky`, this will be handled automatically). - The tpage needs to be added to the custom level `.gd` and to `textures` in the JSON. - In `level-info.gc`, `sky` needs to be `#t`, your level's mood needs to call `update-mood-sky-texture` (the default mood, `update-mood-default`, handles this as an example) and `sun-fade` needs to be nonzero for the sun to show up. For `generic` textures: - `tex_remap` needs to be the name of a vanilla level. - When using a vanilla level's remap table, you need to adhere to the order of the files in that level's `.gd` in your own level. - Code files are first. - Then the tpages (in the order `tfrag`, `pris`, `shrub`, `alpha`, `water`). - Then the art groups. - Lastly, the level file. - The tpages need to be added to the `textures` in the JSON. --- .../jak1/levels/test-zone/test-zone.jsonc | 79 +++++++--- .../jak1/levels/test-zone/testzone.gd | 11 +- .../jak2/levels/test-zone/test-zone.jsonc | 34 ++-- .../jak3/levels/test-zone/test-zone.jsonc | 32 ++-- decompiler/config/jak1/all-types.gc | 51 ++++++ decompiler/level_extractor/BspHeader.cpp | 20 +++ decompiler/level_extractor/BspHeader.h | 9 ++ decompiler/level_extractor/extract_level.h | 3 + goal_src/jak1/engine/common-obs/water-anim.gc | 51 ++++++ goal_src/jak1/engine/level/level-info.gc | 11 +- goalc/build_level/jak1/LevelFile.cpp | 46 ++++++ goalc/build_level/jak1/LevelFile.h | 13 +- goalc/build_level/jak1/build_level.cpp | 148 ++++++++++++++---- 13 files changed, 415 insertions(+), 93 deletions(-) diff --git a/custom_assets/jak1/levels/test-zone/test-zone.jsonc b/custom_assets/jak1/levels/test-zone/test-zone.jsonc index a5b22cc89..a08d1b62e 100644 --- a/custom_assets/jak1/levels/test-zone/test-zone.jsonc +++ b/custom_assets/jak1/levels/test-zone/test-zone.jsonc @@ -67,21 +67,60 @@ "custom_models": ["test-actor"], // Any textures you want to include in your custom level. This is mainly useful for things such as the zoomer HUD, - // which is not in the common level files and has no art group associated with it. + // sky textures and cases where you want to use objects drawn with the generic renderer, + // which also requires you to set a texture remap table and a list of tpages. // To get a list of all the textures, you can extract all of the game's textures // 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": [ - // all textures required for the zoomer HUD - // "zoomerhud", - // "zoomerhud-dial", - // "zoomerhud-main-02", - // "zoomerhud-main-03", - // "zoomerhud-pieslice", - // "zoomerhud-heatbg-01", - // "zoomerhud-main-03arrow", - // "zoomerhud-main-03knob" + // Zoomer HUD textures + // ["zoomerhud"], + // Sandover sky textures + ["village1-vis-alpha"] ], + // Which vanilla level's texture remap table to use. + // This is needed if you want to have proper textures for objects that are drawn using generic, such as + // dark eco pools or dying enemies. If you choose to use this, + // the order of art groups and tpages in your level's .gd MUST match the vanilla level's for the textures to work. + "tex_remap": "village1", + + // Which sky to use for your custom level. This needs to be the name of a vanilla level with sky textures. + // Your level's mood update needs to be set up to copy the sky texture (update-mood-default already handles this as an example). + // You also need to add a specific tpage to your level .gd depending on the level you chose: + // training: tpage-1308.go + // village1: tpage-401.go + // beach: tpage-215.go + // jungle: tpage-388.go + // misty: tpage-520.go + // firecanyon: tpage-1123.go + // village2: tpage-921.go + // rolling: tpage-925.go + // sunkenb: tpage-162.go + // swamp: tpage-630.go + // ogre: tpage-1117.go + // village3: tpage-1194.go + // snow: tpage-712.go + // finalboss: tpage-1418.go + // The tpage you choose also has to be in the alpha (4th) slot in the "tpages" list below. + "sky": "village1", + + // Any texture pages that are added to the level's "texture-ids" array. + // These will be logged in and linked as part of level loading. + // There are five tpage categories, in this order: + // - tfrag + // - pris + // - shrub + // - alpha + // - water + // This is also (with the exception of levels that use the zoomer) the order that the tpages are listed in in the vanilla .gd files. + // You need to also add these to your level .gd as well. + // Sky textures use the alpha slot. + // If this is empty and "tex_remap" and "sky" are the same level, this will be auto-filled with the tpages from the + // corresponding level (check its .gd file for the art group/tpage order to copy over). + "tpages": [], + // 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": [ @@ -97,10 +136,10 @@ } ], - "actors" : [ + "actors": [ { "trans": [-21.6238, 20.0496, 17.1191], // translation - "etype": "fuel-cell", // actor type + "etype": "fuel-cell", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "quat": [0, 0, 0, 1], // quaternion "bsphere": [-21.6238, 19.3496, 17.1191, 10], // bounding sphere @@ -111,11 +150,11 @@ }, { - "trans": [-15.2818, 15.2461, 17.1360], // translation - "etype": "crate", // actor type + "trans": [-15.2818, 15.2461, 17.136], // translation + "etype": "crate", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-15.2818, 15.2461, 17.1360, 10], // bounding sphere + "bsphere": [-15.2818, 15.2461, 17.136, 10], // bounding sphere "lump": { "name": "test-crate", "crate-type": "'steel", @@ -124,18 +163,18 @@ }, { - "trans": [-5.4630, 17.4553, 1.6169], // translation - "etype": "eco-yellow", // actor type + "trans": [-5.463, 17.4553, 1.6169], // translation + "etype": "eco-yellow", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-5.4630, 17.4553, 1.6169, 10], // bounding sphere + "bsphere": [-5.463, 17.4553, 1.6169, 10], // bounding sphere "lump": { "name": "test-eco" } }, { "trans": [-5.41, 3.5, 28.42], // translation - "etype": "test-actor", // actor type + "etype": "test-actor", // 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 @@ -154,4 +193,4 @@ // } // } ] -} \ No newline at end of file +} diff --git a/custom_assets/jak1/levels/test-zone/testzone.gd b/custom_assets/jak1/levels/test-zone/testzone.gd index 8889f9bbc..49821b03a 100644 --- a/custom_assets/jak1/levels/test-zone/testzone.gd +++ b/custom_assets/jak1/levels/test-zone/testzone.gd @@ -3,9 +3,14 @@ ;; the actual file name still needs to be 8.3 ("TSZ.DGO" - ("static-screen.o" - "test-zone-obs.o" + ("test-zone-obs.o" + "tpage-398.go" + "tpage-400.go" + "tpage-399.go" + "tpage-401.go" + "tpage-1470.go" "plat-ag.go" "test-actor-ag.go" "test-zone.go" - )) \ No newline at end of file + ) + ) \ No newline at end of file diff --git a/custom_assets/jak2/levels/test-zone/test-zone.jsonc b/custom_assets/jak2/levels/test-zone/test-zone.jsonc index 56f08a3ad..1c9319b5a 100644 --- a/custom_assets/jak2/levels/test-zone/test-zone.jsonc +++ b/custom_assets/jak2/levels/test-zone/test-zone.jsonc @@ -49,27 +49,29 @@ // adds a 'type' tag (using the "symbol" and "string" lump types works the same way): // "spawn-types": ["type", "spyder", "juicer"] - // The base actor id for your custom level. If you have multiple levels, this should be unique! - "base_id": 100, + // The base actor id for your custom level. If you have multiple levels, this should be unique! + "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"], - // Any textures you want to include in your custom level. - // This is mainly useful for textures which are not in the common level files and have no art group associated with them. - // To get a list of all the textures, you can extract all of the game's textures - // by setting "save_texture_pngs" to true in the decompiler config. - "textures": [], + // Any textures you want to include in your custom level. + // This is mainly useful for textures which are not in the common level files and have no art group associated with them. + // To get a list of all the textures, you can extract all of the game's textures + // 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": [], - "actors" : [ + "actors": [ { - "trans": [-15.2818, 15.2461, 17.1360], // translation - "etype": "crate", // actor type + "trans": [-15.2818, 15.2461, 17.136], // translation + "etype": "crate", // actor type "game_task": 0, // associated game task (for powercells, etc) "kill_mask": 0, "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-15.2818, 15.2461, 17.1360, 10], // bounding sphere + "bsphere": [-15.2818, 15.2461, 17.136, 10], // bounding sphere "lump": { "name": "test-crate", "eco-info": ["eco-info", "(pickup-type health)", 2] @@ -77,12 +79,12 @@ }, { - "trans": [-5.4630, 17.4553, 1.6169], // translation - "etype": "eco-yellow", // actor type + "trans": [-5.463, 17.4553, 1.6169], // translation + "etype": "eco-yellow", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "kill_mask": 0, "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-5.4630, 17.4553, 1.6169, 10], // bounding sphere + "bsphere": [-5.463, 17.4553, 1.6169, 10], // bounding sphere "lump": { "name": "test-eco" } @@ -90,7 +92,7 @@ { "trans": [-7.41, 13.5, 28.42], // translation - "etype": "prsn-torture", // actor type + "etype": "prsn-torture", // 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 @@ -99,4 +101,4 @@ } } ] -} \ No newline at end of file +} diff --git a/custom_assets/jak3/levels/test-zone/test-zone.jsonc b/custom_assets/jak3/levels/test-zone/test-zone.jsonc index 1b4b9948d..dc3d86672 100644 --- a/custom_assets/jak3/levels/test-zone/test-zone.jsonc +++ b/custom_assets/jak3/levels/test-zone/test-zone.jsonc @@ -49,26 +49,28 @@ // adds a 'type' tag (using the "symbol" and "string" lump types works the same way): // "spawn-types": ["type", "spyder", "juicer"] - // The base actor id for your custom level. If you have multiple levels, this should be unique! - "base_id": 100, + // The base actor id for your custom level. If you have multiple levels, this should be unique! + "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": [], - // Any textures you want to include in your custom level. - // This is mainly useful for textures which are not in the common level files and have no art group associated with them. - // To get a list of all the textures, you can extract all of the game's textures - // by setting "save_texture_pngs" to true in the decompiler config. - // "textures": [], + // Any textures you want to include in your custom level. + // This is mainly useful for textures which are not in the common level files and have no art group associated with them. + // To get a list of all the textures, you can extract all of the game's textures + // 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": [], - "actors" : [ + "actors": [ { - "trans": [-15.2818, 15.2461, 17.1360], // translation - "etype": "crate", // actor type + "trans": [-15.2818, 15.2461, 17.136], // translation + "etype": "crate", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "kill_mask": 0, "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-15.2818, 15.2461, 17.1360, 10], // bounding sphere + "bsphere": [-15.2818, 15.2461, 17.136, 10], // bounding sphere "lump": { "name": "test-crate", "eco-info": ["eco-info", "(pickup-type gem)", 1] @@ -76,15 +78,15 @@ }, { - "trans": [-5.4630, 17.4553, 1.6169], // translation - "etype": "eco-yellow", // actor type + "trans": [-5.463, 17.4553, 1.6169], // translation + "etype": "eco-yellow", // actor type "game_task": "(game-task none)", // associated game task (for powercells, etc) "kill_mask": 0, "quat": [0, 0, 0, 1], // quaternion - "bsphere": [-5.4630, 17.4553, 1.6169, 10], // bounding sphere + "bsphere": [-5.463, 17.4553, 1.6169, 10], // bounding sphere "lump": { "name": "test-eco" } } ] -} \ No newline at end of file +} diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc index fa6d3e118..31c04df00 100644 --- a/decompiler/config/jak1/all-types.gc +++ b/decompiler/config/jak1/all-types.gc @@ -19628,6 +19628,57 @@ (wt31) ) +(defenum water-look + (water-anim-sunken-big-room 0) + (water-anim-sunken-first-room-from-entrance 1) + (water-anim-sunken-qbert-room 2) + (water-anim-sunken-first-right-branch 3) + (water-anim-sunken-circular-with-bullys 4) + (water-anim-sunken-hall-with-one-whirlpool 5) + (water-anim-sunken-hall-with-three-whirlpools 6) + (water-anim-sunken-start-of-helix-slide 7) + (water-anim-sunken-room-above-exit-chamber 8) + (water-anim-sunken-hall-before-big-room 9) + (water-anim-sunken-dark-eco-qbert 10) + (water-anim-sunken-short-piece 11) + (water-anim-sunken-big-room-upper-water 12) + (water-anim-sunken-dark-eco-platform-room 13) + (water-anim-maincave-water-with-crystal 14) + (water-anim-maincave-center-pool 15) + (water-anim-maincave-lower-right-pool 16) + (water-anim-maincave-mid-right-pool 17) + (water-anim-maincave-lower-left-pool 18) + (water-anim-maincave-mid-left-pool 19) + (water-anim-robocave-main-pool 20) + (water-anim-misty-mud-by-arena 21) + (water-anim-misty-mud-above-skeleton 22) + (water-anim-misty-mud-behind-skeleton 23) + (water-anim-misty-mud-above-skull-back 24) + (water-anim-misty-mud-above-skull-front 25) + (water-anim-misty-mud-other-near-skull 26) + (water-anim-misty-mud-near-skull 27) + (water-anim-misty-mud-under-spine 28) + (water-anim-misty-mud-by-dock 29) + (water-anim-misty-mud-island-near-dock 30) + (water-anim-misty-mud-lonely-island 31) + (water-anim-misty-dark-eco-pool 32) + (water-anim-ogre-lava 33) + (water-anim-jungle-river 34) + (water-anim-village3-lava 35) + (water-anim-training-lake 36) + (water-anim-darkcave-water-with-crystal 37) + (water-anim-rolling-water-back 38) + (water-anim-rolling-water-front 39) + (water-anim-sunken-dark-eco-helix-room 40) + (water-anim-finalboss-dark-eco-pool 41) + (water-anim-lavatube-energy-lava 42) + (water-anim-village1-rice-paddy 43) + (water-anim-village1-fountain 44) + (water-anim-village1-rice-paddy-mid 45) + (water-anim-village1-rice-paddy-top 46) + (water-anim-village2-bucket 47) + ) + (deftype water-control (basic) ((flags water-flags :offset-assert 4) (process process-drawable :offset-assert 8) diff --git a/decompiler/level_extractor/BspHeader.cpp b/decompiler/level_extractor/BspHeader.cpp index b502af8f9..5413407f3 100644 --- a/decompiler/level_extractor/BspHeader.cpp +++ b/decompiler/level_extractor/BspHeader.cpp @@ -1987,6 +1987,13 @@ void HFragment::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSys size = read_plain_data_field(ref, "size", dts); } +void AdgifShaderArray::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) { + auto length = read_plain_data_field(ref, "length", dts); + adgifs.resize(length); + memcpy_plain_data((u8*)adgifs.data(), get_field_ref(ref, "data", dts), + sizeof(AdGifData) * length); +} + void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file, const decompiler::DecompilerTypeSystem& dts, GameVersion version, @@ -2001,6 +2008,19 @@ void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file, bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); name = read_symbol_field(ref, "name", dts); + if (version == GameVersion::Jak1) { + adgifs.read_from_file(get_and_check_ref_to_basic(ref, "adgifs", "adgif-shader-array", dts), + dts); + } + + texture_page_count = read_plain_data_field(ref, "texture-page-count", dts); + if (texture_page_count > 0) { + auto tex_id_ptr = deref_label(get_field_ref(ref, "texture-ids", dts)); + for (int i = 0; i < texture_page_count; i++) { + texture_ids.push_back(deref_u32(tex_id_ptr, i)); + } + } + texture_remap_table.clear(); s32 tex_remap_len = read_plain_data_field(ref, "texture-remap-table-len", dts); if (tex_remap_len > 0) { diff --git a/decompiler/level_extractor/BspHeader.h b/decompiler/level_extractor/BspHeader.h index 128912a82..e0b47969f 100644 --- a/decompiler/level_extractor/BspHeader.h +++ b/decompiler/level_extractor/BspHeader.h @@ -840,6 +840,12 @@ struct DrawableTreeArray { std::vector> trees; }; +struct AdgifShaderArray { + std::vector adgifs; + + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts); +}; + // The "file info" struct FileInfo { std::string file_type; @@ -884,7 +890,9 @@ struct BspHeader { u16 texture_flags[kNumTextureFlags]; // jak 2 only // // (texture-ids (pointer texture-id) :offset-assert 60) + std::vector texture_ids; // (texture-page-count int32 :offset-assert 64) + s32 texture_page_count; // // (unk-zero-0 basic :offset-assert 68) // @@ -907,6 +915,7 @@ struct BspHeader { // (unk-data-4 float :offset-assert 160) // (unk-data-5 float :offset-assert 164) // (adgifs adgif-shader-array :offset-assert 168) + AdgifShaderArray adgifs; // (actor-birth-order (pointer uint32) :offset-assert 172) // (split-box-indices (pointer uint16) :offset-assert 176) // (unk-data-8 uint32 55 :offset-assert 180) diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index 48800629a..ac4712f94 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -26,4 +26,7 @@ tfrag3::Texture make_texture(u32 id, bool pool_load); std::vector extract_tex_remap(const ObjectFileDB& db, const std::string& dgo_name); +std::optional get_bsp_file(const std::vector& records, + const std::string& dgo_name); +bool is_valid_bsp(const LinkedObjectFile& file); } // namespace decompiler diff --git a/goal_src/jak1/engine/common-obs/water-anim.gc b/goal_src/jak1/engine/common-obs/water-anim.gc index ec0fd32ab..5397ebfc3 100644 --- a/goal_src/jak1/engine/common-obs/water-anim.gc +++ b/goal_src/jak1/engine/common-obs/water-anim.gc @@ -353,6 +353,57 @@ (anim int32) (ambient-sound-spec sound-spec))) +;; og:preserve-this added +(defenum water-look + (water-anim-sunken-big-room 0) + (water-anim-sunken-first-room-from-entrance 1) + (water-anim-sunken-qbert-room 2) + (water-anim-sunken-first-right-branch 3) + (water-anim-sunken-circular-with-bullys 4) + (water-anim-sunken-hall-with-one-whirlpool 5) + (water-anim-sunken-hall-with-three-whirlpools 6) + (water-anim-sunken-start-of-helix-slide 7) + (water-anim-sunken-room-above-exit-chamber 8) + (water-anim-sunken-hall-before-big-room 9) + (water-anim-sunken-dark-eco-qbert 10) + (water-anim-sunken-short-piece 11) + (water-anim-sunken-big-room-upper-water 12) + (water-anim-sunken-dark-eco-platform-room 13) + (water-anim-maincave-water-with-crystal 14) + (water-anim-maincave-center-pool 15) + (water-anim-maincave-lower-right-pool 16) + (water-anim-maincave-mid-right-pool 17) + (water-anim-maincave-lower-left-pool 18) + (water-anim-maincave-mid-left-pool 19) + (water-anim-robocave-main-pool 20) + (water-anim-misty-mud-by-arena 21) + (water-anim-misty-mud-above-skeleton 22) + (water-anim-misty-mud-behind-skeleton 23) + (water-anim-misty-mud-above-skull-back 24) + (water-anim-misty-mud-above-skull-front 25) + (water-anim-misty-mud-other-near-skull 26) + (water-anim-misty-mud-near-skull 27) + (water-anim-misty-mud-under-spine 28) + (water-anim-misty-mud-by-dock 29) + (water-anim-misty-mud-island-near-dock 30) + (water-anim-misty-mud-lonely-island 31) + (water-anim-misty-dark-eco-pool 32) + (water-anim-ogre-lava 33) + (water-anim-jungle-river 34) + (water-anim-village3-lava 35) + (water-anim-training-lake 36) + (water-anim-darkcave-water-with-crystal 37) + (water-anim-rolling-water-back 38) + (water-anim-rolling-water-front 39) + (water-anim-sunken-dark-eco-helix-room 40) + (water-anim-finalboss-dark-eco-pool 41) + (water-anim-lavatube-energy-lava 42) + (water-anim-village1-rice-paddy 43) + (water-anim-village1-fountain 44) + (water-anim-village1-rice-paddy-mid 45) + (water-anim-village1-rice-paddy-top 46) + (water-anim-village2-bucket 47)) + (define *water-anim-look* (new 'static 'boxed-array diff --git a/goal_src/jak1/engine/level/level-info.gc b/goal_src/jak1/engine/level/level-info.gc index f3f2958f8..8a6ff4b8e 100644 --- a/goal_src/jak1/engine/level/level-info.gc +++ b/goal_src/jak1/engine/level/level-info.gc @@ -2485,10 +2485,11 @@ :music-bank #f :ambient-sounds '() - :mood '*default-mood* - :mood-func 'update-mood-default - :ocean #f + :mood '*village1-mood* + :mood-func 'update-mood-village1 + :ocean '*ocean-map-village1* :sky #t + :sun-fade 1.0 :continues '((new 'static 'continue-point @@ -2507,8 +2508,8 @@ :vis-nick 'none :lev0 'test-zone :disp0 'display - :lev1 'village1 - :disp1 'display)) + :lev1 #f + :disp1 #f)) :tasks '() :priority 100 diff --git a/goalc/build_level/jak1/LevelFile.cpp b/goalc/build_level/jak1/LevelFile.cpp index 8b566aafd..a49cc34e5 100644 --- a/goalc/build_level/jak1/LevelFile.cpp +++ b/goalc/build_level/jak1/LevelFile.cpp @@ -74,6 +74,44 @@ size_t generate_u32_array(const std::vector& array, DataObjectGenerator& ge return result; } +size_t generate_adgif_shader_array(const std::vector& data, DataObjectGenerator& gen) { + gen.align_to_basic(); + gen.add_type_tag("adgif-shader-array"); + size_t result = gen.current_offset_bytes(); + for (auto& word : data) { + gen.add_word(word); + } + return result; +} + +size_t generate_adgif_shader_array(const AdgifShaderArray& adgifs, DataObjectGenerator& gen) { + gen.align_to_basic(); + gen.add_type_tag("adgif-shader-array"); + size_t result = gen.current_offset_bytes(); + gen.add_word(adgifs.adgifs.size()); + gen.add_word(adgifs.adgifs.size()); + gen.add_word(0); + for (auto& adgif : adgifs.adgifs) { + for (size_t i = 0; i < sizeof(AdGifData) / sizeof(u32); i++) { + u32 data; + memcpy(&data, (u32*)&adgif + i, sizeof(u32)); + gen.add_word(data); + } + } + return result; +} + +size_t generate_tex_remap_table(const std::vector& remap_table, + DataObjectGenerator& gen) { + gen.align(4); + size_t result = gen.current_offset_bytes(); + for (auto& entry : remap_table) { + gen.add_word(entry.orig_texid); + gen.add_word(entry.new_texid); + } + return result; +} + std::vector LevelFile::save_object_file() const { DataObjectGenerator gen; gen.add_type_tag("bsp-header"); @@ -97,9 +135,15 @@ std::vector LevelFile::save_object_file() const { //(pat pointer :offset-assert 44) //(pat-length int32 :offset-assert 48) //(texture-remap-table (pointer uint64) :offset-assert 52) + if (!texture_remap_table.empty()) + gen.link_word_to_byte(52 / 4, generate_tex_remap_table(texture_remap_table, gen)); //(texture-remap-table-len int32 :offset-assert 56) + gen.set_word(56 / 4, texture_remap_table.size()); //(texture-ids (pointer texture-id) :offset-assert 60) + if (!texture_ids.empty()) + gen.link_word_to_byte(60 / 4, generate_u32_array(texture_ids, gen)); //(texture-page-count int32 :offset-assert 64) + gen.set_word(64 / 4, texture_ids.size()); //(unk-zero-0 basic :offset-assert 68) //(name symbol :offset-assert 72) gen.link_word_to_symbol(name, 72 / 4); @@ -120,6 +164,8 @@ std::vector LevelFile::save_object_file() const { //(unk-data-4 float :offset-assert 160) //(unk-data-5 float :offset-assert 164) //(adgifs adgif-shader-array :offset-assert 168) + if (!adgifs.adgifs.empty()) + gen.link_word_to_byte(168 / 4, generate_adgif_shader_array(adgifs, gen)); //(actor-birth-order (pointer uint32) :offset-assert 172) gen.link_word_to_byte(172 / 4, generate_u32_array(actor_birth_order, gen)); //(split-box-indices (pointer uint16) :offset-assert 176) diff --git a/goalc/build_level/jak1/LevelFile.h b/goalc/build_level/jak1/LevelFile.h index bfeadad67..3f718ec2f 100644 --- a/goalc/build_level/jak1/LevelFile.h +++ b/goalc/build_level/jak1/LevelFile.h @@ -36,7 +36,10 @@ struct DrawableTreeArray { size_t add_to_object_file(DataObjectGenerator& gen) const; }; -struct TextureRemap {}; +struct TexRemap { + u32 orig_texid; + u32 new_texid; +}; struct TextureId {}; @@ -52,7 +55,9 @@ struct DrawableInlineArrayAmbient { std::vector ambients; }; -struct AdgifShaderArray {}; +struct AdgifShaderArray { + std::vector adgifs; +}; // This is a place to collect all the data that should go into the bsp-header file. struct LevelFile { @@ -72,11 +77,11 @@ struct LevelFile { // (texture-remap-table (pointer uint64) :offset-assert 52) // (texture-remap-table-len int32 :offset-assert 56) - std::vector texture_remap_table; + std::vector texture_remap_table; // (texture-ids (pointer texture-id) :offset-assert 60) // (texture-page-count int32 :offset-assert 64) - std::vector texture_ids; + std::vector texture_ids; // (unk-zero-0 basic :offset-assert 68) // "misc", seems like it can be zero and is unused. diff --git a/goalc/build_level/jak1/build_level.cpp b/goalc/build_level/jak1/build_level.cpp index 7a9e1607c..6ac0ac16b 100644 --- a/goalc/build_level/jak1/build_level.cpp +++ b/goalc/build_level/jak1/build_level.cpp @@ -3,6 +3,8 @@ #include "common/util/gltf_util.h" #include "decompiler/extractor/extractor_util.h" +#include "decompiler/level_extractor/BspHeader.h" +#include "decompiler/level_extractor/extract_level.h" #include "decompiler/level_extractor/extract_merc.h" #include "goalc/build_level/collide/jak1/collide_bvh.h" #include "goalc/build_level/collide/jak1/collide_pack.h" @@ -17,7 +19,7 @@ bool run_build_level(const std::string& input_file, const std::string& output_prefix) { auto level_json = parse_commented_json( file_util::read_text_file(file_util::get_file_path({input_file})), input_file); - LevelFile file; // GOAL level file + LevelFile file{}; // GOAL level file tfrag3::Level pc_level; // PC level file gltf_util::TexturePool tex_pool; // pc level texture pool @@ -48,7 +50,7 @@ bool run_build_level(const std::string& input_file, // actors std::vector actors; auto dts = decompiler::DecompilerTypeSystem(GameVersion::Jak1); - dts.parse_enum_defs({"decompiler", "config", "jak1", "all-types.gc"}); + dts.parse_type_defs({"decompiler", "config", "jak1", "all-types.gc"}); add_actors_from_json(level_json.at("actors"), actors, level_json.value("base_id", 1234), dts); std::sort(actors.begin(), actors.end(), [](auto& a, auto& b) { return a.aid < b.aid; }); auto duplicates = std::adjacent_find(actors.begin(), actors.end(), @@ -104,13 +106,9 @@ bool run_build_level(const std::string& input_file, collide_drawable_tree.packed_frags = pack_collide_frags(collide_drawable_tree.bvh.frags.frags); } - // Save the GOAL level - auto result = file.save_object_file(); - lg::print("Level bsp file size {} bytes\n", result.size()); - auto save_path = file_util::get_jak_project_dir() / bsp_output_file; - file_util::create_dir_if_needed_for_file(save_path); - lg::print("Saving to {}\n", save_path.string()); - file_util::write_binary_file(save_path, result.data(), result.size()); + auto sky_name = level_json.value("sky", "none"); + auto texture_remap = level_json.value("tex_remap", "none"); + auto tpages = level_json.value("tpages", std::vector({})); // Add textures and models // TODO remove hardcoded config settings @@ -161,20 +159,64 @@ bool run_build_level(const std::string& input_file, std::vector processed_art_groups; - // find all art groups used by the custom level in other dgos - if (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) { + // find all art groups used by the custom level in other dgos and extract sky and texture remap + // if desired + auto should_process_art_groups = + (level_json.contains("art_groups") && !level_json.at("art_groups").empty()) || + (sky_name != "none" || texture_remap != "none"); + if (should_process_art_groups) { 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); + level_json.value("art_groups", std::vector{}), files); + std::vector tex_remap{}; + if (auto bsp = get_bsp_file(files, dgo_name)) { + const auto& link_data = db.lookup_record(bsp.value()).linked_data; + if (is_valid_bsp(link_data)) { + level_tools::BspHeader level_file; + level_file.read_from_file(link_data, dts, GameVersion::Jak1, true); + auto bsp_name = bsp.value().name.substr(0, bsp.value().name.size() - 4); + tex_remap = level_file.texture_remap_table; + auto is_sky_bsp = bsp_name == sky_name; + auto is_tex_remap_bsp = bsp_name == texture_remap; + auto sky_and_tex_remap_same = sky_name == texture_remap; + if (is_tex_remap_bsp) { + lg::info("custom level: copying texture remap data from {}", texture_remap); + // copy texture remap data from bsp + file.texture_remap_table.resize(tex_remap.size()); + memcpy(file.texture_remap_table.data(), level_file.texture_remap_table.data(), + tex_remap.size() * sizeof(level_tools::TextureRemap)); + } + if (is_sky_bsp) { + // copy adgif data from bsp + lg::info("custom level: copying adgifs from {}", sky_name); + auto& adgifs = file.adgifs.adgifs; + adgifs.resize(level_file.adgifs.adgifs.size()); + memcpy(adgifs.data(), level_file.adgifs.adgifs.data(), + level_file.adgifs.adgifs.size() * sizeof(AdGifData)); + } + if (sky_and_tex_remap_same && is_sky_bsp && is_tex_remap_bsp && tpages.empty()) { + // if tpages json is empty and sky and tex remap are the same level, auto fill + file.texture_ids.resize(level_file.texture_page_count); + memcpy(file.texture_ids.data(), level_file.texture_ids.data(), + sizeof(u32) * level_file.texture_page_count); + std::vector tex_ids; + tex_ids.reserve(level_file.texture_page_count); + for (auto& id : level_file.texture_ids) { + tex_ids.push_back(id >> 20); + } + lg::info("custom level: login tpages automatically set to [{}]", + fmt::join(tex_ids, ", ")); + } + } + } 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); + lg::info("custom level: extracting art group {}", ag_file.name_in_dgo); decompiler::extract_merc(ag_file, tex_db, db.dts, tex_remap, pc_level, false, db.version()); } @@ -184,31 +226,69 @@ bool run_build_level(const std::string& input_file, // add textures if (level_json.contains("textures") && !level_json.at("textures").empty()) { - std::vector processed_textures; - std::vector wanted_texs = - level_json.at("textures").get>(); + std::map> processed_textures; + auto tex_json = level_json.value("textures", std::vector>{}); + std::map> wanted_texs; + for (auto& arr : tex_json) { + auto tpage_name = arr[0]; + // we only want a select few textures + if (arr.size() > 1) { + for (size_t i = 1; i < arr.size(); i++) { + wanted_texs.insert({tpage_name, {arr.begin() + 1, arr.end()}}); + } + } else { + // we want all textures from this tpage + auto it = std::find_if(tex_db.tpage_names.begin(), tex_db.tpage_names.end(), + [tpage_name](const std::pair& t) { + return t.second == tpage_name; + }); + if (it != tex_db.tpage_names.end()) { + lg::info("custom level: adding all textures from tpage {}:", tpage_name); + std::vector tex_names; + for (auto& [id, tex] : tex_db.textures) { + if (tex_db.tpage_names.at(tex.page) == tpage_name) { + lg::info("custom level: adding texture {} (tpage {})", tex.name, tex.page); + tex_names.push_back(tex.name); + pc_level.textures.push_back(make_texture(id, tex, tpage_name, true)); + processed_textures[tpage_name].push_back(tex.name); + } + } + wanted_texs.insert({tpage_name, tex_names}); + } + } + } + // first check the texture is not already in the level for (auto& level_tex : pc_level.textures) { - if (std::find(wanted_texs.begin(), wanted_texs.end(), level_tex.debug_name) != - wanted_texs.end()) { - processed_textures.push_back(level_tex.debug_name); + auto tpage = level_tex.debug_tpage_name; + auto name = level_tex.debug_name; + auto it = std::find_if( + wanted_texs.begin(), wanted_texs.end(), + [tpage, name](const std::pair>& elt) { + return elt.first == tpage && + std::find(elt.second.begin(), elt.second.end(), name) != elt.second.end(); + }); + if (it != wanted_texs.end()) { + processed_textures[level_tex.debug_tpage_name].push_back(level_tex.debug_name); } } // then add for (auto& [id, tex] : tex_db.textures) { - for (auto& tex0 : wanted_texs) { - if (std::find(processed_textures.begin(), processed_textures.end(), tex.name) != - processed_textures.end()) { + auto db_tpage_name = tex_db.tpage_names.at(tex.page); + for (auto& [wanted_tpage_name, wanted_tex_list] : wanted_texs) { + auto processed = processed_textures[db_tpage_name]; + if (std::find(processed.begin(), processed.end(), tex.name) != processed.end()) { + // lg::info("custom level: ignoring duplicate texture {} from {}", tex.name, tex.page); continue; } - if (tex.name == tex0) { - lg::info("custom level: adding texture {} from tpage {} ({})", tex.name, tex.page, - tex_db.tpage_names.at(tex.page)); - pc_level.textures.push_back( - make_texture(id, tex, tex_db.tpage_names.at(tex.page), true)); - processed_textures.push_back(tex.name); - } + for (auto& wanted_tex : wanted_tex_list) + if (db_tpage_name == wanted_tpage_name && tex.name == wanted_tex) { + lg::info("custom level: adding texture {} from tpage {} ({})", tex.name, tex.page, + db_tpage_name); + pc_level.textures.push_back(make_texture(id, tex, db_tpage_name, true)); + processed_textures[db_tpage_name].push_back(tex.name); + } } } } @@ -222,6 +302,14 @@ bool run_build_level(const std::string& input_file, } } + // Save the GOAL level + auto result = file.save_object_file(); + lg::print("Level bsp file size {} bytes\n", result.size()); + auto save_path = file_util::get_jak_project_dir() / bsp_output_file; + file_util::create_dir_if_needed_for_file(save_path); + lg::print("Saving to {}\n", save_path.string()); + file_util::write_binary_file(save_path, result.data(), result.size()); + // Save the PC level save_pc_data(file.name, pc_level, file_util::get_jak_project_dir() / "out" / output_prefix / "fr3");