# Add documentation of the entity template XML file format.

Simplify the format a bit.
Use less <interleave> in the RNG so that error reports become
understandable.
Fixes #491.

This was SVN commit r7478.
This commit is contained in:
Ykkrosh 2010-04-23 16:09:03 +00:00
parent 116645ff74
commit dd809f83e8
39 changed files with 665 additions and 111 deletions

View File

@ -1,13 +1,19 @@
function Armour() {}
Armour.prototype.Schema =
"<element name='Hack'>" +
"<a:help>Controls the damage resistance of the unit.</a:help>" +
"<a:example>" +
"<Hack>10.0</Hack>" +
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"</a:example>" +
"<element name='Hack' a:help='Hack damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Pierce'>" +
"<element name='Pierce' a:help='Pierce damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Crush'>" +
"<element name='Crush' a:help='Crush damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>";

View File

@ -1,35 +1,44 @@
function Attack() {}
Attack.prototype.Schema =
"<element name='Hack'>" +
"<a:help>Controls the attack abilities and strengths of the unit.</a:help>" +
"<a:example>" +
"<Hack>10.0</Hack>" +
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"<MaxRange>10.0</MaxRange>" +
"<MinRange>0.0</MinRange>" +
"<PrepareTime>800</PrepareTime>" +
"<RepeatTime>1600</RepeatTime>" +
"<ProjectileSpeed>50.0</ProjectileSpeed>" +
"</a:example>" +
"<element name='Hack' a:help='Hack damage strength'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Pierce'>" +
"<element name='Pierce' a:help='Pierce damage strength'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Crush'>" +
"<element name='Crush' a:help='Crush damage strength'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Range'>" +
"<element name='MaxRange' a:help='Maximum attack range (in metres)'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='MinRange' a:help='Minimum attack range (in metres)'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<optional>" +
"<element name='MinRange'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='PrepareTime'>" +
"<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor&apos;s attack animation'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='RepeatTime'>" +
"<element name='RepeatTime' a:help='Time between attacks (in milliseconds). The attack animation will be stretched to match this time'>" +
"<data type='positiveInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='ProjectileSpeed'>" +
"<element name='ProjectileSpeed' a:help='Speed of projectiles (in metres per second). If unspecified, then it is a melee attack instead'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>";
@ -64,8 +73,8 @@ Attack.prototype.GetAttackStrengths = function()
Attack.prototype.GetRange = function()
{
var max = +this.template.Range;
var min = +(this.template.MinRange || 0);
var max = +this.template.MaxRange;
var min = +this.template.MinRange;
return { "max": max, "min": min };
}

View File

@ -1,12 +1,18 @@
function Builder() {}
Builder.prototype.Schema =
"<element name='Entities'>" +
"<attribute name='datatype'><value>tokens</value></attribute>" +
"<text/>" +
"</element>" +
"<element name='Rate'>" +
"<a:help>Allows the unit to construct and repair buildings.</a:help>" +
"<a:example>" +
"<Rate>1.0</Rate>" +
"<Entities>" +
"\n structures/{civ}_barracks\n structures/{civ}_civil_centre\n structures/celt_sb1\n " +
"</Entities>" +
"</a:example>" +
"<element name='Rate' a:help='Construction speed multiplier (1.0 is normal speed, higher values are faster)'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this unit can build. The special string \"{civ}\" will be automatically replaced by the unit&apos;s four-character civ code'>" +
"<text/>" +
"</element>";
Builder.prototype.Init = function()
@ -15,7 +21,7 @@ Builder.prototype.Init = function()
Builder.prototype.GetEntitiesList = function()
{
var string = this.template.Entities._string;
var string = this.template.Entities;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);

View File

@ -1,22 +1,34 @@
function Cost() {}
Cost.prototype.Schema =
"<a:help>Specifies the construction/training costs of this entity.</a:help>" +
"<a:example>" +
"<Population>1</Population>" +
"<PopulationBonus>15</PopulationBonus>" +
"<BuildTime>20.0</BuildTime>" +
"<Resources>" +
"<food>50</food>" +
"<wood>0</wood>" +
"<stone>0</stone>" +
"<metal>25</metal>" +
"</Resources>" +
"</a:example>" +
"<optional>" +
"<element name='Population'>" +
"<element name='Population' a:help='Population cost'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='PopulationBonus'>" +
"<element name='PopulationBonus' a:help='Population cap increase while this entity exists'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='BuildTime'>" +
"<element name='BuildTime' a:help='Time taken to construct/train this unit (in seconds)'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"</optional>" +
"<element name='Resources'>" +
"<element name='Resources' a:help='Resource costs to construct/train this unit'>" +
"<interleave>" +
"<element name='food'><data type='nonNegativeInteger'/></element>" +
"<element name='wood'><data type='nonNegativeInteger'/></element>" +

View File

@ -1,5 +1,8 @@
function Foundation() {}
Foundation.prototype.Schema =
"<a:component type='internal'/><empty/>";
Foundation.prototype.Init = function()
{
this.buildProgress = 0.0; // 0 <= progress <= 1

View File

@ -1,5 +1,8 @@
function GuiInterface() {}
GuiInterface.prototype.Schema =
"<a:component type='system'/><empty/>";
GuiInterface.prototype.Init = function()
{
// TODO: need to not serialise this value

View File

@ -1,24 +1,31 @@
function Health() {}
Health.prototype.Schema =
"<element name='Max'>" +
"<a:help>Deals with hitpoints and death.</a:help>" +
"<a:example>" +
"<Max>100</Max>" +
"<RegenRate>1.0</RegenRate>" +
"<DeathType>corpse</DeathType>" +
"</a:example>" +
"<element name='Max' a:help='Maximum hitpoints'>" +
"<data type='positiveInteger'/>" +
"</element>" +
"<optional>" +
"<element name='Initial'>" +
"<element name='Initial' a:help='Initial hitpoints. Default if unspecified is equal to Max'>" +
"<data type='positiveInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='RegenRate'>" +
"<element name='RegenRate' a:help='Hitpoint regeneration rate per second. Not yet implemented'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='DeathType'>" +
"<value>corpse</value>" +
"</element>" +
"</optional>";
"<element name='DeathType' a:help='Graphical behaviour when the unit dies'>" +
"<choice>" +
"<value a:help='Disappear instantly'>vanish</value>" +
"<value a:help='Turn into a corpse'>corpse</value>" +
"</choice>" +
"</element>";
Health.prototype.Init = function()
{

View File

@ -1,23 +1,44 @@
function Identity() {}
Identity.prototype.Schema =
"<element name='Civ'>" +
"<text/>" + // TODO: <choice>?
"<a:help>Specifies various names and values associated with the unit type, typically for GUI display to users.</a:help>" +
"<a:example>" +
"<Civ>hele</Civ>" +
"<GenericName>Infantry Spearman</GenericName>" +
"<SpecificName>Hoplite</SpecificName>" +
"<IconCell>3</IconCell>" +
"<IconSheet>PortraitSheet</IconSheet>" +
"</a:example>" +
"<element name='Civ' a:help='Civilisation that this unit is primarily associated with'>" +
"<choice>" +
"<value a:help='Gaia (world objects)'>gaia</value>" +
"<value a:help='Carthaginians'>cart</value>" +
"<value a:help='Celts'>celt</value>" +
"<value a:help='Hellenes'>hele</value>" +
"<value a:help='Iberians'>iber</value>" +
"<value a:help='Persians'>pers</value>" +
"<value a:help='Romans'>rome</value>" +
"</choice>" +
"</element>" +
"<element name='GenericName'>" +
"<element name='GenericName' a:help='Generic English-language name for this class of unit'>" +
"<text/>" +
"</element>" +
"<optional>" +
"<element name='SpecificName'>" +
"<element name='SpecificName' a:help='Specific native-language name for this unit type'>" +
"<text/>" +
"</element>" +
"</optional>" +
"<element name='IconCell'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"<element name='IconSheet'>" +
"<text/>" +
"</element>";
"<optional>" +
"<element name='IconCell'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='IconSheet'>" +
"<text/>" +
"</element>" +
"</optional>";
Identity.prototype.Init = function()
{

View File

@ -1,5 +1,8 @@
function MotionBallScripted() {}
MotionBallScripted.prototype.Schema =
"<a:component type='test'/><empty/>";
MotionBallScripted.prototype.Init = function() {
this.speedX = 0;
this.speedZ = 0;

View File

@ -1,5 +1,8 @@
function Player() {}
Player.prototype.Schema =
"<a:component type='system'/><empty/>";
Player.prototype.Init = function()
{
this.playerID = undefined;

View File

@ -1,5 +1,8 @@
function PlayerManager() {}
PlayerManager.prototype.Schema =
"<a:component type='system'/><empty/>";
PlayerManager.prototype.Init = function()
{
this.playerEntities = []; // list of player entity IDs

View File

@ -1,21 +1,31 @@
function ResourceGatherer() {}
ResourceGatherer.prototype.Schema =
"<element name='Rates'>" +
"<interleave>" +
"<optional><element name='food'><data type='decimal'/></element></optional>" +
"<optional><element name='wood'><data type='decimal'/></element></optional>" +
"<optional><element name='stone'><data type='decimal'/></element></optional>" +
"<optional><element name='metal'><data type='decimal'/></element></optional>" +
"<optional><element name='food.fish'><data type='decimal'/></element></optional>" +
"<optional><element name='food.fruit'><data type='decimal'/></element></optional>" +
"<optional><element name='food.grain'><data type='decimal'/></element></optional>" +
"<optional><element name='food.meat'><data type='decimal'/></element></optional>" +
"<optional><element name='food.milk'><data type='decimal'/></element></optional>" +
"</interleave>" +
"<a:help>Lets the unit gather resources from entities that have the ResourceSupply component.</a:help>" +
"<a:example>" +
"<BaseSpeed>1.0</BaseSpeed>" +
"<Rates>" +
"<food.fish>1</food.fish>" +
"<metal>3</metal>" +
"<stone>3</stone>" +
"<wood>2</wood>" +
"</Rates>" +
"</a:example>" +
"<element name='BaseSpeed' a:help='Base resource-gathering rate (in resource units per second)'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='BaseSpeed'>" +
"<data type='positiveInteger'/>" +
"<element name='Rates' a:help='Per-resource-type gather rate multipliers. If a resource type is not specified then it cannot be gathered by this unit'>" +
"<interleave>" +
"<optional><element name='food' a:help='Food gather rate (may be overridden by \"food.*\" subtypes)'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='wood' a:help='Wood gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='stone' a:help='Stone gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='metal' a:help='Metal gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.fish' a:help='Fish gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.fruit' a:help='Fruit gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.grain' a:help='Grain gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.meat' a:help='Meat gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.milk' a:help='Milk gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"</interleave>" +
"</element>";
ResourceGatherer.prototype.Init = function()

View File

@ -1,34 +1,26 @@
function ResourceSupply() {}
ResourceSupply.prototype.Schema =
"<element name='Amount'>" +
"<a:help>Provides a supply of one particular type of resource.</a:help>" +
"<a:example>" +
"<Amount>1000</Amount>" +
"<Type>food.meat</Type>" +
"</a:example>" +
"<element name='Amount' a:help='Amount of resources available from this entity'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"<choice>" +
"<interleave>" +
"<element name='Type'><value>food</value></element>" +
"<element name='Subtype'><value>fish</value></element>" +
"</interleave>" +
"<interleave>" +
"<element name='Type'><value>food</value></element>" +
"<element name='Subtype'><value>fruit</value></element>" +
"</interleave>" +
"<interleave>" +
"<element name='Type'><value>food</value></element>" +
"<element name='Subtype'><value>grain</value></element>" +
"</interleave>" +
"<interleave>" +
"<element name='Type'><value>food</value></element>" +
"<element name='Subtype'><value>meat</value></element>" +
"</interleave>" +
"<interleave>" +
"<element name='Type'><value>food</value></element>" +
"<element name='Subtype'><value>milk</value></element>" +
"</interleave>" +
"<element name='Type'><value>wood</value></element>" +
"<element name='Type'><value>stone</value></element>" +
"<element name='Type'><value>metal</value></element>" +
"</choice>";
"<element name='Type' a:help='Type of resources'>" +
"<choice>" +
"<value>wood</value>" +
"<value>stone</value>" +
"<value>metal</value>" +
"<value>food.fish</value>" +
"<value>food.fruit</value>" +
"<value>food.grain</value>" +
"<value>food.meat</value>" +
"<value>food.milk</value>" +
"</choice>" +
"</element>";
ResourceSupply.prototype.Init = function()
{
@ -54,7 +46,7 @@ ResourceSupply.prototype.TakeResources = function(rate)
// difference between rounded values:
var old = this.amount;
this.amount = Math.max(0, old - rate/1000);
this.amount = Math.max(0, old - rate);
var change = Math.ceil(old) - Math.ceil(this.amount);
// (use ceil instead of floor so that we continue returning non-zero values even if
// 0 < amount < 1)
@ -63,10 +55,15 @@ ResourceSupply.prototype.TakeResources = function(rate)
ResourceSupply.prototype.GetType = function()
{
if (this.template.Subtype)
return { "generic": this.template.Type, "specific": this.template.Subtype };
else
if (this.template.Type.indexOf('.') == -1)
{
return { "generic": this.template.Type };
}
else
{
var [type, subtype] = this.template.Type.split('.');
return { "generic": type, "specific": subtype };
}
};
Engine.RegisterComponentType(IID_ResourceSupply, "ResourceSupply", ResourceSupply);

View File

@ -1,8 +1,17 @@
function Sound() {}
Sound.prototype.Schema =
"<a:help>Lists the sound groups associated with this unit.</a:help>" +
"<a:example>" +
"<SoundGroups>" +
"<walk>actor/human/movement/walk.xml</walk>" +
"<run>actor/human/movement/walk.xml</run>" +
"<attack>attack/weapon/sword.xml</attack>" +
"<death>actor/human/death/death.xml</death>" +
"</SoundGroups>" +
"</a:example>" +
"<element name='SoundGroups'>" +
"<zeroOrMore>" +
"<zeroOrMore>" + /* TODO: make this more specific, like a list of specific elements */
"<element>" +
"<anyName/>" +
"<text/>" +

View File

@ -1,5 +1,8 @@
function Timer() {}
Timer.prototype.Schema =
"<a:component type='system'/><empty/>";
Timer.prototype.Init = function()
{
this.id = 0;

View File

@ -3,8 +3,13 @@ var g_ProgressInterval = 1000;
function TrainingQueue() {}
TrainingQueue.prototype.Schema =
"<element name='Entities'>" +
"<attribute name='datatype'><value>tokens</value></attribute>" +
"<a:help>Allows the building to train new units.</a:help>" +
"<a:example>" +
"<Entities>" +
"\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"</Entities>" +
"</a:example>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this building can train. The special string \"{civ}\" will be automatically replaced by the building&apos;s four-character civ code'>" +
"<text/>" +
"</element>";
@ -28,7 +33,7 @@ TrainingQueue.prototype.Init = function()
TrainingQueue.prototype.GetEntitiesList = function()
{
var string = this.template.Entities._string;
var string = this.template.Entities;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);

View File

@ -34,6 +34,11 @@ const STATE_GATHERING = 4;
function UnitAI() {}
UnitAI.prototype.Schema =
"<a:help>Controls the unit's movement, attacks, etc, in response to commands from the player.</a:help>" +
"<a:example/>" +
"<empty/>";
UnitAI.prototype.Init = function()
{
this.state = STATE_IDLE;

View File

@ -41,6 +41,11 @@ public:
std::vector<Command> m_CmdQueue;
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;

View File

@ -44,16 +44,32 @@ public:
static std::string GetSchema()
{
return
"<a:help>Approximation of the entity's shape, for collision detection and outline rendering. "
"Shapes are flat horizontal squares or circles, extended vertically to a given height.</a:help>"
"<a:example>"
"<Square width='3.0' height='3.0'/>"
"<Height>0.0</Height>"
"</a:example>"
"<a:example>"
"<Circle radius='0.5'/>"
"<Height>0.0</Height>"
"</a:example>"
"<choice>"
"<element name='Square'>"
"<attribute name='width'><ref name='positiveDecimal'/></attribute>"
"<attribute name='depth'><ref name='positiveDecimal'/></attribute>"
"<element name='Square' a:help='Set the footprint to a square of the given size'>"
"<attribute name='width' a:help='Size of the footprint along the left/right direction (in metres)'>"
"<ref name='positiveDecimal'/>"
"</attribute>"
"<attribute name='depth' a:help='Size of the footprint along the front/back direction (in metres)'>"
"<ref name='positiveDecimal'/>"
"</attribute>"
"</element>"
"<element name='Circle'>"
"<attribute name='radius'><ref name='positiveDecimal'/></attribute>"
"<element name='Circle' a:help='Set the footprint to a circle of the given size'>"
"<attribute name='radius' a:help='Radius of the footprint (in metres)'>"
"<ref name='positiveDecimal'/>"
"</attribute>"
"</element>"
"</choice>"
"<element name='Height'>"
"<element name='Height' a:help='Vertical extent of the footprint (in metres)'>"
"<ref name='nonNegativeDecimal'/>"
"</element>";
}

View File

@ -38,6 +38,11 @@ public:
// Current speed in metres per second
float m_SpeedX, m_SpeedZ;
static std::string GetSchema()
{
return "<a:component type='test'/><ref name='anything'/>";
}
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
{
m_SpeedX = 0;

View File

@ -49,10 +49,15 @@ public:
static std::string GetSchema()
{
return
"<a:example/>"
"<a:help>Causes this entity's footprint to obstruct the motion of other units.</a:help>"
"<optional>"
"<element name='Inactive'><empty/></element>"
"<element name='Inactive' a:help='If this element is present, this entity will be ignored in collision tests by other units but can still perform its own collision tests'>"
"<empty/>"
"</element>"
"</optional>";
}
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{
m_Context = &context;

View File

@ -78,6 +78,11 @@ public:
u32 m_CircleNext; // next allocated id
u32 m_SquareNext;
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_DebugOverlayEnabled = false;

View File

@ -39,6 +39,14 @@ public:
int32_t m_Owner;
static std::string GetSchema()
{
return
"<a:example/>"
"<a:help>Allows this entity to be owned by players.</a:help>"
"<empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;

View File

@ -84,6 +84,11 @@ public:
Path* m_DebugPath;
PathfinderOverlay* m_DebugOverlay;
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;

View File

@ -78,6 +78,12 @@ public:
static std::string GetSchema()
{
return
"<a:help>Allows this entity to exist at a location (and orientation) in the world, and defines some details of the positioning.</a:help>"
"<a:example>"
"<Anchor>upright</Anchor>"
"<Altitude>0.0</Altitude>"
"<Floating>false</Floating>"
"</a:example>"
"<element name='Anchor' a:help='Automatic rotation to follow the slope of terrain'>"
"<choice>"
"<value a:help='Always stand straight up'>upright</value>"

View File

@ -45,6 +45,11 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;

View File

@ -48,6 +48,14 @@ public:
m_Overlay.m_Color = CColor(0, 0, 0, 0);
}
static std::string GetSchema()
{
return
"<a:help>Allows this entity to be selected by the player.</a:help>"
"<a:example/>"
"<empty/>";
}
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
{
}

View File

@ -39,6 +39,11 @@ public:
std::map<std::wstring, CSoundGroup*> m_SoundGroups;
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Context = &context;

View File

@ -41,6 +41,11 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(TemplateManager)
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Validator.LoadGrammar(context.GetComponentManager().GenerateSchema());
@ -380,6 +385,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
// (but can still be used for testing this entity for collisions against others)
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "<Entity><Obstruction><Inactive/></Obstruction></Entity>");
}

View File

@ -33,6 +33,11 @@ public:
CTerrain* m_Terrain; // not null
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode))
{
m_Terrain = &context.GetTerrain();

View File

@ -38,7 +38,7 @@ public:
static std::string GetSchema()
{
return "<ref name='anything'/>";
return "<a:component type='test'/><ref name='anything'/>";
}
virtual void Init(const CSimContext&, const CParamNode& paramNode)
@ -100,6 +100,11 @@ public:
int32_t m_x;
static std::string GetSchema()
{
return "<a:component type='test'/><empty/>";
}
virtual void Init(const CSimContext&, const CParamNode&)
{
m_x = 12000;
@ -156,6 +161,11 @@ public:
int32_t m_x;
static std::string GetSchema()
{
return "<a:component type='test'/><empty/>";
}
virtual void Init(const CSimContext&, const CParamNode&)
{
m_x = 21000;

View File

@ -59,10 +59,15 @@ public:
static std::string GetSchema()
{
return
"<element name='WalkSpeed'>"
"<a:help>Provides the unit with the ability to move around the world by itself.</a:help>"
"<a:example>"
"<WalkSpeed>7.0</WalkSpeed>"
"</a:example>"
"<element name='WalkSpeed' a:help='Basic movement speed (in metres per second)'>"
"<ref name='positiveDecimal'/>"
"</element>";
}
virtual void Init(const CSimContext& context, const CParamNode& paramNode)
{
m_Context = &context;

View File

@ -63,13 +63,27 @@ public:
static std::string GetSchema()
{
return
"<a:help>Display the unit using the engine's actor system.</a:help>"
"<a:example>"
"<Actor>units/hellenes/infantry_spearman_b.xml</Actor>"
"</a:example>"
"<a:example>"
"<Actor>structures/hellenes/barracks.xml</Actor>"
"<FoundationActor>structures/fndn_4x4.xml</FoundationActor>"
"</a:example>"
"<element name='Actor' a:help='Filename of the actor to be used for this unit'>"
"<text/>"
"</element>"
"<optional>"
"<element name='Foundation'><empty/></element>"
"<element name='FoundationActor' a:help='Filename of the actor to be used the foundation while this unit is being constructed'>"
"<text/>"
"</element>"
"</optional>"
"<optional>"
"<element name='FoundationActor'><text/></element>"
"</optional>"
"<element name='Actor'><text/></element>";
"<element name='Foundation' a:help='Used internally; if present the unit will be rendered as a foundation'>"
"<empty/>"
"</element>"
"</optional>";
}
virtual void Init(const CSimContext& context, const CParamNode& paramNode)

View File

@ -56,6 +56,10 @@
delete static_cast<CCmp##cname*> (cmp); \
} \
CCmp##cname(ScriptInterface& scriptInterface, jsval instance) : m_Script(scriptInterface, instance) { } \
static std::string GetSchema() \
{ \
return "<a:component type='script-wrapper'/><empty/>"; \
} \
virtual void Init(const CSimContext& context, const CParamNode& paramNode) \
{ \
m_Script.Init(context, paramNode, GetEntityId()); \

View File

@ -754,6 +754,8 @@ std::string CComponentManager::GenerateSchema()
std::map<InterfaceId, std::vector<std::string> > interfaceComponentTypes;
std::vector<std::string> componentTypes;
for (std::map<ComponentTypeId, ComponentType>::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it)
{
schema +=
@ -764,8 +766,10 @@ std::string CComponentManager::GenerateSchema()
"</define>";
interfaceComponentTypes[it->second.iid].push_back(it->second.name);
componentTypes.push_back(it->second.name);
}
// Declare the implementation of each interface, for documentation
for (std::map<std::string, InterfaceId>::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it)
{
schema += "<define name='interface." + it->first + "'><choice>";
@ -775,15 +779,18 @@ std::string CComponentManager::GenerateSchema()
schema += "</choice></define>";
}
// List all the component types, in alphabetical order (to match the reordering performed by CParamNode).
// (We do it this way, rather than <interleave>ing all the interface definitions (which would additionally perform
// a check that we don't use multiple component types of the same interface in one file), because libxml2 gives
// useless error messages in the latter case; this way lets it report the real error.)
std::sort(componentTypes.begin(), componentTypes.end());
schema +=
"<start>"
"<element name='Entity'>"
"<optional><attribute name='parent'/></optional>"
"<interleave>";
for (std::map<std::string, InterfaceId>::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it)
schema += "<optional><ref name='interface." + it->first + "'/></optional>";
"<optional><attribute name='parent'/></optional>";
for (std::vector<std::string>::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it)
schema += "<optional><ref name='component." + *it + "'/></optional>";
schema +=
"</interleave>"
"</element>"
"</start>";

View File

@ -0,0 +1,2 @@
#!/bin/sh
xsltproc convertrng.xsl ../../../binaries/system/entity.rng >entity-docs.html

View File

@ -0,0 +1,294 @@
<?xml version="1.0"?>
<xsl:stylesheet
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rng="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://ns.wildfiregames.com/entity"
exclude-result-prefixes="rng a"
version="1.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="defname" match="rng:define" use="@name"/>
<xsl:template match="/">
<xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;&#10;</xsl:text>
<html>
<head>
<meta charset="utf-8"/>
<title>0 A.D. entity XML documentation</title>
<link rel="stylesheet" href="entity-docs.css"/>
</head>
<body>
<h1>Entity component documentation</h1>
<p>
In <a href="http://www.wildfiregames.com/0ad/">0 A.D.</a>,
entities (units and buildings and other world objects)
consist of a collection of components, each of which determines
part of the entity's behaviour.
Entity template XML files specify the list of components that are loaded for
each entity type, plus initialisation data for the components.
</p>
<p>
This page lists the components that can be added to entities
and the XML syntax for their initialisation data.
</p>
<p>
Available components:
</p>
<xsl:apply-templates mode="components-toc"/>
<input type="checkbox" id="show-grammar"/> <i>Display RELAX NG grammar fragments</i>
<xsl:apply-templates mode="components"/>
</body>
</html>
</xsl:template>
<!-- List all the interfaces in alphabetic order -->
<xsl:template match="rng:grammar" mode="components-toc">
<ul>
<xsl:apply-templates select="rng:define[starts-with(@name, 'interface.')]" mode="components-toc">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="rng:define" mode="components-toc">
<xsl:choose>
<xsl:when test="count(rng:choice/rng:ref[not(key('defname', @name)//a:component/@type)]) &lt;= 1">
<xsl:apply-templates mode="components-toc"/>
</xsl:when>
<xsl:otherwise> <!-- multiple implementations of the same interface: -->
<li>
<xsl:value-of select="substring-after(@name, 'interface.')"/>
<ul>
<xsl:apply-templates mode="components-toc"/>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- List components that are not special types -->
<xsl:template match="rng:ref[not(key('defname', @name)//a:component/@type)]" mode="components-toc">
<li>
<a>
<xsl:attribute name="href">
<xsl:text>#component.</xsl:text>
<xsl:value-of select="substring-after(@name, 'component.')"/>
</xsl:attribute>
<xsl:value-of select="substring-after(@name, 'component.')"/>
</a>
<xsl:value-of select="key('defname', @name)//a:component/@type"/>
</li>
</xsl:template>
<!-- List all the components in alphabetic order, excluding ones that are special types -->
<xsl:template match="rng:grammar" mode="components">
<xsl:apply-templates select="rng:define[starts-with(@name, 'component.') and not(key('defname', @name)//a:component/@type)]" mode="components">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</xsl:template>
<!-- Component definition -->
<xsl:template match="rng:define" mode="components">
<xsl:variable name="name" select="substring-after(@name, 'component.')"/>
<section>
<xsl:attribute name="id"><xsl:value-of select="@name"/></xsl:attribute>
<h2><xsl:value-of select="$name"/></h2>
<xsl:if test=".//a:help">
<p><xsl:value-of select=".//a:help"/></p>
</xsl:if>
<xsl:if test="count(.//a:example) = 1"><h3>Example</h3></xsl:if>
<xsl:if test="count(.//a:example) > 1"><h3>Examples</h3></xsl:if>
<xsl:for-each select=".//a:example">
<xsl:call-template name="example">
<xsl:with-param name="name" select="$name"/>
</xsl:call-template>
</xsl:for-each>
<xsl:apply-templates mode="components"/>
<div class="grammar-box">
<h3>RELAX NG Grammar</h3>
<pre class="grammar">
<xsl:apply-templates mode="literal"/>
</pre>
</div>
</section>
</xsl:template>
<!-- Component XML example -->
<xsl:template match="*" name="example">
<xsl:param name="name"/>
<pre class="example">
<xsl:choose>
<xsl:when test="count(*) = 0">
<xsl:text>&lt;</xsl:text>
<span class="n"><xsl:value-of select="$name"/></span>
<xsl:text>/&gt;&#10;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>&lt;</xsl:text>
<span class="n"><xsl:value-of select="$name"/></span>
<xsl:text>&gt;&#10;</xsl:text>
<xsl:apply-templates select="*" mode="literal">
<xsl:with-param name="depth" select="1"/>
</xsl:apply-templates>
<xsl:text>&lt;/</xsl:text>
<span class="n"><xsl:value-of select="$name"/></span>
<xsl:text>&gt;&#10;</xsl:text>
</xsl:otherwise>
</xsl:choose>
</pre>
</xsl:template>
<!-- List each of the component's elements -->
<xsl:template match="rng:element" mode="components">
<xsl:apply-templates mode="component"/>
</xsl:template>
<!-- Component element description -->
<xsl:template match="rng:element[@name]" mode="component">
<h3><code><xsl:value-of select="@name"/></code></h3>
<xsl:if test="parent::rng:optional">
<p><em>Optional.</em></p>
</xsl:if>
<xsl:if test="@a:help">
<p><xsl:value-of select="@a:help"/>.</p>
</xsl:if>
<xsl:apply-templates mode="datatype"/>
<xsl:apply-templates mode="component"/>
</xsl:template>
<xsl:template match="rng:attribute" mode="component">
<h3><code><xsl:value-of select="@name"/></code></h3>
<xsl:if test="@a:help">
<p><xsl:value-of select="@a:help"/>.</p>
</xsl:if>
<xsl:apply-templates mode="datatype"/>
</xsl:template>
<xsl:template match="text()" mode="component"/> <!-- ignore text inside the grammar -->
<!-- Datatype description when all children are <value> -->
<xsl:template match="rng:choice[count(./*[not(local-name() = 'value')]) = 0]" mode="datatype">
<p><em>Value is one of:</em></p>
<dl>
<xsl:apply-templates mode="choice-values"/>
</dl>
</xsl:template>
<xsl:template match="rng:value" mode="choice-values">
<dt><code><xsl:value-of select="text()"/></code></dt>
<xsl:if test="@a:help">
<dd><xsl:value-of select="@a:help"/>.</dd>
</xsl:if>
</xsl:template>
<!-- Datatype description for fixed string -->
<xsl:template match="rng:value" mode="datatype">
<p><em>Required value:</em>
<xsl:text> </xsl:text>
<code><xsl:value-of select="text()"/></code></p>
</xsl:template>
<!-- Datatype description for text -->
<xsl:template match="rng:text" mode="datatype">
<p><em>Value type:</em> text.</p>
</xsl:template>
<!-- Datatype description for <data> -->
<xsl:template match="rng:data[@type]" mode="datatype">
<p><em>Value type:</em>
<xsl:text> </xsl:text>
<xsl:apply-templates select="@type" mode="datatype-name"/>.</p>
</xsl:template>
<!-- Datatype description for <ref> -->
<xsl:template match="rng:ref" mode="datatype">
<p><em>Value type:</em>
<xsl:text> </xsl:text>
<xsl:apply-templates select="@name" mode="datatype-name"/>.</p>
</xsl:template>
<xsl:template match="*" mode="datatype"/> <!-- don't recurse -->
<!-- Datatype names -->
<xsl:template match="@*" mode="datatype-name">
<xsl:choose>
<xsl:when test=". = 'nonNegativeDecimal'">non-negative decimal (e.g. <code>0.0</code> or <code>2.5</code>)</xsl:when>
<xsl:when test=". = 'positiveDecimal'">positive decimal (e.g. <code>1.0</code> or <code>2.5</code>)</xsl:when>
<xsl:when test=". = 'decimal'">decimal (e.g. <code>-10.0</code> or <code>0.0</code> or <code>2.5</code>)</xsl:when>
<xsl:when test=". = 'nonNegativeInteger'">non-negative integer (e.g. <code>0</code> or <code>5</code>)</xsl:when>
<xsl:when test=". = 'positiveInteger'">positive integer (e.g. <code>1</code> or <code>5</code>)</xsl:when>
<xsl:when test=". = 'boolean'">boolean (<code>true</code> or <code>false</code>)</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Templates to output the input grammar as a pretty-printed string: -->
<xsl:template match="node()" mode="literal">
<xsl:param name="depth">0</xsl:param>
<xsl:call-template name="indent"><xsl:with-param name="depth" select="$depth"/></xsl:call-template>
<xsl:text>&lt;</xsl:text>
<span class="n"><xsl:value-of select="name()"/></span>
<xsl:apply-templates select="@*" mode="literal"/>
<xsl:if test="count(*|text()) = 0">
<xsl:text>/&gt;&#10;</xsl:text>
</xsl:if>
<xsl:if test="count(*|text()) > 0">
<xsl:text>&gt;</xsl:text>
<xsl:if test="count(*) > 0">
<xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:apply-templates select="node()" mode="literal">
<xsl:with-param name="depth" select="$depth + 1"/>
</xsl:apply-templates>
<xsl:if test="count(*) > 0">
<xsl:call-template name="indent"><xsl:with-param name="depth" select="$depth"/></xsl:call-template>
</xsl:if>
<xsl:text>&lt;/</xsl:text>
<span class="n"><xsl:value-of select="name()"/></span>
<xsl:text>&gt;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="@*" mode="literal">
<xsl:text> </xsl:text>
<span class="n"><xsl:value-of select="name()"/></span>
<xsl:text>="</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
</xsl:template>
<xsl:template match="a:*|@a:*" mode="literal"/>
<xsl:template match="text()" mode="literal">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template name="indent">
<xsl:param name="depth"/>
<xsl:if test="$depth > 0">
<xsl:text> </xsl:text>
<xsl:call-template name="indent">
<xsl:with-param name="depth" select="$depth - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,24 @@
body { font-family: sans-serif; font-size: 10pt; }
section { display: block; padding-bottom: 1em; border-top: 2px #aaa solid; }
p, dl, pre { margin-top: 0.2em; margin-bottom: 0.2em; }
h1, h2, h3 { margin-left: 0; }
h2, h3 { margin-top: 0.4em; margin-bottom: 0.2em; }
h1 { font-size: 2em; }
h2 { font-size: 1.8em; }
h3 { font-size: 1.2em; }
p, dl { margin-left: 18px; }
dt { font-weight: bold; }
dd { margin-left: 1em; }
pre.grammar, pre.example { font-size: 8pt; margin: 1em 2em 1em 2em; padding: 0.5em; }
pre.example { background-color: #ffd; }
pre.grammar { background-color: #f8f8f8; }
pre.example span.n { font-weight: bold; }
code { color: orangered; }
#show-grammar { margin-bottom: 1em; }
div.grammar-box { display: none; }
#show-grammar:checked ~ * div.grammar-box { display: inherit; }

View File

@ -148,17 +148,22 @@ sub check_all
find({ wanted => $find_process }, "$vfsroot/public/simulation/templates");
find({ wanted => $find_process }, "$vfsroot/internal/simulation/templates") if -e "$vfsroot/internal";
my $count = 0;
my $failed = 0;
for my $f (sort @files) {
next if $f =~ /^template_/;
print "# $f...\n";
++$count;
eval {
validate($f);
};
if ($@) {
++$failed;
print $@;
eval { print to_xml(load_inherited($f)), "\n"; }
}
}
print "\nTotal: $count; failed: $failed\n";
}
check_all();