1
0
forked from mirrors/0ad

Implements relative templates. Fixes #2936. Thanks to leper, wraitii, historicbruno and everyone else that helped.

This was SVN commit r17386.
This commit is contained in:
scythetwirler
2015-12-05 17:02:25 +00:00
parent 8813cb3133
commit 987a7028bd
7 changed files with 165 additions and 29 deletions
@@ -59,15 +59,21 @@ public:
"<choice>"
"<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'/>"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"<attribute name='depth' a:help='Size of the footprint along the front/back direction (in metres)'>"
"<ref name='positiveDecimal'/>"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"</element>"
"<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'/>"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"</element>"
"</choice>"
@@ -146,21 +146,31 @@ public:
"</element>"
"<element name='Box' a:help='Sets the selection shape to a box of specified dimensions'>"
"<attribute name='width'>"
"<ref name='positiveDecimal' />"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"<attribute name='height'>"
"<ref name='positiveDecimal' />"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"<attribute name='depth'>"
"<ref name='positiveDecimal' />"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"</element>"
"<element name='Cylinder' a:help='Sets the selection shape to a cylinder of specified dimensions'>"
"<attribute name='radius'>"
"<ref name='positiveDecimal' />"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"<attribute name='height'>"
"<ref name='positiveDecimal' />"
"<data type='decimal'>"
"<param name='minExclusive'>0.0</param>"
"</data>"
"</attribute>"
"</element>"
"</choice>"
+28 -1
View File
@@ -298,12 +298,39 @@ The content will typically be one of:
- <code>&lt;text/></code>
- <code>&lt;data type='boolean'/></code>
- <code>&lt;data type='decimal'/></code>
- <code>&lt;data type='integer'/></code>
- <code>&lt;data type='nonNegativeInteger'/></code>
- <code>&lt;data type='positiveInteger'/></code>
- <code>&lt;ref name='decimal'/></code>
- <code>&lt;ref name='nonNegativeDecimal'/></code>
- <code>&lt;ref name='positiveDecimal'/></code>
(The last two are slightly different since they're not standard data types.)
The <code>&lt;data&gt;</code> elements are native elements, while the <code>&lt;ref&gt;</code> elements are elements added for our engine. These non-native elements allow the definition of an operation that depends on the parent template. Possible operations are "add" and "mul", and can be applied as the example below.
Say the parent template is
@code
<Entity>
<Example>
<Name>Semi-Humanoids</Name>
<Height>9000</Height>
<Eyes/>
</Example>
<!-- ... other components ... -->
</Entity>
@endcode
and the child template appears like
@code
<Entity>
<Example>
<Name>Barney</Name>
<Height op="add">5</Height>
<Eyes/>
</Example>
<!-- ... other components ... -->
</Entity>
@endcode
then Barney would have a height of 9005.
Elements can be wrapped in <code>&lt;optional></code>.
Groups of elements can be wrapped in <code>&lt;choice></code> to allow only one of them.
@@ -1092,13 +1092,28 @@ void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg)
std::string CComponentManager::GenerateSchema()
{
std::string numericOperation =
"<optional>"
"<attribute name='op'>"
"<choice>"
"<value>add</value>"
"<value>mul</value>"
"</choice>"
"</attribute>"
"</optional>";
std::string schema =
"<grammar xmlns='http://relaxng.org/ns/structure/1.0' xmlns:a='http://ns.wildfiregames.com/entity' datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'>"
"<define name='decimal'>"
"<data type='decimal'/>"
+ numericOperation +
"</define>"
"<define name='nonNegativeDecimal'>"
"<data type='decimal'><param name='minInclusive'>0</param></data>"
+ numericOperation +
"</define>"
"<define name='positiveDecimal'>"
"<data type='decimal'><param name='minExclusive'>0</param></data>"
+ numericOperation +
"</define>"
"<define name='anything'>"
"<zeroOrMore>"
+33 -1
View File
@@ -76,7 +76,13 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
// Look for special attributes
int at_disable = xmb.GetAttributeID("disable");
int at_replace = xmb.GetAttributeID("replace");
int at_op = xmb.GetAttributeID("op");
int at_datatype = xmb.GetAttributeID("datatype");
enum op {
INVALID,
ADD,
MUL
} op = INVALID;
bool replacing = false;
{
XERO_ITER_ATTR(element, attr)
@@ -91,6 +97,15 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
m_Childs.erase(name);
replacing = true;
}
else if (attr.Name == at_op)
{
if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"add")
op = ADD;
else if (std::wstring(attr.Value.begin(), attr.Value.end()) == L"mul")
op = MUL;
else
LOGWARNING("Invalid op '%ls'", attr.Value);
}
}
}
{
@@ -137,6 +152,22 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
// Add this element as a child node
CParamNode& node = m_Childs[name];
if (op != INVALID)
{
// TODO: Support parsing of data types other than fixed; log warnings in other cases
fixed oldval = node.ToFixed();
fixed mod = fixed::FromString(CStrW(value));
switch (op)
{
case ADD:
node.m_Value = (oldval + mod).ToString().FromUTF8();
break;
case MUL:
node.m_Value = (oldval.Multiply(mod)).ToString().FromUTF8();
break;
}
hasSetValue = true;
}
if (!hasSetValue)
node.m_Value = value;
@@ -150,7 +181,8 @@ void CParamNode::ApplyLayer(const XMBFile& xmb, const XMBElement& element, const
XERO_ITER_ATTR(element, attr)
{
// Skip special attributes
if (attr.Name == at_replace) continue;
if (attr.Name == at_replace || attr.Name == at_op)
continue;
// Add any others
std::string attrName = xmb.GetAttributeString(attr.Name);
node.m_Childs["@" + attrName].m_Value = attr.Value.FromUTF8();
+13
View File
@@ -83,6 +83,19 @@ sub apply_layer
}
}
$base->{' content'} = join ' ', @t;
} elsif ($new->{'@op'}) {
my $op = $new->{'@op'}{' content'};
my $op1 = $base->{' content'};
my $op2 = $new->{' content'};
if ($op eq 'add') {
$base->{' content'} = $op1 + $op2;
}
elsif ($op eq 'mul') {
$base->{' content'} = $op1 * $op2;
}
else {
die "Invalid operator '$op'";
}
} else {
$base->{' content'} = $new->{' content'};
}
+52 -19
View File
@@ -1,8 +1,27 @@
# Copyright (c) 2015 Wildfire Games
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import xml.etree.ElementTree as ET
import os
import glob
# What data to use
AttackTypes = ["Hack","Pierce","Crush"]
Resources = ["food", "wood", "stone", "metal"]
@@ -17,7 +36,7 @@ LoadTemplatesIfParent = ["template_unit_infantry.xml", "template_unit_cavalry.xm
Civs = ["athen", "mace", "spart", "sele", "cart", "rome", "pers", "maur", "brit", "gaul", "iber"]
# Remote Civ templates with those strings in their name.
FilterOut = ["marian"]
FilterOut = ["marian", "thureophoros", "thorakites", "kardakes"]
# Sorting parameters for the "roster variety" table
ComparativeSortByCav = True
@@ -58,12 +77,26 @@ def hasParentTemplate(UnitName, parentName):
return False
def NumericStatProcess(unitValue, templateValue):
if not "op" in templateValue.attrib:
return float(templateValue.text)
if (templateValue.attrib["op"] == "add"):
unitValue += float(templateValue.text)
elif (templateValue.attrib["op"] == "sub"):
unitValue -= float(templateValue.text)
elif (templateValue.attrib["op"] == "mul"):
unitValue *= float(templateValue.text)
elif (templateValue.attrib["op"] == "div"):
unitValue /= float(templateValue.text)
return unitValue
# This function parses the entity values manually.
def CalcUnit(UnitName, existingUnit = None):
unit = { 'HP' : "0", "BuildTime" : "0", "Cost" : { 'food' : "0", "wood" : "0", "stone" : "0", "metal" : "0", "population" : "0"},
'Attack' : { "Melee" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 }, "Ranged" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 } },
'RepeatRate' : {"Melee" : "0", "Ranged" : "0"},'PrepRate' : {"Melee" : "0", "Ranged" : "0"}, "Armour" : {},
"Ranged" : False, "Classes" : [], "AttackBonuses" : {}, "Restricted" : [],
'RepeatRate' : {"Melee" : "0", "Ranged" : "0"},'PrepRate' : {"Melee" : "0", "Ranged" : "0"}, "Armour" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0},
"Ranged" : False, "Classes" : [], "AttackBonuses" : {}, "Restricted" : [], "WalkSpeed" : 0, "Range" : 0, "Spread" : 0,
"Civ" : None }
if (existingUnit != None):
@@ -80,26 +113,26 @@ def CalcUnit(UnitName, existingUnit = None):
unit['Civ'] = Template.find("./Identity/Civ").text
if (Template.find("./Health/Max") != None):
unit['HP'] = Template.find("./Health/Max").text
unit['HP'] = NumericStatProcess(unit['HP'], Template.find("./Health/Max"))
if (Template.find("./Cost/BuildTime") != None):
unit['BuildTime'] = Template.find("./Cost/BuildTime").text
unit['BuildTime'] = NumericStatProcess(unit['BuildTime'], Template.find("./Cost/BuildTime"))
if (Template.find("./Cost/Resources") != None):
for type in list(Template.find("./Cost/Resources")):
unit['Cost'][type.tag] = type.text
unit['Cost'][type.tag] = NumericStatProcess(unit['Cost'][type.tag], type)
if (Template.find("./Cost/Population") != None):
unit['Cost']["population"] = Template.find("./Cost/Population").text
unit['Cost']["population"] = NumericStatProcess(unit['Cost']["population"], Template.find("./Cost/Population"))
if (Template.find("./Attack/Melee") != None):
if (Template.find("./Attack/Melee/RepeatTime") != None):
unit['RepeatRate']["Melee"] = Template.find("./Attack/Melee/RepeatTime").text
unit['RepeatRate']["Melee"] = NumericStatProcess(unit['RepeatRate']["Melee"], Template.find("./Attack/Melee/RepeatTime"))
if (Template.find("./Attack/Melee/PrepareTime") != None):
unit['PrepRate']["Melee"] = Template.find("./Attack/Melee/PrepareTime").text
unit['PrepRate']["Melee"] = NumericStatProcess(unit['PrepRate']["Melee"], Template.find("./Attack/Melee/PrepareTime"))
for atttype in AttackTypes:
if (Template.find("./Attack/Melee/"+atttype) != None):
unit['Attack']['Melee'][atttype] = Template.find("./Attack/Melee/"+atttype).text
unit['Attack']['Melee'][atttype] = NumericStatProcess(unit['Attack']['Melee'][atttype], Template.find("./Attack/Melee/"+atttype))
if (Template.find("./Attack/Melee/Bonuses") != None):
for Bonus in Template.find("./Attack/Melee/Bonuses"):
Against = []
@@ -123,16 +156,16 @@ def CalcUnit(UnitName, existingUnit = None):
if (Template.find("./Attack/Ranged") != None):
unit['Ranged'] = True
if (Template.find("./Attack/Ranged/MaxRange") != None):
unit['Range'] = Template.find("./Attack/Ranged/MaxRange").text
unit['Range'] = NumericStatProcess(unit['Range'], Template.find("./Attack/Ranged/MaxRange"))
if (Template.find("./Attack/Ranged/Spread") != None):
unit['Spread'] = Template.find("./Attack/Ranged/Spread").text
unit['Spread'] = NumericStatProcess(unit['Spread'], Template.find("./Attack/Ranged/Spread"))
if (Template.find("./Attack/Ranged/RepeatTime") != None):
unit['RepeatRate']["Ranged"] = Template.find("./Attack/Ranged/RepeatTime").text
unit['RepeatRate']["Ranged"] = NumericStatProcess(unit['RepeatRate']["Ranged"], Template.find("./Attack/Ranged/RepeatTime"))
if (Template.find("./Attack/Ranged/PrepareTime") != None):
unit['PrepRate']["Ranged"] = Template.find("./Attack/Ranged/PrepareTime").text
unit['PrepRate']["Ranged"] = NumericStatProcess(unit['PrepRate']["Ranged"], Template.find("./Attack/Ranged/PrepareTime"))
for atttype in AttackTypes:
if (Template.find("./Attack/Ranged/"+atttype) != None):
unit['Attack']['Ranged'][atttype] = Template.find("./Attack/Ranged/"+atttype).text
unit['Attack']['Ranged'][atttype] = NumericStatProcess(unit['Attack']['Ranged'][atttype], Template.find("./Attack/Ranged/"+atttype))
if (Template.find("./Attack/Ranged/Bonuses") != None):
for Bonus in Template.find("./Attack/Ranged/Bonuses"):
Against = []
@@ -155,11 +188,11 @@ def CalcUnit(UnitName, existingUnit = None):
if (Template.find("./Armour") != None):
for atttype in AttackTypes:
if (Template.find("./Armour/"+atttype) != None):
unit['Armour'][atttype] = Template.find("./Armour/"+atttype).text
unit['Armour'][atttype] = NumericStatProcess(unit['Armour'][atttype], Template.find("./Armour/"+atttype))
if (Template.find("./UnitMotion") != None):
if (Template.find("./UnitMotion/WalkSpeed") != None):
unit['WalkSpeed'] = Template.find("./UnitMotion/WalkSpeed").text
unit['WalkSpeed'] = NumericStatProcess(unit['WalkSpeed'], Template.find("./UnitMotion/WalkSpeed"))
if (Template.find("./Identity/VisibleClasses") != None):
newClasses = Template.find("./Identity/VisibleClasses").text.split(" ")
@@ -209,7 +242,7 @@ def WriteUnit(Name, UnitDict):
ret += "<td> - </td>"
ret += "<td> - </td>"
if UnitDict["Ranged"] == True:
if UnitDict["Ranged"] == True and UnitDict["Range"] > 0:
ret += "<td>" + str("%.1f" % float(UnitDict["Range"])) + "</td>"
spread = (float(UnitDict["Spread"]) / float(UnitDict["Range"]))*100.0
ret += "<td>" + str("%.1f" % spread) + "</td>"