Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml (revision 9391)
@@ -1,20 +1,20 @@
iberCaetrati LusitanoIberians, especially the Lusitanians, were good at ranged combat and ambushing enemy columns. They throw heavy iron javelins and sometimes even add burning pitch to them, making them good as a cheap siege weapon.units/cart_cavalry_javelinist.png
- iber_infantry_javelinist_a
+ units/iber_infantry_javelinist_aunits/iberians/infantry_javelinist_b.xml
structures/iber_sb1
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_a.xml (revision 9391)
@@ -1,34 +1,37 @@
+
+ Advanced
+
- iber_infantry_swordsman_e
+ units/iber_infantry_swordsman_eunits/iberians/infantry_swordsman_a.xml0.7511023.046.06.04.06.06.018.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml (revision 9391)
@@ -1,33 +1,33 @@
iberCaetratiunits/iber_infantry_swordsman.pngThe Iberians were master sword-smiths and the falcata was their greatest creation. Wielded by superb swordsmen equipped with light armor and a buckler known as a caetra, they caused untold carnage. Thanks to this Iberian infantry were fast and agile unlike many of their opponents and could bite hard when they attacked. Their skill with sword and buckler were legendary, allowing them to go toe-to-toe with heavy infantry.
- iber_infantry_swordsman_a
+ units/iber_infantry_swordsman_aunits/iberians/infantry_swordsman_b.xml60
structures/iber_sb1
22.044.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_a.xml (revision 9391)
@@ -1,38 +1,38 @@
- Advanced
+ Advanced
- hele_cavalry_swordsman_e
+ units/hele_cavalry_swordsman_e0.751408.826.46.08.06.021.063.0units/hellenes/cavalry_swordsman_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_e.xml (revision 9391)
@@ -1,31 +1,34 @@
+
+ Elite
+ units/iberians/infantry_swordsman_e.xml0.512024.048.08.05.07.05.616.8
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml (revision 9391)
@@ -1,20 +1,20 @@
iberKarskenunits/iber_infantry_slinger.pngIberian slingers were the undisputed masters of the weapon and extracted a high toll of the enemy. Going into combat scantily clad at best, the slinger carried three slings tied around his waist, each of a different length allowing him to attack opponents from all ranges. Unlike other cultures, the Iberian slingers threw rocks instead of specially made lead shot.
- iber_infantry_slinger_a
+ units/iber_infantry_slinger_aunits/iberians/infantry_slinger_b.xml
structures/iber_sb1
Index: ps/trunk/binaries/data/mods/public/gui/session/selection.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection.js (revision 9391)
@@ -1,381 +1,406 @@
const MAX_SELECTION_SIZE = 64; // Limits selection size
function _setHighlight(ents, alpha)
{
if (ents.length)
Engine.GuiInterfaceCall("SetSelectionHighlight", { "entities":ents, "alpha":alpha });
}
function _setStatusBars(ents, enabled)
{
if (ents.length)
Engine.GuiInterfaceCall("SetStatusBars", { "entities":ents, "enabled":enabled });
}
function _setMotionOverlay(ents, enabled)
{
if (ents.length)
Engine.GuiInterfaceCall("SetMotionDebugOverlay", { "entities":ents, "enabled":enabled });
}
function _playSound(ent)
{
Engine.GuiInterfaceCall("PlaySound", { "name":"select", "entity":ent });
}
//-------------------------------- -------------------------------- --------------------------------
// EntityGroups class for managing grouped entities
//-------------------------------- -------------------------------- --------------------------------
function EntityGroups()
{
this.groups = {};
this.ents = {};
}
EntityGroups.prototype.reset = function()
{
this.groups = {};
this.ents = {};
};
EntityGroups.prototype.add = function(ents)
{
for each (var ent in ents)
{
if (!this.ents[ent])
{
var entState = GetEntityState(ent);
var templateName = entState.template;
var template = GetTemplateData(templateName);
var key = template.selectionGroupName || templateName;
if (this.groups[key])
this.groups[key] += 1;
else
this.groups[key] = 1;
this.ents[ent] = key;
}
}
};
EntityGroups.prototype.removeEnt = function(ent)
{
var templateName = this.ents[ent];
// Remove the entity
delete this.ents[ent];
this.groups[templateName]--;
// Remove the entire group
if (this.groups[templateName] == 0)
delete this.groups[templateName];
};
EntityGroups.prototype.getCount = function(templateName)
{
return this.groups[templateName];
};
EntityGroups.prototype.getTotalCount = function()
{
var totalCount = 0;
for each (var group in this.groups)
{
totalCount += group;
}
return totalCount;
};
EntityGroups.prototype.getTemplateNames = function()
{
var templateNames = [];
for (var templateName in this.groups)
templateNames.push(templateName);
//Preserve order even when shuffling units around
//Can be optimized by moving the sorting elsewhere
templateNames.sort();
return templateNames;
};
EntityGroups.prototype.getEntsByName = function(templateName)
{
var entTemplateNames = [];
for each (var entTemplateName in this.ents)
entTemplateNames.push(entTemplateName);
var i = 0;
var ents = [];
for (var ent in this.ents)
{
if (entTemplateNames[i] == templateName)
ents.push(parseInt(ent));
i++;
}
return ents;
};
// Gets all ents in every group except ones of the specified group
EntityGroups.prototype.getEntsByNameInverse = function(templateName)
{
var entTemplateNames = [];
for each (var entTemplateName in this.ents)
entTemplateNames.push(entTemplateName);
var i = 0;
var ents = [];
for (var ent in this.ents)
{
if (entTemplateNames[i] != templateName)
ents.push(parseInt(ent));
i++;
}
return ents;
};
//-------------------------------- -------------------------------- --------------------------------
// EntitySelection class for managing the entity selection list and the primary selection
//-------------------------------- -------------------------------- --------------------------------
function EntitySelection()
{
// Private properties:
//--------------------------------
this.selected = {}; // { id:id, id:id, ... } for each selected entity ID 'id'
// { id:id, ... } for mouseover-highlighted entity IDs in these, the key is a string and the value is an int;
// we want to use the int form wherever possible since it's more efficient to send to the simulation code)
this.highlighted = {};
this.motionDebugOverlay = false;
// Public properties:
//--------------------------------
this.dirty = false; // set whenever the selection has changed
this.groups = new EntityGroups();
}
// Deselect everything but entities of the chosen type if the modifier is true otherwise deselect just the chosen entity
EntitySelection.prototype.makePrimarySelection = function(templateName, modifierKey)
{
var selection = this.toList();
var ent;
// Find an ent of a unit of the same type
for (var i = 0; i < selection.length; i++)
{
var entState = GetEntityState(selection[i]);
if (!entState)
continue;
if (entState.template == templateName)
ent = selection[i];
}
var template = GetTemplateData(templateName);
var key = template.selectionGroupName || templateName;
var ents = [];
if (modifierKey)
ents = this.groups.getEntsByNameInverse(key);
else
ents = this.groups.getEntsByName(key);
this.reset();
this.addList(ents);
}
// Get a list of the template names
EntitySelection.prototype.getTemplateNames = function()
{
var templateNames = [];
var ents = this.toList();
for each (var ent in ents)
{
var entState = GetEntityState(ent);
if (entState)
templateNames.push(entState.template);
}
return templateNames;
}
// Update the selection to take care of changes (like units that have been killed)
EntitySelection.prototype.update = function()
{
+ this.checkRenamedEntities();
for each (var ent in this.selected)
{
var entState = GetEntityState(ent);
// Remove deleted units
if (!entState)
{
delete this.selected[ent];
this.groups.removeEnt(ent);
this.dirty = true;
continue;
}
// Remove non-visible units (e.g. moved back into fog-of-war)
if (entState.visibility == "hidden")
{
// Disable any highlighting of the disappeared unit
_setHighlight([ent], 0);
_setStatusBars([ent], false);
_setMotionOverlay([ent], false);
delete this.selected[ent];
this.dirty = true;
continue;
}
}
};
+/**
+ * Update selection if some selected entities was renamed
+ * (in case of unit promotion or finishing building structure)
+ */
+EntitySelection.prototype.checkRenamedEntities = function()
+{
+ var renamedEntities = Engine.GuiInterfaceCall("GetRenamedEntities", true);
+ if (renamedEntities.length > 0)
+ {
+ var removeFromSelectionList = [];
+ var addToSelectionList = [];
+ for each (var renamedEntity in renamedEntities)
+ {
+ if (this.selected[renamedEntity.entity])
+ {
+ removeFromSelectionList.push(renamedEntity.entity);
+ addToSelectionList.push(renamedEntity.newentity);
+ }
+ }
+ this.removeList(removeFromSelectionList);
+ this.addList(addToSelectionList);
+ }
+}
+
EntitySelection.prototype.addList = function(ents)
{
var selectionSize = this.toList().length;
var i = 1;
var added = [];
for each (var ent in ents)
{
if (!this.selected[ent] && (selectionSize + i) <= MAX_SELECTION_SIZE)
{
added.push(ent);
this.selected[ent] = ent;
i++;
}
}
_setHighlight(added, 1);
_setStatusBars(added, true);
_setMotionOverlay(added, this.motionDebugOverlay);
if (added.length)
_playSound(added[0]);
this.groups.add(this.toList()); // Create Selection Groups
this.dirty = true;
};
EntitySelection.prototype.removeList = function(ents)
{
var removed = [];
for each (var ent in ents)
{
if (this.selected[ent])
{
this.groups.removeEnt(ent);
removed.push(ent);
delete this.selected[ent];
}
}
_setHighlight(removed, 0);
_setStatusBars(removed, false);
_setMotionOverlay(removed, false);
this.dirty = true;
};
EntitySelection.prototype.reset = function()
{
_setHighlight(this.toList(), 0);
_setStatusBars(this.toList(), false);
_setMotionOverlay(this.toList(), false);
this.selected = {};
this.groups.reset();
this.dirty = true;
};
EntitySelection.prototype.toList = function()
{
var ents = [];
for each (var ent in this.selected)
ents.push(ent);
return ents;
};
EntitySelection.prototype.setHighlightList = function(ents)
{
var highlighted = {};
for each (var ent in ents)
highlighted[ent] = ent;
var removed = [];
var added = [];
// Remove highlighting for the old units that are no longer highlighted
// (excluding ones that are actively selected too)
for each (var ent in this.highlighted)
if (!highlighted[ent] && !this.selected[ent])
removed.push(+ent);
// Add new highlighting for units that aren't already highlighted
for each (var ent in ents)
if (!this.highlighted[ent] && !this.selected[ent])
added.push(+ent);
_setHighlight(removed, 0);
_setStatusBars(removed, false);
_setHighlight(added, 0.5);
_setStatusBars(added, true);
// Store the new highlight list
this.highlighted = highlighted;
};
EntitySelection.prototype.SetMotionDebugOverlay = function(enabled)
{
this.motionDebugOverlay = enabled;
_setMotionOverlay(this.toList(), enabled);
};
var g_Selection = new EntitySelection();
//-------------------------------- -------------------------------- --------------------------------
// EntityGroupsContainer class for managing grouped entities
//-------------------------------- -------------------------------- --------------------------------
function EntityGroupsContainer()
{
this.groups = {};
for (var i = 0; i < 10; ++i)
{
this.groups[i] = new EntityGroups();
}
}
EntityGroupsContainer.prototype.addEntities = function(groupName, ents)
{
for each (var ent in ents)
{
for each (var group in this.groups)
{
if (ent in group.ents)
{
group.removeEnt(ent);
}
}
}
this.groups[groupName].add(ents);
}
EntityGroupsContainer.prototype.update = function()
{
for each (var group in this.groups)
{
for (var ent in group.ents)
{
var entState = GetEntityState(+ent);
// Remove deleted units
if (!entState)
{
group.removeEnt(ent);
}
}
}
}
var g_Groups = new EntityGroupsContainer();
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 9391)
@@ -1,716 +1,723 @@
Return to Main MenuleaveGame()
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 9391)
@@ -1,262 +1,281 @@
const RESOURCE_ICON_CELL_IDS = {food : 0, wood : 1, stone : 2, metal : 3};
function layoutSelectionSingle()
{
getGUIObjectByName("detailsAreaSingle").hidden = false;
getGUIObjectByName("detailsAreaMultiple").hidden = true;
// getGUIObjectByName("specific").hidden = false;
// getGUIObjectByName("iconBorder").hidden = false;
//getGUIObjectByName("attackIcon").size = "0 0 48 48";
//getGUIObjectByName("armourIcon").size = "0 48 48 96";
//getGUIObjectByName("barsArea").size = "50%+48 40 100% 136"
}
function layoutSelectionMultiple()
{
// getGUIObjectByName("specific").hidden = true;
// getGUIObjectByName("iconBorder").hidden = true;
//getGUIObjectByName("attackIcon").size = "-4 10 32 46";
//getGUIObjectByName("armourIcon").size = "-4 46 32 82";
//getGUIObjectByName("barsArea").size = "50%+60 40 100% 136"
getGUIObjectByName("detailsAreaMultiple").hidden = false;
getGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Fills out information that most entities have
function displaySingle(entState, template)
{
// Get general unit and player data
var specificName = template.name.specific;
var genericName = template.name.generic != template.name.specific? template.name.generic : "";
var civName = g_CivData[g_Players[entState.player].civ].Name;
var playerName = g_Players[entState.player].name;
var playerColor = g_Players[entState.player].color.r + " " + g_Players[entState.player].color.g + " " +
g_Players[entState.player].color.b+ " " + g_Players[entState.player].color.a;
// Indicate disconnected players by prefixing their name
if (g_Players[entState.player].offline)
{
playerName = "[OFFLINE] " + playerName;
}
// Rank
getGUIObjectByName("rankIcon").cell_id = getRankIconCellId(entState);
// Hitpoints
var hitpoints = "";
if (entState.hitpoints)
{
var unitHealthBar = getGUIObjectByName("healthBar");
var healthSize = unitHealthBar.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
unitHealthBar.size = healthSize;
hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + entState.hitpoints + "/" + entState.maxHitpoints;
getGUIObjectByName("health").tooltip = hitpoints;
getGUIObjectByName("health").hidden = false;
}
else
{
getGUIObjectByName("health").hidden = true;
}
+
+ // Experience
+ if (entState.promotion)
+ {
+ var experienceBar = getGUIObjectByName("experienceBar");
+ var experienceSize = experienceBar.size;
+ experienceSize.rtop = 100 - 100 * Math.max(0, Math.min(1, 1.0 * entState.promotion.curr / entState.promotion.req));
+ experienceBar.size = experienceSize;
+
+ var experience = "[font=\"serif-bold-13\"]XP [/font]" + entState.promotion.curr;
+ if (entState.promotion.curr < entState.promotion.req)
+ experience += "/" + entState.promotion.req;
+ getGUIObjectByName("experience").tooltip = experience;
+ getGUIObjectByName("experience").hidden = false;
+ }
+ else
+ {
+ getGUIObjectByName("experience").hidden = true;
+ }
// Resource stats
var resources = "";
var resourceType = "";
if (entState.resourceSupply)
{
resources = Math.ceil(+entState.resourceSupply.amount) + "/" + entState.resourceSupply.max;
resourceType = entState.resourceSupply.type["generic"];
if (resourceType == "treasure")
resourceType = entState.resourceSupply.type["specific"];
var unitResourceBar = getGUIObjectByName("resourceBar");
var resourceSize = unitResourceBar.size;
resourceSize.rtop = 100-100*Math.max(0, Math.min(1, +entState.resourceSupply.amount / entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
var unitResources = getGUIObjectByName("resources");
unitResources.tooltip = "[font=\"serif-bold-13\"]Resources: [/font]" + resources + " " + resourceType;
if (!entState.hitpoints)
unitResources.size = getGUIObjectByName("health").size;
else
unitResources.size = getGUIObjectByName("stamina").size;
getGUIObjectByName("resources").hidden = false;
}
else
{
getGUIObjectByName("resources").hidden = true;
}
// Resource carrying
if (entState.resourceCarrying && entState.resourceCarrying.length)
{
// (we should only be carrying one resource type at once, so just display the first)
var carried = entState.resourceCarrying[0];
// if (carried.amount >= 1)
// {
getGUIObjectByName("resourceCarryingIcon").hidden = false;
getGUIObjectByName("resourceCarryingText").hidden = false;
getGUIObjectByName("resourceCarryingIcon").cell_id = RESOURCE_ICON_CELL_IDS[carried.type];
getGUIObjectByName("resourceCarryingText").caption = carried.amount + "/" + carried.max;
// }
// else
// {
// getGUIObjectByName("resourceCarryingIcon").hidden = true;
// getGUIObjectByName("resourceCarryingText").hidden = true;
// }
}
else
{
getGUIObjectByName("resourceCarryingIcon").hidden = true;
getGUIObjectByName("resourceCarryingText").hidden = true;
}
// Set Captions
getGUIObjectByName("specific").caption = specificName;
getGUIObjectByName("player").caption = playerName;
getGUIObjectByName("player").textcolor = playerColor;
// Icon image
if (template.icon)
{
getGUIObjectByName("icon").sprite = "stretched:session/portraits/" + template.icon;
}
else
{
// TODO: we should require all entities to have icons, so this case never occurs
getGUIObjectByName("icon").sprite = "bkFillBlack";
}
// Tooltips
getGUIObjectByName("specific").tooltip = genericName;
getGUIObjectByName("player").tooltip = civName;
getGUIObjectByName("health").tooltip = hitpoints;
// getGUIObjectByName("attackIcon").tooltip = damageTypesToText(entState.attack);
getGUIObjectByName("armourIcon").tooltip = "[font=\"serif-bold-16\"]Attack: [/font]" + damageTypesToText(entState.attack) +
"\n[font=\"serif-bold-16\"]Armor: [/font]" + damageTypesToText(entState.armour);
// Icon Tooltip
var iconTooltip = "";
if (genericName)
iconTooltip = "[font=\"serif-bold-16\"]" + genericName + "[/font]";
if (template.tooltip)
iconTooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
getGUIObjectByName("iconBorder").tooltip = iconTooltip;
// Unhide Details Area
getGUIObjectByName("detailsAreaSingle").hidden = false;
getGUIObjectByName("detailsAreaMultiple").hidden = true;
}
// Fills out information for multiple entities
function displayMultiple(selection, template)
{
var averageHealth = 0;
var maxHealth = 0;
for (var i = 0; i < selection.length; i++)
{
var entState = GetEntityState(selection[i])
if (entState)
{
if (entState.hitpoints)
{
averageHealth += entState.hitpoints;
maxHealth += entState.maxHitpoints;
}
}
}
if (averageHealth > 0)
{
var unitHealthBar = getGUIObjectByName("healthBarMultiple");
var healthSize = unitHealthBar.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
unitHealthBar.size = healthSize;
var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + "/" + maxHealth;
var healthMultiple = getGUIObjectByName("healthMultiple");
healthMultiple.tooltip = hitpoints;
healthMultiple.hidden = false;
}
else
{
getGUIObjectByName("healthMultiple").hidden = true;
}
getGUIObjectByName("numberOfUnits").caption = selection.length;
// Unhide Details Area
getGUIObjectByName("detailsAreaMultiple").hidden = false;
getGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates middle entity Selection Details Panel
function updateSelectionDetails()
{
var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
var detailsPanel = getGUIObjectByName("selectionDetails");
var commandsPanel = getGUIObjectByName("unitCommands");
g_Selection.update();
var selection = g_Selection.toList();
if (selection.length == 0)
{
getGUIObjectByName("detailsAreaMultiple").hidden = true;
getGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands();
supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true;
commandsPanel.hidden = true;
// getGUIObjectByName("unitSelectionPanel").hidden = true;
return;
}
/* If the unit has no data (e.g. it was killed), don't try displaying any
data for it. (TODO: it should probably be removed from the selection too;
also need to handle multi-unit selections) */
var entState = GetEntityState(selection[0]);
if (!entState)
return;
var template = GetTemplateData(entState.template);
// Fill out general info and display it
if (selection.length == 1)
displaySingle(entState, template);
else
displayMultiple(selection, template);
var player = Engine.GetPlayerID();
if (entState.player == player || g_DevSettings.controlAll)
{
//if (entState.stamina != undefined)
getGUIObjectByName("stamina").hidden = false;
//else
// getGUIObjectByName("stamina").hidden = true;
}
// Show Panels
supplementalDetailsPanel.hidden = false;
detailsPanel.hidden = false;
commandsPanel.hidden = false;
-
+
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/sprites.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/sprites.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/gui/session/sprites.xml (revision 9391)
@@ -1,766 +1,774 @@
+
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 9391)
@@ -1,541 +1,565 @@
function GuiInterface() {}
GuiInterface.prototype.Schema =
"";
GuiInterface.prototype.Serialize = function()
{
// This component isn't network-synchronised so we mustn't serialise
// its non-deterministic data. Instead just return an empty object.
return {};
};
GuiInterface.prototype.Deserialize = function(obj)
{
this.Init();
};
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
this.rallyPoints = undefined;
this.notifications = [];
+ this.renamedEntities = [];
};
/*
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
* from GUI scripts, and executed here with arguments (player, arg).
*/
/**
* Returns global information about the current game state.
* This is used by the GUI and also by AI scripts.
*/
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
"players": []
};
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
var playerData = {
"name": cmpPlayer.GetName(),
"civ": cmpPlayer.GetCiv(),
"colour": cmpPlayer.GetColour(),
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
"trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(),
"state": cmpPlayer.GetState(),
"team": cmpPlayer.GetTeam(),
"diplomacy": cmpPlayer.GetDiplomacy(),
"phase": cmpPlayer.GetPhase()
};
ret.players.push(playerData);
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
{
ret.circularMap = cmpRangeManager.GetLosCircular();
}
// Add timeElapsed
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
ret.timeElapsed = cmpTimer.GetTime();
return ret;
};
GuiInterface.prototype.GetExtendedSimulationState = function(player)
{
// Get basic simulation info
var ret = this.GetSimulationState();
// Add statistics to each player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
}
return ret;
};
+GuiInterface.prototype.GetRenamedEntities = function(player, clearList)
+{
+ var result = this.renamedEntities;
+ if (clearList)
+ this.renamedEntities = [];
+ return result;
+};
+
GuiInterface.prototype.GetEntityState = function(player, ent)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// All units must have a template; if not then it's a nonexistent entity id
var template = cmpTempMan.GetCurrentTemplateName(ent);
if (!template)
return null;
var ret = {
"id": ent,
"template": template
}
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
ret.identity = {
"rank": cmpIdentity.GetRank(),
"classes": cmpIdentity.GetClassesList()
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
{
ret.position = cmpPosition.GetPosition();
}
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
{
ret.hitpoints = cmpHealth.GetHitpoints();
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
}
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
if (cmpAttack)
{
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show?
ret.attack = cmpAttack.GetAttackStrengths(type);
}
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
if (cmpArmour)
{
ret.armour = cmpArmour.GetArmourStrengths();
}
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
{
ret.buildEntities = cmpBuilder.GetEntitiesList();
}
var cmpTrainingQueue = Engine.QueryInterface(ent, IID_TrainingQueue);
if (cmpTrainingQueue)
{
ret.training = {
"entities": cmpTrainingQueue.GetEntitiesList(),
"queue": cmpTrainingQueue.GetQueue(),
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
ret.foundation = {
"progress": cmpFoundation.GetBuildPercentage()
};
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
{
ret.player = cmpOwnership.GetOwner();
}
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply)
{
ret.resourceSupply = {
"max": cmpResourceSupply.GetMaxAmount(),
"amount": cmpResourceSupply.GetCurrentAmount(),
"type": cmpResourceSupply.GetType()
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
}
var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
ret.resourceDropsite = {
"types": cmpResourceDropsite.GetTypes()
};
}
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = { };
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
ret.garrisonHolder = {
"entities": cmpGarrisonHolder.GetEntities(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClassesList()
};
}
+ var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
+ if (cmpPromotion)
+ {
+ ret.promotion = {
+ "curr": cmpPromotion.GetCurrentXp(),
+ "req": cmpPromotion.GetRequiredXp()
+ };
+ }
+
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
return ret;
};
GuiInterface.prototype.GetTemplateData = function(player, name)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(name);
if (!template)
return null;
var ret = {};
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
}
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food) ret.cost.food = +template.Cost.Resources.food;
if (template.Cost.Resources.wood) ret.cost.wood = +template.Cost.Resources.wood;
if (template.Cost.Resources.stone) ret.cost.stone = +template.Cost.Resources.stone;
if (template.Cost.Resources.metal) ret.cost.metal = +template.Cost.Resources.metal;
if (template.Cost.Population) ret.cost.population = +template.Cost.Population;
if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus;
}
return ret;
};
GuiInterface.prototype.PushNotification = function(notification)
{
this.notifications.push(notification);
};
GuiInterface.prototype.GetNextNotification = function()
{
if (this.notifications.length)
return this.notifications.pop();
else
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);
var playerColours = {}; // cache of owner -> colour map
for each (var ent in cmd.entities)
{
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
if (!cmpSelectable)
continue;
if (cmd.alpha == 0)
{
cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0});
continue;
}
// Find the entity's owner's colour:
var owner = -1;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
var colour = playerColours[owner];
if (!colour)
{
colour = [1, 1, 1];
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
if (cmpPlayer)
colour = cmpPlayer.GetColour();
playerColours[owner] = colour;
}
cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha});
}
};
GuiInterface.prototype.SetStatusBars = function(player, cmd)
{
for each (var ent in cmd.entities)
{
var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
if (cmpStatusBars)
cmpStatusBars.SetEnabled(cmd.enabled);
}
};
/**
* Displays the rally point of a building
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
// If there are rally points already displayed, destroy them
for each (var ent in this.rallyPoints)
{
// Hide it first (the destruction won't be instantaneous)
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.MoveOutOfWorld();
Engine.DestroyEntity(ent);
}
this.rallyPoints = [];
var positions = [];
// DisplayRallyPoints is called passing a list of entities for which
// rally points must be displayed
for each (var ent in cmd.entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = {"x": cmd.x, "z": cmd.z};
else
pos = cmpRallyPoint.GetPosition();
if (pos)
{
// TODO: it'd probably be nice if we could draw some kind of line
// between the building and pos, to make the marker easy to find even
// if it's a long way from the building
positions.push(pos);
}
}
// Add rally point entity for each building
for each (var pos in positions)
{
var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
cmpPosition.JumpTo(pos.x, pos.z);
this.rallyPoints.push(rallyPoint);
}
};
/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
* Returns true if the placement is okay (everything is valid and the entity is not obstructed by others).
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
// Destroy the old preview if there was one
if (this.placementEntity)
Engine.DestroyEntity(this.placementEntity[1]);
// Load the new template
if (cmd.template == "")
{
this.placementEntity = undefined;
}
else
{
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
}
}
if (this.placementEntity)
{
var ent = this.placementEntity[1];
// Move the preview into the right location
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
// Check whether it's obstructed by other entities
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
var colliding = (cmpObstruction && cmpObstruction.CheckFoundationCollisions());
// Check whether it's in a visible region
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible");
var ok = (!colliding && visible);
// Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (!ok)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
return ok;
}
return false;
};
GuiInterface.prototype.PlaySound = function(player, data)
{
// Ignore if no entity was passed
if (!data.entity)
return;
PlaySound(data.name, data.entity);
};
function isIdleWorker(ent, idleClass)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && idleClass && cmpIdentity.HasClass(idleClass));
}
GuiInterface.prototype.FindIdleWorker = function(player, data)
{
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerEntities = rangeMan.GetEntitiesByPlayer(player);
// Find the first matching entity that is after the previous selection,
// so that we cycle around in a predictable order
for each (var ent in playerEntities)
{
if (ent > data.prevWorker && isIdleWorker(ent, data.idleClass))
return ent;
}
// No idle entities left in the class
return 0;
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
cmpPathfinder.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
{
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
cmpObstructionManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for each (var ent in data.entities)
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetDebugOverlay(enabled);
};
+GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
+{
+ this.renamedEntities.push(msg);
+}
+
// List the GuiInterface functions that can be safely called by GUI scripts.
// (GUI scripts are non-deterministic and untrusted, so these functions must be
// appropriately careful. They are called with a first argument "player", which is
// trusted and indicates the player associated with the current client; no data should
// be returned unless this player is meant to be able to see it.)
var exposedFunctions = {
"GetSimulationState": 1,
"GetExtendedSimulationState": 1,
+ "GetRenamedEntities": 1,
"GetEntityState": 1,
"GetTemplateData": 1,
"GetNextNotification": 1,
"CanMoveEntsIntoFormation": 1,
"SetSelectionHighlight": 1,
"SetStatusBars": 1,
"DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1,
"PlaySound": 1,
"FindIdleWorker": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1,
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
{
if (exposedFunctions[name])
return this[name](player, args);
else
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
};
Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Foundation.js (revision 9391)
@@ -1,204 +1,206 @@
function Foundation() {}
Foundation.prototype.Schema =
"";
Foundation.prototype.Init = function()
{
// Foundations are initially 'uncommitted' and do not block unit movement at all
// (to prevent players exploiting free foundations to confuse enemy units).
// The first builder to reach the uncommitted foundation will tell friendly units
// and animals to move out of the way, then will commit the foundation and enable
// its obstruction once there's nothing in the way.
this.committed = false;
this.buildProgress = 0.0; // 0 <= progress <= 1
// Set up a timer so we can count the number of builders in a 1-second period.
// (We assume each builder only builds once per second, which is what UnitAI
// implements.)
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetInterval(this.entity, IID_Foundation, "UpdateTimeout", 1000, 1000, {});
this.recentBuilders = []; // builder entities since the last timeout
this.numRecentBuilders = 0; // number of builder entities as of the last timeout
};
Foundation.prototype.UpdateTimeout = function()
{
this.numRecentBuilders = this.recentBuilders.length;
this.recentBuilders = [];
Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.numRecentBuilders);
};
Foundation.prototype.InitialiseConstruction = function(owner, template)
{
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
this.finalTemplateName = template;
this.addedHitpoints = cmpHealth.GetHitpoints();
this.maxHitpoints = cmpHealth.GetMaxHitpoints();
// We need to know the owner in OnDestroy, but at that point the entity has already been
// decoupled from its owner, so we need to remember it in here (and assume it won't change)
this.owner = owner;
this.initialised = true;
};
Foundation.prototype.GetBuildPercentage = function()
{
return Math.floor(this.buildProgress * 100);
};
Foundation.prototype.IsFinished = function()
{
return (this.buildProgress >= 1.0);
};
Foundation.prototype.OnDestroy = function()
{
// Refund a portion of the construction cost, proportional to the amount of build progress remaining
if (!this.initialised) // this happens if the foundation was destroyed because the player had insufficient resources
return;
if (this.buildProgress == 1.0)
return;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(this.owner), IID_Player);
var cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
var costs = cmpCost.GetResourceCosts();
for (var r in costs)
{
var scaled = Math.floor(costs[r] * (1.0 - this.buildProgress));
if (scaled)
cmpPlayer.AddResource(r, scaled);
}
// Reset the timer
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
};
/**
* Perform some number of seconds of construction work.
* Returns true if the construction is completed.
*/
Foundation.prototype.Build = function(builderEnt, work)
{
// Do nothing if we've already finished building
// (The entity will be destroyed soon after completion so
// this won't happen much)
if (this.buildProgress == 1.0)
return;
// Handle the initial 'committing' of the foundation
if (!this.committed)
{
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (cmpObstruction)
{
// If there's any units in the way, ask them to move away
// and return early from this method.
// Otherwise enable this obstruction so it blocks any further
// units, and continue building.
var collisions = cmpObstruction.GetConstructionCollisions();
if (collisions.length)
{
for each (var ent in collisions)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.LeaveFoundation(this.entity);
// TODO: What if an obstruction has no UnitAI?
}
// TODO: maybe we should tell the builder to use a special
// animation to indicate they're waiting for people to get
// out the way
return;
}
// The obstruction always blocks new foundations/construction,
// but we've temporarily allowed units to walk all over it
// (via CCmpTemplateManager). Now we need to remove that temporary
// blocker-disabling, so that we'll perform standard unit blocking instead.
cmpObstruction.SetDisableBlockMovementPathfinding(false);
}
this.committed = true;
}
// Calculate the amount of progress that will be added (where 1.0 = completion)
var cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
var amount = work / cmpCost.GetBuildTime();
// Record this builder so we can count the total number
this.recentBuilders.push(builderEnt);
// TODO: implement some kind of diminishing returns for multiple builders.
// e.g. record the set of entities that build this, then every ~2 seconds
// count them (and reset the list), and apply some function to the count to get
// a factor, and apply that factor here.
this.buildProgress += amount;
if (this.buildProgress > 1.0)
this.buildProgress = 1.0;
// Add an appropriate proportion of hitpoints
var targetHP = Math.max(0, Math.min(this.maxHitpoints, Math.floor(this.maxHitpoints * this.buildProgress)));
var deltaHP = targetHP - this.addedHitpoints;
if (deltaHP > 0)
{
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
cmpHealth.Increase(deltaHP);
this.addedHitpoints += deltaHP;
}
if (this.buildProgress >= 1.0)
{
// Finished construction
// Create the real entity
var building = Engine.AddEntity(this.finalTemplateName);
// Copy various parameters from the foundation
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
var pos = cmpPosition.GetPosition();
cmpBuildingPosition.JumpTo(pos.x, pos.z);
var rot = cmpPosition.GetRotation();
cmpBuildingPosition.SetYRotation(rot.y);
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
// TODO: should add a ICmpPosition::CopyFrom() instead of all this
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter();
-
+
var cmpIdentity = Engine.QueryInterface(building, IID_Identity);
if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter();
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
Engine.PostMessage(this.entity, MT_ConstructionFinished,
{ "entity": this.entity, "newentity": building });
+ Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: building });
+
Engine.DestroyEntity(this.entity);
}
};
Engine.RegisterComponentType(IID_Foundation, "Foundation", Foundation);
Index: ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 9391)
@@ -1,272 +1,284 @@
function ResourceGatherer() {}
ResourceGatherer.prototype.Schema =
"Lets the unit gather resources from entities that have the ResourceSupply component." +
"" +
"2.0" +
"1.0" +
"" +
"1" +
"3" +
"3" +
"2" +
"" +
"" +
"10" +
"10" +
"10" +
"10" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
ResourceGatherer.prototype.Init = function()
{
this.carrying = {}; // { generic type: integer amount currently carried }
// (Note that this component supports carrying multiple types of resources,
// each with an independent capacity, but the rest of the game currently
// ensures and assumes we'll only be carrying one type at once)
// The last exact type gathered, so we can render appropriate props
this.lastCarriedType = undefined; // { generic, specific }
};
/**
* Returns data about what resources the unit is currently carrying,
* in the form [ {"type":"wood", "amount":7, "max":10} ]
*/
ResourceGatherer.prototype.GetCarryingStatus = function()
{
var ret = [];
for (var type in this.carrying)
{
ret.push({
"type": type,
"amount": this.carrying[type],
"max": +this.template.Capacities[type]
});
}
return ret;
};
/**
+ * Used to instantly give resources to unit
+ * @param resources The same structure as returned form GetCarryingStatus
+ */
+ResourceGatherer.prototype.GiveResources = function(resources)
+{
+ for each (var resource in resources)
+ {
+ this.carrying[resource.type] = +(resource.amount);
+ }
+};
+
+/**
* Returns the generic type of one particular resource this unit is
* currently carrying, or undefined if none.
*/
ResourceGatherer.prototype.GetMainCarryingType = function()
{
// Return the first key, if any
for (var type in this.carrying)
return type;
return undefined;
};
/**
* Returns the exact resource type we last picked up, as long as
* we're still carrying something similar enough, in the form
* { generic, specific }
*/
ResourceGatherer.prototype.GetLastCarriedType = function()
{
if (this.lastCarriedType && this.lastCarriedType.generic in this.carrying)
return this.lastCarriedType;
else
return undefined;
};
ResourceGatherer.prototype.GetGatherRates = function()
{
var ret = {};
for (var r in this.template.Rates)
ret[r] = this.template.Rates[r] * this.template.BaseSpeed;
return ret;
};
ResourceGatherer.prototype.GetRange = function()
{
return { "max": +this.template.MaxDistance, "min": 0 };
// maybe this should depend on the unit or target or something?
}
/**
* Try to gather treasure
* @return 'true' if treasure is successfully gathered and 'false' in the other case
*/
ResourceGatherer.prototype.TryInstantGather = function(target)
{
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
if (type.generic != "treasure") return false;
var status = cmpResourceSupply.TakeResources(cmpResourceSupply.GetCurrentAmount());
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
cmpPlayer.AddResource(type.specific, status.amount);
var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseTreasuresCollectedCounter();
return true;
};
/**
* Gather from the target entity. This should only be called after a successful range check,
* and if the target has a compatible ResourceSupply.
* It should be called at a rate of once per second.
*/
ResourceGatherer.prototype.PerformGather = function(target)
{
var rate = this.GetTargetGatherRate(target);
if (!rate)
return { "exhausted": true };
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
// Initialise the carried count if necessary
if (!this.carrying[type.generic])
this.carrying[type.generic] = 0;
// Find the maximum so we won't exceed our capacity
var maxGathered = this.template.Capacities[type.generic] - this.carrying[type.generic];
var status = cmpResourceSupply.TakeResources(Math.min(rate, maxGathered));
this.carrying[type.generic] += status.amount;
this.lastCarriedType = type;
// Update stats of how much the player collected.
// (We have to do it here rather than at the dropsite, because we
// need to know what subtype it was)
var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific);
// Tell the target we're gathering from it
Engine.PostMessage(target, MT_ResourceGather,
{ "entity": target, "gatherer": this.entity });
return {
"amount": status.amount,
"exhausted": status.exhausted,
"filled": (this.carrying[type.generic] >= this.template.Capacities[type.generic])
};
};
/**
* Compute the amount of resources collected per second from the target.
* Returns 0 if resources cannot be collected (e.g. the target doesn't
* exist, or is the wrong type).
*/
ResourceGatherer.prototype.GetTargetGatherRate = function(target)
{
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
if (!cmpResourceSupply)
return 0;
var type = cmpResourceSupply.GetType();
var rate;
if (type.specific && this.template.Rates[type.generic+"."+type.specific])
rate = this.template.Rates[type.generic+"."+type.specific];
else
rate = this.template.Rates[type.generic];
return (rate || 0) * this.template.BaseSpeed;
}
/**
* Returns whether this unit can carry more of the given type of resource.
* (This ignores whether the unit is actually able to gather that
* resource type or not.)
*/
ResourceGatherer.prototype.CanCarryMore = function(type)
{
var amount = (this.carrying[type] || 0);
return (amount < this.template.Capacities[type]);
};
/**
* Returns whether this unit is carrying any resources of a type that is
* not the requested type. (This is to support cases where the unit is
* only meant to be able to carry one type at once.)
*/
ResourceGatherer.prototype.IsCarryingAnythingExcept = function(exceptedType)
{
for (var type in this.carrying)
if (type != exceptedType)
return true;
return false;
};
/**
* Transfer our carried resources to our owner immediately.
* Only resources of the given types will be transferred.
* (This should typically be called after reaching a dropsite).
*/
ResourceGatherer.prototype.CommitResources = function(types)
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
for each (var type in types)
{
if (type in this.carrying)
{
cmpPlayer.AddResource(type, this.carrying[type]);
delete this.carrying[type];
}
}
};
/**
* Drop all currently-carried resources.
* (Currently they just vanish after being dropped - we don't bother depositing
* them onto the ground.)
*/
ResourceGatherer.prototype.DropResources = function()
{
this.carrying = {};
};
Engine.RegisterComponentType(IID_ResourceGatherer, "ResourceGatherer", ResourceGatherer);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Loot.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Loot.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Loot.js (revision 9391)
@@ -1,26 +1,37 @@
function Loot() {}
Loot.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
-/*
- * TODO: this all needs to be designed and implemented
- */
-
Loot.prototype.Serialize = null; // we have no dynamic state to save
+Loot.prototype.GetXp = function()
+{
+ return this.template.xp;
+};
+
+Loot.prototype.GetResources = function()
+{
+ return {
+ "food": +(this.template.food || 0),
+ "wood": +(this.template.wood || 0),
+ "metal": +(this.template.metal || 0),
+ "stone": +(this.template.stone || 0)
+ };
+};
+
Engine.RegisterComponentType(IID_Loot, "Loot", Loot);
Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 9391)
@@ -1,291 +1,299 @@
function GarrisonHolder() {}
GarrisonHolder.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
/**
* Initialize GarrisonHolder Component
*/
GarrisonHolder.prototype.Init = function()
{
// Garrisoned Units
this.entities = [];
this.spaceOccupied = 0;
this.timer = undefined;
this.healRate = this.template.BuffHeal;
};
/**
* Return the list of entities garrisoned inside
*/
GarrisonHolder.prototype.GetEntities = function()
{
return this.entities;
}
/**
* Returns an array of unit classes which can be garrisoned inside this
* particualar entity. Obtained from the entity's template
*/
GarrisonHolder.prototype.GetAllowedClassesList = function()
{
var string = this.template.List._string;
return string.split(/\s+/);
};
/**
* Get Maximum pop which can be garrisoned
*/
GarrisonHolder.prototype.GetCapacity = function()
{
return this.template.Max;
};
/**
* Get number of garrisoned units capable of shooting arrows
* Not necessarily archers
*/
GarrisonHolder.prototype.GetGarrisonedArcherCount = function()
{
var count = 0;
for each (var entity in this.entities)
{
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
var classes = cmpIdentity.GetClassesList();
if (classes.indexOf("Infantry") != -1 || classes.indexOf("Ranged") != -1)
count++;
}
return count;
}
/**
* Checks if an entity can be allowed to garrison in the building
* based on it's class
*/
GarrisonHolder.prototype.AllowedToGarrison = function(entity)
{
var allowedClasses = this.GetAllowedClassesList();
var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
// Check if the unit is allowed to be garrisoned inside the building
for each (var allowedClass in allowedClasses)
{
if (entityClasses.indexOf(allowedClass) != -1)
{
return true;
}
}
return false;
};
/**
* Garrison a unit inside.
* Returns true if successful, false if not
* The timer for AutoHeal is started here
*/
GarrisonHolder.prototype.Garrison = function(entity)
{
var entityPopCost = (Engine.QueryInterface(entity, IID_Cost)).GetPopCost();
var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList();
if (!this.HasEnoughHealth())
return false;
// Check if the unit is allowed to be garrisoned inside the building
if(!this.AllowedToGarrison(entity))
return false;
if (this.GetCapacity() < this.spaceOccupied + 1)
return false;
if (!this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
}
var cmpPosition = Engine.QueryInterface(entity, IID_Position);
if (cmpPosition)
{
// Actual garrisoning happens here
this.entities.push(entity);
this.spaceOccupied += 1;
cmpPosition.MoveOutOfWorld();
this.UpdateGarrisonFlag();
return true;
}
return false;
};
/**
* Simply eject the unit from the garrisoning entity without
* moving it
*/
GarrisonHolder.prototype.Eject = function(entity)
{
var entityIndex = this.entities.indexOf(entity);
this.spaceOccupied -= 1;
this.entities.splice(entityIndex, 1);
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
var pos = cmpFootprint.PickSpawnPoint(entity);
if (pos.y < 0)
{
// Whoops, something went wrong (maybe there wasn't any space to place the unit).
// What should we do here?
// For now, just move the unit into the middle of the building where it'll probably get stuck
pos = cmpPosition.GetPosition();
warn("Can't find free space to spawn trained unit");
}
var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
// TODO: what direction should they face in?
}
/**
* Order entities to walk to the Rally Point
*/
GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities)
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
if (cmpRallyPoint)
{
var rallyPos = cmpRallyPoint.GetPosition();
if (rallyPos)
{
ProcessCommand(cmpOwnership.GetOwner(), {
"type": "walk",
"entities": entities,
"x": rallyPos.x,
"z": rallyPos.z,
"queued": false
});
}
}
}
/**
* Unload units from the garrisoning entity and order them
* to move to the Rally Point
*/
GarrisonHolder.prototype.Unload = function(entity)
{
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
this.Eject(entity);
this.OrderWalkToRallyPoint([entity]);
this.UpdateGarrisonFlag();
};
/**
* Unload all units from the entity
*/
GarrisonHolder.prototype.UnloadAll = function()
{
//The entities list is saved to a temporary variable
//because during each loop an element is removed
//from the list
var entities = this.entities.splice(0);
for each (var entity in entities)
{
this.Eject(entity);
}
this.OrderWalkToRallyPoint(entities);
this.UpdateGarrisonFlag();
};
/**
* Used to check if the garrisoning entity's health has fallen below
* a certain limit after which all garrisoned units are unloaded
*/
GarrisonHolder.prototype.OnHealthChanged = function(msg)
{
if (!this.HasEnoughHealth())
{
this.UnloadAll();
}
};
/**
* Check if this entity has enough health to garrison units inside it
*/
GarrisonHolder.prototype.HasEnoughHealth = function()
{
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health)
var hitpoints = cmpHealth.GetHitpoints();
var maxHitpoints = cmpHealth.GetMaxHitpoints();
var ejectHitpoints = parseInt(parseFloat(this.template.EjectHealth) * maxHitpoints);
return hitpoints > ejectHitpoints;
};
/**
* Called every second. Heals garrisoned units
*/
GarrisonHolder.prototype.HealTimeout = function(data)
{
if (this.entities.length == 0)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
}
else
{
for each (var entity in this.entities)
{
var cmpHealth = Engine.QueryInterface(entity, IID_Health);
if (cmpHealth)
{
if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
cmpHealth.Increase(this.healRate);
}
}
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
}
};
GarrisonHolder.prototype.UpdateGarrisonFlag = function()
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
cmpVisual.SelectAnimation("garrisoned", true, 0, "");
// TODO: ought to extend ICmpVisual to let us just select variant
// keywords without changing the animation too
if (this.entities.length)
cmpVisual.SelectAnimation("garrisoned", false, 1.0, "");
else
cmpVisual.SelectAnimation("idle", false, 1.0, "");
}
GarrisonHolder.prototype.OnOwnershipChanged = function(msg)
{
};
/**
* Cancel timer when destroyed
*/
GarrisonHolder.prototype.OnDestroy = function()
{
if (this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
+GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
+{
+ if (this.entities.indexOf(msg.entity) != -1)
+ {
+ this.entities[this.entities.indexOf(msg.entity)] = msg.newentity;
+ }
+}
+
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js (revision 9391)
@@ -1,248 +1,249 @@
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Identity.js");
+Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/RallyPoint.js");
Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/TrainingQueue.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("GuiInterface.js");
var cmp = ConstructComponent(SYSTEM_ENTITY, "GuiInterface");
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
GetNumPlayers: function() { return 2; },
GetPlayerByID: function(id) { TS_ASSERT(id === 0 || id === 1); return 100+id; }
});
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
GetCurrentTemplateName: function(ent) { return "example"; },
GetTemplate: function(name) { return ""; },
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
GetLosVisibility: function(ent, player) { return "visible"; },
GetLosCircular: function() { return false; },
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
GetTime: function() { return 0; },
SetTimeout: function(ent, iid, funcname, time, data) { return 0; },
});
AddMock(100, IID_Player, {
GetName: function() { return "Player 1"; },
GetCiv: function() { return "gaia"; },
GetColour: function() { return { r: 1, g: 1, b: 1, a: 1}; },
GetPopulationCount: function() { return 10; },
GetPopulationLimit: function() { return 20; },
GetResourceCounts: function() { return { food: 100 }; },
IsTrainingQueueBlocked: function() { return false; },
GetState: function() { return "active"; },
GetTeam: function() { return -1; },
GetDiplomacy: function() { return []; },
GetPhase: function() { return ""; },
GetConquestCriticalEntitiesCount: function() { return 1; },
});
AddMock(100, IID_StatisticsTracker, {
GetStatistics: function() {
return {
"unitsTrained": 10,
"unitsLost": 9,
"buildingsConstructed": 5,
"buildingsLost": 4,
"civCentresBuilt": 1,
"resourcesGathered": {
"food": 100,
"wood": 0,
"metal": 0,
"stone": 0,
"vegetarianFood": 0,
},
"treasuresCollected": 0,
"percentMapExplored": 10,
};
},
IncreaseTrainedUnitsCounter: function() { return 1; },
IncreaseConstructedBuildingsCounter: function() { return 1; },
IncreaseBuiltCivCentresCounter: function() { return 1; },
});
AddMock(101, IID_Player, {
GetName: function() { return "Player 2"; },
GetCiv: function() { return "celt"; },
GetColour: function() { return { r: 1, g: 0, b: 0, a: 1}; },
GetPopulationCount: function() { return 40; },
GetPopulationLimit: function() { return 30; },
GetResourceCounts: function() { return { food: 200 }; },
IsTrainingQueueBlocked: function() { return false; },
GetState: function() { return "active"; },
GetTeam: function() { return -1; },
GetDiplomacy: function() { return [1]; },
GetPhase: function() { return "village"; },
GetConquestCriticalEntitiesCount: function() { return 1; },
});
AddMock(101, IID_StatisticsTracker, {
GetStatistics: function() {
return {
"unitsTrained": 10,
"unitsLost": 9,
"buildingsConstructed": 5,
"buildingsLost": 4,
"civCentresBuilt": 1,
"resourcesGathered": {
"food": 100,
"wood": 0,
"metal": 0,
"stone": 0,
"vegetarianFood": 0,
},
"treasuresCollected": 0,
"percentMapExplored": 10,
};
},
IncreaseTrainedUnitsCounter: function() { return 1; },
IncreaseConstructedBuildingsCounter: function() { return 1; },
IncreaseBuiltCivCentresCounter: function() { return 1; },
});
TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
players: [
{
name: "Player 1",
civ: "gaia",
colour: { r:1, g:1, b:1, a:1 },
popCount: 10,
popLimit: 20,
resourceCounts: { food: 100 },
trainingQueueBlocked: false,
state: "active",
team: -1,
diplomacy: [],
phase: "",
},
{
name: "Player 2",
civ: "celt",
colour: { r:1, g:0, b:0, a:1 },
popCount: 40,
popLimit: 30,
resourceCounts: { food: 200 },
trainingQueueBlocked: false,
state: "active",
team: -1,
diplomacy: [1],
phase: "village",
}
],
circularMap: false,
timeElapsed: 0,
});
TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
players: [
{
name: "Player 1",
civ: "gaia",
colour: { r:1, g:1, b:1, a:1 },
popCount: 10,
popLimit: 20,
resourceCounts: { food: 100 },
trainingQueueBlocked: false,
state: "active",
team: -1,
diplomacy: [],
phase: "",
statistics: {
unitsTrained: 10,
unitsLost: 9,
buildingsConstructed: 5,
buildingsLost: 4,
civCentresBuilt: 1,
resourcesGathered: {
food: 100,
wood: 0,
metal: 0,
stone: 0,
vegetarianFood: 0,
},
treasuresCollected: 0,
percentMapExplored: 10,
},
},
{
name: "Player 2",
civ: "celt",
colour: { r:1, g:0, b:0, a:1 },
popCount: 40,
popLimit: 30,
resourceCounts: { food: 200 },
trainingQueueBlocked: false,
state: "active",
team: -1,
diplomacy: [1],
phase: "village",
statistics: {
unitsTrained: 10,
unitsLost: 9,
buildingsConstructed: 5,
buildingsLost: 4,
civCentresBuilt: 1,
resourcesGathered: {
food: 100,
wood: 0,
metal: 0,
stone: 0,
vegetarianFood: 0,
},
treasuresCollected: 0,
percentMapExplored: 10,
},
}
],
circularMap: false,
timeElapsed: 0,
});
AddMock(10, IID_Position, {
GetPosition: function() {
return {x:1, y:2, z:3};
},
IsInWorld: function() {
return true;
},
});
AddMock(10, IID_Health, {
GetHitpoints: function() { return 50; },
GetMaxHitpoints: function() { return 60; },
IsRepairable: function() { return false; },
});
AddMock(10, IID_Builder, {
GetEntitiesList: function() {
return ["test1", "test2"];
}
});
var state = cmp.GetEntityState(-1, 10);
TS_ASSERT_UNEVAL_EQUALS(state, {
id: 10,
template: "example",
position: {x:1, y:2, z:3},
hitpoints: 50,
maxHitpoints: 60,
needsRepair: false,
buildEntities: ["test1", "test2"],
visibility: "visible",
});
Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js (revision 9391)
@@ -1,2073 +1,2147 @@
function UnitAI() {}
UnitAI.prototype.Schema =
"Controls the unit's movement, attacks, etc, in response to commands from the player." +
"" +
"" +
"" +
"aggressive" +
"holdfire" +
"noncombat" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"violent" +
"aggressive" +
"defensive" +
"passive" +
"skittish" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
""+
"" +
"";
// Very basic stance support (currently just for test maps where we don't want
// everyone killing each other immediately after loading, and for female citizens)
// There some targeting options:
// targetVisibleEnemies: anything in vision range is a viable target
// targetAttackers: anything that hurts us is a viable target
// There are some response options, triggered when targets are detected:
// respondFlee: run away
// respondChase: start chasing after the enemy
// TODO: maybe add respondStandGround, respondHoldGround (allow chasing a short distance then return),
// targetAggressiveEnemies (don't worry about lone scouts, do worry around armies slaughtering
// the guy standing next to you), etc.
// TODO: even this limited version isn't implemented properly yet (e.g. it can't handle
// dynamic stance changes).
var g_Stances = {
"aggressive": {
targetVisibleEnemies: true,
targetAttackers: true,
respondFlee: false,
respondChase: true,
},
"holdfire": {
targetVisibleEnemies: false,
targetAttackers: true,
respondFlee: false,
respondChase: true,
},
"noncombat": {
targetVisibleEnemies: false,
targetAttackers: true,
respondFlee: true,
respondChase: false,
},
};
// See ../helpers/FSM.js for some documentation of this FSM specification syntax
var UnitFsmSpec = {
// Default event handlers:
"MoveCompleted": function() {
// ignore spurious movement messages
// (these can happen when stopping moving at the same time
// as switching states)
},
"MoveStarted": function() {
// ignore spurious movement messages
},
"ConstructionFinished": function(msg) {
// ignore uninteresting construction messages
},
"LosRangeUpdate": function(msg) {
// ignore newly-seen units by default
},
"Attacked": function(msg) {
// ignore attacker
},
"HealthChanged": function(msg) {
// ignore
},
+ "EntityRenamed": function(msg) {
+ // ignore
+ },
+
// Formation handlers:
"FormationLeave": function(msg) {
// ignore when we're not in FORMATIONMEMBER
},
// Called when being told to walk as part of a formation
"Order.FormationWalk": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.MoveToFormationOffset(msg.data.target, msg.data.x, msg.data.z);
this.SetNextState("FORMATIONMEMBER.WALKING");
},
// Special orders:
// (these will be overridden by various states)
"Order.LeaveFoundation": function(msg) {
// Default behaviour is to ignore the order since we're busy
this.FinishOrder();
},
// Individual orders:
// (these will switch the unit out of formation mode)
"Order.Walk": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
this.MoveToPoint(this.order.data.x, this.order.data.z);
this.SetNextState("INDIVIDUAL.WALKING");
},
"Order.WalkToTarget": function(msg) {
if (this.IsAnimal())
{
// TODO: let players move captured animals around
this.FinishOrder();
return;
}
var ok = this.MoveToTarget(this.order.data.target);
if (ok)
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.WALKING");
}
else
{
// We are already at the target, or can't move at all
this.FinishOrder();
}
},
"Order.Flee": function(msg) {
// TODO: if we were attacked by a ranged unit, we need to flee much further away
var ok = this.MoveToTargetRangeExplicit(this.order.data.target, +this.template.FleeDistance, -1);
if (ok)
{
// We've started fleeing from the given target
this.SetNextState("INDIVIDUAL.FLEEING");
}
else
{
// We are already at the target, or can't move at all
this.FinishOrder();
}
},
"Order.Attack": function(msg) {
// Check the target is alive
if (!this.TargetIsAlive(this.order.data.target))
{
this.FinishOrder();
return;
}
// Work out how to attack the given target
var type = this.GetBestAttack();
if (!type)
{
// Oops, we can't attack at all
this.FinishOrder();
return;
}
this.attackType = type;
// Try to move within attack range
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
// We've started walking to the given point
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.APPROACHING");
else
this.SetNextState("INDIVIDUAL.COMBAT.APPROACHING");
}
else
{
// We are already at the target, or can't move at all,
// so try attacking it from here.
// TODO: need better handling of the can't-reach-target case
if (this.IsAnimal())
this.SetNextState("ANIMAL.COMBAT.ATTACKING");
else
this.SetNextState("INDIVIDUAL.COMBAT.ATTACKING");
}
},
"Order.Gather": function(msg) {
// If the target is still alive, we need to kill it first
if (this.MustKillGatherTarget(this.order.data.target))
{
// Make sure we can attack the target, else we'll get very stuck
if (!this.GetBestAttack())
{
// Oops, we can't attack at all - give up
// TODO: should do something so the player knows why this failed
this.FinishOrder();
return;
}
this.PushOrderFront("Attack", { "target": this.order.data.target });
return;
}
// Try to move within range
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.GATHER.APPROACHING");
}
else
{
// We are already at the target, or can't move at all,
// so try gathering it from here.
// TODO: need better handling of the can't-reach-target case
this.SetNextState("INDIVIDUAL.GATHER.GATHERING");
}
},
"Order.ReturnResource": function(msg) {
// Try to move to the dropsite
if (this.MoveToTarget(this.order.data.target))
{
// We've started walking to the target
this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING");
}
else
{
// Oops, we can't reach the dropsite.
// Maybe we should try to pick another dropsite, to find an
// accessible one?
// For now, just give up.
this.FinishOrder();
return;
}
},
"Order.Repair": function(msg) {
// Try to move within range
if (this.MoveToTargetRange(this.order.data.target, IID_Builder))
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.REPAIR.APPROACHING");
}
else
{
// We are already at the target, or can't move at all,
// so try repairing it from here.
// TODO: need better handling of the can't-reach-target case
this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING");
}
},
"Order.Garrison": function(msg) {
if (this.MoveToTarget(this.order.data.target))
{
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
}
else
{
this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED");
}
},
+
+ "Order.Cheering": function(msg) {
+ this.SetNextState("INDIVIDUAL.CHEERING");
+ },
// States for the special entity representing a group of units moving in formation:
"FORMATIONCONTROLLER": {
"Order.Walk": function(msg) {
this.MoveToPoint(this.order.data.x, this.order.data.z);
this.SetNextState("WALKING");
},
"Order.Attack": function(msg) {
// TODO: we should move in formation towards the target,
// then break up into individuals when close enough to it
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("Attack", [msg.data.target, false]);
// TODO: we should wait until the target is killed, then
// move on to the next queued order.
// Don't bother now, just disband the formation immediately.
cmpFormation.Disband();
},
"Order.Repair": function(msg) {
// TODO: see notes in Order.Attack
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("Repair", [msg.data.target, msg.data.autocontinue, false]);
cmpFormation.Disband();
},
"Order.Gather": function(msg) {
// TODO: see notes in Order.Attack
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("Gather", [msg.data.target, false]);
cmpFormation.Disband();
},
"Order.ReturnResource": function(msg) {
// TODO: see notes in Order.Attack
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("ReturnResource", [msg.data.target, false]);
cmpFormation.Disband();
},
"Order.Garrison": function(msg) {
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.CallMemberFunction("Garrison", [msg.data.target, false]);
cmpFormation.Disband();
},
"IDLE": {
},
"WALKING": {
"MoveStarted": function(msg) {
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.MoveMembersIntoFormation(true);
},
"MoveCompleted": function(msg) {
if (this.FinishOrder())
return;
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.Disband();
},
},
},
// States for entities moving as part of a formation:
"FORMATIONMEMBER": {
"FormationLeave": function(msg) {
// Stop moving as soon as the formation disbands
this.StopMoving();
// We're leaving the formation, so stop our FormationWalk order
if (this.FinishOrder())
return;
// No orders left, we're an individual now
this.SetNextState("INDIVIDUAL.IDLE");
},
"IDLE": {
"enter": function() {
this.SelectAnimation("idle");
},
},
"WALKING": {
"enter": function () {
this.SelectAnimation("move");
},
},
},
// States for entities not part of a formation:
"INDIVIDUAL": {
"enter": function() {
// Sanity-checking
if (this.IsAnimal())
error("Animal got moved into INDIVIDUAL.* state");
},
"Attacked": function(msg) {
if (this.GetStance().targetAttackers)
{
this.RespondToTargetedEntities([msg.data.attacker]);
}
},
"IDLE": {
"enter": function() {
// Switch back to idle animation to guarantee we won't
// get stuck with an incorrect animation
this.SelectAnimation("idle");
// The GUI and AI want to know when a unit is idle, but we don't
// want to send frequent spurious messages if the unit's only
// idle for an instant and will quickly go off and do something else.
// So we'll set a timer here and only report the idle event if we
// remain idle
this.StartTimer(1000);
// If we entered the idle state we must have nothing better to do,
// so immediately check whether there's anybody nearby to attack.
// (If anyone approaches later, it'll be handled via LosRangeUpdate.)
if (this.losRangeQuery)
{
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var ents = rangeMan.ResetActiveQuery(this.losRangeQuery);
if (this.GetStance().targetVisibleEnemies)
{
if (this.RespondToTargetedEntities(ents))
return true; // (abort the FSM transition since we may have already switched state)
}
}
// Nobody to attack - stay in idle
return false;
},
"leave": function() {
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
rangeMan.DisableActiveQuery(this.losRangeQuery);
this.StopTimer();
if (this.isIdle)
{
this.isIdle = false;
Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle });
}
},
"LosRangeUpdate": function(msg) {
if (this.GetStance().targetVisibleEnemies)
{
// Start attacking one of the newly-seen enemy (if any)
this.RespondToTargetedEntities(msg.data.added);
}
},
"Timer": function(msg) {
if (!this.isIdle)
{
this.isIdle = true;
Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle });
}
},
// Override the LeaveFoundation order since we're not doing
// anything more important
"Order.LeaveFoundation": function(msg) {
// Move a tile outside the building
var range = 4;
var ok = this.MoveToTargetRangeExplicit(msg.data.target, range, range);
if (ok)
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.WALKING");
}
else
{
// We are already at the target, or can't move at all
this.FinishOrder();
}
},
},
"WALKING": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.FinishOrder();
},
},
"FLEEING": {
"enter": function() {
this.PlaySound("panic");
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("move");
this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed
this.SetMoveSpeed(this.GetWalkSpeed());
},
"MoveCompleted": function() {
// When we've run far enough, stop fleeing
this.FinishOrder();
},
// TODO: what if we run into more enemies while fleeing?
},
"COMBAT": {
+ "EntityRenamed": function(msg) {
+ if (this.order.data.target == msg.entity)
+ this.order.data.target = msg.newentity;
+ },
+
"Attacked": function(msg) {
// If we're already in combat mode, ignore anyone else
// who's attacking us
},
"APPROACHING": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.SetNextState("ATTACKING");
},
},
"ATTACKING": {
"enter": function() {
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
this.attackTimers = cmpAttack.GetTimers(this.attackType);
this.SelectAnimation("melee", false, 1.0, "attack");
this.SetAnimationSync(this.attackTimers.prepare, this.attackTimers.repeat);
this.StartTimer(this.attackTimers.prepare, this.attackTimers.repeat);
// TODO: we should probably only bother syncing projectile attacks, not melee
// TODO: if .prepare is short, players can cheat by cycling attack/stop/attack
// to beat the .repeat time; should enforce a minimum time
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
// Check the target is still alive
if (this.TargetIsAlive(this.order.data.target))
{
// Check we can still reach the target
if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
cmpAttack.PerformAttack(this.attackType, this.order.data.target);
return;
}
// Can't reach it - try to chase after it
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
{
this.SetNextState("COMBAT.CHASING");
return;
}
}
// Can't reach it, or it doesn't exist any more - give up
this.FinishOrder();
// TODO: see if we can switch to a new nearby enemy
},
// TODO: respond to target deaths immediately, rather than waiting
// until the next Timer event
},
"CHASING": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.SetNextState("ATTACKING");
},
},
},
"GATHER": {
"APPROACHING": {
"enter": function() {
this.SelectAnimation("move");
},
"MoveCompleted": function(msg) {
if (msg.data.error)
{
// We failed to reach the target
// Save the current order's data in case we need it later
var oldType = this.order.data.type;
var oldTarget = this.order.data.target;
// Try the next queued order if there is any
if (this.FinishOrder())
return;
// Try to find another nearby target of the same specific type
var nearby = this.FindNearbyResource(function (ent, type) {
return (ent != oldTarget && type.specific == oldType.specific);
});
if (nearby)
{
this.Gather(nearby, true);
return;
}
// Couldn't find anything else. Just try this one again,
// maybe we'll succeed next time
this.Gather(oldTarget, true);
return;
}
// We reached the target - start gathering from it now
this.SetNextState("GATHERING");
},
},
"GATHERING": {
"enter": function() {
this.StartTimer(1000, 1000);
// We want to start the gather animation as soon as possible,
// but only if we're actually at the target and it's still alive
// (else it'll look like we're chopping empty air).
// (If it's not alive, the Timer handler will deal with sending us
// off to a different target.)
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
{
var typename = "gather_" + this.order.data.type.specific;
this.SelectAnimation(typename, false, 1.0, typename);
}
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
// Check we can still reach the target
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
{
// Gather the resources:
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
// Try to gather treasure
if (cmpResourceGatherer.TryInstantGather(this.order.data.target))
return;
// If we've already got some resources but they're the wrong type,
// drop them first to ensure we're only ever carrying one type
if (cmpResourceGatherer.IsCarryingAnythingExcept(this.order.data.type.generic))
cmpResourceGatherer.DropResources();
// Collect from the target
var status = cmpResourceGatherer.PerformGather(this.order.data.target);
// TODO: if exhausted, we should probably stop immediately
// and choose a new target
// If we've collected as many resources as possible,
// return to the nearest dropsite
if (status.filled)
{
var nearby = this.FindNearestDropsite(this.order.data.type.generic);
if (nearby)
{
// (Keep this Gather order on the stack so we'll
// continue gathering after returning)
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// Oh no, couldn't find any drop sites. Give up on gathering.
this.FinishOrder();
}
}
else
{
// Try to follow the target
if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
{
this.SetNextState("APPROACHING");
return;
}
// Can't reach the target, or it doesn't exist any more
// We want to carry on gathering resources in the same area as
// the old one. So try to get close to the old resource's
// last known position
var maxRange = 8; // get close but not too close
if (this.order.data.lastPos &&
this.MoveToPointRange(this.order.data.lastPos.x, this.order.data.lastPos.z,
0, maxRange))
{
this.SetNextState("APPROACHING");
return;
}
// We're already in range, or can't get anywhere near it.
// Save the current order's type in case we need it later
var oldType = this.order.data.type;
// Give up on this order and try our next queued order
if (this.FinishOrder())
return;
// No remaining orders - pick a useful default behaviour
// Try to find a new resource of the same specific type near our current position:
var nearby = this.FindNearbyResource(function (ent, type) {
return (type.specific == oldType.specific);
});
if (nearby)
{
this.Gather(nearby, true);
return;
}
// Nothing else to gather - if we're carrying anything then we should
// drop it off, and if not then we might as well head to the dropsite
// anyway because that's a nice enough place to congregate and idle
var nearby = this.FindNearestDropsite(oldType.generic);
if (nearby)
{
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// No dropsites - just give up
}
},
},
},
// Returning to dropsite
"RETURNRESOURCE": {
"APPROACHING": {
"enter": function () {
// Work out what we're carrying, in order to select an appropriate animation
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
var type = cmpResourceGatherer.GetLastCarriedType();
if (type)
{
var typename = "carry_" + type.generic;
// Special case for meat
if (type.specific == "meat")
typename = "carry_" + type.specific;
this.SelectAnimation(typename, false, this.GetWalkSpeed());
}
else
{
// We're returning empty-handed
this.SelectAnimation("move");
}
},
"MoveCompleted": function() {
// Switch back to idle animation to guarantee we won't
// get stuck with the carry animation after stopping moving
this.SelectAnimation("idle");
// Check the dropsite really is in range
// (we didn't get stopped before reaching it)
if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
{
var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
// Dump any resources we can
var dropsiteTypes = cmpResourceDropsite.GetTypes();
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
cmpResourceGatherer.CommitResources(dropsiteTypes);
// Our next order should always be a Gather,
// so just switch back to that order
this.FinishOrder();
return;
}
}
// The dropsite was destroyed, or we couldn't reach it.
// Look for a new one.
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
var genericType = cmpResourceGatherer.GetMainCarryingType();
var nearby = this.FindNearestDropsite(genericType);
if (nearby)
{
this.FinishOrder();
this.PushOrderFront("ReturnResource", { "target": nearby });
return;
}
// Oh no, couldn't find any drop sites. Give up on returning.
this.FinishOrder();
},
},
},
"REPAIR": {
"APPROACHING": {
"enter": function () {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.SetNextState("REPAIRING");
},
},
"REPAIRING": {
"enter": function() {
this.SelectAnimation("build", false, 1.0, "build");
this.StartTimer(1000, 1000);
},
"leave": function() {
this.StopTimer();
},
"Timer": function(msg) {
var target = this.order.data.target;
// Check we can still reach the target
if (!this.CheckTargetRange(target, IID_Builder))
{
// Can't reach it, or it doesn't exist any more
this.FinishOrder();
return;
}
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
cmpBuilder.PerformBuilding(target);
},
},
"ConstructionFinished": function(msg) {
if (msg.data.entity != this.order.data.target)
return; // ignore other buildings
// Save the current order's data in case we need it later
var oldAutocontinue = this.order.data.autocontinue;
// We finished building it.
// Switch to the next order (if any)
if (this.FinishOrder())
return;
// No remaining orders - pick a useful default behaviour
// If autocontinue explicitly disabled (e.g. by AI) then
// do nothing automatically
if (!oldAutocontinue)
return;
// If this building was e.g. a farm, we should start gathering from it
// if we are capable of doing so
if (this.CanGather(msg.data.newentity))
{
this.Gather(msg.data.newentity, true);
return;
}
// If this building was e.g. a farmstead, we should look for nearby
// resources we can gather
var cmpResourceDropsite = Engine.QueryInterface(msg.data.newentity, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
var types = cmpResourceDropsite.GetTypes();
var nearby = this.FindNearbyResource(function (ent, type) {
return (types.indexOf(type.generic) != -1);
});
if (nearby)
{
this.Gather(nearby, true);
return;
}
}
// Look for a nearby foundation to help with
var nearbyFoundation = this.FindNearbyFoundation();
if (nearbyFoundation)
{
this.Repair(nearbyFoundation, oldAutocontinue, true);
return;
}
},
// Override the LeaveFoundation order since we don't want to be
// accidentally blocking our own building
"Order.LeaveFoundation": function(msg) {
// Move a tile outside the building
var range = 4;
var ok = this.MoveToTargetRangeExplicit(msg.data.target, range, range);
if (ok)
{
// We've started walking to the given point
this.SetNextState("INDIVIDUAL.WALKING");
}
else
{
// We are already at the target, or can't move at all
this.FinishOrder();
}
},
},
"GARRISON": {
"APPROACHING": {
"enter": function() {
this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.PlaySound("walk");
},
"MoveCompleted": function() {
this.SetNextState("GARRISONED");
},
"leave": function() {
this.StopTimer();
}
},
"GARRISONED": {
"enter": function() {
var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
cmpGarrisonHolder.Garrison(this.entity);
}
if (this.FinishOrder())
return;
},
"leave": function() {
}
},
},
+ "CHEERING": {
+ "enter": function() {
+ // Unit is invulnerable while cheering
+ var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
+ cmpDamageReceiver.SetInvulnerability(true);
+ this.SelectAnimation("promotion");
+ this.StartTimer(4000, 4000);
+ return false;
+ },
+
+ "leave": function() {
+ this.StopTimer();
+ var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
+ cmpDamageReceiver.SetInvulnerability(false);
+ },
+
+ "Timer": function(msg) {
+ this.FinishOrder();
+ },
+ },
},
"ANIMAL": {
"HealthChanged": function(msg) {
// If we died (got reduced to 0 hitpoints), stop the AI and act like a corpse
if (msg.to == 0)
this.SetNextState("CORPSE");
},
"Attacked": function(msg) {
if (this.template.NaturalBehaviour == "skittish" ||
this.template.NaturalBehaviour == "passive")
{
this.MoveToTargetRangeExplicit(msg.data.attacker, +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
}
else if (this.template.NaturalBehaviour == "violent" ||
this.template.NaturalBehaviour == "aggressive" ||
this.template.NaturalBehaviour == "defensive")
{
if (this.CanAttack(msg.data.attacker))
this.ReplaceOrder("Attack", { "target": msg.data.attacker });
}
},
"Order.LeaveFoundation": function(msg) {
// Run away from the foundation
this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
},
"IDLE": {
// (We need an IDLE state so that FinishOrder works)
"enter": function() {
// Start feeding immediately
this.SetNextState("FEEDING");
return true;
},
},
"CORPSE": {
"enter": function() {
this.StopMoving();
},
// Ignore all orders that animals might otherwise respond to
"Order.FormationWalk": function() { },
"Order.Walk": function() { },
"Order.WalkToTarget": function() { },
"Order.Attack": function() { },
"Attacked": function(msg) {
// Do nothing, because we're dead already
},
"Order.LeaveFoundation": function(msg) {
// We can't walk away from the foundation (since we're dead),
// but we mustn't block its construction (since the builders would get stuck),
// and we don't want to trick gatherers into trying to reach us when
// we're stuck in the middle of a building, so just delete our corpse.
Engine.DestroyEntity(this.entity);
},
},
"ROAMING": {
"enter": function() {
// Walk in a random direction
this.SelectAnimation("walk", false, this.GetWalkSpeed());
this.MoveRandomly(+this.template.RoamDistance);
// Set a random timer to switch to feeding state
this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
},
"leave": function() {
this.StopTimer();
},
"LosRangeUpdate": function(msg) {
if (this.template.NaturalBehaviour == "skittish")
{
if (msg.data.added.length > 0)
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
return;
}
}
// Start attacking one of the newly-seen enemy (if any)
else if (this.template.NaturalBehaviour == "violent" ||
this.template.NaturalBehaviour == "aggressive")
{
this.AttackVisibleEntity(msg.data.added);
}
// TODO: if two units enter our range together, we'll attack the
// first and then the second won't trigger another LosRangeUpdate
// so we won't notice it. Probably we should do something with
// ResetActiveQuery in ROAMING.enter/FEEDING.enter in order to
// find any units that are already in range.
},
"Timer": function(msg) {
this.SetNextState("FEEDING");
},
"MoveCompleted": function() {
this.MoveRandomly(+this.template.RoamDistance);
},
},
"FEEDING": {
"enter": function() {
// Stop and eat for a while
this.SelectAnimation("feeding");
this.StopMoving();
this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax));
},
"leave": function() {
this.StopTimer();
},
"LosRangeUpdate": function(msg) {
if (this.template.NaturalBehaviour == "skittish")
{
if (msg.data.added.length > 0)
{
this.MoveToTargetRangeExplicit(msg.data.added[0], +this.template.FleeDistance, +this.template.FleeDistance);
this.SetNextState("FLEEING");
return;
}
}
// Start attacking one of the newly-seen enemy (if any)
else if (this.template.NaturalBehaviour == "violent")
{
this.AttackVisibleEntity(msg.data.added);
}
},
"MoveCompleted": function() { },
"Timer": function(msg) {
this.SetNextState("ROAMING");
},
},
"FLEEING": { // TODO: would be nice to share more of this with non-animal units
"enter": function() {
this.PlaySound("panic");
// Run quickly
var speed = this.GetRunSpeed();
this.SelectAnimation("run", false, speed);
this.SetMoveSpeed(speed);
},
"leave": function() {
// Reset normal speed
this.SetMoveSpeed(this.GetWalkSpeed());
},
"MoveCompleted": function() {
// When we've run far enough, go back to the roaming state
this.SetNextState("ROAMING");
},
},
"COMBAT": "INDIVIDUAL.COMBAT", // reuse the same combat behaviour for animals
},
};
var UnitFsm = new FSM(UnitFsmSpec);
UnitAI.prototype.Init = function()
{
this.orderQueue = []; // current order is at the front of the list
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);
};
UnitAI.prototype.IsFormationController = function()
{
return (this.template.FormationController == "true");
};
UnitAI.prototype.IsAnimal = function()
{
return (this.template.NaturalBehaviour ? true : false);
};
/**
* Returns whether this is an animal that is too difficult to hunt.
* (Currently this just includes skittish animals, which are probably
* too fast to chase.)
*/
UnitAI.prototype.IsUnhuntable = function()
{
// return (this.template.NaturalBehaviour && this.template.NaturalBehaviour == "skittish");
// Actually, since the AI is currently rubbish at hunting, skip all animals
// that aren't really weak:
return this.IsAnimal() && Engine.QueryInterface(this.entity, IID_Health).GetMaxHitpoints() >= 10;
};
UnitAI.prototype.IsIdle = function()
{
return this.isIdle;
};
UnitAI.prototype.OnCreate = function()
{
if (this.IsAnimal())
UnitFsm.Init(this, "ANIMAL.FEEDING");
else if (this.IsFormationController())
UnitFsm.Init(this, "FORMATIONCONTROLLER.IDLE");
else
UnitFsm.Init(this, "INDIVIDUAL.IDLE");
};
UnitAI.prototype.OnOwnershipChanged = function(msg)
{
this.SetupRangeQuery(msg.to);
};
UnitAI.prototype.OnDestroy = function()
{
// Clean up any timers that are now obsolete
this.StopTimer();
// Clean up range queries
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (this.losRangeQuery)
rangeMan.DestroyActiveQuery(this.losRangeQuery);
};
// Set up a range query for all enemy units within LOS range
// which can be attacked.
// This should be called whenever our ownership changes.
UnitAI.prototype.SetupRangeQuery = function(owner)
{
var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
if (!cmpVision)
return;
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (this.losRangeQuery)
rangeMan.DestroyActiveQuery(this.losRangeQuery);
var range = cmpVision.GetRange();
var players = [];
if (owner != -1)
{
// If unit not just killed, get enemy players via diplomacy
var player = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player);
// Get our diplomacy array
var diplomacy = player.GetDiplomacy();
var numPlayers = playerMan.GetNumPlayers();
for (var i = 1; i < numPlayers; ++i)
{
// Exclude gaia, allies, and self
// TODO: How to handle neutral players - Special query to attack military only?
if (i != owner && diplomacy[i - 1] < 0)
players.push(i);
}
}
this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, 0, range, players, IID_DamageReceiver);
rangeMan.EnableActiveQuery(this.losRangeQuery);
};
//// FSM linkage functions ////
UnitAI.prototype.SetNextState = function(state)
{
UnitFsm.SetNextState(this, state);
};
UnitAI.prototype.DeferMessage = function(msg)
{
UnitFsm.DeferMessage(this, msg);
};
/**
* Call when the current order has been completed (or failed).
* Removes the current order from the queue, and processes the
* next one (if any). Returns false and defaults to IDLE
* if there are no remaining orders.
*/
UnitAI.prototype.FinishOrder = function()
{
if (!this.orderQueue.length)
error("FinishOrder called when order queue is empty");
this.orderQueue.shift();
this.order = this.orderQueue[0];
if (this.orderQueue.length)
{
UnitFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
return true;
}
else
{
this.SetNextState("IDLE");
return false;
}
};
/**
* Add an order onto the back of the queue,
* and execute it if we didn't already have an order.
*/
UnitAI.prototype.PushOrder = function(type, data)
{
var order = { "type": type, "data": data };
this.orderQueue.push(order);
// If we didn't already have an order, then process this new one
if (this.orderQueue.length == 1)
{
this.order = order;
UnitFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
}
};
/**
* Add an order onto the front of the queue,
* and execute it immediately.
*/
UnitAI.prototype.PushOrderFront = function(type, data)
{
var order = { "type": type, "data": data };
- this.orderQueue.unshift(order);
-
- this.order = order;
- UnitFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
+ // If current order is cheering then add new order after it
+ if (this.order && this.order.type == "Cheering")
+ {
+ var cheeringOrder = this.orderQueue.shift();
+ this.orderQueue.unshift(cheeringOrder, order);
+ }
+ else
+ {
+ this.orderQueue.unshift(order);
+ this.order = order;
+ UnitFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data});
+ }
};
UnitAI.prototype.ReplaceOrder = function(type, data)
{
- this.orderQueue = [];
- this.PushOrder(type, data);
+ // If current order is cheering then add new order after it
+ if (this.order && this.order.type == "Cheering")
+ {
+ var order = { "type": type, "data": data };
+ var cheeringOrder = this.orderQueue.shift();
+ this.orderQueue = [ cheeringOrder, order ];
+ }
+ else
+ {
+ this.orderQueue = [];
+ this.PushOrder(type, data);
+ }
};
+UnitAI.prototype.GetOrders = function()
+{
+ return this.orderQueue.slice();
+}
+
+UnitAI.prototype.AddOrders = function(orders)
+{
+ for each (var order in orders)
+ {
+ this.PushOrder(order.type, order.data);
+ }
+}
+
UnitAI.prototype.TimerHandler = function(data, lateness)
{
// Reset the timer
if (data.timerRepeat === undefined)
{
this.timer = undefined;
}
else
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "TimerHandler", data.timerRepeat - lateness, data);
}
UnitFsm.ProcessMessage(this, {"type": "Timer", "data": data, "lateness": lateness});
};
/**
* Set up the UnitAI timer to run after 'offset' msecs, and then
* every 'repeat' msecs until StopTimer is called. A "Timer" message
* will be sent each time the timer runs.
*/
UnitAI.prototype.StartTimer = function(offset, repeat)
{
if (this.timer)
error("Called StartTimer when there's already an active timer");
var data = { "timerRepeat": repeat };
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "TimerHandler", offset, data);
};
/**
* Stop the current UnitAI timer.
*/
UnitAI.prototype.StopTimer = function()
{
if (!this.timer)
return;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
};
//// Message handlers /////
UnitAI.prototype.OnMotionChanged = function(msg)
{
if (msg.starting && !msg.error)
{
UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
}
else if (!msg.starting || msg.error)
{
UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
}
};
UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
{
// TODO: This is a bit inefficient since every unit listens to every
// construction message - ideally we could scope it to only the one we're building
UnitFsm.ProcessMessage(this, {"type": "ConstructionFinished", "data": msg});
};
+UnitAI.prototype.OnGlobalEntityRenamed = function(msg)
+{
+ UnitFsm.ProcessMessage(this, {"type": "EntityRenamed", "entity": msg.entity, "newentity": msg.newentity});
+}
+
UnitAI.prototype.OnAttacked = function(msg)
{
UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
};
UnitAI.prototype.OnHealthChanged = function(msg)
{
UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
};
UnitAI.prototype.OnRangeUpdate = function(msg)
{
if (msg.tag == this.losRangeQuery)
UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg});
};
//// Helper functions to be called by the FSM ////
UnitAI.prototype.GetWalkSpeed = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.GetWalkSpeed();
};
UnitAI.prototype.GetRunSpeed = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.GetRunSpeed();
};
/**
* Returns true if the target exists and has non-zero hitpoints.
*/
UnitAI.prototype.TargetIsAlive = function(ent)
{
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (!cmpHealth)
return false;
return (cmpHealth.GetHitpoints() != 0);
};
/**
* Returns true if the target exists and needs to be killed before
* beginning to gather resources from it.
*/
UnitAI.prototype.MustKillGatherTarget = function(ent)
{
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (!cmpResourceSupply)
return false;
if (!cmpResourceSupply.GetKillBeforeGather())
return false;
return this.TargetIsAlive(ent);
};
/**
* Returns the entity ID of the nearest resource supply where the given
* filter returns true, or undefined if none can be found.
* TODO: extend this to exclude resources that already have lots of
* gatherers.
*/
UnitAI.prototype.FindNearbyResource = function(filter)
{
var range = 64; // TODO: what's a sensible number?
// Accept any resources owned by Gaia
var players = [0];
// Also accept resources owned by this unit's player:
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, IID_ResourceSupply);
for each (var ent in nearby)
{
// Never automatically pick resources that are animals that are too hard to hunt
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI && cmpUnitAI.IsUnhuntable())
continue;
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
if (filter(ent, type))
return ent;
}
return undefined;
};
/**
* Returns the entity ID of the nearest resource dropsite that accepts
* the given type, or undefined if none can be found.
*/
UnitAI.prototype.FindNearestDropsite = function(genericType)
{
// Find dropsites owned by this unit's player
var players = [];
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var nearby = rangeMan.ExecuteQuery(this.entity, 0, -1, players, IID_ResourceDropsite);
for each (var ent in nearby)
{
var cmpDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (!cmpDropsite.AcceptsType(genericType))
continue;
return ent;
}
return undefined;
};
/**
* Returns the entity ID of the nearest building that needs to be constructed,
* or undefined if none can be found close enough.
*/
UnitAI.prototype.FindNearbyFoundation = function()
{
var range = 64; // TODO: what's a sensible number?
// Find buildings owned by this unit's player
var players = [];
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
players.push(cmpOwnership.GetOwner());
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var nearby = rangeMan.ExecuteQuery(this.entity, 0, range, players, IID_Foundation);
for each (var ent in nearby)
{
// Skip foundations that are already complete. (This matters since
// we process the ConstructionFinished message before the foundation
// we're working on has been deleted.)
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation.IsFinished())
continue;
return ent;
}
return undefined;
};
/**
* Play a sound appropriate to the current entity.
*/
UnitAI.prototype.PlaySound = function(name)
{
// If we're a formation controller, use the sounds from our first member
if (this.IsFormationController())
{
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
var member = cmpFormation.GetPrimaryMember();
if (member)
PlaySound(name, member);
}
else
{
// Otherwise use our own sounds
PlaySound(name, this.entity);
}
};
UnitAI.prototype.SelectAnimation = function(name, once, speed, sound)
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
// Special case: the "move" animation gets turned into a special
// movement mode that deals with speeds and walk/run automatically
if (name == "move")
{
// Speed to switch from walking to running animations
var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2;
cmpVisual.SelectMovementAnimation(runThreshold);
return;
}
var soundgroup;
if (sound)
{
var cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
if (cmpSound)
soundgroup = cmpSound.GetSoundGroup(sound);
}
// Set default values if unspecified
if (typeof once == "undefined")
once = false;
if (typeof speed == "undefined")
speed = 1.0;
if (typeof soundgroup == "undefined")
soundgroup = "";
cmpVisual.SelectAnimation(name, once, speed, soundgroup);
};
UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime)
{
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (!cmpVisual)
return;
cmpVisual.SetAnimationSyncRepeat(repeattime);
cmpVisual.SetAnimationSyncOffset(actiontime);
};
UnitAI.prototype.StopMoving = function()
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.StopMoving();
};
UnitAI.prototype.MoveToPoint = function(x, z)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
};
UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
};
UnitAI.prototype.MoveToTarget = function(target)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
};
UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
{
var cmpRanged = Engine.QueryInterface(this.entity, iid);
var range = cmpRanged.GetRange(type);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, min, max);
};
UnitAI.prototype.CheckTargetRange = function(target, iid, type)
{
var cmpRanged = Engine.QueryInterface(this.entity, iid);
var range = cmpRanged.GetRange(type);
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
};
UnitAI.prototype.GetBestAttack = function()
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return undefined;
return cmpAttack.GetBestAttack();
};
/**
* Try to find one of the given entities which can be attacked,
* and start attacking it.
* Returns true if it found something to attack.
*/
UnitAI.prototype.AttackVisibleEntity = function(ents)
{
for each (var target in ents)
{
if (this.CanAttack(target))
{
this.PushOrderFront("Attack", { "target": target });
return true;
}
}
return false;
};
/**
* Try to respond appropriately given our current stance,
* given a list of entities that match our stance's target criteria.
* Returns true if it responded.
*/
UnitAI.prototype.RespondToTargetedEntities = function(ents)
{
if (!ents.length)
return false;
if (this.GetStance().respondChase)
return this.AttackVisibleEntity(ents);
if (this.GetStance().respondFlee)
{
this.PushOrderFront("Flee", { "target": ents[0] });
return true;
}
return false;
};
//// External interface functions ////
UnitAI.prototype.SetFormationController = function(ent)
{
this.formationController = ent;
// Set obstruction group, so we can walk through members
// of our own formation (or ourself if not in formation)
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (cmpObstruction)
{
if (ent == INVALID_ENTITY)
cmpObstruction.SetControlGroup(this.entity);
else
cmpObstruction.SetControlGroup(ent);
}
// If we were removed from a formation, let the FSM switch back to INDIVIDUAL
if (ent == INVALID_ENTITY)
UnitFsm.ProcessMessage(this, { "type": "FormationLeave" });
};
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).
* Intended for Formation to switch to column layout on long walks.
*/
UnitAI.prototype.ComputeWalkingDistance = function()
{
var distance = 0;
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
return 0;
// Keep track of the position at the start of each order
var pos = cmpPosition.GetPosition();
for (var i = 0; i < this.orderQueue.length; ++i)
{
var order = this.orderQueue[i];
switch (order.type)
{
case "Walk":
// Add the distance to the target point
var dx = order.data.x - pos.x;
var dz = order.data.z - pos.z;
var d = Math.sqrt(dx*dx + dz*dz);
distance += d;
// Remember this as the start position for the next order
pos = order.data;
break; // and continue the loop
case "WalkToTarget":
case "Flee":
case "LeaveFoundation":
case "Attack":
case "Gather":
case "ReturnResource":
case "Repair":
case "Garrison":
// Find the target unit's position
var cmpTargetPosition = Engine.QueryInterface(order.data.target, IID_Position);
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
return distance;
var targetPos = cmpTargetPosition.GetPosition();
// Add the distance to the target unit
var dx = targetPos.x - pos.x;
var dz = targetPos.z - pos.z;
var d = Math.sqrt(dx*dx + dz*dz);
distance += d;
// Return the total distance to the target
return distance;
default:
error("ComputeWalkingDistance: Unrecognised order type '"+order.type+"'");
return distance;
}
}
// Return the total distance to the end of the order queue
return distance;
};
UnitAI.prototype.AddOrder = function(type, data, queued)
{
if (queued)
this.PushOrder(type, data);
else
this.ReplaceOrder(type, data);
};
UnitAI.prototype.Walk = function(x, z, queued)
{
this.AddOrder("Walk", { "x": x, "z": z }, queued);
};
UnitAI.prototype.WalkToTarget = function(target, queued)
{
this.AddOrder("WalkToTarget", { "target": target }, queued);
};
UnitAI.prototype.LeaveFoundation = function(target)
{
// TODO: we should verify this is a friendly foundation, otherwise
// there's no reason we should let them build here
// If we're already being told to leave a foundation, then
// ignore this new request so we don't end up being too indecisive
// to ever actually move anywhere
if (this.order && this.order.type == "LeaveFoundation")
return;
this.PushOrderFront("LeaveFoundation", { "target": target });
};
UnitAI.prototype.Attack = function(target, queued)
{
if (!this.CanAttack(target))
{
this.WalkToTarget(target, queued);
return;
}
this.AddOrder("Attack", { "target": target }, queued);
};
UnitAI.prototype.Garrison = function(target, queued)
{
if (!this.CanGarrison(target))
{
this.WalkToTarget(target, queued);
return;
}
this.AddOrder("Garrison", { "target": target }, queued);
};
UnitAI.prototype.Gather = function(target, queued)
{
if (!this.CanGather(target))
{
this.WalkToTarget(target, queued);
return;
}
// Save the resource type now, so if the resource gets destroyed
// before we process the order then we still know what resource
// type to look for more of
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
var type = cmpResourceSupply.GetType();
// Remember the position of our target, if any, in case it disappears
// later and we want to head to its last known position
// (TODO: if the target moves a lot (e.g. it's an animal), maybe we
// need to update this lastPos regularly rather than just here?)
var lastPos = undefined;
var cmpPosition = Engine.QueryInterface(target, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
lastPos = cmpPosition.GetPosition();
this.AddOrder("Gather", { "target": target, "type": type, "lastPos": lastPos }, queued);
};
UnitAI.prototype.ReturnResource = function(target, queued)
{
if (!this.CanReturnResource(target))
{
this.WalkToTarget(target, queued);
return;
}
this.AddOrder("ReturnResource", { "target": target }, queued);
};
UnitAI.prototype.Repair = function(target, autocontinue, queued)
{
if (!this.CanRepair(target))
{
this.WalkToTarget(target, queued);
return;
}
this.AddOrder("Repair", { "target": target, "autocontinue": autocontinue }, queued);
};
+UnitAI.prototype.Cheer = function()
+{
+ this.AddOrder("Cheering", null, false);
+};
+
UnitAI.prototype.SetStance = function(stance)
{
if (g_Stances[stance])
this.stance = stance;
else
error("UnitAI: Setting to invalid stance '"+stance+"'");
};
UnitAI.prototype.GetStance = function()
{
return g_Stances[this.stance];
};
//// Helper functions ////
UnitAI.prototype.CanAttack = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to Attack commands
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return false;
// TODO: verify that this is a valid target
return true;
};
UnitAI.prototype.CanGarrison = function(target)
{
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
// Don't let animals garrison for now
// (If we want to support that, we'll need to change Order.Garrison so it
// doesn't move the animal into an INVIDIDUAL.* state)
if (this.IsAnimal())
return false;
return true;
};
UnitAI.prototype.CanGather = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to Gather commands
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
if (!cmpResourceGatherer)
return false;
// Verify that we can gather from this target
if (!cmpResourceGatherer.GetTargetGatherRate(target))
return false;
// TODO: should verify it's owned by the correct player, etc
return true;
};
UnitAI.prototype.CanReturnResource = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to ReturnResource commands
var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
if (!cmpResourceGatherer)
return false;
// Verify that the target is a dropsite
var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite);
if (!cmpResourceDropsite)
return false;
// Verify that we are carrying some resources,
// and can return our current resource to this target
var type = cmpResourceGatherer.GetMainCarryingType();
if (!type || !cmpResourceDropsite.AcceptsType(type))
return false;
// TODO: should verify it's owned by the correct player, etc
return true;
};
UnitAI.prototype.CanRepair = function(target)
{
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
return true;
// Verify that we're able to respond to Repair (Builder) commands
var cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
if (!cmpBuilder)
return false;
// TODO: verify that this is a valid target
return true;
};
//// Animal specific functions ////
UnitAI.prototype.MoveRandomly = function(distance)
{
// We want to walk in a random direction, but avoid getting stuck
// in obstacles or narrow spaces.
// So pick a circular range from approximately our current position,
// and move outwards to the nearest point on that circle, which will
// lead to us avoiding obstacles and moving towards free space.
// TODO: we probably ought to have a 'home' point, and drift towards
// that, so we don't spread out all across the whole map
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition)
return;
if (!cmpPosition.IsInWorld())
return;
var pos = cmpPosition.GetPosition();
var jitter = 0.5;
// Randomly adjust the range's center a bit, so we tend to prefer
// moving in random directions (if there's nothing in the way)
var tx = pos.x + (2*Math.random()-1)*jitter;
var tz = pos.z + (2*Math.random()-1)*jitter;
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.MoveToPointRange(tx, tz, distance, distance);
};
UnitAI.prototype.SetMoveSpeed = function(speed)
{
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpMotion.SetSpeed(speed);
};
Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 9391)
@@ -1,144 +1,142 @@
function Identity() {}
Identity.prototype.Schema =
"Specifies various names and values associated with the unit type, typically for GUI display to users." +
"" +
"hele" +
"Infantry Spearman" +
"Hoplite" +
"units/hele_infantry_spearman.png" +
"" +
"" +
"" +
"gaia" +
"cart" +
"celt" +
"hele" +
"iber" +
"pers" +
"rome" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"Basic" +
"Advanced" +
"Elite" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"Unit" +
"Infantry" +
"Cavalry" +
"Ranged" +
"Melee" +
"Mechanical" +
"Ship" +
"Siege" +
"Super" +
"Hero" +
"Support" +
"Animal" +
"Organic" +
"Structure" +
"Civic" +
"CivCentre" +
"Economic" +
"Defensive" +
"Village" +
"Town" +
"City" +
"ConquestCritical" +
"Worker" +
"CitizenSoldier" +
"Trade" +
"Bow" + // TODO: what are these used for?
"Javelin" +
"Spear" +
"Sword" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Identity.prototype.Init = function()
{
};
Identity.prototype.Serialize = null; // we have no dynamic state to save
Identity.prototype.GetCiv = function()
{
return this.template.Civ;
};
Identity.prototype.GetRank = function()
{
- if (this.template.Rank)
- return this.template.Rank;
- return "";
+ return (this.template.Rank || "");
};
Identity.prototype.GetClassesList = function()
{
if (this.template.Classes)
{
var string = this.template.Classes._string;
return string.split(/\s+/);
}
else
{
return [];
}
};
Identity.prototype.HasClass = function(name)
{
return this.GetClassesList().indexOf(name) != -1;
};
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Looter.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Looter.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Looter.js (revision 9391)
@@ -1,11 +1,27 @@
function Looter() {}
Looter.prototype.Schema =
"";
-
-/*
- * TODO: this all needs to be designed and implemented
+
+/**
+ * Try to collect loot from target entity
*/
-
+Looter.prototype.Collect = function(targetEntity)
+{
+ var cmpLoot = Engine.QueryInterface(targetEntity, IID_Loot);
+ if (!cmpLoot)
+ return;
+
+ var xp = cmpLoot.GetXp();
+ if (xp > 0)
+ {
+ var cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion);
+ if (cmpPromotion)
+ cmpPromotion.IncreaseXp(xp);
+ }
+ var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
+ cmpPlayer.AddResources(cmpLoot.GetResources());
+}
+
Engine.RegisterComponentType(IID_Looter, "Looter", Looter);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Armour.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Armour.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Armour.js (revision 9391)
@@ -1,51 +1,59 @@
function Armour() {}
Armour.prototype.Schema =
"Controls the damage resistance of the unit." +
"" +
"10.0" +
"0.0" +
"5.0" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Armour.prototype.Init = function()
{
+ this.invulnerable = false;
};
Armour.prototype.Serialize = null; // we have no dynamic state to save
+Armour.prototype.SetInvulnerability = function(invulnerability)
+{
+ this.invulnerable = invulnerability;
+};
+
Armour.prototype.TakeDamage = function(hack, pierce, crush)
{
+ if (this.invulnerable)
+ return { "killed": false };
// Adjust damage values based on armour
var adjHack = Math.max(0, hack - this.template.Hack);
var adjPierce = Math.max(0, pierce - this.template.Pierce);
var adjCrush = Math.max(0, crush - this.template.Crush);
// Total is sum of individual damages, with minimum damage 1
var total = Math.max(1, adjHack + adjPierce + adjCrush);
// Reduce health
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
return cmpHealth.Reduce(total);
};
Armour.prototype.GetArmourStrengths = function()
{
// Convert attack values to numbers
return {
hack: +this.template.Hack,
pierce: +this.template.Pierce,
crush: +this.template.Crush
};
};
Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 9391)
@@ -1,247 +1,253 @@
function Attack() {}
Attack.prototype.Schema =
"Controls the attack abilities and strengths of the unit." +
"" +
"" +
"10.0" +
"0.0" +
"5.0" +
"4.0" +
"1000" +
"" +
"" +
"0.0" +
"10.0" +
"0.0" +
"44.0" +
"20.0" +
"800" +
"1600" +
"50.0" +
"" +
"" +
"10.0" +
"0.0" +
"50.0" +
"24.0" +
"20.0" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: how do these work?
"" +
"" +
"" +
"";
Attack.prototype.Init = function()
{
};
Attack.prototype.Serialize = null; // we have no dynamic state to save
/**
* Return the type of the best attack.
* TODO: this should probably depend on range, target, etc,
* so we can automatically switch between ranged and melee
*/
Attack.prototype.GetBestAttack = function()
{
if (this.template.Ranged)
return "Ranged";
else if (this.template.Melee)
return "Melee";
else if (this.template.Charge)
return "Charge";
else
return undefined;
};
Attack.prototype.GetTimers = function(type)
{
var prepare = +(this.template[type].PrepareTime || 0);
var repeat = +(this.template[type].RepeatTime || 1000);
return { "prepare": prepare, "repeat": repeat, "recharge": repeat - prepare };
};
Attack.prototype.GetAttackStrengths = function(type)
{
// Convert attack values to numbers, default 0 if unspecified
return {
hack: +(this.template[type].Hack || 0),
pierce: +(this.template[type].Pierce || 0),
crush: +(this.template[type].Crush || 0)
};
};
Attack.prototype.GetRange = function(type)
{
var max = +this.template[type].MaxRange;
var min = +(this.template[type].MinRange || 0);
return { "max": max, "min": min };
}
/**
* Attack the target entity. This should only be called after a successful range check,
* and should only be called after GetTimers().repeat msec has passed since the last
* call to PerformAttack.
*/
Attack.prototype.PerformAttack = function(type, target)
{
// If this is a ranged attack, then launch a projectile
if (type == "Ranged")
{
// To implement (in)accuracy, for arrows and javelins, we want to do the following:
// * Compute an accuracy rating, based on the entity's characteristics and the distance to the target
// * Pick a random point 'close' to the target (based on the accuracy) which is the real target point
// * Pick a real target unit, based on their footprint's proximity to the real target point
// * If there is none, then harmlessly shoot to the real target point instead
// * If the real target unit moves after being targeted, the projectile will follow it and hit it anyway
//
// In the future this should be extended:
// * If the target unit moves too far, the projectile should 'detach' and not hit it, so that
// players can dodge projectiles. (Or it should pick a new target after detaching, so it can still
// hit somebody.)
// * Obstacles like trees could reduce the probability of the target being hit
// * Obstacles like walls should block projectiles entirely
// * There should be more control over the probabilities of hitting enemy units vs friendly units vs missing,
// for gameplay balance tweaks
// * Larger, slower projectiles (catapults etc) shouldn't pick targets first, they should just
// hurt anybody near their landing point
// Get some data about the entity
var horizSpeed = +this.template[type].ProjectileSpeed;
var gravity = 9.81; // this affects the shape of the curve; assume it's constant for now
var accuracy = 6; // TODO: get from entity template
//horizSpeed /= 8; gravity /= 8; // slow it down for testing
// Find the distance to the target
var selfPosition = Engine.QueryInterface(this.entity, IID_Position).GetPosition();
var targetPosition = Engine.QueryInterface(target, IID_Position).GetPosition();
var horizDistance = Math.sqrt(Math.pow(targetPosition.x - selfPosition.x, 2) + Math.pow(targetPosition.z - selfPosition.z, 2));
// Compute the real target point (based on accuracy)
var angle = Math.random() * 2*Math.PI;
var r = 1 - Math.sqrt(Math.random()); // triangular distribution [0,1] (cluster around the center)
var offset = r * accuracy; // TODO: should be affected by range
var offsetX = offset * Math.sin(angle);
var offsetZ = offset * Math.cos(angle);
var realTargetPosition = { "x": targetPosition.x + offsetX, "y": targetPosition.y, "z": targetPosition.z + offsetZ };
// TODO: what we should really do here is select the unit whose footprint is closest to the realTargetPosition
// (and harmlessly hit the ground if there's none), but as a simplification let's just randomly decide whether to
// hit the original target or not.
var realTargetUnit = undefined;
if (Math.random() < 0.5) // TODO: this is yucky and hardcoded
{
// Hit the original target
realTargetUnit = target;
realTargetPosition = targetPosition;
}
else
{
// Hit the ground
// TODO: ought to make sure Y is on the ground
}
// Hurt the target after the appropriate time
if (realTargetUnit)
{
var realHorizDistance = Math.sqrt(Math.pow(realTargetPosition.x - selfPosition.x, 2) + Math.pow(realTargetPosition.z - selfPosition.z, 2));
var timeToTarget = realHorizDistance / horizSpeed;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.SetTimeout(this.entity, IID_Attack, "CauseDamage", timeToTarget*1000, {"type": type, "target": target});
}
// Launch the graphical projectile
var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
if (realTargetUnit)
cmpProjectileManager.LaunchProjectileAtEntity(this.entity, realTargetUnit, horizSpeed, gravity);
else
cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
}
else
{
// Melee attack - hurt the target immediately
this.CauseDamage({"type": type, "target": target});
}
// TODO: charge attacks (need to design how they work)
};
/**
* Called when some units kills something (another unit, building, animal etc)
- * update player statistics only for now
*/
Attack.prototype.TargetKilled = function(killerEntity, targetEntity)
{
var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
+
+ // if unit can collect loot, lets try to collect it
+ var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
+ if (cmpLooter)
+ {
+ cmpLooter.Collect(targetEntity);
+ }
}
/**
* Inflict damage on the target
*/
Attack.prototype.CauseDamage = function(data)
{
var strengths = this.GetAttackStrengths(data.type);
var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
if (!cmpDamageReceiver)
return;
var targetState = cmpDamageReceiver.TakeDamage(strengths.hack, strengths.pierce, strengths.crush);
// if target killed pick up loot and credit experience
if (targetState.killed == true)
{
this.TargetKilled(this.entity, data.target);
}
Engine.PostMessage(data.target, MT_Attacked,
{ "attacker": this.entity, "target": data.target });
PlaySound("attack_impact", this.entity);
};
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Promotion.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Promotion.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Promotion.js (revision 9391)
@@ -1,15 +1,98 @@
function Promotion() {}
Promotion.prototype.Schema =
"" +
"" +
"" +
- "" +
+ "" +
"" +
"";
-/*
- * TODO: this all needs to be designed and implemented
- */
+Promotion.prototype.Init = function()
+{
+ this.currentXp = 0;
+}
+
+Promotion.prototype.GetRequiredXp = function()
+{
+ return +(this.template.RequiredXp);
+};
+
+Promotion.prototype.GetCurrentXp = function()
+{
+ return this.currentXp;
+};
+
+Promotion.prototype.GetPromotedTemplateName = function()
+{
+ return this.template.Entity;
+}
+
+Promotion.prototype.Promote = function(promotedTemplateName)
+{
+ // Create promoted unit entity
+ var promotedUnitEntity = Engine.AddEntity(promotedTemplateName);
+
+ // Copy parameters from current entity to promoted one
+ var cmpCurrentUnitPosition = Engine.QueryInterface(this.entity, IID_Position);
+ var cmpPromotedUnitPosition = Engine.QueryInterface(promotedUnitEntity, IID_Position);
+ if (cmpCurrentUnitPosition.IsInWorld())
+ {
+ var pos = cmpCurrentUnitPosition.GetPosition2D();
+ cmpPromotedUnitPosition.JumpTo(pos.x, pos.y);
+ }
+ var rot = cmpCurrentUnitPosition.GetRotation();
+ cmpPromotedUnitPosition.SetYRotation(rot.y);
+ cmpPromotedUnitPosition.SetXZRotation(rot.x, rot.z);
+ var heightOffset = cmpCurrentUnitPosition.GetHeightOffset();
+ cmpPromotedUnitPosition.SetHeightOffset(heightOffset);
+
+ var cmpCurrentUnitOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ var cmpPromotedUnitOwnership = Engine.QueryInterface(promotedUnitEntity, IID_Ownership);
+ cmpPromotedUnitOwnership.SetOwner(cmpCurrentUnitOwnership.GetOwner());
+
+ var cmpPromotedUnitPromotion = Engine.QueryInterface(promotedUnitEntity, IID_Promotion);
+ if (cmpPromotedUnitPromotion)
+ cmpPromotedUnitPromotion.IncreaseXp(this.currentXp);
+
+ var cmpCurrentUnitResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
+ var cmpPromotedUnitResourceGatherer = Engine.QueryInterface(promotedUnitEntity, IID_ResourceGatherer);
+ if (cmpCurrentUnitResourceGatherer && cmpPromotedUnitResourceGatherer)
+ {
+ var carriedResorces = cmpCurrentUnitResourceGatherer.GetCarryingStatus();
+ cmpPromotedUnitResourceGatherer.GiveResources(carriedResorces);
+ }
+
+ var cmpCurrentUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
+ var cmpPromotedUnitAI = Engine.QueryInterface(promotedUnitEntity, IID_UnitAI);
+ cmpPromotedUnitAI.Cheer();
+ var orders = cmpCurrentUnitAI.GetOrders();
+ cmpPromotedUnitAI.AddOrders(orders);
+
+ Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: promotedUnitEntity });
+
+ // Destroy current entity
+ Engine.DestroyEntity(this.entity);
+}
+
+Promotion.prototype.IncreaseXp = function(amount)
+{
+ this.currentXp += +(amount);
+
+ if (this.currentXp >= this.template.RequiredXp)
+ {
+ var promotionTemplate = this.template;
+ do
+ {
+ this.currentXp -= promotionTemplate.RequiredXp;
+ var promotedTemplateName = promotionTemplate.Entity;
+ var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ var template = cmpTemplateManager.GetTemplate(promotedTemplateName);
+ promotionTemplate = template.Promotion || null;
+ }
+ while (promotionTemplate != null && this.currentXp >= promotionTemplate.RequiredXp);
+ this.Promote(promotedTemplateName);
+ }
+}
Engine.RegisterComponentType(IID_Promotion, "Promotion", Promotion);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Formation.js (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Formation.js (revision 9391)
@@ -1,612 +1,626 @@
function Formation() {}
Formation.prototype.Schema =
"";
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()
{
return this.members.length;
};
/**
* Returns the 'primary' member of this formation (typically the most
* important unit type), for e.g. playing a representative sound.
* Returns undefined if no members.
* TODO: actually implement something like that; currently this just returns
* the arbitrary first one.
*/
Formation.prototype.GetPrimaryMember = function()
{
return this.members[0];
};
/**
* Initialise the members of this formation.
* Must only be called once.
* All members must implement UnitAI.
*/
Formation.prototype.SetMembers = function(ents)
{
this.members = ents;
for each (var ent in this.members)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SetFormationController(this.entity);
}
// Locate this formation controller in the middle of its members
this.MoveToMembersCenter();
this.ComputeMotionParameters();
};
/**
* Remove the given list of entities.
* The entities must already be members of this formation.
*/
Formation.prototype.RemoveMembers = function(ents)
{
this.members = this.members.filter(function(e) { return ents.indexOf(e) == -1; });
for each (var ent in ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SetFormationController(INVALID_ENTITY);
}
// If there's nobody left, destroy the formation
if (this.members.length == 0)
{
Engine.DestroyEntity(this.entity);
return;
}
this.ComputeMotionParameters();
// Rearrange the remaining members
this.MoveMembersIntoFormation(true);
};
/**
* Remove all members and destroy the formation.
*/
Formation.prototype.Disband = function()
{
for each (var ent in this.members)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SetFormationController(INVALID_ENTITY);
}
this.members = [];
Engine.DestroyEntity(this.entity);
};
/**
* Call obj.funcname(args) on UnitAI components of all members.
*/
Formation.prototype.CallMemberFunction = function(funcname, args)
{
for each (var ent in this.members)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI[funcname].apply(cmpUnitAI, args);
}
};
/**
* Set all members to form up into the formation shape.
* If moveCenter is true, the formation center will be reinitialised
* to the center of the units.
*/
Formation.prototype.MoveMembersIntoFormation = function(moveCenter)
{
var active = [];
var positions = [];
for each (var ent in this.members)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
continue;
active.push(ent);
positions.push(cmpPosition.GetPosition());
}
// 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.formationName != "Column Open";
var offsets = this.ComputeFormationOffsets(active, this.columnar);
var avgpos = this.ComputeAveragePosition(positions);
var avgoffset = this.ComputeAveragePosition(offsets);
// Reposition the formation if we're told to or if we don't already have a position
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (moveCenter || !cmpPosition.IsInWorld())
cmpPosition.JumpTo(avgpos.x, avgpos.z);
for (var i = 0; i < offsets.length; ++i)
{
var offset = offsets[i];
var cmpUnitAI = Engine.QueryInterface(offset.ent, IID_UnitAI);
cmpUnitAI.ReplaceOrder("FormationWalk", {
"target": this.entity,
"x": offset.x - avgoffset.x,
"z": offset.z - avgoffset.z
});
}
};
Formation.prototype.MoveToMembersCenter = function()
{
var positions = [];
for each (var ent in this.members)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
continue;
positions.push(cmpPosition.GetPosition());
}
var avgpos = this.ComputeAveragePosition(positions);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
cmpPosition.JumpTo(avgpos.x, avgpos.z);
};
Formation.prototype.ComputeFormationOffsets = function(active, columnar)
{
var separation = 4; // TODO: don't hardcode this
var types = {
"Cavalry" : [],
"Hero" : [],
"Melee" : [],
"Ranged" : [],
"Support" : [],
"Unknown": []
}; // TODO: make this work so we put ranged behind infantry etc
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 || this.formationName == "Column Closed")
{
// Have at most 3 files
if (count <= 3)
cols = count;
else
cols = 3;
shape = "square";
}
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;
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 = "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";
}
if (shape == "square")
{
var ranks = Math.ceil(count / cols);
var left = count;
for (var r = 0; r < ranks; ++r)
{
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;
}
}
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;
};
Formation.prototype.ComputeAveragePosition = function(positions)
{
var sx = 0;
var sz = 0;
for each (var pos in positions)
{
sx += pos.x;
sz += pos.z;
}
return { "x": sx / positions.length, "z": sz / positions.length };
};
/**
* Set formation controller's radius and speed based on its current members.
*/
Formation.prototype.ComputeMotionParameters = function()
{
var maxRadius = 0;
var minSpeed = Infinity;
for each (var ent in this.members)
{
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction)
maxRadius = Math.max(maxRadius, cmpObstruction.GetUnitRadius());
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed());
}
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
cmpUnitMotion.SetUnitRadius(maxRadius);
cmpUnitMotion.SetSpeed(minSpeed);
// TODO: we also need to do something about PassabilityClass, CostClass
};
Formation.prototype.OnUpdate_Final = function(msg)
{
// Switch between column and box if necessary
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
var walkingDistance = cmpUnitAI.ComputeWalkingDistance();
var columnar = (walkingDistance > g_ColumnDistanceThreshold);
if (columnar != this.columnar)
this.MoveMembersIntoFormation(false);
// (disable moveCenter so we can't get stuck in a loop of switching
// shape causing center to change causing shape to switch back)
};
Formation.prototype.OnGlobalOwnershipChanged = function(msg)
{
// When an entity is captured or destroyed, it should no longer be
// controlled by this formation
if (this.members.indexOf(msg.entity) != -1)
this.RemoveMembers([msg.entity]);
};
+Formation.prototype.OnGlobalEntityRenamed = function(msg)
+{
+ if (this.members.indexOf(msg.entity) != -1)
+ {
+ this.members[this.members.indexOf(msg.entity)] = msg.newentity;
+
+ var cmpOldUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI);
+ cmpOldUnitAI.SetFormationController(INVALID_ENTITY);
+
+ var cmpNewUnitAI = Engine.QueryInterface(msg.newentity, IID_UnitAI);
+ cmpNewUnitAI.SetFormationController(this.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);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_b.xml (revision 9391)
@@ -1,36 +1,36 @@
heleunits/hele_infantry_javelinist_bPeltastes ThrakikosPeltasts were javelinists originating in Thrace but their form of combat was widely copied by the Hellenes, Macedonians, and Persians. Equipped with a small oval or crescent shield, a peltast would charge at enemy formations whilst hurling his javelins then fall back to avoid close combat. They wore no armor and were at a significant disadvantage against heavy infantry and cavalry, relying on their speed and skill for survival. Thracians sold their services to Hellene cities as mercenaries and added a much needed ranged and skirmishing ability to Hellenic armies.units/hele_infantry_javelinist.png
- hele_infantry_javelinist_a
+ units/hele_infantry_javelinist_a508844.0units/hellenes/infantry_javelinist_b.xml
structures/hele_gymnasion
structures/hele_theatron
structures/hele_tholos
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_e.xml (revision 9391)
@@ -1,35 +1,36 @@
- Elite
+ Elite0.512037.052.0units/hellenes/infantry_javelinist_e.xml
+ 1105.05.04.06.018.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_a.xml (revision 9391)
@@ -1,35 +1,35 @@
- Advanced
+ Advanced
- celt_cavalry_javelinist_e
+ units/celt_cavalry_javelinist_e0.751303.04.03.011.4434.3236.048units/celts/cavalry_javelinist_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_b.xml (revision 9391)
@@ -1,25 +1,25 @@
celtunits/celt_cavalry_javelinist_bGaisaredosThe Celts were extremely proficient horsemen and created excellent tack for their mounts. This included an early form of saddle with horns at each corner, giving them a huge edge in terms or control over their counterparts. Since the cavalry was made up of rich nobles armor and fine weapons were in great supply, making them formidable opponents.units/celt_cavalry_javelinist.png
- celt_cavalry_javelinist_a
+ units/celt_cavalry_javelinist_a2.011.8835.64units/celts/cavalry_javelinist_b.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_javelinist_e.xml (revision 9391)
@@ -1,32 +1,33 @@
- Elite
+ Elite0.51404.05.04.010.5631.6837.052units/celts/cavalry_javelinist_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_marian.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_marian.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_marian.xml (revision 9391)
@@ -1,24 +1,24 @@
romeRoman LegionMarian LegionariusThe Marian Legionnaire.The famed legion of the Late Republic.units/rome_super_legion_marian.png
- rome_legionnaire_imperial
- 500
+ units/rome_legionnaire_imperial
+ 500units/romans/super_unit_3.xml6.018.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_a.xml (revision 9391)
@@ -1,40 +1,40 @@
- Advanced
+ Advanced
- hele_infantry_spearman_e
+ units/hele_infantry_spearman_e0.751105.08.06.05.616.86.016.012.032.0units/hellenes/infantry_spearman_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_b.xml (revision 9391)
@@ -1,43 +1,43 @@
heleunits/hele_infantry_spearman_bHoplitesHoplites were the very symbol of Hellenistic prestige and citizenship, armed with a spear and a large round, bronze-coated shield known as an aspis. Armor was heavy, with bronze helmets and a cuirass of either bronze or linen, in addition to greaves. Hoplites fought in a tight formation called a phalanx, guarding each other with their shields while they attacked the enemy with their 2.5 meter spear or short iron sword.units/hele_infantry_spearman.png
- hele_infantry_spearman_a
+ units/hele_infantry_spearman_a60887.06.06.0units/hellenes/infantry_spearman_b.xml
structures/hele_gymnasion
structures/hele_theatron
structures/hele_tholos
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_a.xml (revision 9391)
@@ -1,36 +1,36 @@
- Advanced
+ Advanced
- hele_cavalry_javelinist_e
+ units/hele_cavalry_javelinist_e0.751304.04.03.010.431.236.048units/hellenes/cavalry_javelinist_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_a.xml (revision 9391)
@@ -1,35 +1,35 @@
- Advanced
+ Advanced
- celt_infantry_javelinist_e
+ units/celt_infantry_javelinist_e0.751003.04.04.07.4822.4436.044units/celts/infantry_javelinist_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_e.xml (revision 9391)
@@ -1,33 +1,33 @@
- Elite
+ Elite0.51405.05.04.09.628.837.052units/hellenes/cavalry_javelinist_e.xml
-
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/mace_hypaspist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/mace_hypaspist.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/mace_hypaspist.xml (revision 9391)
@@ -1,49 +1,49 @@
heleHypaspist (Shield Bearer)The Hypaspistai, or "shield bearers", were the flower of the Macedonian infantry. They were the most battle hardened veterans within the army and followed Philip II and Alexander the Great into over a dozen full scale engagements. These heavily armed and opulently armoured units acted as an intermediary between the phalanx and the cavalry arm, many times charging headlong with Alexander into a breech in the enemy lines. Sometimes they fought as slow pikemen, like the Pezhetairoi with 6 meter "sarissas", and other times they fought as Hoplites with large aspides and 2.5 meter-long spears, or "dorata". In later times they became known as the Argyraspidai, or "Silver Shields" when Alexander bestowed upon them armor and shields plated in pure silver, and played a decisive role in the early Diadochoi Wars of Alexander's "Successors."Elite Macedonian infantry.units/macedonian_hypaspist.png
- mace_argyraspis
- 500
+ units/mace_argyraspis
+ 500201359.012.09.05.516.510.020.06.020.040.00.06.0units/macedonians/mace_su1_hypaspist.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_e.xml (revision 9391)
@@ -1,31 +1,34 @@
+
+ Elite
+ units/iberians/infantry_javelinist_e.xml0.51105.05.04.06.018.00.037.0442000
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_a.xml (revision 9391)
@@ -1,36 +1,39 @@
+
+ Advanced
+
- iber_cavalry_spearman_e
+ units/iber_cavalry_spearman_eunits/iberians/cavalry_spearman_a.xml0.751405.09.06.08.425.26.016.018.048.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml (revision 9391)
@@ -1,15 +1,15 @@
iberEponesunits/iber_cavalry_spearman.pngArmed like the light infantry, Iberian cavalry were often pursued as mercenaries, especially by the Carthaginians. Mounted on excellent horses and wielding high-grade swords they were capable of taking on heavy or light cavalry. As with all Iberians armor was scarce, but they wore the ubiquitous sinew caps made famous by the peoples of the peninsula.
- iber_cavalry_spearman_a
+ units/iber_cavalry_spearman_aunits/iberians/cavalry_spearman_b.xml
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_b.xml (revision 9391)
@@ -1,20 +1,20 @@
heleunits/hele_cavalry_swordsman_bHippeusCavalry were made up of the upper class since they were the only ones who could afford the breeding and caring for horses. Initially they were missile troops who avoided close combat, throwing javelins and spears at enemy troops. Later on thanks to developments by the Macedonians they began to close with enemy troops to use their swords. As with all ancient horsemen the Hippeus did not have stirrups or a saddle.units/hele_cavalry_swordsman.png
- hele_cavalry_swordsman_a
+ units/hele_cavalry_swordsman_a110units/hellenes/cavalry_swordsman_b.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_swordsman_e.xml (revision 9391)
@@ -1,35 +1,35 @@
- Elite
+ Elite0.51508.425.28.010.07.022.066.0units/hellenes/cavalry_swordsman_e.xml
-
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_a.xml (revision 9391)
@@ -1,36 +1,39 @@
+
+ Advanced
+
- iber_infantry_spearman_e
+ units/iber_infantry_spearman_eunits/iberians/infantry_spearman_a.xml0.751105.07.06.05.215.66.016.012.032.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_spearman_e.xml (revision 9391)
@@ -1,37 +1,37 @@
- Elite
+ Elite0.51207.010.07.05.215.67.017.014.034.0units/hellenes/infantry_spearman_e.xml
-
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_cavalry_javelinist_b.xml (revision 9391)
@@ -1,20 +1,20 @@
heleunits/hele_cavalry_javelinist_bProdromosProdromoi were the light scouts of Greek armies.units/hele_cavalry_javelinist.png
- hele_cavalry_javelinist_a
+ units/hele_cavalry_javelinist_a110units/hellenes/cavalry_javelinist_b.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_b.xml (revision 9391)
@@ -1,34 +1,34 @@
celtunits/celt_infantry_javelinist_bBaguadaGesatae were young men who devoted themselves to war, often serving as mercenaries for other tribes. They were the last Celts to fight stark naked to show their courage, often carrying only a shield with several javelins and a regular Celtic spear. More often than not they were covered in geometric designs painted in woad, a blue dye.units/celt_infantry_javelinist.png
- celt_infantry_javelinist_a
+ units/celt_infantry_javelinist_a2.07.9223.76units/celts/infantry_javelinist_b.xml
-structures/{civ}_fortress
structures/celt_kennel
structures/celt_sb1
structures/celt_fortress_b
structures/celt_fortress_g
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_javelinist_e.xml (revision 9391)
@@ -1,32 +1,33 @@
- Elite
+ Elite0.51104.05.04.06.619.837.048units/celts/infantry_javelinist_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_a.xml (revision 9391)
@@ -1,32 +1,35 @@
+
+ Advanced
+
- iber_infantry_javelinist_e
+ units/iber_infantry_javelinist_eunits/iberians/infantry_javelinist_a.xml0.751004.04.04.06.820.436.044
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_a.xml (revision 9391)
@@ -1,39 +1,39 @@
- Advanced
+ Advanced
- celt_infantry_spearman_e
+ units/celt_infantry_spearman_e0.751104.06.06.06.1618.486.016.012.032.0units/celts/infantry_spearman_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_b.xml (revision 9391)
@@ -1,34 +1,34 @@
celtunits/celt_infantry_spearman_bGaesataThe spear was the main weapon of the Celts, arming the bulk of their forces. The average Celt would only have to take up his long spear and body shield to be ready for battle. While armor was rare the rabid fighting spirit of the Celts more than made up for in vigor what was lost in protection.units/celt_infantry_spearman.png
- celt_infantry_spearman_a
+ units/celt_infantry_spearman_a3.06.619.8units/celts/infantry_spearman_b.xml
-structures/{civ}_fortress
structures/celt_kennel
structures/celt_sb1
structures/celt_fortress_b
structures/celt_fortress_g
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_infantry_spearman_e.xml (revision 9391)
@@ -1,36 +1,37 @@
- Elite
+ Elite0.51206.08.07.05.7217.167.017.014.034.0units/celts/infantry_spearman_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_e.xml (revision 9391)
@@ -1,33 +1,36 @@
+
+ Elite
+ units/iberians/cavalry_spearman_e.xml0.51507.011.07.08.024.07.017.021.051.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_a.xml (revision 9391)
@@ -1,32 +1,35 @@
+
+ Advanced
+
- iber_infantry_slinger_e
+ units/iber_infantry_slinger_eunits/iberians/infantry_slinger_a.xml0.751003.03.03.07.221.621.052.0
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_a.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_a.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_a.psa (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_a.psa (nonexistent)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_a.psa
___________________________________________________________________
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_b.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_b.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_b.psa (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_b.psa (nonexistent)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_b.psa
___________________________________________________________________
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_c.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_c.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_c.psa (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_c.psa (nonexistent)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_c.psa
___________________________________________________________________
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_d.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_d.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_d.psa (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_d.psa (nonexistent)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_d.psa
___________________________________________________________________
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_e.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_e.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_e.psa (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_e.psa (nonexistent)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/not used/inf_salute_e.psa
___________________________________________________________________
Deleted: svn:mime-type
## -1 +0,0 ##
-application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml (revision 9391)
@@ -1,29 +1,29 @@
iberScutariunits/iber_infantry_spearman.pngA long-bladed spear was a chief melee weapon of the Iberian infantry, often used after the javelins had been thrown. Typically carried by infantry known as scutarii for their long oval body shields, the spearmen would close in formation to attack their opponents. Usually lightly armored, they were quick and had a ferocious reputation.
- iber_infantry_spearman_a
+ units/iber_infantry_spearman_aunits/iberians/infantry_spearman_b.xml
structures/iber_sb1
6.05.616.8
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_imperial.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_imperial.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_legionnaire_imperial.xml (revision 9391)
@@ -1,30 +1,30 @@
romeImperial LegionariusThe Imperial Legionnaire.The famed legion of the Early Empire.units/rome_super_legion_imperial.png
- rome_centurio_imperial
- 700
+ units/rome_centurio_imperial
+ 70010.06.010.025.050.0units/romans/super_unit_4_imp_legion.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_e.xml (revision 9391)
@@ -1,34 +1,35 @@
- Elite
+ Elite0.51509.2427.727.010.07.022.066.0units/celts/cavalry_swordsman_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_e.xml (revision 9391)
@@ -1,36 +1,37 @@
- Elite
+ Elite0.51050.46904.02.06.06.716.7535.068.0units/hellenes/infantry_archer_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_e.xml (revision 9391)
@@ -1,29 +1,32 @@
+
+ Elite
+ units/iberians/infantry_slinger_e.xml0.51104.04.04.06.820.422.056.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_b.xml (revision 9391)
@@ -1,75 +1,75 @@
celtunits/celt_cavalry_spearman_bWar DogCounThe Celts used large dogs such as mastiffs or wolfhounds in combat, especially against enemy cavalry. The Romans were very impressed with the strength and ferocity of these dogs. Many were brought back to Rome for the gladiator arena or to serve as guard dogs.Bonused vs. Infantry Swordsmen, Infantry Javelinists, and Fauna.units/celt_cavalry_spearman.png
- celt_cavalry_spearman_a
+ units/celt_cavalry_spearman_a1017501003.03.03.09.6829.048020.00.00.060.00.0units/celts/cavalry_spearman_b.xml1.5voice/celts/civ/civ_dog_move.xmlvoice/celts/civ/civ_dog_move.xmlvoice/celts/civ/civ_dog_move.xmlvoice/celts/civ/civ_dog_move.xmlactor/mounted/movement/walk.xmlactor/mounted/movement/walk.xmlattack/weapon/sword.xmlactor/fauna/death/death_animal_gen.xmlinterface/alarm/alarm_create_cav.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_b.xml (revision 9391)
@@ -1,25 +1,25 @@
celtunits/celt_cavalry_swordsman_bEposLike a sword, a horse was a sign of nobility and as a result the Celtic cavalry was often better equipped than the infantry. Armor and helmets were common, while weapons consisted of a large bladed spear and a long slashing sword for close combat. Shields could be round, hexagonal, or oval, but the most common was a regular oval body shield with the top and bottom shorn off. Unlike other horseman, the Celts were not afraid to leap off their mount to fight on foot then climb into the saddle again when possible.units/celt_cavalry_swordsman.png
- celt_cavalry_swordsman_a
+ units/celt_cavalry_swordsman_a3.010.1230.36units/celts/cavalry_swordsman_b.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_javelinist_a.xml (revision 9391)
@@ -1,35 +1,35 @@
- Advanced
+ Advanced
- hele_infantry_javelinist_e
+ units/hele_infantry_javelinist_e0.751004.04.04.06.820.436.048.0units/hellenes/infantry_javelinist_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 9391)
@@ -1,99 +1,100 @@
UnitUnit ConquestCriticalunitaggressive12.0false1010000corpse1000truefalse80.00.010.01.01.01.02.01.0120202020false7.015.050.00.00.10.2defaultdefault2.00.3335.02.5truetruefalsefalsetruefalsefalse24falsefalse
+ truefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_e.xml (revision 9391)
@@ -1,33 +1,36 @@
+
+ Elite
+ units/iberians/infantry_spearman_e.xml0.51207.09.07.04.814.47.017.014.034.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_e.xml (revision 9391)
@@ -1,31 +1,32 @@
- Elite
+ Elite0.56.06.06.08.826.422.066.0units/celts/cavalry_spearman_e.xml
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_a.xml (revision 9391)
@@ -1,33 +1,33 @@
- Advanced
+ Advanced
- hele_infantry_archer_e
+ units/hele_infantry_archer_e0.75950.37003.01.03.030.064.0units/hellenes/infantry_archer_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml (revision 9391)
@@ -1,105 +1,105 @@
InfantryInfantry CitizenSoldier Organic
- Basic
+ Basic
- 600
+ 60095000010011112.01.00.50.50.5211011011000.210001.01.09.07.017.5infantry801.0
structures/{civ}_house
structures/{civ}_mill
structures/{civ}_farmstead
structures/{civ}_field
structures/{civ}_corral
structures/{civ}_temple
structures/{civ}_market
structures/{civ}_civil_centre
structures/{civ}_barracks
structures/{civ}_dock
structures/{civ}_scout_tower
structures/{civ}_wall
structures/{civ}_wall_tower
structures/{civ}_wall_gate
structures/{civ}_fortress
voice/hellenes/civ/civ_male_ack.xmlvoice/hellenes/civ/civ_male_attack.xmlvoice/hellenes/civ/civ_male_ack.xmlvoice/hellenes/civ/civ_male_ack.xmlactor/human/movement/walk.xmlactor/human/movement/run.xmlattack/weapon/sword.xmlactor/human/death/death.xmlresource/construction/con_wood.xmlresource/foraging/forage_leaves.xmlresource/farming/farm.xmlresource/lumbering/lumbering.xmlresource/mining/pickaxe.xmlresource/mining/mining.xmlactor/singlesteps/steps_gravel.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_spearman_a.xml (revision 9391)
@@ -1,34 +1,34 @@
- Advanced
+ Advanced
- celt_cavalry_spearman_e
+ units/celt_cavalry_spearman_e0.754.04.04.09.2427.7221.063.0units/celts/cavalry_spearman_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/celt_cavalry_swordsman_a.xml (revision 9391)
@@ -1,37 +1,37 @@
- Advanced
+ Advanced
- celt_cavalry_swordsman_e
+ units/celt_cavalry_swordsman_e0.751409.6829.045.08.06.021.063.0units/celts/cavalry_swordsman_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_b.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/hele_infantry_archer_b.xml (revision 9391)
@@ -1,35 +1,35 @@
heleunits/hele_infantry_archer_bMercenary Infantry ArcherToxotes KretikosArchers were used in Hellenistic armies to support the phalanx by splitting up enemy formations. The best Greek archers were from Crete, but mercenaries from Scythia and Asia Minor were sometimes employed. Hellenistic archers wore their quivers on their backs and the more successful ones were able to procure armor.units/hele_infantry_archer.png
- hele_infantry_archer_a
+ units/hele_infantry_archer_a007588units/hellenes/infantry_archer_b.xml
structures/hele_gymnasion
structures/hele_theatron
structures/hele_tholos
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml (revision 9391)
@@ -1,84 +1,84 @@
CavalryCavalry CitizenSoldier Organic
- Basic
+ Basic
- 900
+ 900pitch213100200104112.01.010303030301300.220000.00.02.08.826.4600.05.07.5100voice/hellenes/civ/civ_male_ack.xmlvoice/hellenes/civ/civ_male_attack.xmlvoice/hellenes/civ/civ_male_ack.xmlactor/mounted/movement/walk.xmlactor/mounted/movement/walk.xmlattack/weapon/sword.xmlactor/fauna/death/death_horse.xmlinterface/alarm/alarm_create_cav.xml
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_c.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_c.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_c.psa (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_c.psa (revision 9391)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_c.psa
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_e.xml (revision 9391)
@@ -1,193 +1,194 @@
+ skeletal/m_tunic_short.daeskeletal/hele_isp_e_a.ddsskeletal/hele_isp_e_c.ddsskeletal/hele_isp_e_f.ddsskeletal/hele_isp_e_g.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_e.xml (revision 9391)
@@ -1,168 +1,169 @@
+ skeletal/m_tunic_long.daeskeletal/hele_ijv_e_2.ddsskeletal/hele_ijv_e_3.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_e.xml (revision 9391)
@@ -1,180 +1,181 @@
-
+
+ skeletal/m_tights.daeskeletal/celt_ijv_e_01.ddsskeletal/celt_ijv_e_02.ddsskeletal/kart_isw_a_01.ddsskeletal/kart_isw_a_02.dds
-
+ player_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_d.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_d.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_d.psa (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_d.psa (revision 9391)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_d.psa
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_a.xml (revision 9391)
@@ -1,150 +1,151 @@
+ skeletal/m_tunic_short.daeskeletal/hele_isp_b.ddsskeletal/hele_isp_b_2.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_a.xml (revision 9391)
@@ -1,186 +1,187 @@
-
+
+ skeletal/m_pants_celt.daeskeletal/celt_isw_a_01.ddsskeletal/celt_isw_a_02.ddsskeletal/celt_isw_a_03.ddsskeletal/celt_isw_a_04.ddsskeletal/celt_isw_a_05.ddsskeletal/celt_isw_a_06.ddsskeletal/celt_isw_a_07.ddsskeletal/celt_isw_a_08.ddsskeletal/celt_isw_a_09.ddsskeletal/celt_isw_a_10.ddsskeletal/celt_isw_a_11.dds
-
+ player_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/celts/super_unit_1.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/celts/super_unit_1.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/celts/super_unit_1.xml (revision 9391)
@@ -1,42 +1,42 @@
-
-
-
-
-
+
+
+
+
+ skeletal/m_pants_celt.daeskeletal/celt_isw_e_01.ddsskeletal/celt_isw_e_02.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_b.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_b.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_b.psa (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_b.psa (revision 9391)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_b.psa
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_spearman_a.xml (revision 9391)
@@ -1,183 +1,184 @@
+ skeletal/m_tunic_short.daeskeletal/hele_isp_b_3.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_javelinist_a.xml (revision 9391)
@@ -1,178 +1,179 @@
-
+
+ skeletal/m_tunic_long.daeskeletal/hele_ijv_a.ddsskeletal/m_tunic_short.daeskeletal/hele_ijv_a_2.ddsskeletal/m_tunic_long.daeskeletal/hele_ijv_a_3.dds
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+ player_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_a.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_a.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_javelinist_a.xml (revision 9391)
@@ -1,194 +1,195 @@
-
+
+ skeletal/m_pants.daeskeletal/celt_ijv_a_01.ddsskeletal/celt_ijv_a_02.ddsskeletal/celt_ijv_a_03.ddsskeletal/celt_ijv_a_04.ddsskeletal/celt_ijv_a_05.ddsskeletal/celt_ijv_a_06.ddsskeletal/celt_ijv_a_07.ddsskeletal/celt_ijv_a_08.ddsskeletal/celt_ijv_a_09.ddsskeletal/celt_ijv_a_10.ddsskeletal/celt_ijv_a_11.dds
-
+ player_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_a.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_a.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_a.psa (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_a.psa (revision 9391)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_a.psa
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_e.psa
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_e.psa
===================================================================
--- ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_e.psa (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_e.psa (revision 9391)
Property changes on: ps/trunk/binaries/data/mods/public/art/animation/biped/inf_salute_e.psa
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/hellenes/infantry_archer_e.xml (revision 9391)
@@ -1,149 +1,150 @@
+ skeletal/m_tunic_long.daeskeletal/hele_iar_e.ddsplayer_trans.xml
Index: ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_e.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_e.xml (revision 9390)
+++ ps/trunk/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_e.xml (revision 9391)
@@ -1,168 +1,169 @@
-
+
+ skeletal/m_pants_celt.daeskeletal/celt_isw_e_01.ddsskeletal/celt_isw_e_02.ddsplayer_trans.xml