From 987a7028bdbe6a17cfefdab07dea35e186e6dbb9 Mon Sep 17 00:00:00 2001 From: scythetwirler Date: Sat, 5 Dec 2015 17:02:25 +0000 Subject: [PATCH] Implements relative templates. Fixes #2936. Thanks to leper, wraitii, historicbruno and everyone else that helped. This was SVN commit r17386. --- .../simulation2/components/CCmpFootprint.cpp | 12 +++- .../components/CCmpVisualActor.cpp | 20 ++++-- source/simulation2/docs/SimulationDocs.h | 29 +++++++- .../simulation2/system/ComponentManager.cpp | 15 ++++ source/simulation2/system/ParamNode.cpp | 34 ++++++++- source/tools/entity/Entity.pm | 13 ++++ source/tools/templatesanalyzer/unitTables.py | 71 ++++++++++++++----- 7 files changed, 165 insertions(+), 29 deletions(-) diff --git a/source/simulation2/components/CCmpFootprint.cpp b/source/simulation2/components/CCmpFootprint.cpp index 3c99f60368..67cdd9ae76 100644 --- a/source/simulation2/components/CCmpFootprint.cpp +++ b/source/simulation2/components/CCmpFootprint.cpp @@ -59,15 +59,21 @@ public: "" "" "" - "" + "" + "0.0" + "" "" "" - "" + "" + "0.0" + "" "" "" "" "" - "" + "" + "0.0" + "" "" "" "" diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 0beb2d0d36..b1e3ac3c80 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -146,21 +146,31 @@ public: "" "" "" - "" + "" + "0.0" + "" "" "" - "" + "" + "0.0" + "" "" "" - "" + "" + "0.0" + "" "" "" "" "" - "" + "" + "0.0" + "" "" "" - "" + "" + "0.0" + "" "" "" "" diff --git a/source/simulation2/docs/SimulationDocs.h b/source/simulation2/docs/SimulationDocs.h index dd64f8b56e..8066e687ec 100644 --- a/source/simulation2/docs/SimulationDocs.h +++ b/source/simulation2/docs/SimulationDocs.h @@ -298,12 +298,39 @@ The content will typically be one of: - <text/> - <data type='boolean'/> - <data type='decimal'/> +- <data type='integer'/> - <data type='nonNegativeInteger'/> - <data type='positiveInteger'/> +- <ref name='decimal'/> - <ref name='nonNegativeDecimal'/> - <ref name='positiveDecimal'/> -(The last two are slightly different since they're not standard data types.) + +The <data> elements are native elements, while the <ref> 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 + + + Semi-Humanoids + 9000 + + + + +@endcode +and the child template appears like +@code + + + Barney + 5 + + + + +@endcode +then Barney would have a height of 9005. Elements can be wrapped in <optional>. Groups of elements can be wrapped in <choice> to allow only one of them. diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index 52a5203109..fb8e0383e4 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -1092,13 +1092,28 @@ void CComponentManager::SendGlobalMessage(entity_id_t ent, const CMessage& msg) std::string CComponentManager::GenerateSchema() { + std::string numericOperation = + "" + "" + "" + "add" + "mul" + "" + "" + ""; std::string schema = "" + "" + "" + + numericOperation + + "" "" "0" + + numericOperation + "" "" "0" + + numericOperation + "" "" "" diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp index 53a661a2a4..c2fc414b6f 100644 --- a/source/simulation2/system/ParamNode.cpp +++ b/source/simulation2/system/ParamNode.cpp @@ -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(); diff --git a/source/tools/entity/Entity.pm b/source/tools/entity/Entity.pm index bf4e66100a..5ac708f9bb 100644 --- a/source/tools/entity/Entity.pm +++ b/source/tools/entity/Entity.pm @@ -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'}; } diff --git a/source/tools/templatesanalyzer/unitTables.py b/source/tools/templatesanalyzer/unitTables.py index 916d616f5f..e7ab7fa59b 100644 --- a/source/tools/templatesanalyzer/unitTables.py +++ b/source/tools/templatesanalyzer/unitTables.py @@ -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 += " - " ret += " - " - if UnitDict["Ranged"] == True: + if UnitDict["Ranged"] == True and UnitDict["Range"] > 0: ret += "" + str("%.1f" % float(UnitDict["Range"])) + "" spread = (float(UnitDict["Spread"]) / float(UnitDict["Range"]))*100.0 ret += "" + str("%.1f" % spread) + ""