1
0
forked from 0ad/0ad

Read formations from civ JSON files. Fixes #1234, #601.

This was SVN commit r11735.
This commit is contained in:
leper 2012-05-03 23:32:10 +00:00
parent 20afc75657
commit 58836c624a
21 changed files with 248 additions and 85 deletions

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -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
}

View File

@ -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,

View File

@ -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 = [];

View File

@ -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")

View File

@ -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,

View File

@ -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;

View File

@ -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);
};

View File

@ -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)

View File

@ -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)
{

View File

@ -27,7 +27,7 @@
<Builder>
<Rate>1.0</Rate>
<Entities datatype="tokens">
structures/{civ}_house
structures/{civ}_house
structures/{civ}_mill
structures/{civ}_farmstead
structures/{civ}_field
@ -52,6 +52,7 @@
<History>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.</History>
<Tooltip>Gather resources, build civic structures, and inspire nearby males to work faster. Bonused at foraging and farming.</Tooltip>
<Classes datatype="tokens">Worker Female Citizen</Classes>
<Formations disable=""/>
</Identity>
<ResourceGatherer>
<MaxDistance>2.0</MaxDistance>
@ -70,8 +71,8 @@
</ResourceGatherer>
<Sound>
<SoundGroups>
<select>voice/hellenes/civ/civ_female_select.xml</select>
<order_walk>voice/hellenes/civ/civ_female_select.xml</order_walk>
<select>voice/hellenes/civ/civ_female_select.xml</select>
<order_walk>voice/hellenes/civ/civ_female_select.xml</order_walk>
<order_attack>voice/hellenes/civ/civ_female_select.xml</order_attack>
<order_gather>voice/hellenes/civ/civ_female_select.xml</order_gather>
<order_repair>voice/hellenes/civ/civ_female_select.xml</order_repair>

View File

@ -81,6 +81,7 @@ CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
m_ScriptInterface.RegisterFunction<int, std::string, CComponentManager::Script_AddLocalEntity> ("AddLocalEntity");
m_ScriptInterface.RegisterFunction<void, int, CComponentManager::Script_DestroyEntity> ("DestroyEntity");
m_ScriptInterface.RegisterFunction<CScriptVal, std::wstring, CComponentManager::Script_ReadJSONFile> ("ReadJSONFile");
m_ScriptInterface.RegisterFunction<CScriptVal, std::wstring, CComponentManager::Script_ReadCivJSONFile> ("ReadCivJSONFile");
m_ScriptInterface.RegisterFunction<std::vector<std::string>, 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<CComponentManager*> (cbdata);
VfsPath path = VfsPath("simulation/data") / fileName;
VfsPath path = VfsPath(filePath) / fileName;
return componentManager->GetScriptInterface().ReadJSONFile(path).get();
}

View File

@ -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<std::string> 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;