custom levels: support vanilla skies and texture remapping tables (#3691)
Some checks are pending
Build / 🖥️ Windows (push) Waiting to run
Build / 🐧 Linux (push) Waiting to run
Build / 🍎 MacOS (push) Waiting to run
Inform Pages Repo / Generate Documentation (push) Waiting to run
Lint / 📝 Formatting (push) Waiting to run
Lint / 📝 Required Checks (push) Waiting to run
Lint / 📝 Optional Checks (push) Waiting to run

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.
This commit is contained in:
Hat Kid 2024-09-30 17:18:28 +02:00 committed by GitHub
parent fedfb6fd09
commit 614c5a663c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 415 additions and 93 deletions

View file

@ -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 @@
// }
// }
]
}
}

View file

@ -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"
))
)
)

View file

@ -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 @@
}
}
]
}
}

View file

@ -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"
}
}
]
}
}

View file

@ -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)

View file

@ -1987,6 +1987,13 @@ void HFragment::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSys
size = read_plain_data_field<u32>(ref, "size", dts);
}
void AdgifShaderArray::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) {
auto length = read_plain_data_field<s32>(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<s32>(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<s32>(ref, "texture-remap-table-len", dts);
if (tex_remap_len > 0) {

View file

@ -840,6 +840,12 @@ struct DrawableTreeArray {
std::vector<std::unique_ptr<DrawableTree>> trees;
};
struct AdgifShaderArray {
std::vector<AdGifData> 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<u32> 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)

View file

@ -26,4 +26,7 @@ tfrag3::Texture make_texture(u32 id,
bool pool_load);
std::vector<level_tools::TextureRemap> extract_tex_remap(const ObjectFileDB& db,
const std::string& dgo_name);
std::optional<ObjectFileRecord> get_bsp_file(const std::vector<ObjectFileRecord>& records,
const std::string& dgo_name);
bool is_valid_bsp(const LinkedObjectFile& file);
} // namespace decompiler

View file

@ -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

View file

@ -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

View file

@ -74,6 +74,44 @@ size_t generate_u32_array(const std::vector<u32>& array, DataObjectGenerator& ge
return result;
}
size_t generate_adgif_shader_array(const std::vector<u32>& 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<TexRemap>& 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<u8> LevelFile::save_object_file() const {
DataObjectGenerator gen;
gen.add_type_tag("bsp-header");
@ -97,9 +135,15 @@ std::vector<u8> 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<u8> 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)

View file

@ -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<EntityAmbient> ambients;
};
struct AdgifShaderArray {};
struct AdgifShaderArray {
std::vector<AdGifData> 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<TextureRemap> texture_remap_table;
std::vector<TexRemap> texture_remap_table;
// (texture-ids (pointer texture-id) :offset-assert 60)
// (texture-page-count int32 :offset-assert 64)
std::vector<TextureId> texture_ids;
std::vector<u32> texture_ids;
// (unk-zero-0 basic :offset-assert 68)
// "misc", seems like it can be zero and is unused.

View file

@ -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<EntityActor> 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<u32>({}));
// Add textures and models
// TODO remove hardcoded config settings
@ -161,20 +159,64 @@ bool run_build_level(const std::string& input_file,
std::vector<std::string> 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<std::vector<std::string>>(), files);
auto tex_remap = decompiler::extract_tex_remap(db, dgo_name);
level_json.value("art_groups", std::vector<std::string>{}), files);
std::vector<level_tools::TextureRemap> 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<u32> 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<std::string> processed_textures;
std::vector<std::string> wanted_texs =
level_json.at("textures").get<std::vector<std::string>>();
std::map<std::string, std::vector<std::string>> processed_textures;
auto tex_json = level_json.value("textures", std::vector<std::vector<std::string>>{});
std::map<std::string, std::vector<std::string>> 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<u32, std::string>& 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<std::string> 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<std::string, std::vector<std::string>>& 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");