1080 lines
48 KiB
C++

#include <std_include.hpp>
#include "ifx.hpp"
#include "itechniqueset.hpp"
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/json.hpp>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#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<native::XAsset> ifx::get_child_assets(const native::XAssetHeader& header) const
{
std::vector<native::XAsset> 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<int64_t>();
if (std::memcmp(&magic, "IW4xFx ", 8))
{
print_error("Reading fx '{}' failed, header is invalid!", name);
return nullptr;
}
int32_t version = buffer.read<int32_t>();
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<native::FxEffectDef>();
if (asset->name)
{
asset->name = buffer.read_cstring();
}
if (asset->elemDefs)
{
asset->elemDefs =
buffer.read_array<native::FxElemDef>(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<native::FxElemVelStateSample>(elemDef->velIntervalCount + 1);
}
if (elemDef->visSamples)
{
elemDef->visSamples = buffer.read_array<native::FxElemVisStateSample>(elemDef->visStateIntervalCount + 1);
}
// Save_FxElemDefVisuals
{
if (elemDef->elemType == native::FX_ELEM_TYPE_DECAL)
{
if (elemDef->visuals.markArray)
{
elemDef->visuals.markArray = buffer.read_array<native::FxElemMarkVisuals>(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::Material>(native::XAssetType::ASSET_TYPE_MATERIAL, buffer.read_string().data());
}
if (elemDef->visuals.markArray[j].materials[1])
{
elemDef->visuals.markArray[j].materials[1] =
find<native::Material>(native::XAssetType::ASSET_TYPE_MATERIAL, buffer.read_string().data());
}
}
}
}
else if (elemDef->visualCount > 1)
{
if (elemDef->visuals.array)
{
elemDef->visuals.array = buffer.read_array<native::FxElemVisuals>(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::FxEffectDef>(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::FxEffectDef>(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::FxEffectDef>(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<native::FxTrailDef>();
elemDef->extended.trailDef = trailDef;
if (trailDef->verts)
{
trailDef->verts = buffer.read_array<native::FxTrailVertex>(trailDef->vertCount);
}
if (trailDef->inds)
{
trailDef->inds = buffer.read_array<uint16_t>(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<native::FxSparkFountainDef>();
}
}
}
}
}
}
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<native::FxEffectDef>();
try
{
const auto version = fx_json["version"].Get<int32_t>();
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<std::string>());
asset->flags = fx_json["flags"].Get<int32_t>();
asset->totalSize = fx_json["totalSize"].Get<int32_t>();
asset->msecLoopingLife = fx_json["msecLoopingLife"].Get<int32_t>();
asset->elemDefCountLooping = fx_json["elemDefCountLooping"].Get<int32_t>();
asset->elemDefCountOneShot = fx_json["elemDefCountOneShot"].Get<int32_t>();
asset->elemDefCountEmission = fx_json["elemDefCountEmission"].Get<int32_t>();
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<int32_t>(json_elem_count) ==
asset->elemDefCountEmission + asset->elemDefCountLooping + asset->elemDefCountOneShot);
asset->elemDefs = local_allocator.allocate_array<iw4of::native::FxElemDef>(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<int32_t>();
elem_def->spawn.looping.count = elem["spawn"]["A"].Get<int32_t>();
elem_def->spawn.looping.intervalMsec = elem["spawn"]["B"].Get<int32_t>();
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<uint8_t>();
elem_def->atlas.index = elem["atlas"]["index"].Get<uint8_t>();
elem_def->atlas.fps = elem["atlas"]["fps"].Get<uint8_t>();
elem_def->atlas.loopCount = elem["atlas"]["loopCount"].Get<uint8_t>();
elem_def->atlas.colIndexBits = elem["atlas"]["colIndexBits"].Get<uint8_t>();
elem_def->atlas.rowIndexBits = elem["atlas"]["rowIndexBits"].Get<uint8_t>();
elem_def->atlas.entryCount = elem["atlas"]["entryCount"].Get<int16_t>();
elem_def->elemType = elem["elemType"].Get<uint8_t>();
elem_def->visualCount = elem["visualCount"].Get<uint8_t>();
elem_def->velIntervalCount = elem["velIntervalCount"].Get<uint8_t>();
elem_def->visStateIntervalCount = elem["visStateIntervalCount"].Get<uint8_t>();
elem_def->velSamples = local_allocator.allocate_array<native::FxElemVelStateSample>(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<native::FxElemVisStateSample>(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::FxEffectDef>(native::ASSET_TYPE_FX, elem["effectOnImpact"].GetString());
}
if (elem.HasMember("effectOnDeath") && !elem["effectOnDeath"].IsNull())
{
elem_def->effectOnDeath.handle = find<native::FxEffectDef>(native::ASSET_TYPE_FX, elem["effectOnDeath"].GetString());
}
if (elem.HasMember("effectEmitted") && !elem["effectEmitted"].IsNull())
{
elem_def->effectEmitted.handle = find<native::FxEffectDef>(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<native::FxElemMarkVisuals>(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::Material>(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<native::FxElemVisuals>(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>();
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<y>()
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<native::FxTrailVertex>(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<uint16_t>(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<iw4of::native::FxSparkFountainDef>();
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<y>()
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<uint8_t>();
elem_def->lightingFrac = elem["lightingFrac"].Get<uint8_t>();
elem_def->useItemClip = elem["useItemClip"].Get<uint8_t>();
elem_def->fadeInfo = elem["fadeInfo"].Get<uint8_t>();
}
}
}
}
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<native::XAsset>& 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::snd_alias_list_t>(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<rapidjson::CrtAllocator>& 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::snd_alias_list_t>(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::XModel>(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::snd_alias_list_t>(native::XAssetType::ASSET_TYPE_SOUND, visuals->soundName);
}
break;
}
case native::FX_ELEM_TYPE_RUNNER:
{
if (visuals->effectDef.handle)
{
visuals->effectDef.handle = find<native::FxEffectDef>(native::XAssetType::ASSET_TYPE_FX, reader->read_string().data());
assert(visuals->effectDef.handle);
}
break;
}
default:
{
if (visuals->material)
{
visuals->material = find<native::Material>(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::XModel>(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::snd_alias_list_t>(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::FxEffectDef>(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::Material>(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