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.
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");