g/jak1: Extract ambient data to json (#3945)

I added extraction of ambients to json files when extracting the levels.
All of the ambient json files are written to the same folder as actors
are. Ambients aren't used in jak2 and 3 so it only is used on jak1.


![image](https://github.com/user-attachments/assets/26e5d655-6a31-49eb-8514-3374b41f72dd)

---------

Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
This commit is contained in:
Soggy_Pancake 2025-07-02 20:19:21 -07:00 committed by GitHub
parent 012ff7bb16
commit 7320bfc068
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 246 additions and 1 deletions

View File

@ -527,6 +527,13 @@ std::string DrawableActor::print(const PrintSettings& /*settings*/, int indent)
return result;
}
std::string DrawableAmbient::print(const PrintSettings& /*settings*/, int indent) const {
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}bsphere: {}", is, bsphere.print_meters());
return result;
}
void InstanceTie::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion /*version*/) {
@ -1952,6 +1959,110 @@ void DrawableInlineArrayActor::read_from_file(TypedRef ref,
}
}
void EntityAmbient::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion /*version*/) {
trans.read_from_file(get_field_ref(ref, "trans", dts));
aid = read_plain_data_field<u32>(ref, "aid", dts);
// ambientData = read_plain_data_field<u128>(ref, "extra", dts); // to-do: get ambient-data, not
// sure if nessecary tho
int res_length = read_plain_data_field<int32_t>(ref, "length", dts);
// int res_allocated_length = read_plain_data_field<int32_t>(ref, "allocated-length", dts);
auto tags = deref_label(get_field_ref(ref, "tag", dts));
auto data_base = deref_label(get_field_ref(ref, "data-base", dts));
for (int i = 0; i < res_length; i++) {
auto& res = res_list.emplace_back();
res.name = read_symbol(tags);
tags.byte_offset += 4;
res.key_frame = deref_float(tags, 0);
tags.byte_offset += 4;
res.elt_type = read_type(tags);
tags.byte_offset += 4;
const u32 vals = deref_u32(tags, 0);
const u32 offset = vals & 0xffff; // 16 bits
res.count = (vals >> 16) & 0x7fff; // 15 bits
res.inlined = vals & 0x8000'0000;
Ref data = data_base;
data.byte_offset += offset;
if (res.elt_type == "string") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_string_ref(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "symbol") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_symbol(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "type") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_type(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "vector") {
ASSERT(res.inlined);
res.inlined_storage = bytes_from_plain_data(data, 16 * res.count);
} else if (res.elt_type == "float") {
fill_res_with_value_types<float>(res, data);
} else if (res.elt_type == "int32") {
fill_res_with_value_types<int32_t>(res, data);
} else if (res.elt_type == "int16") {
fill_res_with_value_types<int16_t>(res, data);
} else if (res.elt_type == "int8") {
fill_res_with_value_types<int8_t>(res, data);
} else if (res.elt_type == "uint32") {
fill_res_with_value_types<uint32_t>(res, data);
} else if (res.elt_type == "uint8") {
fill_res_with_value_types<uint8_t>(res, data);
} else if (res.elt_type == "actor-group") {
// TODO: unsupported.
} else if (res.elt_type == "pair") {
ASSERT(res.count == 1);
ASSERT(!res.inlined);
data = deref_label(data);
res.script = data.data->to_form_script(data.seg, (data.byte_offset) / 4, nullptr);
} else {
fmt::print("unhandled elt_type: {}\n", res.elt_type);
ASSERT_NOT_REACHED();
}
tags.byte_offset += 4;
}
}
void DrawableAmbient::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
ambient.read_from_file(get_and_check_ref_to_basic(ref, "ambient", "entity-ambient", dts), dts,
version);
}
void DrawableInlineArrayAmbient::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version) {
int numAmbients = read_plain_data_field<int16_t>(ref, "length", dts);
auto data_ref = get_field_ref(ref, "data", dts);
for (int i = 0; i < numAmbients; i++) {
Ref obj_ref = data_ref;
obj_ref.byte_offset += 32 * i; // todo not a constant here
auto type = get_type_of_basic(obj_ref);
if (type != "drawable-ambient") {
throw Error("bad drawable-ambient type: {}", type);
}
drawable_ambients.emplace_back();
drawable_ambients.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, version);
}
}
void CollideHash::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion /*version*/) {
@ -2065,6 +2176,11 @@ void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file,
actors.read_from_file(
get_and_check_ref_to_basic(ref, "actors", "drawable-inline-array-actor", dts), dts,
version);
if (get_word_kind_for_field(ref, "ambients", dts) == decompiler::LinkedWord::PTR) {
ambients.read_from_file(
get_and_check_ref_to_basic(ref, "ambients", "drawable-inline-array-ambient", dts), dts,
version);
}
}
if (version > GameVersion::Jak1 &&

View File

@ -189,6 +189,31 @@ struct DrawableActor : public Drawable {
std::string my_type() const override { return "drawable-actor"; }
};
struct EntityAmbient {
Vector trans;
u32 aid = 0;
u128 ambientData;
std::vector<Res> res_list;
void read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version);
};
struct DrawableAmbient : public Drawable {
Vector bsphere;
EntityAmbient ambient;
void read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version) override;
std::string print(const PrintSettings& settings, int indent) const override;
std::string my_type() const override { return "drawable-ambient"; }
};
struct DrawableTreeActor : public DrawableTree {
void read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
@ -765,6 +790,13 @@ struct DrawableInlineArrayActor {
GameVersion version);
};
struct DrawableInlineArrayAmbient {
std::vector<DrawableAmbient> drawable_ambients;
void read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
GameVersion version);
};
struct CollideHash {
Ref item_array;
int num_items = 0;
@ -912,6 +944,7 @@ struct BspHeader {
// (boxes box8s-array :offset-assert 148)
// (current-bsp-back-flags uint32 :offset-assert 152)
// (ambients drawable-inline-array-ambient :offset-assert 156)
DrawableInlineArrayAmbient ambients;
// (unk-data-4 float :offset-assert 160)
// (unk-data-5 float :offset-assert 164)
// (adgifs adgif-shader-array :offset-assert 168)

View File

@ -146,4 +146,95 @@ std::string extract_actors_to_json(const level_tools::DrawableInlineArrayActor&
return json.dump(2);
}
std::string extract_ambients_to_json(const level_tools::DrawableInlineArrayAmbient& actors) {
nlohmann::json json;
for (const auto& damb : actors.drawable_ambients) {
const auto& ambient = damb.ambient;
auto& json_ambient = json.emplace_back();
json_ambient["bsphere"] = vectorm_json(damb.bsphere);
// drawable ID?
json_ambient["trans"] = vectorm_json(ambient.trans);
json_ambient["aid"] = ambient.aid; // aid
auto& json_lump = json_ambient["lump"];
nlohmann::json effects;
int effectCount = 0,
effectParamCount =
0; // just to keep track since names cound all be together and then params
for (const auto& res : ambient.res_list) {
if (res.elt_type == "string") {
if (res.name == "name")
json_lump[res.name] = strings_json(res.strings, false);
else
json_lump[res.name] = strings_json(res.strings, true);
} else if (res.elt_type == "symbol") {
if (res.name == "effect-name") {
if (++effectCount > effectParamCount) {
nlohmann::json effect;
effect["name"] = strings_json(res.strings, false);
effects.push_back(effect);
} else {
auto& effect = effects[effectCount - 1];
effect["name"] = strings_json(res.strings, false);
}
} else {
json_lump[res.name] = strings_json(res.strings, false);
}
} else if (res.elt_type == "type") {
// TODO: confusion with symbols
json_lump[res.name] = strings_json(res.strings, true);
} else if (res.elt_type == "vector") {
const float* data = (const float*)res.inlined_storage.data();
if (res.count == 1) {
json_lump[res.name] = vector_json(data);
} else {
for (int i = 0; i < res.count; i++) {
json_lump[res.name].push_back(vector_json(data + 4 * i));
}
}
} else if (res.elt_type == "pair") {
json_lump[res.name] = pretty_print::to_string(res.script);
} else if (res.elt_type == "float") {
if (res.name == "effect-param") {
if (++effectParamCount > effectCount) {
nlohmann::json effect;
effect["params"] = value_json<float>(res.inlined_storage, res.count);
effects.push_back(effect);
} else {
auto& effect = effects[effectParamCount - 1];
effect["params"] = value_json<float>(res.inlined_storage, res.count);
}
} else {
json_lump[res.name] = value_json<float>(res.inlined_storage, res.count);
}
} else if (res.elt_type == "int32") {
json_lump[res.name] = value_json<int32_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "int16") {
json_lump[res.name] = value_json<int16_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "int8") {
json_lump[res.name] = value_json<int8_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "uint32") {
json_lump[res.name] = value_json<uint32_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "uint16") {
json_lump[res.name] = value_json<uint16_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "uint8") {
json_lump[res.name] = value_json<uint8_t>(res.inlined_storage, res.count);
} else if (res.elt_type == "actor-group") {
// not supported.
} else {
ASSERT_NOT_REACHED();
}
}
if (effectCount || effectParamCount)
json_lump["effects"] = effects;
}
return json.dump(2);
}
} // namespace decompiler

View File

@ -5,5 +5,6 @@
namespace decompiler {
std::string extract_actors_to_json(const level_tools::DrawableInlineArrayActor& actors);
std::string extract_ambients_to_json(const level_tools::DrawableInlineArrayAmbient& actors);
}
} // namespace decompiler

View File

@ -389,6 +389,10 @@ void extract_from_level(const ObjectFileDB& db,
}
file_util::write_text_file(entities_folder / fmt::format("{}-actors.json", level_data.level_name),
extract_actors_to_json(bsp_header.actors));
if (config.game_version == GameVersion::Jak1)
file_util::write_text_file(
entities_folder / fmt::format("{}-ambients.json", level_data.level_name),
extract_ambients_to_json(bsp_header.ambients));
}
void extract_all_levels(const ObjectFileDB& db,