1
0
forked from 0ad/0ad

Garrisoning for buildings based on patch by Evans (fixes #610)

This was SVN commit r8451.
This commit is contained in:
WhiteTreePaladin 2010-10-23 22:43:15 +00:00
parent bcc5c87045
commit 0aaddf62aa
51 changed files with 430 additions and 148 deletions

View File

@ -7,7 +7,8 @@ const SDLK_RSHIFT = 303;
const SDLK_LSHIFT = 304;
const SDLK_RCTRL = 305;
const SDLK_LCTRL = 306;
const SDLK_RALT = 307;
const SDLK_LALT = 308;
// TODO: these constants should be defined somewhere else instead, in
// case any other code wants to use them too
@ -34,6 +35,9 @@ specialKeyStates[SDLK_RSHIFT] = 0;
specialKeyStates[SDLK_LSHIFT] = 0;
specialKeyStates[SDLK_RCTRL] = 0;
specialKeyStates[SDLK_LCTRL] = 0;
specialKeyStates[SDLK_RALT] = 0;
specialKeyStates[SDLK_LALT] = 0;
// (TODO: maybe we should fix the hotkey system to be usable in this situation,
// rather than hardcoding Shift into this code?)
@ -72,7 +76,7 @@ function findGatherType(gatherer, supply)
function determineAction(x, y, fromMinimap)
{
var selection = g_Selection.toList();
var ctrlPressed = specialKeyStates[SDLK_LCTRL] || specialKeyStates[SDLK_RCTRL];
// No action if there's no selection
if (!selection.length)
return undefined;
@ -122,11 +126,16 @@ function determineAction(x, y, fromMinimap)
var enemyOwned = ((targetState.player != entState.player)? true : false);
var gaiaOwned = ((targetState.player == 0)? true : false);
// If the target is a resource and we have the right kind of resource gatherers selected, then gather
// If the target is a foundation and we have builders selected, then build (or repair)
// If the target is an enemy, then attack
if (targetState.resourceSupply && (playerOwned || gaiaOwned))
if (targetState.garrisonHolder && playerOwned && ctrlPressed)
{
return {"type": "garrison", "cursor": "action-garrison", "target": targets[0]};
}
else if (targetState.resourceSupply && (playerOwned || gaiaOwned))
{
// If the target is a resource and we have the right kind of resource gatherers selected, then gather
// If the target is a foundation and we have builders selected, then build (or repair)
// If the target is an enemy, then attack
var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply);
if (resource)
return {"type": "gather", "cursor": "action-gather-"+resource, "target": targets[0]};
@ -522,6 +531,11 @@ function handleInputAfterGui(ev)
Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
return true;
case "garrison":
Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
//Need to play some sound here??
return true;
case "set-rallypoint":
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z});
@ -801,6 +815,9 @@ function performCommand(entity, commandName)
messageBox(320, 180, message, "Confirmation", 0, btCaptions, btCode);
}
break;
case "unload-all":
unloadAll(entity);
break;
default:
break;
}
@ -835,3 +852,12 @@ function setCameraFollow(entity)
Engine.CameraFollow(0);
}
function unload(garrisonHolder, entity)
{
Engine.PostNetworkCommand({"type": "unload", "entity": entity, "garrisonHolder": garrisonHolder});
}
function unloadAll(garrisonHolder)
{
Engine.PostNetworkCommand({"type": "unload-all", "garrisonHolder": garrisonHolder});
}

View File

@ -417,8 +417,6 @@
<object name="unitFormationPanel"
size="20 10 100% 100%"
>
<!--<object size="-5 -2 59 62" type="image" sprite="snIconSheetTab" tooltip_style="snToolTip" cell_id="5" tooltip="Formations"/>-->
<object size="0 0 100% 100%">
<repeat count="16">
<object name="unitFormationButton[n]" hidden="true" style="iconButton" type="button" size="0 0 38 38" z="100">
@ -428,25 +426,9 @@
</object>
</object>
<!--
GARRISON GOES HERE
-->
<object name="unitGarrisonPanel"
size="20 10 100% 100%"
>
<!--<object size="-6 -6 50 50" type="image" sprite="snIconSheetTab" tooltip_style="snToolTip" cell_id="1" tooltip="Garrison"/>-->
<object size="0 0 100% 100%">
<repeat count="16">
<object name="unitGarrisonButton[n]" hidden="true" style="iconButton" type="button" size="0 0 38 38" z="100">
@ -457,21 +439,6 @@
</object>
</object>
</object>
<!-- ================================ ================================ -->
@ -582,9 +549,9 @@
z="30"
>
<object size="0 0 100% 100%">
<repeat count="9">
<object name="unitCommandButton[n]" hidden="true" type="button" size="0 0 32 32" tooltip_style="snToolTipBottom">
<object name="unitCommandIcon[n]" ghost="true" type="image" size="0 0 32 32" style="commandIcon"/>
<repeat count="6">
<object name="unitCommandButton[n]" hidden="true" style="iconButton" type="button" size="0 0 32 32" tooltip_style="snToolTipBottom">
<object name="unitCommandIcon[n]" ghost="true" type="image" size="0 0 100% 100%" style="commandIcon"/>
</object>
</repeat>
</object>

View File

@ -114,13 +114,13 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
usedPanels[guiName] = 1;
var numberOfItems = items.length;
var selection = g_Selection.toList();
var garrisonGroups = new EntityGroups();
if (guiName == "Selection")
{
if (numberOfItems > 16)
numberOfItems = 16;
}
if (guiName == "Formation" || guiName == "Garrison")
if (guiName == "Formation")
{
if (numberOfItems > 16)
numberOfItems = 16;
@ -130,6 +130,13 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
if (numberOfItems > 16)
numberOfItems = 16;
}
else if (guiName == "Garrison")
{
if (numberOfItems > 16)
numberOfItems = 16;
//Group garrisoned units based on class
garrisonGroups.add(unitEntState.garrisonHolder.entities);
}
else if (guiName == "Command")
{
if (numberOfItems > 4)
@ -177,29 +184,10 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
break;
case GARRISON:
/*
!!!!!
GARRISON GOES HERE (need to customize this)
!!!!!
var tooltip = getEntityName(template);
var count = g_Selection.groups.getCount(item);
var name = getEntityName(template);
var tooltip = "Unload " + getEntityName(template);
var count = garrisonGroups.getCount(name);
getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
*/
break;
case FORMATION:
@ -342,35 +330,24 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
setupUnitPanel("Command", usedPanels, entState, commands,
function (item) { performCommand(entState.id, item); } );
/*
!!!!!
GARRISON GOES HERE (need to customize this)
!!!!!
*/
/*
if (selection.length > 1)
setupUnitPanel("Garrison", usedPanels, entState, g_Selection.groups.getTemplateNames(),
function (entType) { changePrimarySelectionGroup(entType); } );
*/
if (entState.garrisonHolder)
{
var groups = new EntityGroups();
groups.add(entState.garrisonHolder.entities);
setupUnitPanel("Garrison", usedPanels, entState, groups.getTemplateNames(),
function (item)
{
var template = GetTemplateData(item);
var unitName = template.name.specific || template.name.generic || "???";
unload(entState.id, groups.getEntsByName(unitName)[0]);
} );
}
var formations = getEntityFormationsList(entState);
if (isUnit(entState) && !isAnimal(entState) && formations.length)
if (isUnit(entState) && !isAnimal(entState) && !entState.garrisonHolder && formations.length)
setupUnitPanel("Formation", usedPanels, entState, formations,
function (item) { performFormation(entState.id, item); } );
if (entState.buildEntities && entState.buildEntities.length)
{
setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);

View File

@ -186,6 +186,8 @@ function getCommandCellId(commandName)
{
case "delete":
return 1;
case "unload-all":
return 2;
default:
return -1;
}
@ -214,6 +216,8 @@ function getEntityFormationsList(entState)
function getEntityCommandsList(entState)
{
var commands = [];
if (entState.garrisonHolder)
commands.push("unload-all");
commands.push("delete");
return commands;
}

View File

@ -3,10 +3,230 @@ function GarrisonHolder() {}
GarrisonHolder.prototype.Schema =
"<element name='Max'>" +
"<data type='positiveInteger'/>" +
"</element>" +
"<element name='List'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"<element name='EjectHealth'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='BuffHeal'>" +
"<data type='positiveInteger'/>" +
"</element>";
/*
* TODO: this all needs to be designed and implemented
/**
* 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;
};
/**
* 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();
var allowedClasses = this.GetAllowedClassesList();
var classNotAllowed = true;
if (!this.HasEnoughHealth())
return false;
//Check if the unit is allowed to be garrisoned inside the building
for each (var allowedClass in allowedClasses)
{
if (entityClasses.indexOf(allowedClass) != -1)
{
classNotAllowed = false;
break;
}
}
if (classNotAllowed)
return false;
if (this.GetCapacity() < (this.spaceOccupied + entityPopCost))
return false;
var cmpPosition = Engine.QueryInterface(entity, IID_Position);
if (!this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {});
}
if (cmpPosition)
{
//Actual garrisoning happens here
this.entities.push(entity);
this.spaceOccupied += entityPopCost;
cmpPosition.MoveOutOfWorld();
return true;
}
else
{
return false;
}
};
/**
* Unload units from the garrisoning entity
*/
GarrisonHolder.prototype.Unload = function(entity)
{
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
var entityIndex = this.entities.indexOf(entity);
this.spaceOccupied -= (Engine.QueryInterface(entity, IID_Cost)).GetPopCost();
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?
// If a rally point is set, walk towards it
var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
if (cmpUnitAI && cmpRallyPoint)
{
var rallyPos = cmpRallyPoint.GetPosition();
if (rallyPos)
{
cmpUnitAI.Walk(rallyPos.x, rallyPos.z, false);
}
else
{
//Reset state. This needs to be done since they were walking before being moved
//out of the world
}
}
};
/**
* 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;
};
/**
* 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.Unload(entity);
}
};
/**
* 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.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);
}
};
Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);

View File

@ -69,7 +69,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition)
if (cmpPosition && cmpPosition.IsInWorld())
{
ret.position = cmpPosition.GetPosition();
}
@ -146,6 +146,14 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
ret.rallyPoint = { };
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
ret.garrisonHolder = {
"entities": cmpGarrisonHolder.GetEntities()
};
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);

View File

@ -60,11 +60,15 @@ Identity.prototype.Schema =
"<zeroOrMore>" +
"<choice>" +
"<value>Unit</value>" +
"<value>Foot</value>" +
"<value>Mounted</value>" +
"<value>Infantry</value>" +
"<value>Cavalry</value>" +
"<value>Ranged</value>" +
"<value>Mechanical</value>" +
"<value>Ship</value>" +
"<value>Siege</value>" +
"<value>Super</value>" +
"<value>Hero</value>" +
"<value>Support</value>" +
"<value>Animal</value>" +
"<value>Organic</value>" +
"<value>Structure</value>" +

View File

@ -162,7 +162,17 @@ var UnitFsmSpec = {
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");
}
},
// States for the special entity representing a group of units moving in formation:
"FORMATIONCONTROLLER": {
@ -508,6 +518,41 @@ var UnitFsmSpec = {
}
},
},
"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() {
}
},
},
},
};
@ -1002,6 +1047,16 @@ UnitAI.prototype.Attack = function(target, queued)
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))
@ -1062,6 +1117,15 @@ UnitAI.prototype.CanAttack = function(target)
return true;
};
UnitAI.prototype.CanGarrison = function(target)
{
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
return true;
};
UnitAI.prototype.CanGather = function(target)
{
// Formation controllers should always respond to commands

View File

@ -160,6 +160,39 @@ function ProcessCommand(player, cmd)
Engine.PostMessage(playerEnt, MT_PlayerDefeated, null);
break;
case "garrison":
var targetCmpOwnership = Engine.QueryInterface(cmd.target, IID_Ownership);
if (!targetCmpOwnership || targetCmpOwnership.GetOwner() != player)
break;
for each (var ent in cmd.entities)
{
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
break;
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Garrison(cmd.target);
}
break;
case "unload":
var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
break;
var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
if (cmpGarrisonHolder)
cmpGarrisonHolder.Unload(cmd.entity);
break;
case "unload-all":
var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
break;
var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder);
cmpGarrisonHolder.UnloadAll();
break;
default:
error("Ignoring unrecognised command type '" + cmd.type + "'");
}

View File

@ -39,9 +39,6 @@
<Vision>
<Range>80</Range>
</Vision>
<GarrisonHolder>
<Max>15</Max>
</GarrisonHolder>
<ResourceDropsite>
<Radius>60</Radius>
<Types>food wood stone metal</Types>

View File

@ -32,9 +32,6 @@
<Obstruction>
<Static width="17.0" depth="17.0"/>
</Obstruction>
<GarrisonHolder>
<Max>10</Max>
</GarrisonHolder>
<Auras>
<Heal>
<Radius>30</Radius>

View File

@ -36,4 +36,5 @@
<Obstruction>
<Static width="7.5" depth="6.75"/>
</Obstruction>
<GarrisonHolder disable=""/>
</Entity>

View File

@ -43,7 +43,10 @@
<Range>100</Range>
</Vision>
<GarrisonHolder>
<Max>20</Max>
<Max>15</Max>
<EjectHealth>0.1</EjectHealth>
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
</GarrisonHolder>
<ResourceDropsite>
<Radius>60</Radius>

View File

@ -39,9 +39,6 @@
units/{civ}_support_healer
</Entities>
</TrainingQueue>
<GarrisonHolder>
<Max>10</Max>
</GarrisonHolder>
<Auras>
<Heal>
<Radius>40</Radius>

View File

@ -44,5 +44,8 @@
</Vision>
<GarrisonHolder>
<Max>5</Max>
<EjectHealth>0.1</EjectHealth>
<List datatype="tokens">Infantry</List>
<BuffHeal>1</BuffHeal>
</GarrisonHolder>
</Entity>

View File

@ -35,7 +35,4 @@
<Vision>
<Range>64</Range>
</Vision>
<GarrisonHolder>
<Max>4</Max>
</GarrisonHolder>
</Entity>

View File

@ -36,9 +36,6 @@
<Vision>
<Range>60</Range>
</Vision>
<GarrisonHolder>
<Max>5</Max>
</GarrisonHolder>
<ResourceDropsite>
<Radius>56</Radius>
<Types>food</Types>

View File

@ -41,7 +41,4 @@
units/{civ}_support_trader
</Entities>
</TrainingQueue>
<GarrisonHolder>
<Max>10</Max>
</GarrisonHolder>
</Entity>

View File

@ -36,9 +36,6 @@
<Vision>
<Range>60</Range>
</Vision>
<GarrisonHolder>
<Max>5</Max>
</GarrisonHolder>
<ResourceDropsite>
<Radius>56</Radius>
<Types>wood stone metal</Types>

View File

@ -40,5 +40,8 @@
</Vision>
<GarrisonHolder>
<Max>10</Max>
<EjectHealth>0.1</EjectHealth>
<List datatype="tokens">Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
</GarrisonHolder>
</Entity>

View File

@ -46,7 +46,4 @@
<Vision>
<Range>60</Range>
</Vision>
<GarrisonHolder>
<Max>5</Max>
</GarrisonHolder>
</Entity>

View File

@ -40,5 +40,8 @@
</Vision>
<GarrisonHolder>
<Max>20</Max>
<EjectHealth>0.1</EjectHealth>
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>1</BuffHeal>
</GarrisonHolder>
</Entity>

View File

@ -40,5 +40,8 @@
</Vision>
<GarrisonHolder>
<Max>10</Max>
<EjectHealth>0.1</EjectHealth>
<List datatype="tokens">Animal</List>
<BuffHeal>1</BuffHeal>
</GarrisonHolder>
</Entity>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit">
<Identity>
<GenericName>Cavalry</GenericName>
<Classes datatype="tokens">Mounted Organic</Classes>
<Classes datatype="tokens">Cavalry Organic</Classes>
<Rank>Basic</Rank>
</Identity>
<Promotion>

View File

@ -4,7 +4,6 @@
<GenericName>Cavalry Spearman</GenericName>
<History>This was the weapon of choice from horseback. This was the unit that would eventually evolve to the medieval knight (after heavy influence by the Sarmartians and their lances). Infantry were an easy kill; just ride them down and skewer them with your stick and its point on the end. As with all cavalry - it was only the rich and the nobles who were able to fight from horseback due to the cost of owning such a beast.</History>
<Tooltip>Bonused vs. Infantry Swordsmen and Infantry Javelinists.</Tooltip>
<Classes datatype="tokens">Spear</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -4,7 +4,6 @@
<GenericName>Cavalry Swordsman</GenericName>
<History>Fighting from horseback with a sword is a tricky thing to do. This required usage of a sword that was longer than the typical infantry sword. One needed a good reach to attack from the height of a horse. If you were without spear (the ideal weapon of choice) it was probably because you needed your hands free to do other tasks such as riding hard and fast. It wasn't uncommon for the men to dismount and attack from foot if they were armed with only a sword.</History>
<Tooltip>Bonused vs. Support Units and Infantry Archers.</Tooltip>
<Classes datatype="tokens">Sword</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -2,6 +2,7 @@
<Entity parent="template_unit_cavalry">
<Identity>
<GenericName>Ranged Cavalry</GenericName>
<Classes datatype="tokens">Ranged</Classes>
</Identity>
<Attack>
<Ranged>

View File

@ -4,7 +4,6 @@
<GenericName>Cavalry Archer</GenericName>
<Rollover>A very rare unit in Part 1. It was used by the Persians, but it didn't gain much traction until the Parthians, Huns, Mongols, and other people of the nomadic steeps introduced them to western Europe. This was the most effective unit on the battlefield for several hundred years until the well armoured knight came along. Therefore, this unit will gain much more prominence in Part 2.</Rollover>
<Tooltip>Bonused vs. Infantry Spearman and Infantry Swordsman.</Tooltip>
<Classes datatype="tokens">Bow</Classes>
</Identity>
<Cost>
<BuildTime>20</BuildTime>

View File

@ -4,7 +4,6 @@
<GenericName>Cavalry Javelinist</GenericName>
<Rollover>The javelins thrown from a horse's back were probably 3 at most. The idea was to quickly advance with 3 in hand, then throw them all. After you had done that it was time to switch to your secondary weapon which was usually a spear or a sword. However, in the game - these units will only have ranged attack.</Rollover>
<Tooltip>Bonused vs. Infantry Archer and Cavalry Swordsman.</Tooltip>
<Classes datatype="tokens">Javelin</Classes>
</Identity>
<Cost>
<BuildTime>15</BuildTime>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super_cavalry">
<Identity>
<GenericName>Hero</GenericName>
<Classes datatype="tokens">Hero Organic Mounted</Classes>
<Classes datatype="tokens">Hero Cavalry</Classes>
</Identity>
<Minimap>
<Type>hero</Type>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super_infantry">
<Identity>
<GenericName>Hero</GenericName>
<Classes datatype="tokens">Hero Organic Foot</Classes>
<Classes datatype="tokens">Hero Infantry</Classes>
</Identity>
<Minimap>
<Type>hero</Type>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super_ranged">
<Identity>
<GenericName>Hero</GenericName>
<Classes datatype="tokens">Hero Organic</Classes>
<Classes datatype="tokens">Hero Infantry Ranged</Classes>
</Identity>
<Minimap>
<Type>hero</Type>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit">
<Identity>
<GenericName>Infantry</GenericName>
<Classes datatype="tokens">Foot Organic</Classes>
<Classes datatype="tokens">Infantry Organic</Classes>
<Rank>Basic</Rank>
</Identity>
<Promotion>

View File

@ -4,7 +4,6 @@
<GenericName>Infantry Spearman</GenericName>
<Rollover>Probably one of the most primitive units in the game. Fighting with a sharp object at the end of the pole didn't require a lot of technology to develop. It also allowed the human to distance themselves from their attacker. As time passed the spears got longer and longer. Started with a fighting style similar to using a quarterstaff, then to using them in numbers as a 'pin cushion' vs. humans (sarissa in a phalanx). Later it was developed to combat cavalry. During the medieval period it evolved to the pike. These units tended to be armoured as heavily as possible.</Rollover>
<Tooltip>Bonused vs. Cavalry Swordsmen and Cavalry Spearmen.</Tooltip>
<Classes datatype="tokens">Spear</Classes>
</Identity>
<Cost>
<BuildTime>10</BuildTime>

View File

@ -4,7 +4,6 @@
<GenericName>Infantry Swordsman</GenericName>
<Rollover>Weapon is basically a developed sickle. Probably from the club, to the axe, to the sickle to the sword. It was the Romans who used them to combat the long range of the sarissa. Their spears were so long they had to use two hands to wield them. In a formation they were almost impossible to maneouvre. If flanked, they were easily cut down by a sword as demonstrated by the Romans at the battle of Cynoscephalae. Generally swordsmen were well armoured, had shields, and tended to be nobles. A good sword was an expensive weapon.</Rollover>
<Tooltip>Bonused vs. Infantry Spearman and Infantry Javelinist.</Tooltip>
<Classes datatype="tokens">Sword</Classes>
</Identity>
<Cost>
<BuildTime>10</BuildTime>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_infantry">
<Identity>
<GenericName>Ranged Infantry</GenericName>
<GenericName>Ranged</GenericName>
</Identity>
<Health>
<Max>90</Max>

View File

@ -4,7 +4,6 @@
<GenericName>Infantry Archer</GenericName>
<Rollover>They tended to be lightly armoured. They usually only participated in the first stage of a battle, sending a volley of arrows raining down the enemy. Of course they would have to stop shooting once the melee units closed in. This means their job was largely over once the 'true battle' was underway. They spent hours training with a bow, but if you were hit by an arrow it was more likely an act of random chance than being specifically targeted by an archer.</Rollover>
<Tooltip>Bonused vs. Infantry Swordsmen and Cavalry Spearmen.</Tooltip>
<Classes datatype="tokens">Bow</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -4,7 +4,6 @@
<GenericName>Infantry Javelinist</GenericName>
<Rollover>These were the skirmishers. These lightly armoured units would advance quickly, throw a hail of javelins and then retreat back to their ranks. Grab another spear and repeat. They would do well against any unit that wasn't wearing proper armour, but more poorly if they fought hand to hand vs a well armoured unit. They didn't always have to throw their spears either. They used these light small spears in hand to hand similar to a quarterstaff. Also note that the development of the pilum was a key transition. The pilum was a weapon with a long steel shaft that would sink into a shield and was nearly impossible to remove. This rendered the shield useless. They also weighted and balanced them to make them accurately hit with a punch.</Rollover>
<Tooltip>Bonused vs. Infantry Spearmen and Cavalry Archers.</Tooltip>
<Classes datatype="tokens">Javelin</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super">
<Identity>
<GenericName>Super Cavalry</GenericName>
<Classes datatype="tokens">Organic Mounted</Classes>
<Classes datatype="tokens">Cavalry</Classes>
</Identity>
<Cost>
<Population>2</Population>

View File

@ -2,7 +2,6 @@
<Entity parent="template_unit_super_cavalry">
<Identity>
<GenericName>Super Cavalry Spearman</GenericName>
<Classes datatype="tokens">Organic Mounted</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super">
<Identity>
<GenericName>Super Infantry</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
<Classes datatype="tokens">Infantry</Classes>
</Identity>
<Cost>
<BuildTime>13</BuildTime>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super_infantry">
<Identity>
<GenericName>Super Ranged</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
<Classes datatype="tokens">Ranged</Classes>
</Identity>
<Cost>
<BuildTime>12</BuildTime>

View File

@ -2,7 +2,6 @@
<Entity parent="template_unit_super_infantry">
<Identity>
<GenericName>Super Infantry Pikeman</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
</Identity>
<Cost>
<BuildTime>13</BuildTime>

View File

@ -2,7 +2,6 @@
<Entity parent="template_unit_super_infantry">
<Identity>
<GenericName>Super Infantry Spearman</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -2,7 +2,6 @@
<Entity parent="template_unit_super_infantry">
<Identity>
<GenericName>Super Infantry Swordsman</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super">
<Identity>
<GenericName>Super Ranged</GenericName>
<Classes datatype="tokens">Organic Foot</Classes>
<Classes datatype="tokens">Ranged</Classes>
</Identity>
<Cost>
<BuildTime>30</BuildTime>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit_super">
<Identity>
<GenericName>Super Siege</GenericName>
<Classes datatype="tokens">Mechanical</Classes>
<Classes datatype="tokens">Mechanical Siege</Classes>
</Identity>
<Position>
<Anchor>pitch-roll</Anchor>

View File

@ -2,7 +2,7 @@
<Entity parent="template_unit">
<Identity>
<GenericName>Support</GenericName>
<Classes datatype="tokens">Organic</Classes>
<Classes datatype="tokens">Support Organic</Classes>
</Identity>
<Minimap>
<Type>support</Type>

View File

@ -4,7 +4,6 @@
<GenericName>Female Citizen</GenericName>
<History>Women in the ancient world took on a variety of roles - from leadership (Celts) to servant (Greeks). Women are hard workers, the economic backbone of any civilisation. In history, it was typical when all the males (capable of fighting) were killed for the females, children, and elderly to be sold as slaves.</History>
<Tooltip>Gather resources, build civic structures, and inspire nearby males to work faster.</Tooltip>
<Classes datatype="tokens">Foot</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -3,7 +3,6 @@
<Identity>
<GenericName>Healer</GenericName>
<Tooltip>Heal units within his Aura.</Tooltip>
<Classes datatype="tokens">Foot</Classes>
</Identity>
<Cost>
<Resources>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero">
<Entity parent="template_unit_hero_infantry">
<Identity>
<Civ>hele</Civ>
<SpecificName>Demetrius</SpecificName>