From bfd6614b558593130801112c05e2f41b2a5440bf Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Sun, 1 May 2011 20:40:53 +0000 Subject: [PATCH] # Add support for many formation shapes, based on patch from Badmadblacksad. See #13. This was SVN commit r9385. --- .../data/mods/public/gui/session/input.js | 9 +- .../mods/public/gui/session/unit_commands.js | 17 +- .../public/gui/session/utility_functions.js | 60 +-- .../public/simulation/components/Formation.js | 385 ++++++++++++++++-- .../simulation/components/GuiInterface.js | 7 + .../public/simulation/components/Identity.js | 1 + .../public/simulation/components/UnitAI.js | 11 + .../public/simulation/helpers/Commands.js | 141 +++++++ .../template_unit_hero_infantry_pikeman.xml | 2 +- .../template_unit_hero_infantry_spearman.xml | 2 +- .../template_unit_hero_infantry_swordsman.xml | 2 +- .../template_unit_infantry_melee.xml | 1 + .../template_unit_infantry_ranged.xml | 1 + ...template_unit_super_cavalry_javelinist.xml | 2 +- .../template_unit_super_cavalry_spearman.xml | 1 + .../template_unit_super_cavalry_swordsman.xml | 1 + .../template_unit_super_infantry_pikeman.xml | 1 + .../template_unit_super_infantry_spearman.xml | 1 + ...template_unit_super_infantry_swordsman.xml | 1 + 19 files changed, 578 insertions(+), 68 deletions(-) diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index c00cca1d91..783a9304c3 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -1090,11 +1090,14 @@ function performCommand(entity, commandName) // Performs the specified formation function performFormation(entity, formationName) { - submitChatDirectly("FORMATIONS are not implemented yet."); - if (entity) { - console.write(formationName); + var selection = g_Selection.toList(); + Engine.PostNetworkCommand({ + "type": "formation", + "entities": selection, + "name": formationName + }); } } diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js index b39d2b73be..ad8b4ab454 100644 --- a/binaries/data/mods/public/gui/session/unit_commands.js +++ b/binaries/data/mods/public/gui/session/unit_commands.js @@ -248,9 +248,22 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) if (guiName == "Formation") { icon.cell_id = getFormationCellId(item); - icon.enabled = false; + var formationOk = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", { + "ents": g_Selection.toList(), + "formationName": item + }); + + icon.enabled = formationOk; + button.enabled = formationOk; if (!icon.enabled) + { icon.sprite = "formation_disabled"; + button.tooltip += " (disabled)"; + } + else + { + icon.sprite = "formation"; + } } else if (guiName == "Command") { @@ -399,4 +412,4 @@ function hideUnitCommands() { for each (var panelName in g_unitPanels) getGUIObjectByName("unit" + panelName + "Panel").hidden = true; -} \ No newline at end of file +} diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js index fefdab0fed..0bbd2adef0 100644 --- a/binaries/data/mods/public/gui/session/utility_functions.js +++ b/binaries/data/mods/public/gui/session/utility_functions.js @@ -223,29 +223,29 @@ function getFormationCellId(formationName) { switch (formationName) { - case "Formation0": + case "Loose": return 0; - case "Formation1": + case "Box": return 1; - case "Formation2": + case "Column Closed": return 2; - case "Formation3": + case "Line Closed": return 3; - case "Formation4": + case "Column Open": return 4; - case "Formation5": + case "Line Open": return 5; - case "Formation6": + case "Flank": return 6; - case "Formation7": + case "Skirmish": return 7; - case "Formation8": + case "Wedge": return 8; - case "Formation9": + case "Testudo": return 9; - case "Formation10": - return 10; - case "Formation11": + case "Phalanx": + return 10; + case "Syntagma": return 11; case "Formation12": return 12; @@ -273,24 +273,28 @@ function getCommandImage(commandName) function getEntityFormationsList(entState) { - var formations = []; - - formations.push("Formation0"); - formations.push("Formation1"); - formations.push("Formation2"); - formations.push("Formation3"); - formations.push("Formation4"); - formations.push("Formation5"); - formations.push("Formation6"); - formations.push("Formation7"); - formations.push("Formation8"); - formations.push("Formation9"); - formations.push("Formation10"); - formations.push("Formation11"); - formations.push("Formation12"); + var civ = g_Players[entState.player].civ; + var formations = getCivFormations(civ); return formations; } +function getCivFormations(civ) +{ + // TODO: this should come from the civ JSON files instead + + var civFormations = ["Loose", "Box", "Column Closed", "Line Closed", "Column Open", "Line Open", "Flank", "Skirmish", "Wedge", "Formation12"]; + if (civ == "hele") + { + civFormations.push("Phalanx"); + civFormations.push("Syntagma"); + } + else if (civ == "rome") + { + civFormations.push("Testudo"); + } + return civFormations; +} + function getEntityCommandsList(entState) { var commands = []; diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js index 0dc4110822..956046e24b 100644 --- a/binaries/data/mods/public/simulation/components/Formation.js +++ b/binaries/data/mods/public/simulation/components/Formation.js @@ -3,12 +3,13 @@ function Formation() {} Formation.prototype.Schema = ""; -var g_ColumnDistanceThreshold = 48; // distance at which we'll switch between column/box formations +var g_ColumnDistanceThreshold = 96; // distance at which we'll switch between column/box formations Formation.prototype.Init = function() { this.members = []; // entity IDs currently belonging to this formation this.columnar = false; // whether we're travelling in column (vs box) formation + this.formationName = "Line Closed"; }; Formation.prototype.GetMemberCount = function() @@ -114,8 +115,6 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter) var active = []; var positions = []; - var types = { "Unknown": 0 }; // TODO: make this work so we put ranged behind infantry etc - for each (var ent in this.members) { var cmpPosition = Engine.QueryInterface(ent, IID_Position); @@ -124,16 +123,14 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter) active.push(ent); positions.push(cmpPosition.GetPosition()); - - types["Unknown"] += 1; // TODO } // Work out whether this should be a column or box formation var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); var walkingDistance = cmpUnitAI.ComputeWalkingDistance(); - this.columnar = (walkingDistance > g_ColumnDistanceThreshold); + this.columnar = (walkingDistance > g_ColumnDistanceThreshold) && this.formationName != "Column Open"; - var offsets = this.ComputeFormationOffsets(types, this.columnar); + var offsets = this.ComputeFormationOffsets(active, this.columnar); var avgpos = this.ComputeAveragePosition(positions); var avgoffset = this.ComputeAveragePosition(offsets); @@ -143,13 +140,11 @@ Formation.prototype.MoveMembersIntoFormation = function(moveCenter) if (moveCenter || !cmpPosition.IsInWorld()) cmpPosition.JumpTo(avgpos.x, avgpos.z); - // TODO: assign to minimise worst-case distances or whatever - - for (var i = 0; i < active.length; ++i) + for (var i = 0; i < offsets.length; ++i) { var offset = offsets[i]; - var cmpUnitAI = Engine.QueryInterface(active[i], IID_UnitAI); + var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI); cmpUnitAI.ReplaceOrder("FormationWalk", { "target": this.entity, "x": offset.x - avgoffset.x, @@ -177,51 +172,369 @@ Formation.prototype.MoveToMembersCenter = function() cmpPosition.JumpTo(avgpos.x, avgpos.z); }; -Formation.prototype.ComputeFormationOffsets = function(types, columnar) +Formation.prototype.ComputeFormationOffsets = function(active, columnar) { var separation = 4; // TODO: don't hardcode this - var count = types["Unknown"]; + var types = { + "Cavalry" : [], + "Hero" : [], + "Melee" : [], + "Ranged" : [], + "Support" : [], + "Unknown": [] + }; // TODO: make this work so we put ranged behind infantry etc - // Choose a sensible width for the basic default formation + for each (var ent in active) + { + var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + var classes = cmpIdentity.GetClassesList(); + var done = false; + for each (var cla in classes) + { + if (cla in types) + { + types[cla].push(ent); + done = true; + break; + } + } + if (!done) + { + types["Unknown"].push(ent); + } + } + + var count = active.length; + + var shape = undefined; + var ordering = "default"; + + var offsets = []; + + // Choose a sensible size/shape for the various formations, depending on number of units var cols; - if (columnar) + if (columnar || this.formationName == "Column Closed") { // Have at most 3 files if (count <= 3) cols = count; else cols = 3; + shape = "square"; } - else + else if (this.formationName == "Phalanx") { // Try to have at least 5 files (so batch training gives a single line), // and at most 8 if (count <= 5) cols = count; - if (count <= 10) + else if (count <= 10) cols = 5; else if (count <= 16) - cols = Math.ceil(count / 2); - else + cols = Math.ceil(count/2); + else if (count <= 48) cols = 8; + else + cols = Math.ceil(count/6); + shape = "square"; + } + else if (this.formationName == "Line Closed") + { + if (count <= 3) + cols = count; + else if (count < 30) + cols = Math.max(Math.ceil(count/2), 3); + else + cols = Math.ceil(count/3); + shape = "square"; + } + else if (this.formationName == "Testudo") + { + cols = Math.ceil(Math.sqrt(count)); + shape = "square"; + } + else if (this.formationName == "Column Open") + { + cols = 2 + shape = "opensquare"; + } + else if (this.formationName == "Line Open") + { + if (count <= 5) + cols = 3; + else if (count <= 11) + cols = 4; + else if (count <= 18) + cols = 5; + else + cols = 6; + shape = "opensquare"; + } + else if (this.formationName == "Loose") + { + var width = Math.sqrt(count) * separation * 5; + + for (var i = 0; i < count; ++i) + { + offsets.push({"x": Math.random()*width, "z": Math.random()*width}); + } + } + else if (this.formationName == "Circle") + { + var depth; + var pop; + if (count <= 36) + { + pop = 12; + depth = Math.ceil(count / pop); + } + else + { + depth = 3 + pop = Math.ceil(count / depth); + } + + var left = count; + var radius = Math.min(left, pop) * separation / (2 * Math.PI); + for (var c = 0; c < depth; ++c) + { + var ctodo = Math.min(left, pop); + var cradius = radius - c * separation / 2; + var delta = 2 * Math.PI / ctodo; + for (var alpha = 0; ctodo; alpha += delta) + { + var x = Math.cos(alpha) * cradius; + var z = Math.sin(alpha) * cradius; + offsets.push({"x": x, "z": z}); + ctodo--; + left--; + } + } + } + else if (this.formationName == "Box") + { + var root = Math.ceil(Math.sqrt(count)); + + var left = count; + var meleeleft = types["Melee"].length; + for (var sq = Math.floor(root/2); sq >= 0; --sq) + { + var width = sq * 2 + 1; + var stodo; + if (sq == 0) + { + stodo = left; + } + else + { + if (meleeleft >= width*width - (width-2)*(width-2)) // form a complete box + { + stodo = width*width - (width-2)*(width-2); + meleeleft -= stodo; + } + else // compact + stodo = Math.max(0, left - (width-2)*(width-2)); + } + + for (var r = -sq; r <= sq && stodo; ++r) + { + for (var c = -sq; c <= sq && stodo; ++c) + { + if (Math.abs(r) == sq || Math.abs(c) == sq) + { + var x = c * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + stodo--; + left--; + } + } + } + } + } + else if (this.formationName == "Skirmish") + { + cols = Math.ceil(count/2); + shape = "opensquare"; + } + else if (this.formationName == "Wedge") + { + var depth = Math.ceil(Math.sqrt(count)); + + var left = count; + var width = 2 * depth - 1; + for (var p = 0; p < depth && left; ++p) + { + for (var r = p; r < depth && left; ++r) + { + var c1 = depth - r + p; + var c2 = depth + r - p; + + if (left) + { + var x = c1 * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + left--; + } + if (left && c1 != c2) + { + var x = c2 * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + left--; + } + } + } + } + else if (this.formationName == "Flank") + { + cols = 3; + var leftside = []; + leftside[0] = Math.ceil(count/2); + leftside[1] = Math.floor(count/2); + ranks = Math.ceil(leftside[0] / cols); + var off = - separation * 4; + for (var side = 0; side < 2; ++side) + { + var left = leftside[side]; + off += side * separation * 8; + for (var r = 0; r < ranks; ++r) + { + var n = Math.min(left, cols); + for (var c = 0; c < n; ++c) + { + var x = off + ((n-1)/2 - c) * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + } + left -= n; + } + } + } + else if (this.formationName == "Syntagma") + { + var cols = Math.ceil(Math.sqrt(count)); + shape = "square"; + } + else if (this.formationName == "Formation12") + { + if (count <= 5) + cols = count; + else if (count <= 10) + cols = 5; + else if (count <= 16) + cols = Math.ceil(count/2); + else if (count <= 48) + cols = 8; + else + cols = Math.ceil(count/6); + shape = "opensquare"; + separation /= 1.5; + ordering = "cavalryOnTheSides"; } - var ranks = Math.ceil(count / cols); - - var offsets = []; - - var left = count; - for (var r = 0; r < ranks; ++r) + if (shape == "square") { - var n = Math.min(left, cols); - for (var c = 0; c < n; ++c) + var ranks = Math.ceil(count / cols); + + var left = count; + for (var r = 0; r < ranks; ++r) { - var x = ((n-1)/2 - c) * separation; - var z = -r * separation; - offsets.push({"x": x, "z": z}); + var n = Math.min(left, cols); + for (var c = 0; c < n; ++c) + { + var x = ((n-1)/2 - c) * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + } + left -= n; } - left -= n; + } + else if (shape == "opensquare") + { + var left = count; + for (var r = 0; left; ++r) + { + var n = Math.min(left, cols - (r&1?1:0)); + for (var c = 0; c < 2*n; c+=2) + { + var x = (- c - (r&1)) * separation; + var z = -r * separation; + offsets.push({"x": x, "z": z}); + } + left -= n; + } + } + + // TODO: assign to minimise worst-case distances or whatever + if (ordering == "default") + { + for each (var offset in offsets) + { + var ent = undefined; + for (var t in types) + { + if (types[t].length) + { + ent = types[t].pop(); + offset.ent = ent; + break; + } + } + } + } + else if (ordering == "cavalryOnTheSides") + { + var noffset = []; + var cavalrycount = types["Cavalry"].length; + offsets.sort(function (a, b) { + if (a.x < b.x) return -1; + else if (a.x == b.x && a.z < b.z) return -1; + return 1; + }); + + while (offsets.length && types["Cavalry"].length && types["Cavalry"].length > cavalrycount/2) + { + var offset = offsets.pop(); + offset.ent = types["Cavalry"].pop(); + noffset.push(offset); + } + + offsets.sort(function (a, b) { + if (a.x > b.x) return -1; + else if (a.x == b.x && a.z < b.z) return -1; + return 1; + }); + + while (offsets.length && types["Cavalry"].length) + { + var offset = offsets.pop(); + offset.ent = types["Cavalry"].pop(); + noffset.push(offset); + } + + offsets.sort(function (a, b) { + if (a.z < b.z) return -1; + else if (a.z == b.z && a.x < b.x) return -1; + return 1; + }); + + while (offsets.length) + { + var offset = offsets.pop(); + for (var t in types) + { + if (types[t].length) + { + offset.ent = types[t].pop(); + break; + } + } + noffset.push(offset); + } + offsets = noffset; } return offsets; @@ -286,4 +599,14 @@ Formation.prototype.OnGlobalOwnershipChanged = function(msg) this.RemoveMembers([msg.entity]); }; +Formation.prototype.LoadFormation = function(formationName) +{ + this.formationName = formationName; + for each (var ent in this.members) + { + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + cmpUnitAI.SetLastFormationName(this.formationName); + } +}; + Engine.RegisterComponentType(IID_Formation, "Formation", Formation); diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 223c7bbccd..17f39d9123 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -264,6 +264,11 @@ GuiInterface.prototype.GetNextNotification = function() return ""; }; +GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data) +{ + return CanMoveEntsIntoFormation(data.ents, data.formationName); +}; + GuiInterface.prototype.SetSelectionHighlight = function(player, cmd) { var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); @@ -510,6 +515,8 @@ var exposedFunctions = { "GetTemplateData": 1, "GetNextNotification": 1, + "CanMoveEntsIntoFormation": 1, + "SetSelectionHighlight": 1, "SetStatusBars": 1, "DisplayRallyPoint": 1, diff --git a/binaries/data/mods/public/simulation/components/Identity.js b/binaries/data/mods/public/simulation/components/Identity.js index a38ce5ff22..5b9303c677 100644 --- a/binaries/data/mods/public/simulation/components/Identity.js +++ b/binaries/data/mods/public/simulation/components/Identity.js @@ -68,6 +68,7 @@ Identity.prototype.Schema = "Infantry" + "Cavalry" + "Ranged" + + "Melee" + "Mechanical" + "Ship" + "Siege" + diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index c0434a7513..cbc955eaad 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1161,6 +1161,7 @@ UnitAI.prototype.Init = function() this.order = undefined; // always == this.orderQueue[0] this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to this.isIdle = false; + this.lastFormationName = "Line Closed"; this.SetStance(this.template.DefaultStance); }; @@ -1740,6 +1741,16 @@ UnitAI.prototype.GetFormationController = function() return this.formationController; }; +UnitAI.prototype.SetLastFormationName = function(name) +{ + this.lastFormationName = name; +}; + +UnitAI.prototype.GetLastFormationName = function() +{ + return this.lastFormationName; +}; + /** * Returns the estimated distance that this unit will travel before either * finishing all of its orders, or reaching a non-walk target (attack, gather, etc). diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 2c15eb51cd..2337d1999e 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -237,6 +237,17 @@ function ProcessCommand(player, cmd) var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder); cmpGarrisonHolder.UnloadAll(); break; + + case "formation": + var cmpUnitAI = GetFormationUnitAI(cmd.entities); + if (!cmpUnitAI) + break; + var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation); + if (!cmpFormation) + break; + cmpFormation.LoadFormation(cmd.name); + cmpFormation.MoveMembersIntoFormation(true); + break; default: error("Ignoring unrecognised command type '" + cmd.type + "'"); @@ -339,9 +350,139 @@ function GetFormationUnitAI(ents) formationEnt = Engine.AddEntity("special/formation"); var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation); cmpFormation.SetMembers(formation.entities); + + // If all the selected units were previously in formations of the same shape, + // then set this new formation to that shape too; otherwise use the default shape + var lastFormationName = undefined; + for each (var ent in formation.entities) + { + var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); + if (cmpUnitAI) + { + var name = cmpUnitAI.GetLastFormationName(); + if (lastFormationName === undefined) + { + lastFormationName = name; + } + else if (lastFormationName != name) + { + lastFormationName = undefined; + break; + } + } + } + var formationName; + if (lastFormationName) + formationName = lastFormationName; + else + formationName = "Line Closed"; + + if (CanMoveEntsIntoFormation(formation.entities, formationName)) + { + cmpFormation.LoadFormation(formationName); + } + else + { + cmpFormation.LoadFormation("Loose"); + } } return Engine.QueryInterface(formationEnt, IID_UnitAI); } +function CanMoveEntsIntoFormation(ents, formationName) +{ + var count = ents.length; + var classesRequired; + + // TODO: should check the player's civ is allowed to use this formation + + if (formationName == "Loose") + { + return true; + } + else if (formationName == "Box") + { + if (count < 4) + return false; + } + else if (formationName == "Column Closed") + { + } + else if (formationName == "Line Closed") + { + } + else if (formationName == "Column Open") + { + } + else if (formationName == "Line Open") + { + } + else if (formationName == "Flank") + { + if (count < 8) + return false; + } + else if (formationName == "Skirmish") + { + classesRequired = ["Ranged"]; + } + else if (formationName == "Wedge") + { + if (count < 3) + return false; + classesRequired = ["Cavalry"]; + } + else if (formationName == "Formation12") + { + } + else if (formationName == "Phalanx") + { + if (count < 10) + return false; + classesRequired = ["Melee", "Infantry"]; + } + else if (formationName == "Syntagma") + { + if (count < 9) + return false; + classesRequired = ["Melee", "Infantry"]; // TODO: pike only + } + else if (formationName == "Testudo") + { + if (count < 9) + return false; + classesRequired = ["Melee", "Infantry"]; + } + else + { + return false; + } + + var looseOnlyUnits = true; + for each (var ent in ents) + { + var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (cmpIdentity) + { + var classes = cmpIdentity.GetClassesList(); + if (looseOnlyUnits && (classes.indexOf("Worker") == -1 || classes.indexOf("Support") == -1)) + looseOnlyUnits = false; + for each (var classRequired in classesRequired) + { + if (classes.indexOf(classRequired) == -1) + { + return false; + } + } + } + } + + if (looseOnlyUnits) + return false; + + return true; +} + +Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation); Engine.RegisterGlobal("ProcessCommand", ProcessCommand); diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml index 4fa67b571f..523af758a2 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_pikeman.xml @@ -2,7 +2,7 @@ Hero - Hero Infantry + Hero Infantry Melee diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml index d898a02e79..a924221d3c 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_spearman.xml @@ -2,7 +2,7 @@ Hero - Hero Infantry + Hero Infantry Melee hero diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml index 069351dc2b..86eff15b66 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_swordsman.xml @@ -2,7 +2,7 @@ Hero - Hero Infantry + Hero Infantry Melee 400 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml index 63ea4bb9d1..791619cb10 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_melee.xml @@ -2,6 +2,7 @@ Melee Infantry + Melee 100 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml index 8f8831e550..c73f81c209 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml @@ -2,6 +2,7 @@ Ranged + Ranged 90 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml index 5811c5d4a9..14b07e2912 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_javelinist.xml @@ -2,7 +2,7 @@ Super Cavalry Javelinist - Cavalry Ranged + Ranged diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml index cedd9bf8c6..9f5923bab8 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_spearman.xml @@ -2,6 +2,7 @@ Super Cavalry Spearman + Melee 16 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml index 3a660f5f95..843ce30676 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_cavalry_swordsman.xml @@ -2,6 +2,7 @@ Super Cavalry Spearman + Melee 16 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml index 4fc85da100..e6a78de35c 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_pikeman.xml @@ -2,6 +2,7 @@ Super Infantry Pikeman + Melee diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml index 124b98a62a..14bda3b643 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_spearman.xml @@ -2,6 +2,7 @@ Super Infantry Spearman + Melee diff --git a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml index 525d677a84..cb8b1985e1 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_super_infantry_swordsman.xml @@ -2,6 +2,7 @@ Super Infantry Swordsman + Melee