Technically seperate Turrets from GarrisonHolder.
While they often look alike, their behaviour is totally different. This split has some implications: - There are now separate auras for garrisoning and turrets. - Entities can now have both turret points and garrison slots, independent of eachother. In general previous behaviour is maintained as much as possible. Differential revision: D3150 Comments by: @Nescio, @wraitii Tested by: @v32itas This was SVN commit r25123.
This commit is contained in:
parent
7bf2f9ed74
commit
21e866fcf0
@ -309,11 +309,13 @@ kill = Delete, Backspace ; Destroy selected units
|
|||||||
stop = "H" ; Stop the current action
|
stop = "H" ; Stop the current action
|
||||||
backtowork = "Y" ; The unit will go back to work
|
backtowork = "Y" ; The unit will go back to work
|
||||||
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
|
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
|
||||||
|
unloadturrets = "U" ; Unload turreted units.
|
||||||
move = "" ; Modifier to move to a point instead of another action (e.g. gather)
|
move = "" ; Modifier to move to a point instead of another action (e.g. gather)
|
||||||
attack = Ctrl ; Modifier to attack instead of another action (e.g. capture)
|
attack = Ctrl ; Modifier to attack instead of another action (e.g. capture)
|
||||||
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
|
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
|
||||||
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point
|
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point
|
||||||
garrison = Ctrl ; Modifier to garrison when clicking on building
|
garrison = Ctrl ; Modifier to garrison when clicking on building
|
||||||
|
occupyturret = Ctrl ; Modifier to occupy a turret when clicking on a turret holder.
|
||||||
autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
|
autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
|
||||||
guard = "G" ; Modifier to escort/guard when clicking on unit/building
|
guard = "G" ; Modifier to escort/guard when clicking on unit/building
|
||||||
patrol = "P" ; Modifier to patrol a unit
|
patrol = "P" ; Modifier to patrol a unit
|
||||||
|
@ -491,6 +491,11 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
|
|||||||
ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource);
|
ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (template.TurretHolder)
|
||||||
|
ret.turretHolder = {
|
||||||
|
"turretPoints": template.TurretHolder.TurretPoints
|
||||||
|
};
|
||||||
|
|
||||||
if (template.WallSet)
|
if (template.WallSet)
|
||||||
{
|
{
|
||||||
ret.wallSet = {
|
ret.wallSet = {
|
||||||
|
@ -588,6 +588,16 @@ function getGarrisonTooltip(template)
|
|||||||
return tooltips.join("\n");
|
return tooltips.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTurretsTooltip(template)
|
||||||
|
{
|
||||||
|
if (!template.turretHolder)
|
||||||
|
return "";
|
||||||
|
return sprintf(translate("%(label)s: %(turretsLimit)s"), {
|
||||||
|
"label": headerFont(translate("Turret Positions")),
|
||||||
|
"turretsLimit": Object.keys(template.turretHolder.turretPoints).length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getProjectilesTooltip(template)
|
function getProjectilesTooltip(template)
|
||||||
{
|
{
|
||||||
if (!template.garrisonHolder || !template.buildingAI)
|
if (!template.garrisonHolder || !template.buildingAI)
|
||||||
|
@ -27,6 +27,10 @@
|
|||||||
"name": "Unload",
|
"name": "Unload",
|
||||||
"desc": "Unload garrisoned units when a building/mechanical unit is selected."
|
"desc": "Unload garrisoned units when a building/mechanical unit is selected."
|
||||||
},
|
},
|
||||||
|
"session.unloadturrets": {
|
||||||
|
"name": "Unload Turrets",
|
||||||
|
"desc": "Unload turreted units."
|
||||||
|
},
|
||||||
"session.unloadtype": {
|
"session.unloadtype": {
|
||||||
"name": "Unload unit type",
|
"name": "Unload unit type",
|
||||||
"desc": "Modifier to unload all units of type."
|
"desc": "Modifier to unload all units of type."
|
||||||
@ -51,6 +55,10 @@
|
|||||||
"name": "Garrison",
|
"name": "Garrison",
|
||||||
"desc": "Modifier to garrison when clicking on building."
|
"desc": "Modifier to garrison when clicking on building."
|
||||||
},
|
},
|
||||||
|
"session.occupyturret": {
|
||||||
|
"name": "Occupy Turret Point",
|
||||||
|
"desc": "Modifier to occupy a turret when clicking on a turret holder."
|
||||||
|
},
|
||||||
"session.autorallypoint": {
|
"session.autorallypoint": {
|
||||||
"name": "Auto-rally point",
|
"name": "Auto-rally point",
|
||||||
"desc": "Modifier to set the rally point on the building itself."
|
"desc": "Modifier to set the rally point on the building itself."
|
||||||
|
@ -57,6 +57,7 @@ ReferencePage.prototype.StatsFunctions = [
|
|||||||
getHealerTooltip,
|
getHealerTooltip,
|
||||||
getResistanceTooltip,
|
getResistanceTooltip,
|
||||||
getGarrisonTooltip,
|
getGarrisonTooltip,
|
||||||
|
getTurretsTooltip,
|
||||||
getProjectilesTooltip,
|
getProjectilesTooltip,
|
||||||
getSpeedTooltip,
|
getSpeedTooltip,
|
||||||
getGatherTooltip,
|
getGatherTooltip,
|
||||||
|
@ -32,6 +32,10 @@
|
|||||||
<action on="KeyDown">unloadAll();</action>
|
<action on="KeyDown">unloadAll();</action>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
|
<object hotkey="session.unloadturrets">
|
||||||
|
<action on="KeyDown">unloadAllTurrets();</action>
|
||||||
|
</object>
|
||||||
|
|
||||||
<object hotkey="session.stop">
|
<object hotkey="session.stop">
|
||||||
<action on="KeyDown">stopUnits(g_Selection.toList());</action>
|
<action on="KeyDown">stopUnits(g_Selection.toList());</action>
|
||||||
</object>
|
</object>
|
||||||
|
@ -325,6 +325,7 @@ function displaySingle(entState)
|
|||||||
getGatherTooltip,
|
getGatherTooltip,
|
||||||
getSpeedTooltip,
|
getSpeedTooltip,
|
||||||
getGarrisonTooltip,
|
getGarrisonTooltip,
|
||||||
|
getTurretsTooltip,
|
||||||
getPopulationBonusTooltip,
|
getPopulationBonusTooltip,
|
||||||
getProjectilesTooltip,
|
getProjectilesTooltip,
|
||||||
getResourceTrickleTooltip,
|
getResourceTrickleTooltip,
|
||||||
|
@ -193,6 +193,7 @@ g_SelectionPanels.Construction = {
|
|||||||
getEntityCostTooltip(template, data.player),
|
getEntityCostTooltip(template, data.player),
|
||||||
getResourceDropsiteTooltip(template),
|
getResourceDropsiteTooltip(template),
|
||||||
getGarrisonTooltip(template),
|
getGarrisonTooltip(template),
|
||||||
|
getTurretsTooltip(template),
|
||||||
getPopulationBonusTooltip(template),
|
getPopulationBonusTooltip(template),
|
||||||
showTemplateViewerOnRightClickTooltip(template)
|
showTemplateViewerOnRightClickTooltip(template)
|
||||||
);
|
);
|
||||||
@ -975,6 +976,7 @@ g_SelectionPanels.Training = {
|
|||||||
getHealerTooltip,
|
getHealerTooltip,
|
||||||
getResistanceTooltip,
|
getResistanceTooltip,
|
||||||
getGarrisonTooltip,
|
getGarrisonTooltip,
|
||||||
|
getTurretsTooltip,
|
||||||
getProjectilesTooltip,
|
getProjectilesTooltip,
|
||||||
getSpeedTooltip,
|
getSpeedTooltip,
|
||||||
getResourceDropsiteTooltip
|
getResourceDropsiteTooltip
|
||||||
|
@ -415,31 +415,6 @@ function unloadTemplate(template, owner)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function unloadSelection()
|
|
||||||
{
|
|
||||||
let parent = 0;
|
|
||||||
let ents = [];
|
|
||||||
for (let ent in g_Selection.selected)
|
|
||||||
{
|
|
||||||
let state = GetEntityState(+ent);
|
|
||||||
if (!state || !state.turretParent)
|
|
||||||
continue;
|
|
||||||
if (!parent)
|
|
||||||
{
|
|
||||||
parent = state.turretParent;
|
|
||||||
ents.push(+ent);
|
|
||||||
}
|
|
||||||
else if (state.turretParent == parent)
|
|
||||||
ents.push(+ent);
|
|
||||||
}
|
|
||||||
if (parent)
|
|
||||||
Engine.PostNetworkCommand({
|
|
||||||
"type": "unload",
|
|
||||||
"entities": ents,
|
|
||||||
"garrisonHolder": parent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function unloadAll()
|
function unloadAll()
|
||||||
{
|
{
|
||||||
let garrisonHolders = g_Selection.toList().filter(e => {
|
let garrisonHolders = g_Selection.toList().filter(e => {
|
||||||
@ -474,6 +449,44 @@ function unloadAll()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unloadAllTurrets()
|
||||||
|
{
|
||||||
|
let turretHolders = g_Selection.toList().filter(e => {
|
||||||
|
let state = GetEntityState(e);
|
||||||
|
return state && !!state.turretHolder;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!turretHolders.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let ownedHolders = [];
|
||||||
|
let ejectables = [];
|
||||||
|
for (let ent of turretHolders)
|
||||||
|
{
|
||||||
|
let turretHolderState = GetEntityState(ent);
|
||||||
|
if (controlsPlayer(turretHolderState.player))
|
||||||
|
ownedHolders.push(ent);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let turret of turretHolderState.turretHolder.turretPoints.map(tp => tp.entity))
|
||||||
|
if (turret && controlsPlayer(GetEntityState(turret).player))
|
||||||
|
ejectables.push(turret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ejectables.length)
|
||||||
|
Engine.PostNetworkCommand({
|
||||||
|
"type": "leave-turret",
|
||||||
|
"entities": ejectables
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ownedHolders.length)
|
||||||
|
Engine.PostNetworkCommand({
|
||||||
|
"type": "unload-turrets",
|
||||||
|
"entities": ownedHolders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function backToWork()
|
function backToWork()
|
||||||
{
|
{
|
||||||
Engine.PostNetworkCommand({
|
Engine.PostNetworkCommand({
|
||||||
|
@ -684,6 +684,77 @@ var g_UnitActions =
|
|||||||
"specificness": 0,
|
"specificness": 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"occupy-turret":
|
||||||
|
{
|
||||||
|
"execute": function(target, action, selection, queued, pushFront)
|
||||||
|
{
|
||||||
|
Engine.PostNetworkCommand({
|
||||||
|
"type": "occupy-turret",
|
||||||
|
"entities": selection,
|
||||||
|
"target": action.target,
|
||||||
|
"queued": queued,
|
||||||
|
"pushFront": pushFront,
|
||||||
|
"formation": g_AutoFormation.getNull()
|
||||||
|
});
|
||||||
|
|
||||||
|
Engine.GuiInterfaceCall("PlaySound", {
|
||||||
|
"name": "order_garrison",
|
||||||
|
"entity": action.firstAbleEntity
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
"getActionInfo": function(entState, targetState)
|
||||||
|
{
|
||||||
|
if (!entState.turretable || !targetState || !targetState.turretHolder ||
|
||||||
|
!playerCheck(entState, targetState, ["Player", "MutualAlly"]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!targetState.turretHolder.turretPoints.find(point =>
|
||||||
|
!point.allowedClasses || MatchesClassList(entState.identity.classes, point.allowedClasses)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let occupiedTurrets = targetState.turretHolder.turretPoints.filter(point => point.entity != null);
|
||||||
|
let tooltip = sprintf(translate("Current turrets: %(occupied)s/%(capacity)s"), {
|
||||||
|
"occupied": occupiedTurrets.length,
|
||||||
|
"capacity": targetState.turretHolder.turretPoints.length
|
||||||
|
});
|
||||||
|
|
||||||
|
if (occupiedTurrets.length == targetState.turretHolder.turretPoints.length)
|
||||||
|
tooltip = coloredText(tooltip, "orange");
|
||||||
|
|
||||||
|
return {
|
||||||
|
"possible": true,
|
||||||
|
"tooltip": tooltip
|
||||||
|
};
|
||||||
|
},
|
||||||
|
"preSelectedActionCheck": function(target, selection)
|
||||||
|
{
|
||||||
|
return preSelectedAction == ACTION_GARRISON &&
|
||||||
|
(this.actionCheck(target, selection) || {
|
||||||
|
"type": "none",
|
||||||
|
"cursor": "action-garrison-disabled",
|
||||||
|
"target": null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"hotkeyActionCheck": function(target, selection)
|
||||||
|
{
|
||||||
|
return Engine.HotkeyIsPressed("session.occupyturret") &&
|
||||||
|
this.actionCheck(target, selection);
|
||||||
|
},
|
||||||
|
"actionCheck": function(target, selection)
|
||||||
|
{
|
||||||
|
let actionInfo = getActionInfo("occupy-turret", target, selection);
|
||||||
|
return actionInfo.possible && {
|
||||||
|
"type": "occupy-turret",
|
||||||
|
"cursor": "action-garrison",
|
||||||
|
"tooltip": actionInfo.tooltip,
|
||||||
|
"target": target
|
||||||
|
};
|
||||||
|
},
|
||||||
|
"specificness": 21,
|
||||||
|
},
|
||||||
|
|
||||||
"garrison":
|
"garrison":
|
||||||
{
|
{
|
||||||
"execute": function(target, action, selection, queued, pushFront)
|
"execute": function(target, action, selection, queued, pushFront)
|
||||||
@ -984,6 +1055,22 @@ var g_UnitActions =
|
|||||||
targetState.garrisonHolder.capacity)
|
targetState.garrisonHolder.capacity)
|
||||||
tooltip = coloredText(tooltip, "orange");
|
tooltip = coloredText(tooltip, "orange");
|
||||||
}
|
}
|
||||||
|
else if (targetState && targetState.turretHolder &&
|
||||||
|
playerCheck(entState, targetState, ["Player", "MutualAlly"]))
|
||||||
|
{
|
||||||
|
data.command = "occupy-turret";
|
||||||
|
data.target = targetState.id;
|
||||||
|
cursor = "action-garrison";
|
||||||
|
|
||||||
|
let occupiedTurrets = targetState.turretHolder.turretPoints.filter(point => point.entity != null);
|
||||||
|
tooltip = sprintf(translate("Current turrets: %(occupied)s/%(capacity)s"), {
|
||||||
|
"occupied": occupiedTurrets.length,
|
||||||
|
"capacity": targetState.turretHolder.turretPoints.length
|
||||||
|
});
|
||||||
|
|
||||||
|
if (occupiedTurrets.length >= targetState.turretHolder.turretPoints.length)
|
||||||
|
tooltip = coloredText(tooltip, "orange");
|
||||||
|
}
|
||||||
else if (targetState && targetState.resourceSupply)
|
else if (targetState && targetState.resourceSupply)
|
||||||
{
|
{
|
||||||
let resourceType = targetState.resourceSupply.type;
|
let resourceType = targetState.resourceSupply.type;
|
||||||
@ -1224,6 +1311,41 @@ var g_EntityCommands =
|
|||||||
"allowedPlayers": ["Player", "Ally"]
|
"allowedPlayers": ["Player", "Ally"]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"unload-all-turrets": {
|
||||||
|
"getInfo": function(entStates)
|
||||||
|
{
|
||||||
|
let count = 0;
|
||||||
|
for (let entState of entStates)
|
||||||
|
{
|
||||||
|
if (!entState.turretHolder)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (allowedPlayersCheck([entState], ["Player"]))
|
||||||
|
count += entState.turretHolder.turretPoints.filter(turretPoint => turretPoint.entity).length;
|
||||||
|
else
|
||||||
|
for (let turretPoint of entState.turretHolder.turretPoints)
|
||||||
|
if (turretPoint.entity && allowedPlayersCheck([GetEntityState(turretPoint.entity)], ["Player"]))
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unloadturrets") +
|
||||||
|
translate("Unload Turrets."),
|
||||||
|
"icon": "garrison-out.png",
|
||||||
|
"count": count,
|
||||||
|
"enabled": true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
"execute": function()
|
||||||
|
{
|
||||||
|
unloadAllTurrets();
|
||||||
|
},
|
||||||
|
"allowedPlayers": ["Player", "Ally"]
|
||||||
|
},
|
||||||
|
|
||||||
"delete": {
|
"delete": {
|
||||||
"getInfo": function(entStates)
|
"getInfo": function(entStates)
|
||||||
{
|
{
|
||||||
@ -1317,15 +1439,11 @@ var g_EntityCommands =
|
|||||||
"allowedPlayers": ["Player"]
|
"allowedPlayers": ["Player"]
|
||||||
},
|
},
|
||||||
|
|
||||||
"unload": {
|
"leave-turret": {
|
||||||
"getInfo": function(entStates)
|
"getInfo": function(entStates)
|
||||||
{
|
{
|
||||||
if (entStates.every(entState => {
|
if (entStates.every(entState => !entState.turretable ||
|
||||||
if (!entState.unitAI || !entState.turretParent)
|
entState.turretable.holder == INVALID_ENTITY))
|
||||||
return true;
|
|
||||||
let parent = GetEntityState(entState.turretParent);
|
|
||||||
return !parent || !parent.garrisonHolder || parent.garrisonHolder.entities.indexOf(entState.id) == -1;
|
|
||||||
}))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -1334,9 +1452,16 @@ var g_EntityCommands =
|
|||||||
"enabled": true
|
"enabled": true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
"execute": function()
|
"execute": function(entStates)
|
||||||
{
|
{
|
||||||
unloadSelection();
|
if (!entStates.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Engine.PostNetworkCommand({
|
||||||
|
"type": "leave-turret",
|
||||||
|
"entities": entStates.filter(entState => entState.turretable &&
|
||||||
|
entState.turretable.holder != INVALID_ENTITY).map(entState => entState.id)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
"allowedPlayers": ["Player"]
|
"allowedPlayers": ["Player"]
|
||||||
},
|
},
|
||||||
|
@ -543,6 +543,8 @@ m.Template = m.Class({
|
|||||||
|
|
||||||
"isGarrisonHolder": function() { return this.get("GarrisonHolder") !== undefined; },
|
"isGarrisonHolder": function() { return this.get("GarrisonHolder") !== undefined; },
|
||||||
|
|
||||||
|
"isTurretHolder": function() { return this.get("TurretHolder") !== undefined; },
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns true if the tempalte can capture the given target entity
|
* returns true if the tempalte can capture the given target entity
|
||||||
* if no target is given, returns true if the template has the Capture attack
|
* if no target is given, returns true if the template has the Capture attack
|
||||||
@ -565,6 +567,8 @@ m.Template = m.Class({
|
|||||||
|
|
||||||
"canGarrison": function() { return "Garrisonable" in this._template; },
|
"canGarrison": function() { return "Garrisonable" in this._template; },
|
||||||
|
|
||||||
|
"canOccupyTurret": function() { return "Turretable" in this._template; },
|
||||||
|
|
||||||
"isTreasureCollecter": function() { return this.get("TreasureCollecter") !== undefined; },
|
"isTreasureCollecter": function() { return this.get("TreasureCollecter") !== undefined; },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -840,23 +844,29 @@ m.Entity = m.Class({
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
"garrison": function(target, queued = false) {
|
"garrison": function(target, queued = false, pushFront = false) {
|
||||||
Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued });
|
Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
"attack": function(unitId, allowCapture = true, queued = false) {
|
"occupy-turret": function(target, queued = false, pushFront = false) {
|
||||||
Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued });
|
Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
"collectTreasure": function(target, autocontinue = false, queued = false) {
|
"attack": function(unitId, allowCapture = true, queued = false, pushFront = false) {
|
||||||
|
Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued, "pushFront": pushFront });
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
"collectTreasure": function(target, autocontinue = false, queued = false, pushFront = false) {
|
||||||
Engine.PostCommand(PlayerID, {
|
Engine.PostCommand(PlayerID, {
|
||||||
"type": "collect-treasure",
|
"type": "collect-treasure",
|
||||||
"entities": [this.id()],
|
"entities": [this.id()],
|
||||||
"target": target.id(),
|
"target": target.id(),
|
||||||
"autocontinue": autocontinue,
|
"autocontinue": autocontinue,
|
||||||
"queued": queued
|
"queued": queued,
|
||||||
|
"pushFront": pushFront
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -175,6 +175,12 @@ m.EntityCollection.prototype.garrison = function(target, queued = false)
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
m.EntityCollection.prototype.occupyTurret = function(target, queued = false)
|
||||||
|
{
|
||||||
|
Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": this.toIdArray(), "target": target.id(), "queued": queued });
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
m.EntityCollection.prototype.destroy = function()
|
m.EntityCollection.prototype.destroy = function()
|
||||||
{
|
{
|
||||||
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
|
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
|
||||||
|
@ -180,6 +180,11 @@ Auras.prototype.IsGarrisonedUnitsAura = function(name)
|
|||||||
return this.GetType(name) == "garrisonedUnits";
|
return this.GetType(name) == "garrisonedUnits";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Auras.prototype.IsTurretedUnitsAura = function(name)
|
||||||
|
{
|
||||||
|
return this.GetType(name) == "turretedUnits";
|
||||||
|
};
|
||||||
|
|
||||||
Auras.prototype.IsRangeAura = function(name)
|
Auras.prototype.IsRangeAura = function(name)
|
||||||
{
|
{
|
||||||
return this.GetType(name) == "range";
|
return this.GetType(name) == "range";
|
||||||
@ -321,6 +326,15 @@ Auras.prototype.OnGarrisonedUnitsChanged = function(msg)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Auras.prototype.OnTurretsChanged = function(msg)
|
||||||
|
{
|
||||||
|
for (let name of this.GetAuraNames().filter(n => this.IsTurretedUnitsAura(n)))
|
||||||
|
{
|
||||||
|
this.ApplyAura(name, msg.added);
|
||||||
|
this.RemoveAura(name, msg.removed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Auras.prototype.ApplyFormationAura = function(memberList)
|
Auras.prototype.ApplyFormationAura = function(memberList)
|
||||||
{
|
{
|
||||||
for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
|
for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
|
||||||
@ -512,7 +526,7 @@ Auras.prototype.OnGarrisonedStateChanged = function(msg)
|
|||||||
|
|
||||||
if (msg.holderID != INVALID_ENTITY)
|
if (msg.holderID != INVALID_ENTITY)
|
||||||
this.ApplyGarrisonAura(msg.holderID);
|
this.ApplyGarrisonAura(msg.holderID);
|
||||||
if (msg.olderHolder != INVALID_ENTITY)
|
if (msg.oldHolder != INVALID_ENTITY)
|
||||||
this.RemoveGarrisonAura(msg.oldHolder);
|
this.RemoveGarrisonAura(msg.oldHolder);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,36 +30,21 @@ BuildingAI.prototype.Init = function()
|
|||||||
this.targetUnits = [];
|
this.targetUnits = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
BuildingAI.prototype.OnGarrisonedUnitsChanged = function()
|
BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
|
||||||
{
|
{
|
||||||
this.RecalculateProjectileCount();
|
|
||||||
};
|
|
||||||
|
|
||||||
BuildingAI.prototype.OnTurretsChanged = function()
|
|
||||||
{
|
|
||||||
this.RecalculateProjectileCount();
|
|
||||||
};
|
|
||||||
|
|
||||||
BuildingAI.prototype.RecalculateProjectileCount = function()
|
|
||||||
{
|
|
||||||
this.archersGarrisoned = 0;
|
|
||||||
let classes = this.template.GarrisonArrowClasses;
|
let classes = this.template.GarrisonArrowClasses;
|
||||||
|
for (let ent of msg.added)
|
||||||
let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder);
|
|
||||||
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
|
|
||||||
for (let ent of cmpGarrisonHolder.GetEntities())
|
|
||||||
{
|
{
|
||||||
// Only count non-visible garrisoned entities towards extra arrows.
|
|
||||||
if (cmpTurretHolder && cmpTurretHolder.OccupiesTurret(ent))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
||||||
if (!cmpIdentity)
|
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
||||||
continue;
|
|
||||||
|
|
||||||
if (MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
|
||||||
++this.archersGarrisoned;
|
++this.archersGarrisoned;
|
||||||
}
|
}
|
||||||
|
for (let ent of msg.removed)
|
||||||
|
{
|
||||||
|
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
||||||
|
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
||||||
|
--this.archersGarrisoned;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BuildingAI.prototype.OnOwnershipChanged = function(msg)
|
BuildingAI.prototype.OnOwnershipChanged = function(msg)
|
||||||
|
@ -199,38 +199,6 @@ GarrisonHolder.prototype.Garrison = function(entity)
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} entity - EntityID to find the spawn position for.
|
|
||||||
* @param {boolean} forced - Optionally whether the spawning is forced.
|
|
||||||
* @return {Vector3D} - An appropriate spawning position.
|
|
||||||
*/
|
|
||||||
GarrisonHolder.prototype.GetSpawnPosition = function(entity, forced)
|
|
||||||
{
|
|
||||||
let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
|
|
||||||
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
|
||||||
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
|
|
||||||
|
|
||||||
// If the garrisonHolder is a sinking ship, restrict the location to the intersection of both passabilities
|
|
||||||
// TODO: should use passability classes to be more generic
|
|
||||||
let pos;
|
|
||||||
if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship"))
|
|
||||||
pos = cmpFootprint.PickSpawnPointBothPass(entity);
|
|
||||||
else
|
|
||||||
pos = cmpFootprint.PickSpawnPoint(entity);
|
|
||||||
|
|
||||||
if (pos.y < 0)
|
|
||||||
{
|
|
||||||
// Error: couldn't find space satisfying the unit's passability criteria
|
|
||||||
if (!forced)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// If ejection is forced, we need to continue, so use center of the building
|
|
||||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
|
||||||
pos = cmpPosition.GetPosition();
|
|
||||||
}
|
|
||||||
return pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} entity - The entity ID of the entity to eject.
|
* @param {number} entity - The entity ID of the entity to eject.
|
||||||
* @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed).
|
* @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed).
|
||||||
@ -256,32 +224,6 @@ GarrisonHolder.prototype.Eject = function(entity, forced)
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} entity - The entity ID of the entity to order to the rally point.
|
|
||||||
*/
|
|
||||||
GarrisonHolder.prototype.OrderToRallyPoint = function(entity)
|
|
||||||
{
|
|
||||||
let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
|
|
||||||
if (!cmpRallyPoint || !cmpRallyPoint.GetPositions()[0])
|
|
||||||
return;
|
|
||||||
|
|
||||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
|
||||||
if (!cmpOwnership)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership);
|
|
||||||
if (!cmpEntOwnership || cmpOwnership.GetOwner() != cmpEntOwnership.GetOwner())
|
|
||||||
return;
|
|
||||||
|
|
||||||
let commands = GetRallyPointCommands(cmpRallyPoint, [entity]);
|
|
||||||
// Ignore the rally point if it is autogarrison
|
|
||||||
if (commands[0].type == "garrison" && commands[0].target == this.entity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (let command of commands)
|
|
||||||
ProcessCommand(cmpOwnership.GetOwner(), command);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell unit to unload from this entity.
|
* Tell unit to unload from this entity.
|
||||||
* @param {number} entity - The entity to unload.
|
* @param {number} entity - The entity to unload.
|
||||||
|
@ -69,7 +69,7 @@ Garrisonable.prototype.CanGarrison = function(target)
|
|||||||
* @param {number} target - The entity ID of the entity this entity is being garrisoned in.
|
* @param {number} target - The entity ID of the entity this entity is being garrisoned in.
|
||||||
* @return {boolean} - Whether garrisoning succeeded.
|
* @return {boolean} - Whether garrisoning succeeded.
|
||||||
*/
|
*/
|
||||||
Garrisonable.prototype.Garrison = function(target, renamed = false)
|
Garrisonable.prototype.Garrison = function(target)
|
||||||
{
|
{
|
||||||
if (!this.CanGarrison(target))
|
if (!this.CanGarrison(target))
|
||||||
return false;
|
return false;
|
||||||
@ -93,35 +93,24 @@ Garrisonable.prototype.Garrison = function(target, renamed = false)
|
|||||||
"holderID": target
|
"holderID": target
|
||||||
});
|
});
|
||||||
|
|
||||||
if (renamed)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
|
||||||
if (cmpTurretHolder)
|
|
||||||
cmpTurretHolder.OccupyTurret(this.entity);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} forced - Optionally whether the spawning is forced.
|
* @param {boolean} forced - Optionally whether the spawning is forced.
|
||||||
* @param {boolean} renamed - Optionally whether the ungarrisoning is due to renaming.
|
|
||||||
* @return {boolean} - Whether the ungarrisoning succeeded.
|
* @return {boolean} - Whether the ungarrisoning succeeded.
|
||||||
*/
|
*/
|
||||||
Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false)
|
Garrisonable.prototype.UnGarrison = function(forced = false)
|
||||||
{
|
{
|
||||||
if (!this.holder)
|
if (!this.holder)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
|
let pos = PositionHelper.GetSpawnPosition(this.holder, this.entity, forced);
|
||||||
if (!cmpGarrisonHolder)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
let pos = cmpGarrisonHolder.GetSpawnPosition(this.entity, forced);
|
|
||||||
if (!pos)
|
if (!pos)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!cmpGarrisonHolder.Eject(this.entity, forced))
|
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
|
||||||
|
if (!cmpGarrisonHolder || !cmpGarrisonHolder.Eject(this.entity, forced))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||||
@ -147,14 +136,9 @@ Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false)
|
|||||||
"holderID": INVALID_ENTITY
|
"holderID": INVALID_ENTITY
|
||||||
});
|
});
|
||||||
|
|
||||||
if (renamed)
|
let cmpRallyPoint = Engine.QueryInterface(this.holder, IID_RallyPoint);
|
||||||
return true;
|
if (cmpRallyPoint)
|
||||||
|
cmpRallyPoint.OrderToRallyPoint(this.entity, ["garrison"]);
|
||||||
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
|
|
||||||
if (cmpTurretHolder)
|
|
||||||
cmpTurretHolder.LeaveTurret(this.entity);
|
|
||||||
|
|
||||||
cmpGarrisonHolder.OrderToRallyPoint(this.entity);
|
|
||||||
|
|
||||||
delete this.holder;
|
delete this.holder;
|
||||||
return true;
|
return true;
|
||||||
@ -165,22 +149,11 @@ Garrisonable.prototype.OnEntityRenamed = function(msg)
|
|||||||
if (!this.holder)
|
if (!this.holder)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
|
let holder = this.holder;
|
||||||
if (cmpGarrisonHolder)
|
this.UnGarrison(true, true);
|
||||||
{
|
let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable);
|
||||||
this.UnGarrison(true, true);
|
if (cmpGarrisonable)
|
||||||
let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable);
|
cmpGarrisonable.Garrison(holder, true);
|
||||||
if (cmpGarrisonable)
|
|
||||||
cmpGarrisonable.Garrison(this.holder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We process EntityRenamed of turrets seperately since we
|
|
||||||
// want to occupy the same position after being renamed.
|
|
||||||
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
|
|
||||||
if (cmpTurretHolder)
|
|
||||||
cmpTurretHolder.SwapEntities(msg.entity, msg.newentity);
|
|
||||||
|
|
||||||
delete this.holder;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable);
|
Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable);
|
||||||
|
@ -388,6 +388,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||||||
"turretPoints": cmpTurretHolder.GetTurretPoints()
|
"turretPoints": cmpTurretHolder.GetTurretPoints()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
|
||||||
|
if (cmpTurretable)
|
||||||
|
ret.turretable = {
|
||||||
|
"holder": cmpTurretable.HolderID()
|
||||||
|
};
|
||||||
|
|
||||||
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
|
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
|
||||||
if (cmpGarrisonable)
|
if (cmpGarrisonable)
|
||||||
ret.garrisonable = {
|
ret.garrisonable = {
|
||||||
|
@ -100,6 +100,31 @@ RallyPoint.prototype.Reset = function()
|
|||||||
cmpRallyPointRenderer.Reset();
|
cmpRallyPointRenderer.Reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} entity - The entity ID of the entity to order to the rally point.
|
||||||
|
* @param {string[]} ignore - The commands to ignore when performed on this.entity.
|
||||||
|
* E.g. "garrison" when unloading.
|
||||||
|
*/
|
||||||
|
RallyPoint.prototype.OrderToRallyPoint = function(entity, ignore = [])
|
||||||
|
{
|
||||||
|
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||||
|
if (!cmpOwnership)
|
||||||
|
return;
|
||||||
|
let owner = cmpOwnership.GetOwner();
|
||||||
|
|
||||||
|
let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership);
|
||||||
|
if (!cmpEntOwnership || cmpEntOwnership.GetOwner() != owner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let commands = GetRallyPointCommands(this, [entity]);
|
||||||
|
if (!commands.length ||
|
||||||
|
commands[0].target == this.entity && ignore.includes(commands[0].type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let command of commands)
|
||||||
|
ProcessCommand(owner, command);
|
||||||
|
};
|
||||||
|
|
||||||
RallyPoint.prototype.OnGlobalEntityRenamed = function(msg)
|
RallyPoint.prototype.OnGlobalEntityRenamed = function(msg)
|
||||||
{
|
{
|
||||||
for (var data of this.data)
|
for (var data of this.data)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* This class holds the functions regarding entities being visible on
|
* This class holds the functions regarding entities being visible on
|
||||||
* another entity, but tied to their parents location.
|
* another entity, but tied to their parents location.
|
||||||
* Currently renaming and changing ownership are still managed by GarrisonHolder.js,
|
|
||||||
* but in the future these components should be independent.
|
|
||||||
*/
|
*/
|
||||||
class TurretHolder
|
class TurretHolder
|
||||||
{
|
{
|
||||||
@ -54,6 +52,15 @@ class TurretHolder
|
|||||||
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses._string);
|
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses._string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} entity - The entity to check for.
|
||||||
|
* @return {boolean} - Whether the entity is allowed to occupy any turret point.
|
||||||
|
*/
|
||||||
|
CanOccupy(entity)
|
||||||
|
{
|
||||||
|
return !!this.turretPoints.find(turretPoint => this.AllowedToOccupyTurret(entity, turretPoint));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Occupy a turret point with the given entity.
|
* Occupy a turret point with the given entity.
|
||||||
* @param {number} entity - The entity to use.
|
* @param {number} entity - The entity to use.
|
||||||
@ -87,6 +94,7 @@ class TurretHolder
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
turretPoint.entity = entity;
|
turretPoint.entity = entity;
|
||||||
|
|
||||||
// Angle of turrets:
|
// Angle of turrets:
|
||||||
// Renamed entities (turretPoint != undefined) should keep their angle.
|
// Renamed entities (turretPoint != undefined) should keep their angle.
|
||||||
// Otherwise if an angle is given in the turretPoint, use it.
|
// Otherwise if an angle is given in the turretPoint, use it.
|
||||||
@ -100,19 +108,6 @@ class TurretHolder
|
|||||||
|
|
||||||
cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset);
|
cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset);
|
||||||
|
|
||||||
let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion);
|
|
||||||
if (cmpUnitMotion)
|
|
||||||
cmpUnitMotion.SetFacePointAfterMove(false);
|
|
||||||
|
|
||||||
let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
|
|
||||||
if (cmpUnitAI)
|
|
||||||
cmpUnitAI.SetTurretStance();
|
|
||||||
|
|
||||||
// Remove the unit's obstruction to avoid interfering with pathing.
|
|
||||||
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
|
|
||||||
if (cmpObstruction)
|
|
||||||
cmpObstruction.SetActive(false);
|
|
||||||
|
|
||||||
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
||||||
"added": [entity],
|
"added": [entity],
|
||||||
"removed": []
|
"removed": []
|
||||||
@ -152,23 +147,11 @@ class TurretHolder
|
|||||||
if (!turretPoint)
|
if (!turretPoint)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position);
|
|
||||||
cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D());
|
|
||||||
|
|
||||||
let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion);
|
|
||||||
if (cmpUnitMotionEntity)
|
|
||||||
cmpUnitMotionEntity.SetFacePointAfterMove(true);
|
|
||||||
|
|
||||||
let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI);
|
|
||||||
if (cmpUnitAIEntity)
|
|
||||||
cmpUnitAIEntity.ResetTurretStance();
|
|
||||||
|
|
||||||
turretPoint.entity = null;
|
turretPoint.entity = null;
|
||||||
|
|
||||||
// Reset the obstruction flags to template defaults.
|
let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position);
|
||||||
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
|
if (cmpPositionEntity)
|
||||||
if (cmpObstruction)
|
cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D());
|
||||||
cmpObstruction.SetActive(true);
|
|
||||||
|
|
||||||
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
||||||
"added": [],
|
"added": [],
|
||||||
@ -205,7 +188,8 @@ class TurretHolder
|
|||||||
*/
|
*/
|
||||||
GetOccupiedTurretName(entity)
|
GetOccupiedTurretName(entity)
|
||||||
{
|
{
|
||||||
return this.GetOccupiedTurret(entity).name || "";
|
let turret = this.GetOccupiedTurret(entity);
|
||||||
|
return turret ? turret.name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,6 +204,60 @@ class TurretHolder
|
|||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} - Whether all the turret points are occupied.
|
||||||
|
*/
|
||||||
|
IsFull()
|
||||||
|
{
|
||||||
|
return !!this.turretPoints.find(turretPoint => turretPoint.entity == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Object} - Max and min ranges at which entities can occupy any turret.
|
||||||
|
*/
|
||||||
|
GetLoadingRange()
|
||||||
|
{
|
||||||
|
return { "min": 0, "max": +(this.template.LoadingRange || 2) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} ent - The entity ID of the turret to be potentially picked up.
|
||||||
|
* @return {boolean} - Whether this entity can pick the specified entity up.
|
||||||
|
*/
|
||||||
|
CanPickup(ent)
|
||||||
|
{
|
||||||
|
if (!this.template.Pickup || this.IsFull())
|
||||||
|
return false;
|
||||||
|
let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||||
|
return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} entities - The entities to ask to leave or to kill.
|
||||||
|
*/
|
||||||
|
EjectOrKill(entities)
|
||||||
|
{
|
||||||
|
let removedEntities = [];
|
||||||
|
for (let entity of entities)
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
|
||||||
|
if (!cmpTurretable || !cmpTurretable.LeaveTurret(true))
|
||||||
|
{
|
||||||
|
let cmpHealth = Engine.QueryInterface(entity, IID_Health);
|
||||||
|
if (cmpHealth)
|
||||||
|
cmpHealth.Kill();
|
||||||
|
else
|
||||||
|
Engine.DestroyEntity(entity);
|
||||||
|
removedEntities.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removedEntities.length)
|
||||||
|
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
||||||
|
"added": [],
|
||||||
|
"removed": removedEntities
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an init turret, present from game start. (E.g. set in Atlas.)
|
* Sets an init turret, present from game start. (E.g. set in Atlas.)
|
||||||
* @param {String} turretName - The name of the turret point to be used.
|
* @param {String} turretName - The name of the turret point to be used.
|
||||||
@ -237,20 +275,6 @@ class TurretHolder
|
|||||||
this.initTurrets.set(turretName, entity);
|
this.initTurrets.set(turretName, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} from - The entity to substitute.
|
|
||||||
* @param {number} to - The entity to subtitute with.
|
|
||||||
*/
|
|
||||||
SwapEntities(from, to)
|
|
||||||
{
|
|
||||||
let turretPoint = this.GetOccupiedTurret(from);
|
|
||||||
if (turretPoint)
|
|
||||||
{
|
|
||||||
this.LeaveTurret(from, turretPoint);
|
|
||||||
this.OccupyTurret(to, turretPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update list of turreted entities when a game inits.
|
* Update list of turreted entities when a game inits.
|
||||||
*/
|
*/
|
||||||
@ -274,10 +298,7 @@ class TurretHolder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialise the turreted units.
|
* Initialise turreted units.
|
||||||
* Really ugly, but because GarrisonHolder is processed earlier, and also turrets
|
|
||||||
* entities on init, we can find an entity that already is present.
|
|
||||||
* In that case we reject and occupy.
|
|
||||||
*/
|
*/
|
||||||
OnGlobalInitGame(msg)
|
OnGlobalInitGame(msg)
|
||||||
{
|
{
|
||||||
@ -285,16 +306,48 @@ class TurretHolder
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for (let [turretPointName, entity] of this.initTurrets)
|
for (let [turretPointName, entity] of this.initTurrets)
|
||||||
{
|
|
||||||
if (this.OccupiesTurret(entity))
|
|
||||||
this.LeaveTurret(entity);
|
|
||||||
if (!this.OccupyNamedTurret(entity, turretPointName))
|
if (!this.OccupyNamedTurret(entity, turretPointName))
|
||||||
warn("Entity " + entity + " could not occupy the turret point " +
|
warn("Entity " + entity + " could not occupy the turret point " +
|
||||||
turretPointName + " of turret holder " + this.entity + ".");
|
turretPointName + " of turret holder " + this.entity + ".");
|
||||||
}
|
|
||||||
|
|
||||||
delete this.initTurrets;
|
delete this.initTurrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} msg - { "entity": number, "newentity": number }.
|
||||||
|
*/
|
||||||
|
OnEntityRenamed(msg)
|
||||||
|
{
|
||||||
|
for (let entity of this.GetEntities())
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
|
||||||
|
if (!cmpTurretable)
|
||||||
|
continue;
|
||||||
|
let currentPoint = this.GetOccupiedTurretName(entity);
|
||||||
|
cmpTurretable.LeaveTurret(true);
|
||||||
|
cmpTurretable.OccupyTurret(msg.newentity, currentPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} msg - { "entity": number, "from": number, "to": number }.
|
||||||
|
*/
|
||||||
|
OnOwnershipChanged(msg)
|
||||||
|
{
|
||||||
|
let entities = this.GetEntities();
|
||||||
|
if (!entities.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (msg.to == INVALID_PLAYER)
|
||||||
|
this.EjectOrKill(entities);
|
||||||
|
else
|
||||||
|
for (let entity of entities.filter(entity => !IsOwnedByMutualAllyOfEntity(entity, this.entity)))
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
|
||||||
|
if (cmpTurretable)
|
||||||
|
cmpTurretable.LeaveTurret();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TurretHolder.prototype.Schema =
|
TurretHolder.prototype.Schema =
|
||||||
@ -328,6 +381,16 @@ TurretHolder.prototype.Schema =
|
|||||||
"</interleave>" +
|
"</interleave>" +
|
||||||
"</element>" +
|
"</element>" +
|
||||||
"</oneOrMore>" +
|
"</oneOrMore>" +
|
||||||
"</element>";
|
"</element>" +
|
||||||
|
"<optional>" +
|
||||||
|
"<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to occupy a turret point. Should be about 2.0 for land entities and preferably greater for ships.'>" +
|
||||||
|
"<ref name='nonNegativeDecimal'/>" +
|
||||||
|
"</element>" +
|
||||||
|
"</optional>"
|
||||||
|
"<optional>" +
|
||||||
|
"<element name='Pickup' a:help='This entity will try to move to pick up units to be turreted.'>" +
|
||||||
|
"<data type='boolean'/>" +
|
||||||
|
"</element>" +
|
||||||
|
"</optional>";
|
||||||
|
|
||||||
Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);
|
Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);
|
||||||
|
165
binaries/data/mods/public/simulation/components/Turretable.js
Normal file
165
binaries/data/mods/public/simulation/components/Turretable.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
function Turretable() {}
|
||||||
|
|
||||||
|
Turretable.prototype.Schema =
|
||||||
|
"<empty/>";
|
||||||
|
|
||||||
|
Turretable.prototype.Init = function()
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number} - The entity ID of the entity this entity is turreted on.
|
||||||
|
*/
|
||||||
|
Turretable.prototype.HolderID = function()
|
||||||
|
{
|
||||||
|
return this.holder || INVALID_ENTITY;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} - Whether we're turreted.
|
||||||
|
*/
|
||||||
|
Turretable.prototype.IsTurreted = function()
|
||||||
|
{
|
||||||
|
return !!this.holder;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} target - The entity ID to check.
|
||||||
|
* @return {boolean} - Whether we can occupy the turret.
|
||||||
|
*/
|
||||||
|
Turretable.prototype.CanOccupy = function(target)
|
||||||
|
{
|
||||||
|
if (this.holder)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
||||||
|
return cmpTurretHolder && cmpTurretHolder.CanOccupy(this.entity);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} target - The entity ID of the entity this entity is being turreted on.
|
||||||
|
* @param {string} turretPointName - Optionally the turret point name to occupy.
|
||||||
|
* @return {boolean} - Whether occupying succeeded.
|
||||||
|
*/
|
||||||
|
Turretable.prototype.OccupyTurret = function(target, turretPointName = "")
|
||||||
|
{
|
||||||
|
if (!this.CanOccupy(target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
||||||
|
if (!cmpTurretHolder || !cmpTurretHolder.OccupyNamedTurret(this.entity, turretPointName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.holder = target;
|
||||||
|
|
||||||
|
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
|
||||||
|
if (cmpUnitAI)
|
||||||
|
{
|
||||||
|
cmpUnitAI.SetGarrisoned();
|
||||||
|
cmpUnitAI.SetTurretStance();
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
if (cmpUnitMotion)
|
||||||
|
cmpUnitMotion.SetFacePointAfterMove(false);
|
||||||
|
|
||||||
|
// Remove the unit's obstruction to avoid interfering with pathing.
|
||||||
|
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
|
||||||
|
if (cmpObstruction)
|
||||||
|
cmpObstruction.SetActive(false);
|
||||||
|
|
||||||
|
Engine.PostMessage(this.entity, MT_TurretedStateChanged, {
|
||||||
|
"oldHolder": INVALID_ENTITY,
|
||||||
|
"holderID": target
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} forced - Optionally whether the leaving the turret is forced.
|
||||||
|
* @return {boolean} - Whether leaving the turret succeeded.
|
||||||
|
*/
|
||||||
|
Turretable.prototype.LeaveTurret = function(forced = false)
|
||||||
|
{
|
||||||
|
if (!this.holder)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
let pos = PositionHelper.GetSpawnPosition(this.holder, this.entity, forced);
|
||||||
|
if (!pos)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
|
||||||
|
if (!cmpTurretHolder || !cmpTurretHolder.LeaveTurret(this.entity))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let cmpUnitMotionEntity = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
if (cmpUnitMotionEntity)
|
||||||
|
cmpUnitMotionEntity.SetFacePointAfterMove(true);
|
||||||
|
|
||||||
|
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||||
|
if (cmpPosition)
|
||||||
|
{
|
||||||
|
cmpPosition.JumpTo(pos.x, pos.z);
|
||||||
|
cmpPosition.SetHeightOffset(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmpHolderPosition = Engine.QueryInterface(this.holder, IID_Position);
|
||||||
|
if (cmpHolderPosition)
|
||||||
|
cmpPosition.SetYRotation(cmpHolderPosition.GetPosition().horizAngleTo(pos));
|
||||||
|
|
||||||
|
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
|
||||||
|
if (cmpUnitAI)
|
||||||
|
{
|
||||||
|
cmpUnitAI.Ungarrison();
|
||||||
|
cmpUnitAI.UnsetGarrisoned();
|
||||||
|
cmpUnitAI.ResetTurretStance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the obstruction flags to template defaults.
|
||||||
|
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
|
||||||
|
if (cmpObstruction)
|
||||||
|
cmpObstruction.SetActive(true);
|
||||||
|
|
||||||
|
Engine.PostMessage(this.entity, MT_TurretedStateChanged, {
|
||||||
|
"oldHolder": this.holder,
|
||||||
|
"holderID": INVALID_ENTITY
|
||||||
|
});
|
||||||
|
|
||||||
|
let cmpRallyPoint = Engine.QueryInterface(this.holder, IID_RallyPoint);
|
||||||
|
if (cmpRallyPoint)
|
||||||
|
cmpRallyPoint.OrderToRallyPoint(this.entity, ["occupy-turret"]);
|
||||||
|
|
||||||
|
delete this.holder;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Turretable.prototype.OnEntityRenamed = function(msg)
|
||||||
|
{
|
||||||
|
if (!this.holder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
|
||||||
|
if (!cmpTurretHolder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let holder = this.holder;
|
||||||
|
let currentPoint = cmpTurretHolder.GetOccupiedTurretName(this.entity);
|
||||||
|
this.LeaveTurret(true);
|
||||||
|
let cmpTurretableNew = Engine.QueryInterface(msg.newentity, IID_Turretable);
|
||||||
|
if (cmpTurretableNew)
|
||||||
|
cmpTurretableNew.OccupyTurret(holder, currentPoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
Turretable.prototype.OnOwnershipChanged = function(msg)
|
||||||
|
{
|
||||||
|
if (!this.holder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (msg.to == INVALID_PLAYER)
|
||||||
|
this.LeaveTurret(true);
|
||||||
|
else if (!IsOwnedByMutualAllyOfEntity(this.entity, this.holder))
|
||||||
|
this.LeaveTurret();
|
||||||
|
};
|
||||||
|
|
||||||
|
Engine.RegisterComponentType(IID_Turretable, "Turretable", Turretable);
|
@ -592,7 +592,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
return ACCEPT_ORDER;
|
return ACCEPT_ORDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.CheckGarrisonRange(msg.data.target))
|
if (msg.data.garrison ? this.CheckGarrisonRange(msg.data.target) :
|
||||||
|
this.CheckOccupyTurretRange(msg.data.target))
|
||||||
this.SetNextState("INDIVIDUAL.GARRISON.GARRISONING");
|
this.SetNextState("INDIVIDUAL.GARRISON.GARRISONING");
|
||||||
else
|
else
|
||||||
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
|
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
|
||||||
@ -744,9 +745,11 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
"Order.Garrison": function(msg) {
|
"Order.Garrison": function(msg) {
|
||||||
if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder))
|
if (!Engine.QueryInterface(msg.data.target,
|
||||||
|
msg.data.garrison ? IID_GarrisonHolder : IID_TurretHolder))
|
||||||
return this.FinishOrder();
|
return this.FinishOrder();
|
||||||
if (!this.CheckGarrisonRange(msg.data.target))
|
if (!(msg.data.garrison ? this.CheckGarrisonRange(msg.data.target) :
|
||||||
|
this.CheckOccupyTurretRange(msg.data.target)))
|
||||||
{
|
{
|
||||||
if (!this.CheckTargetVisible(msg.data.target))
|
if (!this.CheckTargetVisible(msg.data.target))
|
||||||
return this.FinishOrder();
|
return this.FinishOrder();
|
||||||
@ -1100,19 +1103,20 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
"GARRISON": {
|
"GARRISON": {
|
||||||
"APPROACHING": {
|
"APPROACHING": {
|
||||||
"enter": function() {
|
"enter": function() {
|
||||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
if (!(this.order.data.garrison ? this.MoveToGarrisonRange(this.order.data.target) :
|
||||||
cmpFormation.SetRearrange(true);
|
this.MoveToOccupyTurretRange(this.order.data.target)))
|
||||||
cmpFormation.MoveMembersIntoFormation(true, true);
|
|
||||||
|
|
||||||
if (!this.MoveToGarrisonRange(this.order.data.target))
|
|
||||||
{
|
{
|
||||||
this.FinishOrder();
|
this.FinishOrder();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the garrisonholder should pickup, warn it so it can take needed action.
|
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
||||||
let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
|
cmpFormation.SetRearrange(true);
|
||||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
|
cmpFormation.MoveMembersIntoFormation(true, true);
|
||||||
|
|
||||||
|
// If the holder should pickup, warn it so it can take needed action.
|
||||||
|
let cmpHolder = Engine.QueryInterface(this.order.data.target, this.order.data.garrison ? IID_GarrisonHolder : IID_TurretHolder);
|
||||||
|
if (cmpHolder && cmpHolder.CanPickup(this.entity))
|
||||||
{
|
{
|
||||||
this.pickup = this.order.data.target; // temporary, deleted in "leave"
|
this.pickup = this.order.data.target; // temporary, deleted in "leave"
|
||||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
||||||
@ -1137,7 +1141,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
|
|
||||||
"GARRISONING": {
|
"GARRISONING": {
|
||||||
"enter": function() {
|
"enter": function() {
|
||||||
this.CallMemberFunction("Garrison", [this.order.data.target, false]);
|
this.CallMemberFunction(this.order.data.garrison ? "Garrison" : "OccupyTurret", [this.order.data.target, false]);
|
||||||
// We might have been disbanded due to the lack of members.
|
// We might have been disbanded due to the lack of members.
|
||||||
if (Engine.QueryInterface(this.entity, IID_Formation).GetMemberCount())
|
if (Engine.QueryInterface(this.entity, IID_Formation).GetMemberCount())
|
||||||
this.SetNextState("MEMBER");
|
this.SetNextState("MEMBER");
|
||||||
@ -3209,13 +3213,15 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
"GARRISON": {
|
"GARRISON": {
|
||||||
"APPROACHING": {
|
"APPROACHING": {
|
||||||
"enter": function() {
|
"enter": function() {
|
||||||
if (!this.CanGarrison(this.order.data.target))
|
if (this.order.data.garrison ? !this.CanGarrison(this.order.data.target) :
|
||||||
|
!this.CanOccupyTurret(this.order.data.target))
|
||||||
{
|
{
|
||||||
this.FinishOrder();
|
this.FinishOrder();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.MoveToGarrisonRange(this.order.data.target))
|
if (this.order.data.garrison ? !this.MoveToGarrisonRange(this.order.data.target) :
|
||||||
|
!this.MoveToOccupyTurretRange(this.order.data.target))
|
||||||
{
|
{
|
||||||
this.FinishOrder();
|
this.FinishOrder();
|
||||||
return true;
|
return true;
|
||||||
@ -3224,8 +3230,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
if (this.pickup)
|
if (this.pickup)
|
||||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
|
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
|
||||||
|
|
||||||
let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
|
let cmpHolder = Engine.QueryInterface(this.order.data.target, this.order.data.garrison ? IID_GarrisonHolder : IID_TurretHolder);
|
||||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
|
if (cmpHolder && cmpHolder.CanPickup(this.entity))
|
||||||
{
|
{
|
||||||
this.pickup = this.order.data.target;
|
this.pickup = this.order.data.target;
|
||||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
||||||
@ -3246,7 +3252,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
if (!msg.likelyFailure && !msg.likelySuccess)
|
if (!msg.likelyFailure && !msg.likelySuccess)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.CheckGarrisonRange(this.order.data.target))
|
if (this.order.data.garrison ? this.CheckGarrisonRange(this.order.data.target) :
|
||||||
|
this.CheckOccupyTurretRange(this.order.data.target))
|
||||||
this.SetNextState("GARRISONING");
|
this.SetNextState("GARRISONING");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -3265,11 +3272,23 @@ UnitAI.prototype.UnitFsmSpec = {
|
|||||||
"GARRISONING": {
|
"GARRISONING": {
|
||||||
"enter": function() {
|
"enter": function() {
|
||||||
let target = this.order.data.target;
|
let target = this.order.data.target;
|
||||||
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
|
if (this.order.data.garrison)
|
||||||
if (!cmpGarrisonable || !cmpGarrisonable.Garrison(target))
|
|
||||||
{
|
{
|
||||||
this.FinishOrder();
|
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
|
||||||
return true;
|
if (!cmpGarrisonable || !cmpGarrisonable.Garrison(target))
|
||||||
|
{
|
||||||
|
this.FinishOrder();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable);
|
||||||
|
if (!cmpTurretable || !cmpTurretable.OccupyTurret(target))
|
||||||
|
{
|
||||||
|
this.FinishOrder();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.formationController)
|
if (this.formationController)
|
||||||
@ -3459,8 +3478,10 @@ UnitAI.prototype.Init = function()
|
|||||||
|
|
||||||
UnitAI.prototype.IsTurret = function()
|
UnitAI.prototype.IsTurret = function()
|
||||||
{
|
{
|
||||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
if (!this.isGarrisoned)
|
||||||
return cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY;
|
return false;
|
||||||
|
let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable);
|
||||||
|
return cmpTurretable && cmpTurretable.HolderID() != INVALID_ENTITY;
|
||||||
};
|
};
|
||||||
|
|
||||||
UnitAI.prototype.IsFormationController = function()
|
UnitAI.prototype.IsFormationController = function()
|
||||||
@ -4170,6 +4191,12 @@ UnitAI.prototype.BackToWork = function()
|
|||||||
|
|
||||||
if (this.isGarrisoned)
|
if (this.isGarrisoned)
|
||||||
{
|
{
|
||||||
|
if (this.IsTurret())
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable);
|
||||||
|
if (!cmpTurretable || !cmpTurretable.LeaveTurret())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
|
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
|
||||||
if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(false))
|
if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(false))
|
||||||
return false;
|
return false;
|
||||||
@ -4795,6 +4822,20 @@ UnitAI.prototype.MoveToGarrisonRange = function(target)
|
|||||||
return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
|
return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.MoveToOccupyTurretRange = function(target)
|
||||||
|
{
|
||||||
|
if (!this.CheckTargetVisible(target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
||||||
|
if (!cmpTurretHolder)
|
||||||
|
return false;
|
||||||
|
let range = cmpTurretHolder.GetLoadingRange();
|
||||||
|
|
||||||
|
let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||||
|
return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic dispatcher for other Check...Range functions.
|
* Generic dispatcher for other Check...Range functions.
|
||||||
* @param iid - Interface ID (optional) implementing GetRange
|
* @param iid - Interface ID (optional) implementing GetRange
|
||||||
@ -4920,6 +4961,16 @@ UnitAI.prototype.CheckGarrisonRange = function(target)
|
|||||||
return this.CheckTargetRangeExplicit(target, range.min, range.max);
|
return this.CheckTargetRangeExplicit(target, range.min, range.max);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.CheckOccupyTurretRange = function(target)
|
||||||
|
{
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
||||||
|
if (!cmpTurretHolder)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let range = cmpTurretHolder.GetLoadingRange();
|
||||||
|
return this.CheckTargetRangeExplicit(target, range.min, range.max);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the target entity is visible through the FoW/SoD.
|
* Returns true if the target entity is visible through the FoW/SoD.
|
||||||
*/
|
*/
|
||||||
@ -5353,7 +5404,7 @@ UnitAI.prototype.AddOrder = function(type, data, queued, pushFront)
|
|||||||
// May happen if an order arrives on the same turn the unit is garrisoned
|
// May happen if an order arrives on the same turn the unit is garrisoned
|
||||||
// in that case, just forget the order as this will lead to an infinite loop.
|
// in that case, just forget the order as this will lead to an infinite loop.
|
||||||
// ToDo: Fix that by checking for the ability to move on orders that need that.
|
// ToDo: Fix that by checking for the ability to move on orders that need that.
|
||||||
if (this.isGarrisoned && !this.IsTurret() && type != "Ungarrison")
|
if (this.isGarrisoned && type != "Ungarrison")
|
||||||
return;
|
return;
|
||||||
this.ReplaceOrder(type, data);
|
this.ReplaceOrder(type, data);
|
||||||
}
|
}
|
||||||
@ -5591,7 +5642,7 @@ UnitAI.prototype.Garrison = function(target, queued, pushFront)
|
|||||||
this.WalkToTarget(target, queued);
|
this.WalkToTarget(target, queued);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.AddOrder("Garrison", { "target": target, "force": true }, queued, pushFront);
|
this.AddOrder("Garrison", { "target": target, "force": true, "garrison": true }, queued, pushFront);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -5604,6 +5655,21 @@ UnitAI.prototype.Ungarrison = function()
|
|||||||
this.AddOrder("Ungarrison", null, false);
|
this.AddOrder("Ungarrison", null, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds garrison order to the queue, forced by the player.
|
||||||
|
*/
|
||||||
|
UnitAI.prototype.OccupyTurret = function(target, queued, pushFront)
|
||||||
|
{
|
||||||
|
if (target == this.entity)
|
||||||
|
return;
|
||||||
|
if (!this.CanOccupyTurret(target))
|
||||||
|
{
|
||||||
|
this.WalkToTarget(target, queued);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.AddOrder("Garrison", { "target": target, "force": true, "garrison": false }, queued, pushFront);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds gather order to the queue, forced by the player
|
* Adds gather order to the queue, forced by the player
|
||||||
* until the target is reached
|
* until the target is reached
|
||||||
@ -6443,6 +6509,17 @@ UnitAI.prototype.CanRepair = function(target)
|
|||||||
return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target);
|
return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UnitAI.prototype.CanOccupyTurret = function(target)
|
||||||
|
{
|
||||||
|
// Formation controllers should always respond to commands
|
||||||
|
// (then the individual units can make up their own minds).
|
||||||
|
if (this.IsFormationController())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable);
|
||||||
|
return cmpTurretable && cmpTurretable.CanOccupy(target);
|
||||||
|
};
|
||||||
|
|
||||||
UnitAI.prototype.CanPack = function()
|
UnitAI.prototype.CanPack = function()
|
||||||
{
|
{
|
||||||
var cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
|
var cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
Engine.RegisterInterface("Turretable");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message of the form { "holderID": number }
|
||||||
|
* sent from the Turretable component whenever the turreted state changes.
|
||||||
|
*/
|
||||||
|
Engine.RegisterMessageType("TurretedStateChanged");
|
@ -2,7 +2,6 @@ Engine.LoadHelperScript("ValueModification.js");
|
|||||||
Engine.LoadHelperScript("Player.js");
|
Engine.LoadHelperScript("Player.js");
|
||||||
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
||||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
|
||||||
Engine.LoadComponentScript("interfaces/Health.js");
|
Engine.LoadComponentScript("interfaces/Health.js");
|
||||||
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
||||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
Engine.LoadHelperScript("Position.js");
|
||||||
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
||||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/Health.js");
|
||||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||||
Engine.LoadComponentScript("Garrisonable.js");
|
Engine.LoadComponentScript("Garrisonable.js");
|
||||||
|
|
||||||
@ -9,12 +11,15 @@ const garrisonHolderID = 1;
|
|||||||
const garrisonableID = 2;
|
const garrisonableID = 2;
|
||||||
AddMock(garrisonHolderID, IID_GarrisonHolder, {
|
AddMock(garrisonHolderID, IID_GarrisonHolder, {
|
||||||
"Garrison": () => true,
|
"Garrison": () => true,
|
||||||
"GetSpawnPosition": () => new Vector3D(0, 0, 0),
|
|
||||||
"IsAllowedToGarrison": () => true,
|
"IsAllowedToGarrison": () => true,
|
||||||
"OrderToRallyPoint": () => {},
|
|
||||||
"Eject": () => true
|
"Eject": () => true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddMock(garrisonHolderID, IID_Footprint, {
|
||||||
|
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
|
||||||
|
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
|
||||||
|
});
|
||||||
|
|
||||||
let size = 1;
|
let size = 1;
|
||||||
let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", {
|
let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", {
|
||||||
"Size": size
|
"Size": size
|
||||||
@ -39,7 +44,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID);
|
|||||||
TS_ASSERT(!cmpGarrisonable.Garrison(garrisonHolderID));
|
TS_ASSERT(!cmpGarrisonable.Garrison(garrisonHolderID));
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID);
|
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID);
|
||||||
|
|
||||||
cmpGarrisonable.UnGarrison();
|
TS_ASSERT(cmpGarrisonable.UnGarrison());
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY);
|
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY);
|
||||||
|
|
||||||
// Test renaming.
|
// Test renaming.
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
Engine.LoadHelperScript("ValueModification.js");
|
Engine.LoadHelperScript("ValueModification.js");
|
||||||
Engine.LoadHelperScript("Player.js");
|
Engine.LoadHelperScript("Player.js");
|
||||||
|
Engine.LoadHelperScript("Position.js");
|
||||||
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
||||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||||
Engine.LoadComponentScript("interfaces/Health.js");
|
Engine.LoadComponentScript("interfaces/Health.js");
|
||||||
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
||||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
|
||||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||||
Engine.LoadComponentScript("Garrisonable.js");
|
Engine.LoadComponentScript("Garrisonable.js");
|
||||||
Engine.LoadComponentScript("GarrisonHolder.js");
|
Engine.LoadComponentScript("GarrisonHolder.js");
|
||||||
Engine.LoadComponentScript("TurretHolder.js");
|
|
||||||
|
|
||||||
const player = 1;
|
const player = 1;
|
||||||
const enemyPlayer = 2;
|
const enemyPlayer = 2;
|
||||||
@ -34,7 +33,6 @@ let createGarrisonCmp = entity => {
|
|||||||
"JumpTo": (posX, posZ) => {},
|
"JumpTo": (posX, posZ) => {},
|
||||||
"MoveOutOfWorld": () => {},
|
"MoveOutOfWorld": () => {},
|
||||||
"SetHeightOffset": height => {},
|
"SetHeightOffset": height => {},
|
||||||
"SetTurretParent": ent => {},
|
|
||||||
"SetYRotation": angle => {}
|
"SetYRotation": angle => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,7 +86,6 @@ AddMock(garrison, IID_Position, {
|
|||||||
"JumpTo": (posX, posZ) => {},
|
"JumpTo": (posX, posZ) => {},
|
||||||
"MoveOutOfWorld": () => {},
|
"MoveOutOfWorld": () => {},
|
||||||
"SetHeightOffset": height => {},
|
"SetHeightOffset": height => {},
|
||||||
"SetTurretParent": entity => {},
|
|
||||||
"SetYRotation": angle => {}
|
"SetYRotation": angle => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,52 +140,3 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), entities
|
|||||||
TS_ASSERT(cmpGarrisonHolder.UnloadAll());
|
TS_ASSERT(cmpGarrisonHolder.UnloadAll());
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
|
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
|
||||||
|
|
||||||
// Turrets!
|
|
||||||
AddMock(holder, IID_Position, {
|
|
||||||
"GetPosition": () => new Vector3D(4, 3, 25),
|
|
||||||
"GetRotation": () => new Vector3D(4, 0, 6)
|
|
||||||
});
|
|
||||||
|
|
||||||
let cmpTurretHolder = ConstructComponent(holder, "TurretHolder", {
|
|
||||||
"TurretPoints": {
|
|
||||||
"archer1": {
|
|
||||||
"X": "12.0",
|
|
||||||
"Y": "5.",
|
|
||||||
"Z": "6.0"
|
|
||||||
},
|
|
||||||
"archer2": {
|
|
||||||
"X": "15.0",
|
|
||||||
"Y": "5.0",
|
|
||||||
"Z": "6.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
TS_ASSERT(cmpGarrisonable.Garrison(holder));
|
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]);
|
|
||||||
TS_ASSERT(cmpTurretHolder.OccupiesTurret(garrison));
|
|
||||||
TS_ASSERT(cmpGarrisonable.UnGarrison());
|
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
|
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), []);
|
|
||||||
|
|
||||||
// Test renaming on a turret.
|
|
||||||
// Ensure we test renaming from the second spot, not the first.
|
|
||||||
const newGarrison = 31;
|
|
||||||
let cmpGarrisonableNew = createGarrisonCmp(newGarrison);
|
|
||||||
TS_ASSERT(cmpGarrisonableNew.Garrison(holder));
|
|
||||||
TS_ASSERT(cmpGarrisonable.Garrison(holder));
|
|
||||||
TS_ASSERT(cmpGarrisonableNew.UnGarrison());
|
|
||||||
let previousTurret = cmpTurretHolder.GetOccupiedTurretName(garrison);
|
|
||||||
cmpGarrisonable.OnEntityRenamed({
|
|
||||||
"entity": garrison,
|
|
||||||
"newentity": newGarrison
|
|
||||||
});
|
|
||||||
let newTurret = cmpTurretHolder.GetOccupiedTurretName(newGarrison);
|
|
||||||
TS_ASSERT_UNEVAL_EQUALS(newTurret, previousTurret);
|
|
||||||
TS_ASSERT(cmpGarrisonableNew.UnGarrison());
|
|
||||||
|
|
||||||
// Test initTurrets.
|
|
||||||
cmpTurretHolder.SetInitEntity("archer1", garrison);
|
|
||||||
cmpTurretHolder.SetInitEntity("archer2", newGarrison);
|
|
||||||
cmpTurretHolder.OnGlobalInitGame();
|
|
||||||
TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [garrison, newGarrison]);
|
|
||||||
|
@ -34,6 +34,7 @@ Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
|||||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||||
Engine.LoadComponentScript("interfaces/Treasure.js");
|
Engine.LoadComponentScript("interfaces/Treasure.js");
|
||||||
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
|
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/Turretable.js");
|
||||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||||
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
|
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
|
||||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||||
|
@ -122,20 +122,3 @@ TS_ASSERT(cmpTurretHolder.OccupyTurret(cavID, cmpTurretHolder.turretPoints[2]));
|
|||||||
TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID));
|
TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID));
|
||||||
TS_ASSERT(!cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[1]));
|
TS_ASSERT(!cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[1]));
|
||||||
TS_ASSERT(cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[2]));
|
TS_ASSERT(cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[2]));
|
||||||
|
|
||||||
// Test renaming.
|
|
||||||
AddMock(turretHolderID, IID_GarrisonHolder, {
|
|
||||||
"IsGarrisoned": () => true
|
|
||||||
});
|
|
||||||
TS_ASSERT(cmpTurretHolder.OccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[2]));
|
|
||||||
cmpTurretHolder.SwapEntities(siegeEngineID, archerID);
|
|
||||||
TS_ASSERT(!cmpTurretHolder.OccupiesTurret(siegeEngineID));
|
|
||||||
TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID));
|
|
||||||
|
|
||||||
// Renaming into an entity not allowed on the same turret point hides us.
|
|
||||||
TS_ASSERT(cmpTurretHolder.OccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[1]));
|
|
||||||
cmpTurretHolder.SwapEntities(siegeEngineID, archerID);
|
|
||||||
TS_ASSERT(!cmpTurretHolder.OccupiesTurret(siegeEngineID));
|
|
||||||
TS_ASSERT(!cmpTurretHolder.OccupiesTurret(archerID));
|
|
||||||
|
|
||||||
// ToDo: Ownership changes are handled by GarrisonHolder.js.
|
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
Engine.LoadHelperScript("ValueModification.js");
|
||||||
|
Engine.LoadHelperScript("Player.js");
|
||||||
|
Engine.LoadHelperScript("Position.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/Health.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/Turretable.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
||||||
|
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||||
|
Engine.LoadComponentScript("Turretable.js");
|
||||||
|
Engine.LoadComponentScript("TurretHolder.js");
|
||||||
|
|
||||||
|
const player = 1;
|
||||||
|
const enemyPlayer = 2;
|
||||||
|
const friendlyPlayer = 3;
|
||||||
|
const turret = 10;
|
||||||
|
const holder = 11;
|
||||||
|
|
||||||
|
let createTurretCmp = entity => {
|
||||||
|
AddMock(entity, IID_Identity, {
|
||||||
|
"GetClassesList": () => ["Ranged"],
|
||||||
|
"GetSelectionGroupName": () => "mace_infantry_archer_a"
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(entity, IID_Ownership, {
|
||||||
|
"GetOwner": () => player
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(entity, IID_Position, {
|
||||||
|
"GetHeightOffset": () => 0,
|
||||||
|
"GetPosition": () => new Vector3D(4, 3, 25),
|
||||||
|
"GetRotation": () => new Vector3D(4, 0, 6),
|
||||||
|
"JumpTo": (posX, posZ) => {},
|
||||||
|
"MoveOutOfWorld": () => {},
|
||||||
|
"SetHeightOffset": height => {},
|
||||||
|
"SetTurretParent": entity => {},
|
||||||
|
"SetYRotation": angle => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ConstructComponent(entity, "Turretable", null);
|
||||||
|
};
|
||||||
|
|
||||||
|
AddMock(holder, IID_Footprint, {
|
||||||
|
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
|
||||||
|
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(holder, IID_Ownership, {
|
||||||
|
"GetOwner": () => player
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(player, IID_Player, {
|
||||||
|
"IsAlly": id => id != enemyPlayer,
|
||||||
|
"IsMutualAlly": id => id != enemyPlayer,
|
||||||
|
"GetPlayerID": () => player
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(friendlyPlayer, IID_Player, {
|
||||||
|
"IsAlly": id => true,
|
||||||
|
"IsMutualAlly": id => true,
|
||||||
|
"GetPlayerID": () => friendlyPlayer
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||||
|
"GetPlayerByID": id => id
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(holder, IID_Position, {
|
||||||
|
"GetPosition": () => new Vector3D(4, 3, 25),
|
||||||
|
"GetRotation": () => new Vector3D(4, 0, 6)
|
||||||
|
});
|
||||||
|
|
||||||
|
let cmpTurretable = createTurretCmp(turret);
|
||||||
|
|
||||||
|
let cmpTurretHolder = ConstructComponent(holder, "TurretHolder", {
|
||||||
|
"TurretPoints": {
|
||||||
|
"archer1": {
|
||||||
|
"X": "12.0",
|
||||||
|
"Y": "5.",
|
||||||
|
"Z": "6.0"
|
||||||
|
},
|
||||||
|
"archer2": {
|
||||||
|
"X": "15.0",
|
||||||
|
"Y": "5.0",
|
||||||
|
"Z": "6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TS_ASSERT(cmpTurretable.OccupyTurret(holder));
|
||||||
|
TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [turret]);
|
||||||
|
TS_ASSERT(cmpTurretHolder.OccupiesTurret(turret));
|
||||||
|
TS_ASSERT(cmpTurretable.LeaveTurret());
|
||||||
|
TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), []);
|
||||||
|
|
||||||
|
// Test renaming on a turret.
|
||||||
|
// Ensure we test renaming from the second spot, not the first.
|
||||||
|
const newTurret = 31;
|
||||||
|
let cmpTurretableNew = createTurretCmp(newTurret);
|
||||||
|
TS_ASSERT(cmpTurretableNew.OccupyTurret(holder));
|
||||||
|
TS_ASSERT(cmpTurretable.OccupyTurret(holder));
|
||||||
|
TS_ASSERT(cmpTurretableNew.LeaveTurret());
|
||||||
|
let previousTurret = cmpTurretHolder.GetOccupiedTurretName(turret);
|
||||||
|
cmpTurretable.OnEntityRenamed({
|
||||||
|
"entity": turret,
|
||||||
|
"newentity": newTurret
|
||||||
|
});
|
||||||
|
let newTurretPos = cmpTurretHolder.GetOccupiedTurretName(newTurret);
|
||||||
|
TS_ASSERT_UNEVAL_EQUALS(newTurretPos, previousTurret);
|
||||||
|
TS_ASSERT(cmpTurretableNew.LeaveTurret());
|
||||||
|
|
||||||
|
// Test initTurrets.
|
||||||
|
cmpTurretHolder.SetInitEntity("archer1", turret);
|
||||||
|
cmpTurretHolder.SetInitEntity("archer2", newTurret);
|
||||||
|
cmpTurretHolder.OnGlobalInitGame();
|
||||||
|
TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [turret, newTurret]);
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "garrisonedUnits",
|
"type": "turretedUnits",
|
||||||
"affects": ["Soldier"],
|
"affects": ["Soldier"],
|
||||||
"modifications": [
|
"modifications": [
|
||||||
{ "value": "Resistance/Entity/Damage/Hack", "add": 3 },
|
{ "value": "Resistance/Entity/Damage/Hack", "add": 3 },
|
||||||
@ -8,5 +8,5 @@
|
|||||||
{ "value": "Vision/Range", "add": 20 }
|
{ "value": "Vision/Range", "add": 20 }
|
||||||
],
|
],
|
||||||
"auraName": "Wall Protection",
|
"auraName": "Wall Protection",
|
||||||
"auraDescription": "Garrisoned Soldiers +3 crush, hack, pierce resistance and +20 vision range."
|
"auraDescription": "Turreted Soldiers +3 crush, hack, pierce resistance and +20 vision range."
|
||||||
}
|
}
|
||||||
|
@ -462,6 +462,13 @@ var g_Commands = {
|
|||||||
data.cmpPlayer.SetState("defeated", markForTranslation("%(player)s has resigned."));
|
data.cmpPlayer.SetState("defeated", markForTranslation("%(player)s has resigned."));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"occupy-turret": function(player, cmd, data)
|
||||||
|
{
|
||||||
|
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
|
||||||
|
cmpUnitAI.OccupyTurret(cmd.target, cmd.queued);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
"garrison": function(player, cmd, data)
|
"garrison": function(player, cmd, data)
|
||||||
{
|
{
|
||||||
if (!CanPlayerOrAllyControlUnit(cmd.target, player, data.controlAllUnits))
|
if (!CanPlayerOrAllyControlUnit(cmd.target, player, data.controlAllUnits))
|
||||||
@ -497,6 +504,38 @@ var g_Commands = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"leave-turret": function(player, cmd, data)
|
||||||
|
{
|
||||||
|
let notUnloaded = 0;
|
||||||
|
for (let ent of data.entities)
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
|
||||||
|
if (!cmpTurretable || !cmpTurretable.LeaveTurret())
|
||||||
|
++notUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notUnloaded)
|
||||||
|
notifyUnloadFailure(player);
|
||||||
|
},
|
||||||
|
|
||||||
|
"unload-turrets": function(player, cmd, data)
|
||||||
|
{
|
||||||
|
let notUnloaded = 0;
|
||||||
|
for (let ent of data.entities)
|
||||||
|
{
|
||||||
|
let cmpTurretHolder = Engine.QueryInterface(ent, IID_TurretHolder);
|
||||||
|
for (let turret of cmpTurretHolder.GetEntities())
|
||||||
|
{
|
||||||
|
let cmpTurretable = Engine.QueryInterface(turret, IID_Turretable);
|
||||||
|
if (!cmpTurretable || !cmpTurretable.LeaveTurret())
|
||||||
|
++notUnloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notUnloaded)
|
||||||
|
notifyUnloadFailure(player);
|
||||||
|
},
|
||||||
|
|
||||||
"unload": function(player, cmd, data)
|
"unload": function(player, cmd, data)
|
||||||
{
|
{
|
||||||
if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits))
|
if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits))
|
||||||
@ -843,13 +882,13 @@ var g_Commands = {
|
|||||||
/**
|
/**
|
||||||
* Sends a GUI notification about unit(s) that failed to ungarrison.
|
* Sends a GUI notification about unit(s) that failed to ungarrison.
|
||||||
*/
|
*/
|
||||||
function notifyUnloadFailure(player, garrisonHolder)
|
function notifyUnloadFailure(player)
|
||||||
{
|
{
|
||||||
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||||
cmpGUIInterface.PushNotification({
|
cmpGUIInterface.PushNotification({
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"players": [player],
|
"players": [player],
|
||||||
"message": markForTranslation("Unable to ungarrison unit(s)"),
|
"message": markForTranslation("Unable to unload unit(s)."),
|
||||||
"translateMessage": true
|
"translateMessage": true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -133,4 +133,39 @@ PositionHelper.prototype.PredictTimeToTarget = function(firstPosition, selfSpeed
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} target - EntityID to find the spawn position for.
|
||||||
|
* @param {number} entity - EntityID to find the spawn position for.
|
||||||
|
* @param {boolean} forced - Optionally whether the spawning is forced.
|
||||||
|
* @return {Vector3D} - An appropriate spawning position.
|
||||||
|
*/
|
||||||
|
PositionHelper.prototype.GetSpawnPosition = function(target, entity, forced)
|
||||||
|
{
|
||||||
|
let cmpFootprint = Engine.QueryInterface(target, IID_Footprint);
|
||||||
|
let cmpHealth = Engine.QueryInterface(target, IID_Health);
|
||||||
|
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||||
|
|
||||||
|
if (!cmpFootprint)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// If the spawner is a sinking ship, restrict the location to the intersection of both passabilities.
|
||||||
|
// TODO: should use passability classes to be more generic.
|
||||||
|
let pos;
|
||||||
|
if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship"))
|
||||||
|
pos = cmpFootprint.PickSpawnPointBothPass(entity);
|
||||||
|
else
|
||||||
|
pos = cmpFootprint.PickSpawnPoint(entity);
|
||||||
|
|
||||||
|
if (pos.y < 0)
|
||||||
|
{
|
||||||
|
if (!forced)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// If ejection is forced, we need to continue, so use center of the entity.
|
||||||
|
let cmpPosition = Engine.QueryInterface(target, IID_Position);
|
||||||
|
pos = cmpPosition.GetPosition();
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
};
|
||||||
|
|
||||||
Engine.RegisterGlobal("PositionHelper", new PositionHelper());
|
Engine.RegisterGlobal("PositionHelper", new PositionHelper());
|
||||||
|
@ -57,6 +57,14 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts)
|
|||||||
"autocontinue": i == rallyPos.length - 1
|
"autocontinue": i == rallyPos.length - 1
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "occupy-turret":
|
||||||
|
ret.push({
|
||||||
|
"type": "occupy-turret",
|
||||||
|
"entities": spawnedEnts,
|
||||||
|
"target": data[i].target,
|
||||||
|
"queued": true
|
||||||
|
});
|
||||||
|
break;
|
||||||
case "garrison":
|
case "garrison":
|
||||||
ret.push({
|
ret.push({
|
||||||
"type": "garrison",
|
"type": "garrison",
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="8.0"/>
|
<Square width="37.0" depth="8.0"/>
|
||||||
<Height>15.5</Height>
|
<Height>15.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>athen</Civ>
|
<Civ>athen</Civ>
|
||||||
<SpecificName>Pylai</SpecificName>
|
<SpecificName>Pylai</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="8.0"/>
|
<Square width="37.0" depth="8.0"/>
|
||||||
<Height>18.0</Height>
|
<Height>18.0</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>4</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>brit</Civ>
|
<Civ>brit</Civ>
|
||||||
<SpecificName>Duoricos</SpecificName>
|
<SpecificName>Duoricos</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="9.0"/>
|
<Square width="37.0" depth="9.0"/>
|
||||||
<Height>16.5</Height>
|
<Height>16.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>cart</Civ>
|
<Civ>cart</Civ>
|
||||||
<SpecificName>Mijdil-šaʿar</SpecificName>
|
<SpecificName>Mijdil-šaʿar</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="8.0"/>
|
<Square width="37.0" depth="8.0"/>
|
||||||
<Height>12.0</Height>
|
<Height>12.0</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>4</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>gaul</Civ>
|
<Civ>gaul</Civ>
|
||||||
<SpecificName>Duoricos</SpecificName>
|
<SpecificName>Duoricos</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="8.0"/>
|
<Square width="37.0" depth="8.0"/>
|
||||||
<Height>12.7</Height>
|
<Height>12.7</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>iber</Civ>
|
<Civ>iber</Civ>
|
||||||
<SpecificName>Biko Sarbide</SpecificName>
|
<SpecificName>Biko Sarbide</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="9.0"/>
|
<Square width="37.0" depth="9.0"/>
|
||||||
<Height>12.6</Height>
|
<Height>12.6</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>6</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>kush</Civ>
|
<Civ>kush</Civ>
|
||||||
<SpecificName>ʿryt</SpecificName>
|
<SpecificName>ʿryt</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="7.5"/>
|
<Square width="37.0" depth="7.5"/>
|
||||||
<Height>15.5</Height>
|
<Height>15.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>mace</Civ>
|
<Civ>mace</Civ>
|
||||||
<SpecificName>Pylai</SpecificName>
|
<SpecificName>Pylai</SpecificName>
|
||||||
|
@ -9,10 +9,6 @@
|
|||||||
<stone>200</stone>
|
<stone>200</stone>
|
||||||
</Resources>
|
</Resources>
|
||||||
</Cost>
|
</Cost>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>20</Max>
|
|
||||||
<List>Infantry+Archer</List>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max>1200</Max>
|
<Max>1200</Max>
|
||||||
</Health>
|
</Health>
|
||||||
|
@ -10,9 +10,6 @@
|
|||||||
<Square width="37.0" depth="8.0"/>
|
<Square width="37.0" depth="8.0"/>
|
||||||
<Height>22.0</Height>
|
<Height>22.0</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>4</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>maur</Civ>
|
<Civ>maur</Civ>
|
||||||
<SpecificName>Dwara</SpecificName>
|
<SpecificName>Dwara</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="7.0"/>
|
<Square width="37.0" depth="7.0"/>
|
||||||
<Height>13.8</Height>
|
<Height>13.8</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>6</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>pers</Civ>
|
<Civ>pers</Civ>
|
||||||
<SpecificName>Duvarθiš</SpecificName>
|
<SpecificName>Duvarθiš</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="40.0" depth="12.0"/>
|
<Square width="40.0" depth="12.0"/>
|
||||||
<Height>17.8</Height>
|
<Height>17.8</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>10</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>ptol</Civ>
|
<Civ>ptol</Civ>
|
||||||
<SpecificName>Pylai</SpecificName>
|
<SpecificName>Pylai</SpecificName>
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
<Square width="37.0" depth="7.0"/>
|
<Square width="37.0" depth="7.0"/>
|
||||||
<Height>12.5</Height>
|
<Height>12.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max op="mul">0.75</Max>
|
<Max op="mul">0.75</Max>
|
||||||
</Health>
|
</Health>
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
<Square width="37.0" depth="5.0"/>
|
<Square width="37.0" depth="5.0"/>
|
||||||
<Height>6.7</Height>
|
<Height>6.7</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>7</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max op="mul">0.75</Max>
|
<Max op="mul">0.75</Max>
|
||||||
</Health>
|
</Health>
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
<Square width="7.0" depth="7.0"/>
|
<Square width="7.0" depth="7.0"/>
|
||||||
<Height>12.5</Height>
|
<Height>12.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>4</Max>
|
|
||||||
<List datatype="tokens">-Support -Infantry</List>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max op="mul">0.75</Max>
|
<Max op="mul">0.75</Max>
|
||||||
</Health>
|
</Health>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="7.0"/>
|
<Square width="37.0" depth="7.0"/>
|
||||||
<Height>11.9</Height>
|
<Height>11.9</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>10</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>rome</Civ>
|
<Civ>rome</Civ>
|
||||||
<SpecificName>Porta</SpecificName>
|
<SpecificName>Porta</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="35.0" depth="8.0"/>
|
<Square width="35.0" depth="8.0"/>
|
||||||
<Height>11.6</Height>
|
<Height>11.6</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>5</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>sele</Civ>
|
<Civ>sele</Civ>
|
||||||
<SpecificName>Pylai</SpecificName>
|
<SpecificName>Pylai</SpecificName>
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
<Square width="37.0" depth="7.0"/>
|
<Square width="37.0" depth="7.0"/>
|
||||||
<Height>15.5</Height>
|
<Height>15.5</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<Civ>spart</Civ>
|
<Civ>spart</Civ>
|
||||||
<SpecificName>Pylai</SpecificName>
|
<SpecificName>Pylai</SpecificName>
|
||||||
|
@ -20,14 +20,6 @@
|
|||||||
<Square width="7.0" depth="7.0"/>
|
<Square width="7.0" depth="7.0"/>
|
||||||
<Height>13.0</Height>
|
<Height>13.0</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>1</Max>
|
|
||||||
<EjectHealth>0.1</EjectHealth>
|
|
||||||
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
|
|
||||||
<List datatype="tokens">Infantry</List>
|
|
||||||
<BuffHeal>0</BuffHeal>
|
|
||||||
<LoadingRange>2</LoadingRange>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max>400</Max>
|
<Max>400</Max>
|
||||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_2x2</SpawnEntityOnDeath>
|
<SpawnEntityOnDeath>decay|rubble/rubble_stone_2x2</SpawnEntityOnDeath>
|
||||||
|
@ -11,14 +11,6 @@
|
|||||||
<Square width="6.0" depth="6.0"/>
|
<Square width="6.0" depth="6.0"/>
|
||||||
<Height>8.0</Height>
|
<Height>8.0</Height>
|
||||||
</Footprint>
|
</Footprint>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>1</Max>
|
|
||||||
<List datatype="tokens">Ranged+Infantry</List>
|
|
||||||
<EjectHealth>0.1</EjectHealth>
|
|
||||||
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
|
|
||||||
<BuffHeal>0</BuffHeal>
|
|
||||||
<LoadingRange>2</LoadingRange>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Identity>
|
<Identity>
|
||||||
<GenericName>Wall</GenericName>
|
<GenericName>Wall</GenericName>
|
||||||
<SelectionGroupName>template_structure_defensive_wall</SelectionGroupName>
|
<SelectionGroupName>template_structure_defensive_wall</SelectionGroupName>
|
||||||
|
@ -9,9 +9,6 @@
|
|||||||
<stone>36</stone>
|
<stone>36</stone>
|
||||||
</Resources>
|
</Resources>
|
||||||
</Cost>
|
</Cost>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>8</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max>3000</Max>
|
<Max>3000</Max>
|
||||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_long</SpawnEntityOnDeath>
|
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_long</SpawnEntityOnDeath>
|
||||||
|
@ -9,9 +9,6 @@
|
|||||||
<stone>24</stone>
|
<stone>24</stone>
|
||||||
</Resources>
|
</Resources>
|
||||||
</Cost>
|
</Cost>
|
||||||
<GarrisonHolder>
|
|
||||||
<Max>4</Max>
|
|
||||||
</GarrisonHolder>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max>2000</Max>
|
<Max>2000</Max>
|
||||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_medium</SpawnEntityOnDeath>
|
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_medium</SpawnEntityOnDeath>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
<stone>12</stone>
|
<stone>12</stone>
|
||||||
</Resources>
|
</Resources>
|
||||||
</Cost>
|
</Cost>
|
||||||
<GarrisonHolder disable=""/>
|
|
||||||
<Health>
|
<Health>
|
||||||
<Max>1000</Max>
|
<Max>1000</Max>
|
||||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_short</SpawnEntityOnDeath>
|
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_short</SpawnEntityOnDeath>
|
||||||
|
@ -40,6 +40,10 @@
|
|||||||
<GarrisonHolder>
|
<GarrisonHolder>
|
||||||
<Max>2</Max>
|
<Max>2</Max>
|
||||||
<List datatype="tokens">Support Infantry</List>
|
<List datatype="tokens">Support Infantry</List>
|
||||||
|
<EjectHealth>0.1</EjectHealth>
|
||||||
|
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
|
||||||
|
<BuffHeal>0</BuffHeal>
|
||||||
|
<LoadingRange>2</LoadingRange>
|
||||||
</GarrisonHolder>
|
</GarrisonHolder>
|
||||||
<Health>
|
<Health>
|
||||||
<Max>4000</Max>
|
<Max>4000</Max>
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
||||||
</SoundGroups>
|
</SoundGroups>
|
||||||
</Sound>
|
</Sound>
|
||||||
|
<Turretable/>
|
||||||
<UnitMotion>
|
<UnitMotion>
|
||||||
<WalkSpeed op="mul">1.2</WalkSpeed>
|
<WalkSpeed op="mul">1.2</WalkSpeed>
|
||||||
</UnitMotion>
|
</UnitMotion>
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
||||||
</SoundGroups>
|
</SoundGroups>
|
||||||
</Sound>
|
</Sound>
|
||||||
|
<Turretable/>
|
||||||
<UnitMotion>
|
<UnitMotion>
|
||||||
<WalkSpeed op="mul">1.2</WalkSpeed>
|
<WalkSpeed op="mul">1.2</WalkSpeed>
|
||||||
</UnitMotion>
|
</UnitMotion>
|
||||||
|
@ -42,4 +42,5 @@
|
|||||||
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
||||||
</SoundGroups>
|
</SoundGroups>
|
||||||
</Sound>
|
</Sound>
|
||||||
|
<Turretable/>
|
||||||
</Entity>
|
</Entity>
|
||||||
|
@ -42,4 +42,5 @@
|
|||||||
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
||||||
</SoundGroups>
|
</SoundGroups>
|
||||||
</Sound>
|
</Sound>
|
||||||
|
<Turretable/>
|
||||||
</Entity>
|
</Entity>
|
||||||
|
@ -19,4 +19,5 @@
|
|||||||
</Damage>
|
</Damage>
|
||||||
</Entity>
|
</Entity>
|
||||||
</Resistance>
|
</Resistance>
|
||||||
|
<Turretable/>
|
||||||
</Entity>
|
</Entity>
|
||||||
|
Loading…
Reference in New Issue
Block a user