#include #include "ifx.hpp" #include "itechniqueset.hpp" #include #include #include #include #include #define IW4X_FX_VERSION 3 namespace iw4of::interfaces { bool ifx::write_internal(const native::XAssetHeader& header) const { AssertSize(native::FxEffectDef, 32); auto asset = header.fx; rapidjson::Document output(rapidjson::kObjectType); auto& allocator = output.GetAllocator(); utils::memory::allocator str_duplicator; output.AddMember("version", IW4X_FX_VERSION, allocator); output.AddMember("name", RAPIDJSON_STR(asset->name), allocator); output.AddMember("flags", asset->flags, allocator); output.AddMember("totalSize", asset->totalSize, allocator); output.AddMember("msecLoopingLife", asset->msecLoopingLife, allocator); output.AddMember("elemDefCountLooping", asset->elemDefCountLooping, allocator); output.AddMember("elemDefCountOneShot", asset->elemDefCountOneShot, allocator); output.AddMember("elemDefCountEmission", asset->elemDefCountEmission, allocator); rapidjson::Value elemTable(rapidjson::kArrayType); if (asset->elemDefs) { for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) { rapidjson::Value json_elem_def(rapidjson::kObjectType); native::FxElemDef* elem_def = &asset->elemDefs[i]; json_elem_def.AddMember("flags", elem_def->flags, allocator); { rapidjson::Value spawn_def(rapidjson::kObjectType); spawn_def.AddMember("A", elem_def->spawn.looping.count, allocator); spawn_def.AddMember("B", elem_def->spawn.looping.intervalMsec, allocator); json_elem_def.AddMember("spawn", spawn_def, allocator); } #define SAVE_PROPERTY(x) json_elem_def.AddMember(#x, elem_def->x, allocator) #define SAVE_RANGE(x) json_elem_def.AddMember(#x, utils::json::to_json(elem_def->x, allocator), allocator) #define SAVE_RANGE_MULTIPLE(x) \ { \ rapidjson::Value arr(rapidjson::kArrayType); \ for (int _ = 0; _ < ARRAYSIZE(elem_def->x); _++) \ arr.PushBack(utils::json::to_json(elem_def->x[_], allocator), allocator); \ json_elem_def.AddMember(#x, arr, allocator); \ } SAVE_RANGE(spawnRange); SAVE_RANGE(fadeInRange); SAVE_RANGE(fadeOutRange); SAVE_PROPERTY(spawnFrustumCullRadius); SAVE_RANGE(spawnDelayMsec); SAVE_RANGE(lifeSpanMsec); SAVE_RANGE_MULTIPLE(spawnOrigin); SAVE_RANGE(spawnOffsetRadius); SAVE_RANGE(spawnOffsetHeight); SAVE_RANGE_MULTIPLE(spawnAngles); SAVE_RANGE_MULTIPLE(angularVelocity); SAVE_RANGE(initialRotation); SAVE_RANGE(gravity); SAVE_RANGE(reflectionFactor); // Save Atlas { #define SAVE_ATLAS_PROPERTY(x) atlas.AddMember(#x, elem_def->atlas.x, allocator) rapidjson::Value atlas(rapidjson::kObjectType); SAVE_ATLAS_PROPERTY(behavior); SAVE_ATLAS_PROPERTY(index); SAVE_ATLAS_PROPERTY(fps); SAVE_ATLAS_PROPERTY(loopCount); SAVE_ATLAS_PROPERTY(colIndexBits); SAVE_ATLAS_PROPERTY(rowIndexBits); SAVE_ATLAS_PROPERTY(entryCount); json_elem_def.AddMember("atlas", atlas, allocator); #undef SAVE_ATLAS_PROPERTY } SAVE_PROPERTY(elemType); SAVE_PROPERTY(visualCount); SAVE_PROPERTY(velIntervalCount); SAVE_PROPERTY(visStateIntervalCount); if (elem_def->velSamples) { rapidjson::Value vel_samples_json(rapidjson::kArrayType); // note the <= for (size_t j = 0; j <= elem_def->velIntervalCount; j++) { rapidjson::Value vel_sample(rapidjson::kObjectType); rapidjson::Value local_sample(rapidjson::kObjectType); local_sample.AddMember("velocity", utils::json::to_json(elem_def->velSamples[j].local.velocity, allocator), allocator); local_sample.AddMember("totalDelta", utils::json::to_json(elem_def->velSamples[j].local.totalDelta, allocator), allocator); rapidjson::Value world_sample(rapidjson::kObjectType); world_sample.AddMember("velocity", utils::json::to_json(elem_def->velSamples[j].world.velocity, allocator), allocator); world_sample.AddMember("totalDelta", utils::json::to_json(elem_def->velSamples[j].world.totalDelta, allocator), allocator); vel_sample.AddMember("local", local_sample, allocator); vel_sample.AddMember("world", world_sample, allocator); vel_samples_json.PushBack(vel_sample, allocator); } json_elem_def.AddMember("velSamples", vel_samples_json, allocator); } if (elem_def->visSamples) { rapidjson::Value vis_samples_json(rapidjson::kArrayType); // note the <= for (size_t j = 0; j <= elem_def->visStateIntervalCount; j++) { rapidjson::Value vis_sample(rapidjson::kObjectType); vis_sample.AddMember("base", utils::json::to_json(elem_def->visSamples[j].base, allocator), allocator); vis_sample.AddMember("amplitude", utils::json::to_json(elem_def->visSamples[j].amplitude, allocator), allocator); vis_samples_json.PushBack(vis_sample, allocator); } json_elem_def.AddMember("visSamples", vis_samples_json, allocator); } // Save_FxElemDefVisuals { if (elem_def->elemType == native::FX_ELEM_TYPE_DECAL) { if (elem_def->visuals.markArray) { rapidjson::Value mark_array_json(rapidjson::kArrayType); for (char j = 0; j < elem_def->visualCount; ++j) { rapidjson::Value materials(rapidjson::kArrayType); for (auto k = 0; k < 2; k++) { if (elem_def->visuals.markArray[j].materials[k]) { materials.PushBack(RAPIDJSON_STR(elem_def->visuals.markArray[j].materials[k]->info.name), allocator); assets->write(native::ASSET_TYPE_MATERIAL, elem_def->visuals.markArray[j].materials[k]); } else { materials.PushBack(rapidjson::Value(rapidjson::kNullType), allocator); } } mark_array_json.PushBack(materials, allocator); } json_elem_def.AddMember("markArray", mark_array_json, allocator); } } else if (elem_def->visualCount > 1) { if (elem_def->visuals.array) { rapidjson::Value visuals_array_json(rapidjson::kArrayType); for (char j = 0; j < elem_def->visualCount; ++j) { visuals_array_json.PushBack(to_json(&elem_def->visuals.array[j], elem_def->elemType, allocator), allocator); } json_elem_def.AddMember("visualsArray", visuals_array_json, allocator); } } else if (elem_def->visualCount) { json_elem_def.AddMember("instance", to_json(&elem_def->visuals.instance, elem_def->elemType, allocator), allocator); } } json_elem_def.AddMember("collBounds", utils::json::to_json(elem_def->collBounds, allocator), allocator); if (elem_def->effectOnImpact.handle) { json_elem_def.AddMember("effectOnImpact", RAPIDJSON_STR(elem_def->effectOnImpact.handle->name), allocator); assets->write(native::ASSET_TYPE_FX, elem_def->effectOnImpact.handle); } if (elem_def->effectOnDeath.handle) { json_elem_def.AddMember("effectOnDeath", RAPIDJSON_STR(elem_def->effectOnDeath.handle->name), allocator); assets->write(native::ASSET_TYPE_FX, elem_def->effectOnDeath.handle); } if (elem_def->effectEmitted.handle) { json_elem_def.AddMember("effectEmitted", RAPIDJSON_STR(elem_def->effectEmitted.handle->name), allocator); assets->write(native::ASSET_TYPE_FX, elem_def->effectEmitted.handle); } SAVE_RANGE(emitDist); SAVE_RANGE(emitDistVariance); // Save_FxElemExtendedDefPtr { if (elem_def->elemType == native::FX_ELEM_TYPE_TRAIL) { // Save_FxTrailDef { if (elem_def->extended.trailDef) { AssertSize(native::FxTrailDef, 36); native::FxTrailDef* trail_def = elem_def->extended.trailDef; rapidjson::Value json_trail_def(rapidjson::kObjectType); #define SAVE_TRAILDEF_MEMBER(x) json_trail_def.AddMember(#x, trail_def->x, allocator) SAVE_TRAILDEF_MEMBER(scrollTimeMsec); SAVE_TRAILDEF_MEMBER(repeatDist); SAVE_TRAILDEF_MEMBER(invSplitDist); SAVE_TRAILDEF_MEMBER(invSplitArcDist); SAVE_TRAILDEF_MEMBER(invSplitTime); rapidjson::Value verts_json(rapidjson::kArrayType); for (auto j = 0; j < trail_def->vertCount; j++) { rapidjson::Value trail_vertex(rapidjson::kObjectType); trail_vertex.AddMember("pos", utils::json::make_json_array(trail_def->verts[j].pos, 2, allocator), allocator); trail_vertex.AddMember( "normal", utils::json::make_json_array(trail_def->verts[j].normal, 2, allocator), allocator); trail_vertex.AddMember("texCoord", trail_def->verts[j].texCoord, allocator); verts_json.PushBack(trail_vertex, allocator); } json_trail_def.AddMember("verts", verts_json, allocator); json_trail_def.AddMember( "inds", utils::json::make_json_array(trail_def->inds, trail_def->indCount, allocator), allocator); json_elem_def.AddMember("trailDef", json_trail_def, allocator); #undef SAVE_TRAILDEF_MEMBER } } } else if (elem_def->elemType == native::FX_ELEM_TYPE_SPARK_FOUNTAIN) { if (elem_def->extended.sparkFountainDef) { const auto spark_fountain = elem_def->extended.sparkFountainDef; rapidjson::Value json_spark_fountain(rapidjson::kObjectType); #define SAVE_SPARK_FOUNTAIN_MEMBER(x) json_spark_fountain.AddMember(#x, spark_fountain->x, allocator) SAVE_SPARK_FOUNTAIN_MEMBER(gravity); SAVE_SPARK_FOUNTAIN_MEMBER(bounceFrac); SAVE_SPARK_FOUNTAIN_MEMBER(bounceRand); SAVE_SPARK_FOUNTAIN_MEMBER(sparkSpacing); SAVE_SPARK_FOUNTAIN_MEMBER(sparkLength); SAVE_SPARK_FOUNTAIN_MEMBER(sparkCount); SAVE_SPARK_FOUNTAIN_MEMBER(loopTime); SAVE_SPARK_FOUNTAIN_MEMBER(velMin); SAVE_SPARK_FOUNTAIN_MEMBER(velMax); SAVE_SPARK_FOUNTAIN_MEMBER(velConeFrac); SAVE_SPARK_FOUNTAIN_MEMBER(restSpeed); SAVE_SPARK_FOUNTAIN_MEMBER(boostTime); SAVE_SPARK_FOUNTAIN_MEMBER(boostFactor); #undef SAVE_SPARK_FOUNTAIN_MEMBER json_elem_def.AddMember("sparkFountain", json_spark_fountain, allocator); } } } SAVE_PROPERTY(sortOrder); SAVE_PROPERTY(lightingFrac); SAVE_PROPERTY(useItemClip); SAVE_PROPERTY(fadeInfo); #undef SAVE_RANGE_MULTIPLE #undef SAVE_PROPERTY #undef SAVE_RANGE elemTable.PushBack(json_elem_def, allocator); } } output.AddMember("elemDefs", elemTable, allocator); rapidjson::StringBuffer buff; rapidjson::PrettyWriter< /*typename OutputStream */ rapidjson::StringBuffer, /*typename SourceEncoding*/ rapidjson::UTF8<>, /*typename TargetEncoding*/ rapidjson::UTF8<>, /*typename StackAllocator*/ rapidjson::CrtAllocator, /*unsigned writeFlags*/ rapidjson::kWriteNanAndInfNullFlag | rapidjson::kWriteNanAndInfFlag> writer(buff); output.Accept(writer); return utils::io::write_file(get_work_path(header).string(), buff.GetString()); } std::filesystem::path ifx::get_legacy_work_path(const std::string& file_name) const { return get_work_path(get_binary_file_name(file_name)); } std::vector ifx::get_child_assets(const native::XAssetHeader& header) const { std::vector result{}; const auto asset = header.fx; if (asset->elemDefs) { for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) { native::FxElemDef* elem_def = &asset->elemDefs[i]; // Save_FxElemDefVisuals { if (elem_def->elemType == native::FX_ELEM_TYPE_DECAL) { if (elem_def->visuals.markArray) { rapidjson::Value mark_array_json(rapidjson::kArrayType); for (char j = 0; j < elem_def->visualCount; ++j) { rapidjson::Value materials(rapidjson::kArrayType); for (auto k = 0; k < 2; k++) { if (elem_def->visuals.markArray[j].materials[k]) { result.push_back({native::ASSET_TYPE_MATERIAL, {elem_def->visuals.markArray[j].materials[k]}}); } } } } } else if (elem_def->visualCount > 1) { if (elem_def->visuals.array) { for (char j = 0; j < elem_def->visualCount; ++j) { const auto visuals = &elem_def->visuals.array[j]; get_dependencies(visuals, elem_def->elemType, result); } } else if (elem_def->visualCount) { get_dependencies(&elem_def->visuals.instance, elem_def->elemType, result); } } if (elem_def->effectOnImpact.handle) { result.push_back({native::ASSET_TYPE_FX, {elem_def->effectOnImpact.handle}}); } if (elem_def->effectOnDeath.handle) { result.push_back({native::ASSET_TYPE_FX, {elem_def->effectOnDeath.handle}}); } if (elem_def->effectEmitted.handle) { result.push_back({native::ASSET_TYPE_FX, {elem_def->effectEmitted.handle}}); } } } } return result; } void* ifx::read_internal(const std::string& name) const { const auto& path = get_work_path(name).string(); if (utils::io::file_exists(path)) { return read_plaintext(name, path); } else { const auto old_path = get_legacy_work_path(name).string(); if (utils::io::file_exists(old_path)) { return read_binary(name, old_path); } } return nullptr; } native::FxEffectDef* ifx::read_binary(const std::string& name, const std::string& path) const { auto contents = utils::io::read_file(path); utils::stream::reader buffer(&local_allocator, contents); int64_t magic = buffer.read(); if (std::memcmp(&magic, "IW4xFx ", 8)) { print_error("Reading fx '{}' failed, header is invalid!", name); return nullptr; } int32_t version = buffer.read(); if (version > IW4X_FX_VERSION) { print_error("Reading fx '{}' failed, expected version is {}, but it was {}!", name, IW4X_FX_VERSION, version); return nullptr; } native::FxEffectDef* asset = buffer.read_object(); if (asset->name) { asset->name = buffer.read_cstring(); } if (asset->elemDefs) { asset->elemDefs = buffer.read_array(asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); for (int i = 0; i < (asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); ++i) { native::FxElemDef* elemDef = &asset->elemDefs[i]; if (elemDef->velSamples) { elemDef->velSamples = buffer.read_array(elemDef->velIntervalCount + 1); } if (elemDef->visSamples) { elemDef->visSamples = buffer.read_array(elemDef->visStateIntervalCount + 1); } // Save_FxElemDefVisuals { if (elemDef->elemType == native::FX_ELEM_TYPE_DECAL) { if (elemDef->visuals.markArray) { elemDef->visuals.markArray = buffer.read_array(elemDef->visualCount); for (char j = 0; j < elemDef->visualCount; ++j) { if (elemDef->visuals.markArray[j].materials[0]) { elemDef->visuals.markArray[j].materials[0] = find(native::XAssetType::ASSET_TYPE_MATERIAL, buffer.read_string().data()); } if (elemDef->visuals.markArray[j].materials[1]) { elemDef->visuals.markArray[j].materials[1] = find(native::XAssetType::ASSET_TYPE_MATERIAL, buffer.read_string().data()); } } } } else if (elemDef->visualCount > 1) { if (elemDef->visuals.array) { elemDef->visuals.array = buffer.read_array(elemDef->visualCount); for (char j = 0; j < elemDef->visualCount; ++j) { read(&elemDef->visuals.array[j], elemDef->elemType, &buffer); } } } else if (elemDef->visualCount == 1) { read(&elemDef->visuals.instance, elemDef->elemType, &buffer); } } if (elemDef->effectOnImpact.handle) { auto fx_name = buffer.read_string(); elemDef->effectOnImpact.handle = find(native::XAssetType::ASSET_TYPE_FX, fx_name.data()); RETURN_IF_NULL(elemDef->effectOnImpact.handle); } if (elemDef->effectOnDeath.handle) { auto fx_name = buffer.read_string(); elemDef->effectOnDeath.handle = find(native::XAssetType::ASSET_TYPE_FX, fx_name.data()); RETURN_IF_NULL(elemDef->effectOnDeath.handle); } if (elemDef->effectEmitted.handle) { auto fx_name = buffer.read_string(); elemDef->effectEmitted.handle = find(native::XAssetType::ASSET_TYPE_FX, fx_name.data()); RETURN_IF_NULL(elemDef->effectEmitted.handle); } // Save_FxElemExtendedDefPtr { if (elemDef->elemType == native::FX_ELEM_TYPE_TRAIL) { // Save_FxTrailDef { if (elemDef->extended.trailDef) { native::FxTrailDef* trailDef = buffer.read_object(); elemDef->extended.trailDef = trailDef; if (trailDef->verts) { trailDef->verts = buffer.read_array(trailDef->vertCount); } if (trailDef->inds) { trailDef->inds = buffer.read_array(trailDef->indCount); } } } } else if (version >= 2) { if (elemDef->elemType == native::FX_ELEM_TYPE_SPARK_FOUNTAIN) { if (elemDef->extended.sparkFountainDef) { elemDef->extended.sparkFountainDef = buffer.read_object(); } } } } } } return asset; } native::FxEffectDef* ifx::read_plaintext(const std::string& name, const std::string& path) const { if (!utils::io::file_exists(path)) return nullptr; auto contents = utils::io::read_file(path); rapidjson::Document fx_json; try { fx_json.Parse(contents.data()); } catch (const std::exception& e) { print_error("Invalid JSON for FX {}! {}", name, e.what()); return nullptr; } auto asset = local_allocator.allocate(); try { const auto version = fx_json["version"].Get(); if (version > IW4X_FX_VERSION) { print_error("Wrong FX version for {}! expected {} and got {}!", name, IW4X_FX_VERSION, version); assert(false); return nullptr; } asset->name = local_allocator.duplicate_string(fx_json["name"].Get()); asset->flags = fx_json["flags"].Get(); asset->totalSize = fx_json["totalSize"].Get(); asset->msecLoopingLife = fx_json["msecLoopingLife"].Get(); asset->elemDefCountLooping = fx_json["elemDefCountLooping"].Get(); asset->elemDefCountOneShot = fx_json["elemDefCountOneShot"].Get(); asset->elemDefCountEmission = fx_json["elemDefCountEmission"].Get(); if (fx_json.HasMember("elemDefs") && !fx_json["elemDefs"].IsNull()) { const auto json_elem_defs = fx_json["elemDefs"].GetArray(); const auto json_elem_count = json_elem_defs.Size(); assert(static_cast(json_elem_count) == asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot); asset->elemDefs = local_allocator.allocate_array(json_elem_count); { size_t i = 0; for (const auto& elem : json_elem_defs) { const auto elem_def = &asset->elemDefs[i++]; elem_def->flags = elem["flags"].Get(); elem_def->spawn.looping.count = elem["spawn"]["A"].Get(); elem_def->spawn.looping.intervalMsec = elem["spawn"]["B"].Get(); elem_def->spawnRange = utils::json::float_range_from_json(elem["spawnRange"]); elem_def->fadeInRange = utils::json::float_range_from_json(elem["fadeInRange"]); elem_def->fadeOutRange = utils::json::float_range_from_json(elem["fadeOutRange"]); elem_def->spawnFrustumCullRadius = elem["spawnFrustumCullRadius"].GetFloat(); elem_def->spawnDelayMsec = utils::json::int_range_from_json(elem["spawnDelayMsec"]); elem_def->lifeSpanMsec = utils::json::int_range_from_json(elem["lifeSpanMsec"]); for (size_t j = 0; j < ARRAYSIZE(elem_def->spawnOrigin); j++) { elem_def->spawnOrigin[j] = utils::json::float_range_from_json(elem["spawnOrigin"][j]); } elem_def->spawnOffsetRadius = utils::json::float_range_from_json(elem["spawnOffsetRadius"]); elem_def->spawnOffsetHeight = utils::json::float_range_from_json(elem["spawnOffsetHeight"]); for (size_t j = 0; j < ARRAYSIZE(elem_def->spawnAngles); j++) { elem_def->spawnAngles[j] = utils::json::float_range_from_json(elem["spawnAngles"][j]); } for (size_t j = 0; j < ARRAYSIZE(elem_def->angularVelocity); j++) { elem_def->angularVelocity[j] = utils::json::float_range_from_json(elem["angularVelocity"][j]); } elem_def->initialRotation = utils::json::float_range_from_json(elem["initialRotation"]); elem_def->gravity = utils::json::float_range_from_json(elem["gravity"]); elem_def->reflectionFactor = utils::json::float_range_from_json(elem["reflectionFactor"]); elem_def->atlas.behavior = elem["atlas"]["behavior"].Get(); elem_def->atlas.index = elem["atlas"]["index"].Get(); elem_def->atlas.fps = elem["atlas"]["fps"].Get(); elem_def->atlas.loopCount = elem["atlas"]["loopCount"].Get(); elem_def->atlas.colIndexBits = elem["atlas"]["colIndexBits"].Get(); elem_def->atlas.rowIndexBits = elem["atlas"]["rowIndexBits"].Get(); elem_def->atlas.entryCount = elem["atlas"]["entryCount"].Get(); elem_def->elemType = elem["elemType"].Get(); elem_def->visualCount = elem["visualCount"].Get(); elem_def->velIntervalCount = elem["velIntervalCount"].Get(); elem_def->visStateIntervalCount = elem["visStateIntervalCount"].Get(); elem_def->velSamples = local_allocator.allocate_array(elem_def->velIntervalCount + 1); if (elem.HasMember("velSamples") && !elem["velSamples"].IsNull()) { // Mind the <= for (size_t j = 0; j <= elem_def->velIntervalCount; j++) { const auto& json_vel_sample = elem["velSamples"][j]; auto vel_sample = &elem_def->velSamples[j]; vel_sample->local.velocity = utils::json::vec3_range_from_json(json_vel_sample["local"]["velocity"]); vel_sample->local.totalDelta = utils::json::vec3_range_from_json(json_vel_sample["local"]["totalDelta"]); vel_sample->world.velocity = utils::json::vec3_range_from_json(json_vel_sample["world"]["velocity"]); vel_sample->world.totalDelta = utils::json::vec3_range_from_json(json_vel_sample["world"]["totalDelta"]); } } elem_def->visSamples = local_allocator.allocate_array(elem_def->visStateIntervalCount + 1); if (elem.HasMember("visSamples") && !elem["visSamples"].IsNull()) { // Mind the <= for (size_t j = 0; j <= elem_def->visStateIntervalCount; j++) { const auto& json_vel_sample = elem["visSamples"][j]; auto vis_sample = &elem_def->visSamples[j]; vis_sample->base = utils::json::visual_state_from_json(json_vel_sample["base"]); vis_sample->amplitude = utils::json::visual_state_from_json(json_vel_sample["amplitude"]); } } elem_def->collBounds = utils::json::read_bounds(elem["collBounds"]); if (elem.HasMember("effectOnImpact") && !elem["effectOnImpact"].IsNull()) { elem_def->effectOnImpact.handle = find(native::ASSET_TYPE_FX, elem["effectOnImpact"].GetString()); } if (elem.HasMember("effectOnDeath") && !elem["effectOnDeath"].IsNull()) { elem_def->effectOnDeath.handle = find(native::ASSET_TYPE_FX, elem["effectOnDeath"].GetString()); } if (elem.HasMember("effectEmitted") && !elem["effectEmitted"].IsNull()) { elem_def->effectEmitted.handle = find(native::ASSET_TYPE_FX, elem["effectEmitted"].GetString()); } // Read_FXElemDefVisuals if (elem_def->elemType == native::FX_ELEM_TYPE_DECAL) { if (elem.HasMember("markArray") && !elem["markArray"].IsNull()) { elem_def->visuals.markArray = local_allocator.allocate_array(elem_def->visualCount); for (size_t j = 0; j < elem_def->visualCount; j++) { for (size_t k = 0; k < 2; k++) { const auto json_material = &elem["markArray"][j][k]; if (json_material->IsNull()) { // Nothing to do } else { elem_def->visuals.markArray[j].materials[k] = find(native::ASSET_TYPE_MATERIAL, json_material->GetString()); assert(elem_def->visuals.markArray[j].materials[k]); } } } } } else if (elem_def->visualCount > 1) { if (elem.HasMember("visualsArray") && !elem["visualsArray"].IsNull()) { elem_def->visuals.array = local_allocator.allocate_array(elem_def->visualCount); for (char j = 0; j < elem_def->visualCount; ++j) { const auto& json_array = elem["visualsArray"]; read(&elem_def->visuals.array[j], elem_def->elemType, json_array[j]); } } } else if (elem_def->visualCount) { read(&elem_def->visuals.instance, elem_def->elemType, elem["instance"]); } elem_def->emitDist = utils::json::float_range_from_json(elem["emitDist"]); elem_def->emitDistVariance = utils::json::float_range_from_json(elem["emitDistVariance"]); elem_def->emitDistVariance = utils::json::float_range_from_json(elem["emitDistVariance"]); // Save_FxElemExtendedDefPtr { if (elem_def->elemType == native::FX_ELEM_TYPE_TRAIL) { // Save_FxTrailDef { if (elem.HasMember("trailDef") && !elem["trailDef"].IsNull()) { elem_def->extended.trailDef = local_allocator.allocate(); native::FxTrailDef* trail_def = elem_def->extended.trailDef; const rapidjson::Value& json_trail_def = elem["trailDef"]; #define READ_TRAILDEF_MEMBER(x, y) trail_def->x = json_trail_def[#x].Get() READ_TRAILDEF_MEMBER(scrollTimeMsec, int32_t); READ_TRAILDEF_MEMBER(repeatDist, int32_t); READ_TRAILDEF_MEMBER(invSplitDist, float); READ_TRAILDEF_MEMBER(invSplitArcDist, float); READ_TRAILDEF_MEMBER(invSplitTime, float); const rapidjson::Value& verts_json = json_trail_def["verts"]; trail_def->vertCount = verts_json.Size(); trail_def->verts = local_allocator.allocate_array(trail_def->vertCount); for (auto j = 0; j < trail_def->vertCount; j++) { auto trail_vertex = &trail_def->verts[j]; utils::json::copy_array(trail_vertex->pos, verts_json[j]["pos"], 2); utils::json::copy_array(trail_vertex->normal, verts_json[j]["normal"], 2); trail_vertex->texCoord = verts_json[j]["texCoord"].GetFloat(); } trail_def->indCount = json_trail_def["inds"].Size(); trail_def->inds = local_allocator.allocate_array(trail_def->indCount); utils::json::copy_array(trail_def->inds, json_trail_def["inds"], trail_def->indCount); #undef READ_TRAILDEF_MEMBER } } } else if (elem_def->elemType == native::FX_ELEM_TYPE_SPARK_FOUNTAIN) { if (elem.HasMember("sparkFountain") && !elem["sparkFountain"].IsNull()) { elem_def->extended.sparkFountainDef = local_allocator.allocate(); auto spark_fountain = elem_def->extended.sparkFountainDef; const rapidjson::Value& json_spark_fountain = elem["sparkFountain"]; #define READ_SPARK_FOUNTAIN_MEMBER(x, y) spark_fountain->x = json_spark_fountain[#x].Get() READ_SPARK_FOUNTAIN_MEMBER(gravity, float); READ_SPARK_FOUNTAIN_MEMBER(bounceFrac, float); READ_SPARK_FOUNTAIN_MEMBER(bounceRand, float); READ_SPARK_FOUNTAIN_MEMBER(sparkSpacing, float); READ_SPARK_FOUNTAIN_MEMBER(sparkLength, float); READ_SPARK_FOUNTAIN_MEMBER(sparkCount, int32_t); READ_SPARK_FOUNTAIN_MEMBER(loopTime, float); READ_SPARK_FOUNTAIN_MEMBER(velMin, float); READ_SPARK_FOUNTAIN_MEMBER(velMax, float); READ_SPARK_FOUNTAIN_MEMBER(velConeFrac, float); READ_SPARK_FOUNTAIN_MEMBER(restSpeed, float); READ_SPARK_FOUNTAIN_MEMBER(boostTime, float); READ_SPARK_FOUNTAIN_MEMBER(boostFactor, float); #undef READ_SPARK_FOUNTAIN_MEMBER } else { // Guaranteed to crash print_error("Missing spark fountain definition for fx {} elem {}, this will crash on iw4!", name, i); } } } elem_def->sortOrder = elem["sortOrder"].Get(); elem_def->lightingFrac = elem["lightingFrac"].Get(); elem_def->useItemClip = elem["useItemClip"].Get(); elem_def->fadeInfo = elem["fadeInfo"].Get(); } } } } catch (const std::exception& e) { print_error("Malformed JSON for clipmap {}! {}", name, e.what()); return nullptr; } return asset; } std::filesystem::path iw4of::interfaces::ifx::get_binary_file_name(const std::string& basename) const { return std::format("{}.iw4xFx", basename); } std::filesystem::path iw4of::interfaces::ifx::get_file_name(const std::string& basename) const { return std::format("{}.iw4x.json", basename); } void iw4of::interfaces::ifx::get_dependencies(const native::FxElemVisuals* visuals, char elemType, std::vector& dependencies) const { switch (elemType) { case native::FX_ELEM_TYPE_MODEL: if (visuals->model) { dependencies.push_back({native::ASSET_TYPE_XMODEL, {visuals->model}}); } break; case native::FX_ELEM_TYPE_OMNI_LIGHT: case native::FX_ELEM_TYPE_SPOT_LIGHT: break; case native::FX_ELEM_TYPE_SOUND: if (visuals->soundName) { auto list = find(native::ASSET_TYPE_SOUND, visuals->soundName); if (list) { dependencies.push_back({native::ASSET_TYPE_SOUND, {list}}); } } break; case native::FX_ELEM_TYPE_RUNNER: if (visuals->effectDef.handle) { dependencies.push_back({native::ASSET_TYPE_FX, {visuals->effectDef.handle}}); } break; default: if (visuals->material) { dependencies.push_back({native::ASSET_TYPE_MATERIAL, {visuals->material}}); } break; } } rapidjson::Value ifx::to_json(const native::FxElemVisuals* visuals, char elemType, rapidjson::MemoryPoolAllocator& allocator) const { rapidjson::Value output = rapidjson::Value(rapidjson::kNullType); switch (elemType) { case native::FX_ELEM_TYPE_MODEL: if (visuals->model) { output = rapidjson::Value(rapidjson::kObjectType); output.AddMember("model", RAPIDJSON_STR(visuals->model->name), allocator); assets->write(native::ASSET_TYPE_XMODEL, visuals->model); } break; case native::FX_ELEM_TYPE_OMNI_LIGHT: case native::FX_ELEM_TYPE_SPOT_LIGHT: break; case native::FX_ELEM_TYPE_SOUND: if (visuals->soundName) { output = rapidjson::Value(rapidjson::kObjectType); output.AddMember("sound", RAPIDJSON_STR(visuals->soundName), allocator); auto list = find(native::ASSET_TYPE_SOUND, visuals->soundName); if (list) { assets->write(native::ASSET_TYPE_SOUND, list); } } break; case native::FX_ELEM_TYPE_RUNNER: if (visuals->effectDef.handle) { output = rapidjson::Value(rapidjson::kObjectType); output.AddMember("runner", RAPIDJSON_STR(visuals->effectDef.handle->name), allocator); assets->write(native::ASSET_TYPE_FX, visuals->effectDef.handle); } break; default: if (visuals->material) { output = rapidjson::Value(rapidjson::kObjectType); output.AddMember("material", RAPIDJSON_STR(visuals->material->info.name), allocator); assets->write(native::ASSET_TYPE_MATERIAL, visuals->material); } break; } return output; } void interfaces::ifx::read(native::FxElemVisuals* visuals, char elemType, utils::stream::reader* reader) const { switch (elemType) { case native::FX_ELEM_TYPE_MODEL: { if (visuals->model) { visuals->model = find(native::XAssetType::ASSET_TYPE_XMODEL, reader->read_string().data()); } break; } case native::FX_ELEM_TYPE_OMNI_LIGHT: case native::FX_ELEM_TYPE_SPOT_LIGHT: break; case native::FX_ELEM_TYPE_SOUND: { if (visuals->soundName) { visuals->soundName = reader->read_cstring(); find(native::XAssetType::ASSET_TYPE_SOUND, visuals->soundName); } break; } case native::FX_ELEM_TYPE_RUNNER: { if (visuals->effectDef.handle) { visuals->effectDef.handle = find(native::XAssetType::ASSET_TYPE_FX, reader->read_string().data()); assert(visuals->effectDef.handle); } break; } default: { if (visuals->material) { visuals->material = find(native::XAssetType::ASSET_TYPE_MATERIAL, reader->read_string().data()); assert(visuals->material); } break; } } } void interfaces::ifx::read(native::FxElemVisuals* visuals, char elemType, const rapidjson::Value& value) const { if (value.IsNull()) { return; } switch (elemType) { case native::FX_ELEM_TYPE_MODEL: { if (value.HasMember("model")) { visuals->model = find(native::XAssetType::ASSET_TYPE_XMODEL, value["model"].GetString()); } break; } case native::FX_ELEM_TYPE_OMNI_LIGHT: case native::FX_ELEM_TYPE_SPOT_LIGHT: break; case native::FX_ELEM_TYPE_SOUND: { if (value.HasMember("sound")) { visuals->soundName = local_allocator.duplicate_string(value["sound"].GetString()); const auto sound = find(native::XAssetType::ASSET_TYPE_SOUND, visuals->soundName); if (!sound) { print_error("Critical error - could not find sound {}. This will crash the game!", visuals->soundName); } } break; } case native::FX_ELEM_TYPE_RUNNER: { if (value.HasMember("runner")) { visuals->effectDef.handle = find(native::XAssetType::ASSET_TYPE_FX, value["runner"].GetString()); if (!visuals->effectDef.handle) { print_error("Critical error - could not find fx {}. This will crash the game!", value["runner"].GetString()); } } break; } default: { if (value.HasMember("material")) { visuals->material = find(native::XAssetType::ASSET_TYPE_MATERIAL, value["material"].GetString()); if (!visuals->material) { print_error("Critical error - could not find fx {}. This will crash the game!", value["material"].GetString()); } } break; } } } } // namespace iw4of::interfaces