From 1e6c12de7bbd26f2b6a738187484331611ab2251 Mon Sep 17 00:00:00 2001 From: sanderd17 Date: Tue, 19 Apr 2016 07:51:59 +0000 Subject: [PATCH] Allow variants to be defined in external files. Fixes #3286 This was SVN commit r18054. --- .../data/mods/public/art/actors/actor.rnc | 1 + .../data/mods/public/art/actors/actor.rng | 3 + .../units/athenians/infantry_archer_b.xml | 9 +- .../units/athenians/infantry_javelinist_b.xml | 9 +- .../units/athenians/infantry_slinger_b.xml | 9 +- .../units/athenians/infantry_spearman_b.xml | 9 +- .../art/variants/biped/attack_capture.xml | 134 +++++++ .../data/mods/public/art/variants/variant.rnc | 54 +++ .../data/mods/public/art/variants/variant.rng | 153 ++++++++ source/graphics/ObjectBase.cpp | 334 +++++++++--------- source/graphics/ObjectBase.h | 5 +- .../ActorEditor/ActorEditorListCtrl.cpp | 15 +- source/tools/xmlvalidator/validate.pl | 31 +- 13 files changed, 548 insertions(+), 218 deletions(-) create mode 100644 binaries/data/mods/public/art/variants/biped/attack_capture.xml create mode 100644 binaries/data/mods/public/art/variants/variant.rnc create mode 100644 binaries/data/mods/public/art/variants/variant.rng diff --git a/binaries/data/mods/public/art/actors/actor.rnc b/binaries/data/mods/public/art/actors/actor.rnc index 3a88e75f9a..88cf832a8f 100644 --- a/binaries/data/mods/public/art/actors/actor.rnc +++ b/binaries/data/mods/public/art/actors/actor.rnc @@ -9,6 +9,7 @@ element actor { element group { element variant { attribute name { text }? & + attribute file { text }? & attribute frequency { xsd:nonNegativeInteger }? & element mesh { text diff --git a/binaries/data/mods/public/art/actors/actor.rng b/binaries/data/mods/public/art/actors/actor.rng index d337b7f4f6..a94bc4ef5e 100644 --- a/binaries/data/mods/public/art/actors/actor.rng +++ b/binaries/data/mods/public/art/actors/actor.rng @@ -18,6 +18,9 @@ + + + diff --git a/binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml b/binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml index fb8afc0b1a..1e87247f21 100644 --- a/binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml +++ b/binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml @@ -6,9 +6,6 @@ - - - @@ -44,11 +41,7 @@ - - - - - + diff --git a/binaries/data/mods/public/art/actors/units/athenians/infantry_javelinist_b.xml b/binaries/data/mods/public/art/actors/units/athenians/infantry_javelinist_b.xml index de64416b68..b8f84afd4b 100644 --- a/binaries/data/mods/public/art/actors/units/athenians/infantry_javelinist_b.xml +++ b/binaries/data/mods/public/art/actors/units/athenians/infantry_javelinist_b.xml @@ -5,9 +5,6 @@ - - - @@ -48,11 +45,7 @@ - - - - - + diff --git a/binaries/data/mods/public/art/actors/units/athenians/infantry_slinger_b.xml b/binaries/data/mods/public/art/actors/units/athenians/infantry_slinger_b.xml index 0df08cdbb7..02902eaa42 100644 --- a/binaries/data/mods/public/art/actors/units/athenians/infantry_slinger_b.xml +++ b/binaries/data/mods/public/art/actors/units/athenians/infantry_slinger_b.xml @@ -11,9 +11,6 @@ - - - @@ -46,11 +43,7 @@ - - - - - + diff --git a/binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_b.xml b/binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_b.xml index 65d599645f..d13b7ec030 100644 --- a/binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_b.xml +++ b/binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_b.xml @@ -12,9 +12,6 @@ - - - @@ -55,11 +52,7 @@ - - - - - + diff --git a/binaries/data/mods/public/art/variants/biped/attack_capture.xml b/binaries/data/mods/public/art/variants/biped/attack_capture.xml new file mode 100644 index 0000000000..7485dc1311 --- /dev/null +++ b/binaries/data/mods/public/art/variants/biped/attack_capture.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + skeletal/m_tunic_short.dae + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + player_trans.xml + diff --git a/binaries/data/mods/public/art/variants/variant.rnc b/binaries/data/mods/public/art/variants/variant.rnc new file mode 100644 index 0000000000..3f445305b5 --- /dev/null +++ b/binaries/data/mods/public/art/variants/variant.rnc @@ -0,0 +1,54 @@ +namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" +## +# NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file +# and use a converter tool like trang to generate the Relax NG XML (.rng) file +## + +element variant { + attribute name { text }? & + attribute frequency { xsd:nonNegativeInteger }? & + element mesh { + text + }? & + element textures { + element texture { + attribute file { text }? & + attribute name { text } + }* + }? & + element decal { + attribute width { xsd:float } & # X + attribute depth { xsd:float } & # Z + attribute angle { xsd:float } & + attribute offsetx { xsd:float } & + attribute offsetz { xsd:float } + }? & + element particles { + attribute file { text } + }? & + element color { list { + xsd:nonNegativeInteger, # R + xsd:nonNegativeInteger, # G + xsd:nonNegativeInteger # B + } }? & + element animations { + element animation { + attribute name { text } & + attribute file { text }? & + attribute speed { xsd:nonNegativeInteger } & + attribute event { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? & + attribute load { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? & + attribute sound { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? + }* + }? & + element props { + element prop { + (attribute actor { text }? & + attribute attachpoint { text } & + attribute minheight { xsd:float }? & + attribute maxheight { xsd:float }? & + attribute selectable { "true" | "false" }?) + }* + }? +} + diff --git a/binaries/data/mods/public/art/variants/variant.rng b/binaries/data/mods/public/art/variants/variant.rng new file mode 100644 index 0000000000..d9e88d11a2 --- /dev/null +++ b/binaries/data/mods/public/art/variants/variant.rng @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + 0 + 1 + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + false + + + + + + + + + + diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 27a816a831..b79e5d0c4d 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -38,6 +38,171 @@ CObjectBase::CObjectBase(CObjectManager& objectManager) m_Properties.m_FloatOnWater = false; } +void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant) +{ + #define EL(x) int el_##x = XeroFile.GetElementID(#x) + #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + EL(animation); + EL(animations); + EL(color); + EL(decal); + EL(mesh); + EL(particles); + EL(prop); + EL(props); + EL(texture); + EL(textures); + EL(variant); + AT(actor); + AT(angle); + AT(attachpoint); + AT(depth); + AT(event); + AT(file); + AT(frequency); + AT(load); + AT(maxheight); + AT(minheight); + AT(name); + AT(offsetx); + AT(offsetz); + AT(selectable); + AT(sound); + AT(speed); + AT(width); + #undef AT + #undef EL + + if (variant.GetNodeName() != el_variant) + { + LOGERROR("Invalid variant format (unrecognised root element '%s')", XeroFile.GetElementString(variant.GetNodeName()).c_str()); + return; + } + + XERO_ITER_ATTR(variant, attr) + { + if (attr.Name == at_file) + { + // Open up an external file to load. + // Don't crash hard when failures happen, but log them and continue + m_UsedFiles.insert(attr.Value); + CXeromyces XeroVariant; + if (XeroVariant.Load(g_VFS, "art/variants/" + attr.Value) == PSRETURN_OK) + { + XMBElement variantRoot = XeroVariant.GetRoot(); + LoadVariant(XeroVariant, variantRoot, currentVariant); + } + else + LOGERROR("Could not open path %s", attr.Value); + // Continue loading extra definitions in this variant to allow nested files + } + else if (attr.Name == at_name) + currentVariant.m_VariantName = attr.Value.LowerCase(); + else if (attr.Name == at_frequency) + currentVariant.m_Frequency = attr.Value.ToInt(); + } + + XERO_ITER_EL(variant, option) + { + int option_name = option.GetNodeName(); + + if (option_name == el_mesh) + { + currentVariant.m_ModelFilename = VfsPath("art/meshes") / option.GetText().FromUTF8(); + } + else if (option_name == el_textures) + { + XERO_ITER_EL(option, textures_element) + { + ENSURE(textures_element.GetNodeName() == el_texture); + + Samp samp; + XERO_ITER_ATTR(textures_element, se) + { + if (se.Name == at_file) + samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8(); + else if (se.Name == at_name) + samp.m_SamplerName = CStrIntern(se.Value); + } + currentVariant.m_Samplers.push_back(samp); + } + } + else if (option_name == el_decal) + { + XMBAttributeList attrs = option.GetAttributes(); + Decal decal; + decal.m_SizeX = attrs.GetNamedItem(at_width).ToFloat(); + decal.m_SizeZ = attrs.GetNamedItem(at_depth).ToFloat(); + decal.m_Angle = DEGTORAD(attrs.GetNamedItem(at_angle).ToFloat()); + decal.m_OffsetX = attrs.GetNamedItem(at_offsetx).ToFloat(); + decal.m_OffsetZ = attrs.GetNamedItem(at_offsetz).ToFloat(); + currentVariant.m_Decal = decal; + } + else if (option_name == el_particles) + { + XMBAttributeList attrs = option.GetAttributes(); + VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8(); + currentVariant.m_Particles = file; + + // For particle hotloading, it's easiest to reload the entire actor, + // so remember the relevant particle file as a dependency for this actor + m_UsedFiles.insert(file); + } + else if (option_name == el_color) + { + currentVariant.m_Color = option.GetText(); + } + else if (option_name == el_animations) + { + XERO_ITER_EL(option, anim_element) + { + ENSURE(anim_element.GetNodeName() == el_animation); + + Anim anim; + XERO_ITER_ATTR(anim_element, ae) + { + if (ae.Name == at_name) + anim.m_AnimName = ae.Value; + else if (ae.Name == at_file) + anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8(); + else if (ae.Name == at_speed) + anim.m_Speed = ae.Value.ToInt() > 0 ? ae.Value.ToInt() / 100.f : 1.f; + else if (ae.Name == at_event) + anim.m_ActionPos = clamp(ae.Value.ToFloat(), 0.f, 1.f); + else if (ae.Name == at_load) + anim.m_ActionPos2 = clamp(ae.Value.ToFloat(), 0.f, 1.f); + else if (ae.Name == at_sound) + anim.m_SoundPos = clamp(ae.Value.ToFloat(), 0.f, 1.f); + } + currentVariant.m_Anims.push_back(anim); + } + } + else if (option_name == el_props) + { + XERO_ITER_EL(option, prop_element) + { + ENSURE(prop_element.GetNodeName() == el_prop); + + Prop prop; + XERO_ITER_ATTR(prop_element, pe) + { + if (pe.Name == at_attachpoint) + prop.m_PropPointName = pe.Value; + else if (pe.Name == at_actor) + prop.m_ModelName = pe.Value.FromUTF8(); + else if (pe.Name == at_minheight) + prop.m_minHeight = pe.Value.ToFloat(); + else if (pe.Name == at_maxheight) + prop.m_maxHeight = pe.Value.ToFloat(); + else if (pe.Name == at_selectable) + prop.m_selectable = pe.Value != "false"; + } + currentVariant.m_Props.push_back(prop); + } + } + } +} + bool CObjectBase::Load(const VfsPath& pathname) { m_UsedFiles.clear(); @@ -53,36 +218,8 @@ bool CObjectBase::Load(const VfsPath& pathname) EL(actor); EL(castshadow); EL(float); - EL(material); EL(group); - EL(variant); - EL(animations); - EL(animation); - EL(props); - EL(prop); - EL(mesh); - EL(texture); - EL(textures); - EL(color); - EL(decal); - EL(particles); - AT(file); - AT(name); - AT(speed); - AT(event); - AT(load); - AT(sound); - AT(attachpoint); - AT(actor); - AT(frequency); - AT(width); - AT(depth); - AT(angle); - AT(offsetx); - AT(offsetz); - AT(minheight); - AT(maxheight); - AT(selectable); + EL(material); #undef AT #undef EL @@ -94,7 +231,6 @@ bool CObjectBase::Load(const VfsPath& pathname) return false; } - m_VariantGroups.clear(); m_Pathname = pathname; @@ -132,155 +268,21 @@ bool CObjectBase::Load(const VfsPath& pathname) std::vector::iterator currentVariant = currentGroup->begin(); XERO_ITER_EL(child, variant) { - ENSURE(variant.GetNodeName() == el_variant); - XERO_ITER_ATTR(variant, attr) - { - if (attr.Name == at_name) - currentVariant->m_VariantName = attr.Value.LowerCase(); - - else if (attr.Name == at_frequency) - currentVariant->m_Frequency = attr.Value.ToInt(); - } - - XERO_ITER_EL(variant, option) - { - int option_name = option.GetNodeName(); - - if (option_name == el_mesh) - { - currentVariant->m_ModelFilename = VfsPath("art/meshes") / option.GetText().FromUTF8(); - } - else if (option_name == el_textures) - { - XERO_ITER_EL(option, textures_element) - { - ENSURE(textures_element.GetNodeName() == el_texture); - - Samp samp; - XERO_ITER_ATTR(textures_element, se) - { - if (se.Name == at_file) - samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8(); - else if (se.Name == at_name) - samp.m_SamplerName = CStrIntern(se.Value); - } - currentVariant->m_Samplers.push_back(samp); - } - } - else if (option_name == el_decal) - { - XMBAttributeList attrs = option.GetAttributes(); - Decal decal; - decal.m_SizeX = attrs.GetNamedItem(at_width).ToFloat(); - decal.m_SizeZ = attrs.GetNamedItem(at_depth).ToFloat(); - decal.m_Angle = DEGTORAD(attrs.GetNamedItem(at_angle).ToFloat()); - decal.m_OffsetX = attrs.GetNamedItem(at_offsetx).ToFloat(); - decal.m_OffsetZ = attrs.GetNamedItem(at_offsetz).ToFloat(); - currentVariant->m_Decal = decal; - } - else if (option_name == el_particles) - { - XMBAttributeList attrs = option.GetAttributes(); - VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8(); - currentVariant->m_Particles = file; - - // For particle hotloading, it's easiest to reload the entire actor, - // so remember the relevant particle file as a dependency for this actor - m_UsedFiles.insert(file); - } - else if (option_name == el_color) - { - currentVariant->m_Color = option.GetText(); - } - else if (option_name == el_animations) - { - XERO_ITER_EL(option, anim_element) - { - ENSURE(anim_element.GetNodeName() == el_animation); - - Anim anim; - XERO_ITER_ATTR(anim_element, ae) - { - if (ae.Name == at_name) - { - anim.m_AnimName = ae.Value; - } - else if (ae.Name == at_file) - { - anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8(); - } - else if (ae.Name == at_speed) - { - anim.m_Speed = ae.Value.ToInt() / 100.f; - if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f; - } - else if (ae.Name == at_event) - { - float pos = ae.Value.ToFloat(); - anim.m_ActionPos = clamp(pos, 0.f, 1.f); - } - else if (ae.Name == at_load) - { - float pos = ae.Value.ToFloat(); - anim.m_ActionPos2 = clamp(pos, 0.f, 1.f); - } - else if (ae.Name == at_sound) - { - float pos = ae.Value.ToFloat(); - anim.m_SoundPos = clamp(pos, 0.f, 1.f); - } - } - currentVariant->m_Anims.push_back(anim); - } - - } - else if (option_name == el_props) - { - XERO_ITER_EL(option, prop_element) - { - ENSURE(prop_element.GetNodeName() == el_prop); - - Prop prop; - XERO_ITER_ATTR(prop_element, pe) - { - if (pe.Name == at_attachpoint) - prop.m_PropPointName = pe.Value; - else if (pe.Name == at_actor) - prop.m_ModelName = pe.Value.FromUTF8(); - else if (pe.Name == at_minheight) - prop.m_minHeight = pe.Value.ToFloat(); - else if (pe.Name == at_maxheight) - prop.m_maxHeight = pe.Value.ToFloat(); - else if (pe.Name == at_selectable) - prop.m_selectable = pe.Value != "false"; - } - currentVariant->m_Props.push_back(prop); - } - } - } - + LoadVariant(XeroFile, variant, *currentVariant); ++currentVariant; } if (currentGroup->size() == 0) - { LOGERROR("Actor group has zero variants ('%s')", pathname.string8()); - } ++currentGroup; } else if (child_name == el_castshadow) - { m_Properties.m_CastShadows = true; - } else if (child_name == el_float) - { m_Properties.m_FloatOnWater = true; - } else if (child_name == el_material) - { m_Material = VfsPath("art/materials") / child.GetText().FromUTF8(); - } } if (m_Material.empty()) diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index 123a6ecd59..c3868ab940 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,6 +29,7 @@ class CObjectManager; #include "lib/file/vfs/vfs_path.h" #include "ps/CStr.h" #include "ps/CStrIntern.h" +#include "ps/XML/Xeromyces.h" #include @@ -187,6 +188,8 @@ private: CObjectManager& m_ObjectManager; boost::unordered_set m_UsedFiles; + + void LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant); }; #endif diff --git a/source/tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp b/source/tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp index 55921abe03..ed79325480 100644 --- a/source/tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp +++ b/source/tools/atlas/AtlasUI/ActorEditor/ActorEditorListCtrl.cpp @@ -46,13 +46,14 @@ ActorEditorListCtrl::ActorEditorListCtrl(wxWindow* parent) #undef COLOR - AddColumnType(_("Variant"), 90, "@name", new FieldEditCtrl_Text()); - AddColumnType(_("Ratio"), 50, "@frequency", new FieldEditCtrl_Text()); - AddColumnType(_("Model"), 140, "mesh", new FieldEditCtrl_File(_T("art/meshes/"), _("Mesh files (*.pmd, *.dae)|*.pmd;*.dae|All files (*.*)|*.*"))); - AddColumnType(_("Textures"), 250, "textures", new FieldEditCtrl_Dialog(&TexListEditor::Create)); - AddColumnType(_("Animations"), 250, "animations", new FieldEditCtrl_Dialog(&AnimListEditor::Create)); - AddColumnType(_("Props"), 220, "props", new FieldEditCtrl_Dialog(&PropListEditor::Create)); - AddColumnType(_("Color"), 80, "color", new FieldEditCtrl_Color()); + AddColumnType(_("Variant"), 90, "@name", new FieldEditCtrl_Text()); + AddColumnType(_("Base File"), 90, "@file", new FieldEditCtrl_File(_T("art/variants/"), _("Variants (*.xml)|*.xml|All files (*.*)|*.*"))); + AddColumnType(_("Ratio"), 50, "@frequency", new FieldEditCtrl_Text()); + AddColumnType(_("Model"), 140, "mesh", new FieldEditCtrl_File(_T("art/meshes/"), _("Mesh files (*.pmd, *.dae)|*.pmd;*.dae|All files (*.*)|*.*"))); + AddColumnType(_("Textures"), 250, "textures", new FieldEditCtrl_Dialog(&TexListEditor::Create)); + AddColumnType(_("Animations"), 250, "animations", new FieldEditCtrl_Dialog(&AnimListEditor::Create)); + AddColumnType(_("Props"), 220, "props", new FieldEditCtrl_Dialog(&PropListEditor::Create)); + AddColumnType(_("Color"), 80, "color", new FieldEditCtrl_Color()); } void ActorEditorListCtrl::DoImport(AtObj& in) diff --git a/source/tools/xmlvalidator/validate.pl b/source/tools/xmlvalidator/validate.pl index e015d529a6..27f2ddc3c5 100644 --- a/source/tools/xmlvalidator/validate.pl +++ b/source/tools/xmlvalidator/validate.pl @@ -77,12 +77,18 @@ sub validate sub validate_actors { my @files = find_files('art/actors', 'xml'); - validate('actor', \@files, 'art/actors/actor.rng'); -} - -sub validate_guis -{ - # there are two different gui XML schemas depending on path + validate('actor', \@files, 'art/actors/actor.rng'); +} + +sub validate_variants +{ + my @files = find_files('art/variants', 'xml'); + validate('variant', \@files, 'art/variants/variant.rng'); +} + +sub validate_guis +{ + # there are two different gui XML schemas depending on path my @files = find_files('gui', 'xml'); my (@guipages, @guixmls); for my $f (@files) @@ -164,12 +170,13 @@ sub validate_textures push @files, $f if $f =~ /textures.xml/; } validate('texture', \@files, 'art/textures/texture.rng'); -} - -validate_actors(); -validate_guis(); -validate_maps(); -validate_materials(); +} + +validate_actors(); +validate_variants(); +validate_guis(); +validate_maps(); +validate_materials(); validate_particles(); validate_simulation(); validate_soundgroups();