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();