Skip to content

Commit

Permalink
[formats] add: gff serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
jd28 committed Dec 8, 2024
1 parent 24086bb commit 1187b1e
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 70 deletions.
207 changes: 140 additions & 67 deletions lib/nw/formats/Palette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Palette::Palette()
Palette::Palette(const Gff& archive)
: Palette()
{
is_valid_ = load(archive.toplevel());
is_valid_ = deserialize(*this, archive.toplevel());
}

Palette::~Palette()
Expand Down Expand Up @@ -126,71 +126,6 @@ bool Palette::is_skeleton() const noexcept
return resource_type != nw::ResourceType::invalid;
}

PaletteTreeNode* read_child(Palette* parent, const GffStruct st)
{
PaletteTreeNode* node = parent->make_node();

st.get_to("STRREF", node->strref, false);
// Only try DELETE_ME if there is no name.
st.get_to("NAME", node->name, false) || st.get_to("DELETE_ME", node->name, false);

if (st.has_field("RESREF")) {
node->type = PaletteNodeType::blueprint;
} else if (st.has_field("ID")) {
node->type = PaletteNodeType::category;
} else {
node->type = PaletteNodeType::branch;
}

// Assume this isn't a skeleton
if (node->type == PaletteNodeType::blueprint) {
st.get_to("RESREF", node->resref, false);
st.get_to("CR", node->cr, false);
st.get_to("FACTION", node->faction, false);
} else {
if (node->type == PaletteNodeType::category) {
st.get_to("ID", node->id);
st.get_to("TYPE", node->display, false);
parent->node_map_.insert({node->id, node});
}
size_t list_size = st["LIST"].size();
node->children.reserve(list_size);
for (size_t i = 0; i < list_size; ++i) {
node->children.push_back(read_child(parent, st["LIST"][i]));
}
}

return node;
}

bool Palette::load(const GffStruct gff)
{
size_t list_size = gff["MAIN"].size();
if (list_size == 0) {
LOG_F(ERROR, "No main palette list!");
return false;
}

// Skeleton Palettes
uint16_t temp;
if (gff.get_to("RESTYPE", temp, false)) {
resource_type = static_cast<ResourceType::type>(temp);
gff.get_to("NEXT_USEABLE_ID", next_id_, false);

if (resource_type == ResourceType::set && !gff.get_to("TILESETRESREF", tileset)) {
LOG_F(ERROR, "palette no tileset resref specified");
return false;
}
}

children.reserve(list_size);
for (size_t i = 0; i < list_size; ++i) {
children.push_back(read_child(this, gff["MAIN"][i]));
}

return true;
}

PaletteTreeNode* read_node(Palette& self, const nlohmann::json& archive)
{
PaletteTreeNode* node = self.make_node();
Expand All @@ -201,7 +136,12 @@ PaletteTreeNode* read_node(Palette& self, const nlohmann::json& archive)
if (archive.find("id") != std::end(archive)) {
node->type = PaletteNodeType::category;
node->id = archive.at("id").get<int>();
node->display = archive.at("display").get<int>();

auto it = archive.find("display");
if (it != archive.end()) {
node->display = archive["display"].get<int>();
}

self.node_map_.insert({node->id, node});
} else if (archive.find("resref") != std::end(archive)) {
node->type = PaletteNodeType::blueprint;
Expand All @@ -213,6 +153,11 @@ PaletteTreeNode* read_node(Palette& self, const nlohmann::json& archive)
}
} else {
node->type = PaletteNodeType::branch;

auto it = archive.find("display");
if (it != archive.end()) {
node->display = archive["display"].get<int>();
}
}

if (archive.find("|children") != std::end(archive)) {
Expand Down Expand Up @@ -253,6 +198,9 @@ nlohmann::json process_node(nw::ResourceType::type restype, const PaletteTreeNod

if (node.type == PaletteNodeType::category) {
res["id"] = node.id;
}

if (node.display) {
res["display"] = node.display;
}

Expand Down Expand Up @@ -301,4 +249,129 @@ nlohmann::json Palette::to_json() const
return j;
}

// == Palette - Serialization - Gff ===========================================
// ============================================================================

PaletteTreeNode* read_child(Palette& parent, const GffStruct st)
{
PaletteTreeNode* node = parent.make_node();

st.get_to("STRREF", node->strref, false);
// Only try DELETE_ME if there is no name.
st.get_to("NAME", node->name, false) || st.get_to("DELETE_ME", node->name, false);

if (st.has_field("RESREF")) {
node->type = PaletteNodeType::blueprint;
} else if (st.has_field("ID")) {
node->type = PaletteNodeType::category;
} else {
node->type = PaletteNodeType::branch;
}

// Assume this isn't a skeleton
if (node->type == PaletteNodeType::blueprint) {
st.get_to("RESREF", node->resref, false);
st.get_to("CR", node->cr, false);
st.get_to("FACTION", node->faction, false);
} else {
st.get_to("TYPE", node->display, false);
if (node->type == PaletteNodeType::category) {
st.get_to("ID", node->id);
parent.node_map_.insert({node->id, node});
}
size_t list_size = st["LIST"].size();
node->children.reserve(list_size);
for (size_t i = 0; i < list_size; ++i) {
node->children.push_back(read_child(parent, st["LIST"][i]));
}
}

return node;
}

bool deserialize(Palette& obj, const GffStruct& archive)
{
size_t list_size = archive["MAIN"].size();
if (list_size == 0) {
LOG_F(ERROR, "No main palette list!");
return false;
}

// Skeleton Palettes
uint16_t temp;
if (archive.get_to("RESTYPE", temp, false)) {
obj.resource_type = static_cast<ResourceType::type>(temp);
archive.get_to("NEXT_USEABLE_ID", obj.next_id_, false);

if (obj.resource_type == ResourceType::set && !archive.get_to("TILESETRESREF", obj.tileset)) {
LOG_F(ERROR, "palette no tileset resref specified");
return false;
}
}

obj.children.reserve(list_size);
for (size_t i = 0; i < list_size; ++i) {
obj.children.push_back(read_child(obj, archive["MAIN"][i]));
}

return true;
}

inline void add_node(const Palette& obj, const PaletteTreeNode* node, GffBuilderStruct& str)
{

str.add_field("STRREF", node->strref);
str.add_field("DELETE_ME", node->name); // This should provide more compatibility over "NAME"

if (node->type == PaletteNodeType::blueprint) {
str.add_field("RESREF", node->resref);

if (node->cr != 0.0f) {
str.add_field("CR", node->cr);
}

if (!node->faction.empty()) {
str.add_field("FACTION", node->faction);
}
} else {
if (node->type == PaletteNodeType::category) {
str.add_field("ID", node->id);
}

if (node->display) {
str.add_field("TYPE", node->display);
}

if (node->children.size()) {
auto& list = str.add_list("LIST");
for (auto it : node->children) {
add_node(obj, it, list.push_back(1));
}
}
}
}

GffBuilder serialize(const Palette& obj)
{
GffBuilder result{Palette::serial_id};
auto& top = result.top;

if (obj.resource_type != ResourceType::invalid) {
top.add_field("RESTYPE", static_cast<uint16_t>(obj.resource_type));
top.add_field("NEXT_USEABLE_ID", obj.next_id_);

if (obj.resource_type == ResourceType::set) {
top.add_field("TILESETRESREF", obj.tileset);
}
}

auto& main = top.add_list("MAIN");
for (auto node : obj.children) {
add_node(obj, node, main.push_back(1));
}

result.build();
return result;
}

} // namespace nw
8 changes: 6 additions & 2 deletions lib/nw/formats/Palette.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,17 @@ struct Palette {
Resref tileset; // Only if restype is ResourceType::set

// Private
bool load(const GffStruct gff);

uint8_t next_id_ = 0;
bool is_valid_ = false;

ObjectPool<PaletteTreeNode> node_pool_;
absl::flat_hash_map<uint8_t, PaletteTreeNode*> node_map_;
};

// == Palette - Serialization - Gff ===========================================
// ============================================================================

bool deserialize(Palette& obj, const GffStruct& archive);
GffBuilder serialize(const Palette& obj);

} // namespace nw
33 changes: 33 additions & 0 deletions tests/legacy_palette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,36 @@ TEST(Palette, JsonConversion)
std::ofstream f2{"tmp/creaturepalstd.itp.json"};
f2 << std::setw(4) << j3;
}

TEST(Palette, GffRoundTrip)
{
nw::Gff g{"test_data/user/scratch/creaturepal.itp"};
EXPECT_TRUE(g.valid());
nw::Palette f{g};
nw::GffBuilder out = nw::serialize(f);
out.write_to("tmp/creaturepal2.itp");

nw::Gff g2{"tmp/creaturepal2.itp"};
EXPECT_TRUE(g2.valid());

// Due to ITPs being a hand edited thing, you can never be certain that these tests will pass,
// uncomment and confirm via json diff / visually.

// auto j1 = nw::gff_to_gffjson(g);
// auto j2 = nw::gff_to_gffjson(g2);

// EXPECT_EQ(j1, j2);

// EXPECT_EQ(out.header.struct_offset, g.head_->struct_offset);
// EXPECT_EQ(out.header.struct_count, g.head_->struct_count);
// EXPECT_EQ(out.header.field_offset, g.head_->field_offset);
// EXPECT_EQ(out.header.field_count, g.head_->field_count);
// EXPECT_EQ(out.header.label_offset, g.head_->label_offset);
// EXPECT_EQ(out.header.label_count, g.head_->label_count);
// EXPECT_EQ(out.header.field_data_offset, g.head_->field_data_offset);
// EXPECT_EQ(out.header.field_data_count, g.head_->field_data_count);
// EXPECT_EQ(out.header.field_idx_offset, g.head_->field_idx_offset);
// EXPECT_EQ(out.header.field_idx_count, g.head_->field_idx_count);
// EXPECT_EQ(out.header.list_idx_offset, g.head_->list_idx_offset);
// EXPECT_EQ(out.header.list_idx_count, g.head_->list_idx_count);
}
2 changes: 1 addition & 1 deletion tests/test_data/user/scratch/creaturepal1.itp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$type": "ITP",
"$version": 1,
"next_available_id": 51,
"next_available_id": 3,
"resource_type": "utc",
"root": [
{
Expand Down

0 comments on commit 1187b1e

Please sign in to comment.