From 58836c624a53d23ddc09a190e4a016564023551f Mon Sep 17 00:00:00 2001 From: leper Date: Thu, 3 May 2012 23:32:10 +0000 Subject: [PATCH] Read formations from civ JSON files. Fixes #1234, #601. This was SVN commit r11735. --- binaries/data/mods/public/civs/athen.json | 15 ++++ binaries/data/mods/public/civs/cart.json | 13 +++ binaries/data/mods/public/civs/celt.json | 13 +++ binaries/data/mods/public/civs/hele.json | 17 +++- binaries/data/mods/public/civs/iber.json | 13 +++ binaries/data/mods/public/civs/mace.json | 15 ++++ binaries/data/mods/public/civs/pers.json | 13 +++ binaries/data/mods/public/civs/rome.json | 14 ++++ binaries/data/mods/public/civs/spart.json | 15 ++++ binaries/data/mods/public/civs/theb.json | 17 +++- .../mods/public/gui/session/unit_commands.js | 2 +- .../public/gui/session/utility_functions.js | 24 ------ .../public/simulation/components/Formation.js | 84 +++++++++---------- .../simulation/components/GuiInterface.js | 8 ++ .../public/simulation/components/Player.js | 12 ++- .../public/simulation/components/UnitAI.js | 2 +- .../public/simulation/helpers/Commands.js | 17 ++-- .../mods/public/simulation/helpers/Player.js | 16 ++++ .../template_unit_support_female_citizen.xml | 7 +- .../simulation2/system/ComponentManager.cpp | 13 ++- source/simulation2/system/ComponentManager.h | 3 + 21 files changed, 248 insertions(+), 85 deletions(-) diff --git a/binaries/data/mods/public/civs/athen.json b/binaries/data/mods/public/civs/athen.json index 8b507192ae..71a9dfcb35 100644 --- a/binaries/data/mods/public/civs/athen.json +++ b/binaries/data/mods/public/civs/athen.json @@ -123,5 +123,20 @@ { "Template": "units/athen_cavalry_javelinist_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Phalanx", + "Syntagma" ] } diff --git a/binaries/data/mods/public/civs/cart.json b/binaries/data/mods/public/civs/cart.json index 03de552656..244a13ab33 100644 --- a/binaries/data/mods/public/civs/cart.json +++ b/binaries/data/mods/public/civs/cart.json @@ -93,5 +93,18 @@ { "Template": "units/cart_cavalry_javelinist_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line" ] } diff --git a/binaries/data/mods/public/civs/celt.json b/binaries/data/mods/public/civs/celt.json index 43d10ad16a..fefcfa04aa 100644 --- a/binaries/data/mods/public/civs/celt.json +++ b/binaries/data/mods/public/civs/celt.json @@ -142,5 +142,18 @@ { "Template": "units/celt_cavalry_swordsman_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line" ] } diff --git a/binaries/data/mods/public/civs/hele.json b/binaries/data/mods/public/civs/hele.json index 78e2f1935c..ea9adbac8d 100644 --- a/binaries/data/mods/public/civs/hele.json +++ b/binaries/data/mods/public/civs/hele.json @@ -160,5 +160,20 @@ "Template": "units/hele_cavalry_swordsman_b" } ], - "SelectableInGameSetup": false + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Phalanx", + "Syntagma" + ], + "SelectableInGameSetup": false } diff --git a/binaries/data/mods/public/civs/iber.json b/binaries/data/mods/public/civs/iber.json index a2ab4a8344..81fad6f9a2 100644 --- a/binaries/data/mods/public/civs/iber.json +++ b/binaries/data/mods/public/civs/iber.json @@ -100,5 +100,18 @@ { "Template": "units/iber_cavalry_spearman_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line" ] } diff --git a/binaries/data/mods/public/civs/mace.json b/binaries/data/mods/public/civs/mace.json index 64ecab83bc..1dbe6c323c 100644 --- a/binaries/data/mods/public/civs/mace.json +++ b/binaries/data/mods/public/civs/mace.json @@ -109,5 +109,20 @@ { "Template": "units/mace_cavalry_javelinist_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Phalanx", + "Syntagma" ] } diff --git a/binaries/data/mods/public/civs/pers.json b/binaries/data/mods/public/civs/pers.json index 05c9ee3332..13db51a7e3 100644 --- a/binaries/data/mods/public/civs/pers.json +++ b/binaries/data/mods/public/civs/pers.json @@ -101,5 +101,18 @@ { "Template": "units/pers_cavalry_javelinist_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line" ] } diff --git a/binaries/data/mods/public/civs/rome.json b/binaries/data/mods/public/civs/rome.json index 8e48b8ab70..0ff84a94ff 100644 --- a/binaries/data/mods/public/civs/rome.json +++ b/binaries/data/mods/public/civs/rome.json @@ -106,5 +106,19 @@ { "Template": "units/rome_cavalry_spearman_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Testudo" ] } diff --git a/binaries/data/mods/public/civs/spart.json b/binaries/data/mods/public/civs/spart.json index 1c97928d51..d889d85dea 100644 --- a/binaries/data/mods/public/civs/spart.json +++ b/binaries/data/mods/public/civs/spart.json @@ -100,5 +100,20 @@ { "Template": "units/spart_cavalry_javelinist_b" } + ], + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Phalanx", + "Syntagma" ] } diff --git a/binaries/data/mods/public/civs/theb.json b/binaries/data/mods/public/civs/theb.json index 486c2ecc1f..720241953e 100644 --- a/binaries/data/mods/public/civs/theb.json +++ b/binaries/data/mods/public/civs/theb.json @@ -101,5 +101,20 @@ "Template": "units/spart_cavalry_javelinist_b" } ], - "SelectableInGameSetup": false + "Formations": + [ + "Scatter", + "Box", + "Column Closed", + "Line Closed", + "Column Open", + "Line Open", + "Flank", + "Skirmish", + "Wedge", + "Battle Line", + "Phalanx", + "Syntagma" + ], + "SelectableInGameSetup": false } diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js index efb7151a10..88b4d8e3c8 100644 --- a/binaries/data/mods/public/gui/session/unit_commands.js +++ b/binaries/data/mods/public/gui/session/unit_commands.js @@ -712,7 +712,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s function (item) { unload(entState.id, groups.getEntsByName(item)); } ); } - var formations = getEntityFormationsList(entState); + var formations = Engine.GuiInterfaceCall("GetAvailableFormations"); if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length) { setupUnitPanel(FORMATION, usedPanels, entState, formations, diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js index 0d95a70538..be892862ed 100644 --- a/binaries/data/mods/public/gui/session/utility_functions.js +++ b/binaries/data/mods/public/gui/session/utility_functions.js @@ -121,30 +121,6 @@ function damageTypesToText(dmg) return dmgArray.join("[font=\"serif-12\"], [/font]"); } -function getEntityFormationsList(entState) -{ - 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 = ["Scatter", "Box", "Column Closed", "Line Closed", "Column Open", "Line Open", "Flank", "Skirmish", "Wedge", "Battle Line"]; - 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 7395eb1855..c98c1a94a8 100644 --- a/binaries/data/mods/public/simulation/components/Formation.js +++ b/binaries/data/mods/public/simulation/components/Formation.js @@ -214,17 +214,21 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) // Choose a sensible size/shape for the various formations, depending on number of units var cols; - if (columnar || this.formationName == "Column Closed") + + if (columnar) + this.formationName = "Column Closed"; + + switch(this.formationName) { + case "Column Closed": // Have at most 3 files if (count <= 3) cols = count; else cols = 3; shape = "square"; - } - else if (this.formationName == "Phalanx") - { + break; + case "Phalanx": // Try to have at least 5 files (so batch training gives a single line), // and at most 8 if (count <= 5) @@ -238,9 +242,8 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) else cols = Math.ceil(count/6); shape = "square"; - } - else if (this.formationName == "Line Closed") - { + break; + case "Line Closed": if (count <= 3) cols = count; else if (count < 30) @@ -248,19 +251,16 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) else cols = Math.ceil(count/3); shape = "square"; - } - else if (this.formationName == "Testudo") - { + break; + case "Testudo": cols = Math.ceil(Math.sqrt(count)); shape = "square"; - } - else if (this.formationName == "Column Open") - { - cols = 2 + break; + case "Column Open": + cols = 2; shape = "opensquare"; - } - else if (this.formationName == "Line Open") - { + break; + case "Line Open": if (count <= 5) cols = 3; else if (count <= 11) @@ -270,18 +270,16 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) else cols = 6; shape = "opensquare"; - } - else if (this.formationName == "Scatter") - { + break; + case "Scatter": 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") - { + break; + case "Circle": var depth; var pop; if (count <= 36) @@ -291,7 +289,7 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) } else { - depth = 3 + depth = 3; pop = Math.ceil(count / depth); } @@ -311,9 +309,8 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) left--; } } - } - else if (this.formationName == "Box") - { + break; + case "Box": var root = Math.ceil(Math.sqrt(count)); var left = count; @@ -334,7 +331,9 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) meleeleft -= stodo; } else // compact + { stodo = Math.max(0, left - (width-2)*(width-2)); + } } for (var r = -sq; r <= sq && stodo; ++r) @@ -352,14 +351,12 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) } } } - } - else if (this.formationName == "Skirmish") - { + break; + case "Skirmish": cols = Math.ceil(count/2); shape = "opensquare"; - } - else if (this.formationName == "Wedge") - { + break; + case "Wedge": var depth = Math.ceil(Math.sqrt(count)); var left = count; @@ -387,9 +384,8 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) } } } - } - else if (this.formationName == "Flank") - { + break; + case "Flank": cols = 3; var leftside = []; leftside[0] = Math.ceil(count/2); @@ -412,14 +408,12 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) left -= n; } } - } - else if (this.formationName == "Syntagma") - { - var cols = Math.ceil(Math.sqrt(count)); + break; + case "Syntagma": + cols = Math.ceil(Math.sqrt(count)); shape = "square"; - } - else if (this.formationName == "Battle Line") - { + break; + case "Battle Line": if (count <= 5) cols = count; else if (count <= 10) @@ -433,6 +427,10 @@ Formation.prototype.ComputeFormationOffsets = function(active, columnar) shape = "opensquare"; separation /= 1.5; ordering = "cavalryOnTheSides"; + break; + default: + warn("Unknown formation: " + this.formationName); + break; } if (shape == "square") diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 2292e94705..870544db41 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -451,6 +451,13 @@ GuiInterface.prototype.GetNextNotification = function() return ""; }; +GuiInterface.prototype.GetAvailableFormations = function(player, data) +{ + var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player); + return cmpPlayer.GetFormations(); +}; + GuiInterface.prototype.GetFormationRequirements = function(player, data) { return GetFormationRequirements(data.formationName); @@ -897,6 +904,7 @@ var exposedFunctions = { "CheckTechnologyRequirements": 1, "GetNextNotification": 1, + "GetAvailableFormations": 1, "GetFormationRequirements": 1, "CanMoveEntsIntoFormation": 1, "IsFormationSelected": 1, diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js index ca01b9866a..768979201f 100644 --- a/binaries/data/mods/public/simulation/components/Player.js +++ b/binaries/data/mods/public/simulation/components/Player.js @@ -25,6 +25,7 @@ Player.prototype.Init = function() this.diplomacy = []; // array of diplomatic stances for this player with respect to other players (including gaia and self) this.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class this.phase = "village"; + this.formations = []; this.startCam = undefined; this.controlAllUnits = false; this.isAI = false; @@ -202,7 +203,6 @@ Player.prototype.GetConquestCriticalEntitiesCount = function() return this.conquestCriticalEntitiesCount; }; - Player.prototype.GetTeam = function() { return this.team; @@ -223,6 +223,16 @@ Player.prototype.SetDiplomacy = function(dipl) this.diplomacy = dipl; }; +Player.prototype.GetFormations = function() +{ + return this.formations; +}; + +Player.prototype.SetFormations = function(formations) +{ + this.formations = formations; +}; + Player.prototype.GetStartingCameraPos = function() { return this.startCam.position; diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 2ac8421b01..b47fa239d6 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1702,7 +1702,7 @@ UnitAI.prototype.Init = function() this.formationController = INVALID_ENTITY; // entity with IID_Formation that we belong to this.isGarrisoned = false; this.isIdle = false; - this.lastFormationName = "Line Closed"; + this.lastFormationName = ""; this.SetStance(this.template.DefaultStance); }; diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 4205c9b6fc..df740ef16b 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -453,7 +453,7 @@ function ProcessCommand(player, cmd) case "formation": var entities = FilterEntityList(cmd.entities, player, controlAllUnits); - GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) { + GetFormationUnitAIs(entities, cmd.name).forEach(function(cmpUnitAI) { var cmpFormation = Engine.QueryInterface(cmpUnitAI.entity, IID_Formation); if (!cmpFormation) return; @@ -567,7 +567,7 @@ function RemoveFromFormation(ents) * Returns a list of UnitAI components, each belonging either to a * selected unit or to a formation entity for groups of the selected units. */ -function GetFormationUnitAIs(ents) +function GetFormationUnitAIs(ents, formName) { // If an individual was selected, remove it from any formation // and command it individually @@ -594,13 +594,11 @@ function GetFormationUnitAIs(ents) continue; var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - // TODO: Currently we use LineClosed as effectively a boolean flag - // to determine whether formations are allowed at all. Instead we - // should check specific formation names and do something sensible - // (like what?) when some units don't support them. - // TODO: We'll also need to fix other formation code to use - // "LineClosed" instead of "Line Closed" etc consistently. - if (cmpIdentity && cmpIdentity.CanUseFormation("LineClosed")) + // TODO: We only check if the formation is usable by some units + // if we move them to it. We should check if we can use formations + // for the other cases. + // We only use "LineClosed" instead of "Line Closed" to access the templates. + if (cmpIdentity && cmpIdentity.CanUseFormation(formName === undefined ? "LineClosed" : formName.replace(/\s+/,''))) formedEnts.push(ent); else nonformedUnitAIs.push(cmpUnitAI); @@ -738,6 +736,7 @@ function CanMoveEntsIntoFormation(ents, formationName) var count = ents.length; // TODO: should check the player's civ is allowed to use this formation + // See simulation/components/Player.js GetFormations() for a list of all allowed formations var requirements = GetFormationRequirements(formationName); if (!requirements) diff --git a/binaries/data/mods/public/simulation/helpers/Player.js b/binaries/data/mods/public/simulation/helpers/Player.js index b4f6640e9b..7e29ed6668 100644 --- a/binaries/data/mods/public/simulation/helpers/Player.js +++ b/binaries/data/mods/public/simulation/helpers/Player.js @@ -118,6 +118,22 @@ function LoadPlayerSettings(settings, newPlayers) } } + // If formations explicitly defined, use that; otherwise use civ defaults + var formations = getSetting(pData, pDefs, "Formations"); + if (formations !== undefined) + { + cmpPlayer.SetFormations(formations); + } + else + { + var rawFormations = Engine.ReadCivJSONFile(cmpPlayer.GetCiv()+".json"); + if (!(rawFormations && rawFormations.Formations)) + { + throw("Player.js: Error reading "+cmpPlayer.GetCiv()+".json"); + } + cmpPlayer.SetFormations(rawFormations.Formations); + } + var startCam = getSetting(pData, pDefs, "StartingCamera"); if (startCam !== undefined) { diff --git a/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml b/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml index 83de7e4353..b5eb5d526b 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_support_female_citizen.xml @@ -27,7 +27,7 @@ 1.0 - structures/{civ}_house + structures/{civ}_house structures/{civ}_mill structures/{civ}_farmstead structures/{civ}_field @@ -52,6 +52,7 @@ Women in the ancient world took on a variety of roles - from leadership (Celts) to servant (Greeks). Women are hard workers, the economic backbone of any civilisation. In history, it was typical when all the males (capable of fighting) were killed for the females, children, and elderly to be sold as slaves. Gather resources, build civic structures, and inspire nearby males to work faster. Bonused at foraging and farming. Worker Female Citizen + 2.0 @@ -70,8 +71,8 @@ - - voice/hellenes/civ/civ_female_select.xml + + voice/hellenes/civ/civ_female_select.xml voice/hellenes/civ/civ_female_select.xml voice/hellenes/civ/civ_female_select.xml voice/hellenes/civ/civ_female_select.xml diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index 1013b259f4..1facd6b0d7 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -81,6 +81,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); m_ScriptInterface.RegisterFunction ("ReadJSONFile"); + m_ScriptInterface.RegisterFunction ("ReadCivJSONFile"); m_ScriptInterface.RegisterFunction, std::wstring, CComponentManager::Script_FindJSONFiles> ("FindJSONFiles"); } @@ -938,10 +939,20 @@ std::string CComponentManager::GenerateSchema() } CScriptVal CComponentManager::Script_ReadJSONFile(void* cbdata, std::wstring fileName) +{ + return ReadJSONFile(cbdata, L"simulation/data", fileName); +} + +CScriptVal CComponentManager::Script_ReadCivJSONFile(void* cbdata, std::wstring fileName) +{ + return ReadJSONFile(cbdata, L"civs", fileName); +} + +CScriptVal CComponentManager::ReadJSONFile(void* cbdata, std::wstring filePath, std::wstring fileName) { CComponentManager* componentManager = static_cast (cbdata); - VfsPath path = VfsPath("simulation/data") / fileName; + VfsPath path = VfsPath(filePath) / fileName; return componentManager->GetScriptInterface().ReadJSONFile(path).get(); } diff --git a/source/simulation2/system/ComponentManager.h b/source/simulation2/system/ComponentManager.h index 8ca0ba81e7..b54180fc66 100644 --- a/source/simulation2/system/ComponentManager.h +++ b/source/simulation2/system/ComponentManager.h @@ -233,8 +233,11 @@ private: static int Script_AddLocalEntity(void* cbdata, std::string templateName); static void Script_DestroyEntity(void* cbdata, int ent); static CScriptVal Script_ReadJSONFile(void* cbdata, std::wstring fileName); + static CScriptVal Script_ReadCivJSONFile(void* cbdata, std::wstring fileName); static std::vector Script_FindJSONFiles(void* cbdata, std::wstring subPath); + static CScriptVal ReadJSONFile(void *cbdata, std::wstring filePath, std::wstring fileName); + CMessage* ConstructMessage(int mtid, CScriptVal data); void SendGlobalMessage(entity_id_t ent, const CMessage& msg) const;