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
|
||||
backtowork = "Y" ; The unit will go back to work
|
||||
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)
|
||||
attack = Ctrl ; Modifier to attack instead of another action (e.g. capture)
|
||||
attackmove = Ctrl ; Modifier to attackmove 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
|
||||
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
|
||||
guard = "G" ; Modifier to escort/guard when clicking on unit/building
|
||||
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);
|
||||
}
|
||||
|
||||
if (template.TurretHolder)
|
||||
ret.turretHolder = {
|
||||
"turretPoints": template.TurretHolder.TurretPoints
|
||||
};
|
||||
|
||||
if (template.WallSet)
|
||||
{
|
||||
ret.wallSet = {
|
||||
|
@ -588,6 +588,16 @@ function getGarrisonTooltip(template)
|
||||
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)
|
||||
{
|
||||
if (!template.garrisonHolder || !template.buildingAI)
|
||||
|
@ -27,6 +27,10 @@
|
||||
"name": "Unload",
|
||||
"desc": "Unload garrisoned units when a building/mechanical unit is selected."
|
||||
},
|
||||
"session.unloadturrets": {
|
||||
"name": "Unload Turrets",
|
||||
"desc": "Unload turreted units."
|
||||
},
|
||||
"session.unloadtype": {
|
||||
"name": "Unload unit type",
|
||||
"desc": "Modifier to unload all units of type."
|
||||
@ -51,6 +55,10 @@
|
||||
"name": "Garrison",
|
||||
"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": {
|
||||
"name": "Auto-rally point",
|
||||
"desc": "Modifier to set the rally point on the building itself."
|
||||
|
@ -57,6 +57,7 @@ ReferencePage.prototype.StatsFunctions = [
|
||||
getHealerTooltip,
|
||||
getResistanceTooltip,
|
||||
getGarrisonTooltip,
|
||||
getTurretsTooltip,
|
||||
getProjectilesTooltip,
|
||||
getSpeedTooltip,
|
||||
getGatherTooltip,
|
||||
|
@ -32,6 +32,10 @@
|
||||
<action on="KeyDown">unloadAll();</action>
|
||||
</object>
|
||||
|
||||
<object hotkey="session.unloadturrets">
|
||||
<action on="KeyDown">unloadAllTurrets();</action>
|
||||
</object>
|
||||
|
||||
<object hotkey="session.stop">
|
||||
<action on="KeyDown">stopUnits(g_Selection.toList());</action>
|
||||
</object>
|
||||
|
@ -325,6 +325,7 @@ function displaySingle(entState)
|
||||
getGatherTooltip,
|
||||
getSpeedTooltip,
|
||||
getGarrisonTooltip,
|
||||
getTurretsTooltip,
|
||||
getPopulationBonusTooltip,
|
||||
getProjectilesTooltip,
|
||||
getResourceTrickleTooltip,
|
||||
|
@ -193,6 +193,7 @@ g_SelectionPanels.Construction = {
|
||||
getEntityCostTooltip(template, data.player),
|
||||
getResourceDropsiteTooltip(template),
|
||||
getGarrisonTooltip(template),
|
||||
getTurretsTooltip(template),
|
||||
getPopulationBonusTooltip(template),
|
||||
showTemplateViewerOnRightClickTooltip(template)
|
||||
);
|
||||
@ -975,6 +976,7 @@ g_SelectionPanels.Training = {
|
||||
getHealerTooltip,
|
||||
getResistanceTooltip,
|
||||
getGarrisonTooltip,
|
||||
getTurretsTooltip,
|
||||
getProjectilesTooltip,
|
||||
getSpeedTooltip,
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
Engine.PostNetworkCommand({
|
||||
|
@ -684,6 +684,77 @@ var g_UnitActions =
|
||||
"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":
|
||||
{
|
||||
"execute": function(target, action, selection, queued, pushFront)
|
||||
@ -984,6 +1055,22 @@ var g_UnitActions =
|
||||
targetState.garrisonHolder.capacity)
|
||||
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)
|
||||
{
|
||||
let resourceType = targetState.resourceSupply.type;
|
||||
@ -1224,6 +1311,41 @@ var g_EntityCommands =
|
||||
"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": {
|
||||
"getInfo": function(entStates)
|
||||
{
|
||||
@ -1317,15 +1439,11 @@ var g_EntityCommands =
|
||||
"allowedPlayers": ["Player"]
|
||||
},
|
||||
|
||||
"unload": {
|
||||
"leave-turret": {
|
||||
"getInfo": function(entStates)
|
||||
{
|
||||
if (entStates.every(entState => {
|
||||
if (!entState.unitAI || !entState.turretParent)
|
||||
return true;
|
||||
let parent = GetEntityState(entState.turretParent);
|
||||
return !parent || !parent.garrisonHolder || parent.garrisonHolder.entities.indexOf(entState.id) == -1;
|
||||
}))
|
||||
if (entStates.every(entState => !entState.turretable ||
|
||||
entState.turretable.holder == INVALID_ENTITY))
|
||||
return false;
|
||||
|
||||
return {
|
||||
@ -1334,9 +1452,16 @@ var g_EntityCommands =
|
||||
"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"]
|
||||
},
|
||||
|
@ -543,6 +543,8 @@ m.Template = m.Class({
|
||||
|
||||
"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
|
||||
* 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; },
|
||||
|
||||
"canOccupyTurret": function() { return "Turretable" in this._template; },
|
||||
|
||||
"isTreasureCollecter": function() { return this.get("TreasureCollecter") !== undefined; },
|
||||
});
|
||||
|
||||
@ -840,23 +844,29 @@ m.Entity = m.Class({
|
||||
return this;
|
||||
},
|
||||
|
||||
"garrison": function(target, queued = false) {
|
||||
Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued });
|
||||
"garrison": function(target, queued = false, pushFront = false) {
|
||||
Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
|
||||
return this;
|
||||
},
|
||||
|
||||
"attack": function(unitId, allowCapture = true, queued = false) {
|
||||
Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued });
|
||||
"occupy-turret": function(target, queued = false, pushFront = false) {
|
||||
Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
|
||||
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, {
|
||||
"type": "collect-treasure",
|
||||
"entities": [this.id()],
|
||||
"target": target.id(),
|
||||
"autocontinue": autocontinue,
|
||||
"queued": queued
|
||||
"queued": queued,
|
||||
"pushFront": pushFront
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
@ -175,6 +175,12 @@ m.EntityCollection.prototype.garrison = function(target, queued = false)
|
||||
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()
|
||||
{
|
||||
Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() });
|
||||
|
@ -180,6 +180,11 @@ Auras.prototype.IsGarrisonedUnitsAura = function(name)
|
||||
return this.GetType(name) == "garrisonedUnits";
|
||||
};
|
||||
|
||||
Auras.prototype.IsTurretedUnitsAura = function(name)
|
||||
{
|
||||
return this.GetType(name) == "turretedUnits";
|
||||
};
|
||||
|
||||
Auras.prototype.IsRangeAura = function(name)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
this.ApplyGarrisonAura(msg.holderID);
|
||||
if (msg.olderHolder != INVALID_ENTITY)
|
||||
if (msg.oldHolder != INVALID_ENTITY)
|
||||
this.RemoveGarrisonAura(msg.oldHolder);
|
||||
};
|
||||
|
||||
|
@ -30,36 +30,21 @@ BuildingAI.prototype.Init = function()
|
||||
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 cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder);
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
|
||||
for (let ent of cmpGarrisonHolder.GetEntities())
|
||||
for (let ent of msg.added)
|
||||
{
|
||||
// Only count non-visible garrisoned entities towards extra arrows.
|
||||
if (cmpTurretHolder && cmpTurretHolder.OccupiesTurret(ent))
|
||||
continue;
|
||||
|
||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
continue;
|
||||
|
||||
if (MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
||||
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes))
|
||||
++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)
|
||||
|
@ -199,38 +199,6 @@ GarrisonHolder.prototype.Garrison = function(entity)
|
||||
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 {boolean} forced - Whether eject is forced (e.g. if building is destroyed).
|
||||
@ -256,32 +224,6 @@ GarrisonHolder.prototype.Eject = function(entity, forced)
|
||||
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.
|
||||
* @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.
|
||||
* @return {boolean} - Whether garrisoning succeeded.
|
||||
*/
|
||||
Garrisonable.prototype.Garrison = function(target, renamed = false)
|
||||
Garrisonable.prototype.Garrison = function(target)
|
||||
{
|
||||
if (!this.CanGarrison(target))
|
||||
return false;
|
||||
@ -93,35 +93,24 @@ Garrisonable.prototype.Garrison = function(target, renamed = false)
|
||||
"holderID": target
|
||||
});
|
||||
|
||||
if (renamed)
|
||||
return true;
|
||||
|
||||
let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder);
|
||||
if (cmpTurretHolder)
|
||||
cmpTurretHolder.OccupyTurret(this.entity);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false)
|
||||
Garrisonable.prototype.UnGarrison = function(forced = false)
|
||||
{
|
||||
if (!this.holder)
|
||||
return true;
|
||||
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
|
||||
if (!cmpGarrisonHolder)
|
||||
return false;
|
||||
|
||||
let pos = cmpGarrisonHolder.GetSpawnPosition(this.entity, forced);
|
||||
let pos = PositionHelper.GetSpawnPosition(this.holder, this.entity, forced);
|
||||
if (!pos)
|
||||
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;
|
||||
|
||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
@ -147,14 +136,9 @@ Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false)
|
||||
"holderID": INVALID_ENTITY
|
||||
});
|
||||
|
||||
if (renamed)
|
||||
return true;
|
||||
|
||||
let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder);
|
||||
if (cmpTurretHolder)
|
||||
cmpTurretHolder.LeaveTurret(this.entity);
|
||||
|
||||
cmpGarrisonHolder.OrderToRallyPoint(this.entity);
|
||||
let cmpRallyPoint = Engine.QueryInterface(this.holder, IID_RallyPoint);
|
||||
if (cmpRallyPoint)
|
||||
cmpRallyPoint.OrderToRallyPoint(this.entity, ["garrison"]);
|
||||
|
||||
delete this.holder;
|
||||
return true;
|
||||
@ -165,22 +149,11 @@ Garrisonable.prototype.OnEntityRenamed = function(msg)
|
||||
if (!this.holder)
|
||||
return;
|
||||
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder);
|
||||
if (cmpGarrisonHolder)
|
||||
{
|
||||
let holder = this.holder;
|
||||
this.UnGarrison(true, true);
|
||||
let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable);
|
||||
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;
|
||||
cmpGarrisonable.Garrison(holder, true);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable);
|
||||
|
@ -388,6 +388,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
"turretPoints": cmpTurretHolder.GetTurretPoints()
|
||||
};
|
||||
|
||||
let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
|
||||
if (cmpTurretable)
|
||||
ret.turretable = {
|
||||
"holder": cmpTurretable.HolderID()
|
||||
};
|
||||
|
||||
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
|
||||
if (cmpGarrisonable)
|
||||
ret.garrisonable = {
|
||||
|
@ -100,6 +100,31 @@ RallyPoint.prototype.Reset = function()
|
||||
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)
|
||||
{
|
||||
for (var data of this.data)
|
||||
|
@ -1,8 +1,6 @@
|
||||
/**
|
||||
* This class holds the functions regarding entities being visible on
|
||||
* 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
|
||||
{
|
||||
@ -54,6 +52,15 @@ class TurretHolder
|
||||
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.
|
||||
* @param {number} entity - The entity to use.
|
||||
@ -87,6 +94,7 @@ class TurretHolder
|
||||
return false;
|
||||
|
||||
turretPoint.entity = entity;
|
||||
|
||||
// Angle of turrets:
|
||||
// Renamed entities (turretPoint != undefined) should keep their angle.
|
||||
// Otherwise if an angle is given in the turretPoint, use it.
|
||||
@ -100,19 +108,6 @@ class TurretHolder
|
||||
|
||||
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, {
|
||||
"added": [entity],
|
||||
"removed": []
|
||||
@ -152,23 +147,11 @@ class TurretHolder
|
||||
if (!turretPoint)
|
||||
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;
|
||||
|
||||
// Reset the obstruction flags to template defaults.
|
||||
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
|
||||
if (cmpObstruction)
|
||||
cmpObstruction.SetActive(true);
|
||||
let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position);
|
||||
if (cmpPositionEntity)
|
||||
cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D());
|
||||
|
||||
Engine.PostMessage(this.entity, MT_TurretsChanged, {
|
||||
"added": [],
|
||||
@ -205,7 +188,8 @@ class TurretHolder
|
||||
*/
|
||||
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 {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.)
|
||||
* @param {String} turretName - The name of the turret point to be used.
|
||||
@ -237,20 +275,6 @@ class TurretHolder
|
||||
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.
|
||||
*/
|
||||
@ -274,10 +298,7 @@ class TurretHolder
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the 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.
|
||||
* Initialise turreted units.
|
||||
*/
|
||||
OnGlobalInitGame(msg)
|
||||
{
|
||||
@ -285,16 +306,48 @@ class TurretHolder
|
||||
return;
|
||||
|
||||
for (let [turretPointName, entity] of this.initTurrets)
|
||||
{
|
||||
if (this.OccupiesTurret(entity))
|
||||
this.LeaveTurret(entity);
|
||||
if (!this.OccupyNamedTurret(entity, turretPointName))
|
||||
warn("Entity " + entity + " could not occupy the turret point " +
|
||||
turretPointName + " of turret holder " + this.entity + ".");
|
||||
}
|
||||
|
||||
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 =
|
||||
@ -328,6 +381,16 @@ TurretHolder.prototype.Schema =
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</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);
|
||||
|
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;
|
||||
}
|
||||
|
||||
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");
|
||||
else
|
||||
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
|
||||
@ -744,9 +745,11 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"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();
|
||||
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))
|
||||
return this.FinishOrder();
|
||||
@ -1100,19 +1103,20 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
"GARRISON": {
|
||||
"APPROACHING": {
|
||||
"enter": function() {
|
||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
||||
cmpFormation.SetRearrange(true);
|
||||
cmpFormation.MoveMembersIntoFormation(true, 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();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the garrisonholder should pickup, warn it so it can take needed action.
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
|
||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
|
||||
let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
|
||||
cmpFormation.SetRearrange(true);
|
||||
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"
|
||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
||||
@ -1137,7 +1141,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"GARRISONING": {
|
||||
"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.
|
||||
if (Engine.QueryInterface(this.entity, IID_Formation).GetMemberCount())
|
||||
this.SetNextState("MEMBER");
|
||||
@ -3209,13 +3213,15 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
"GARRISON": {
|
||||
"APPROACHING": {
|
||||
"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();
|
||||
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();
|
||||
return true;
|
||||
@ -3224,8 +3230,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
if (this.pickup)
|
||||
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
|
||||
|
||||
let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
|
||||
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
|
||||
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;
|
||||
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
|
||||
@ -3246,7 +3252,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
if (!msg.likelyFailure && !msg.likelySuccess)
|
||||
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");
|
||||
else
|
||||
{
|
||||
@ -3265,12 +3272,24 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
"GARRISONING": {
|
||||
"enter": function() {
|
||||
let target = this.order.data.target;
|
||||
if (this.order.data.garrison)
|
||||
{
|
||||
let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable);
|
||||
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)
|
||||
{
|
||||
@ -3459,8 +3478,10 @@ UnitAI.prototype.Init = function()
|
||||
|
||||
UnitAI.prototype.IsTurret = function()
|
||||
{
|
||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
return cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY;
|
||||
if (!this.isGarrisoned)
|
||||
return false;
|
||||
let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable);
|
||||
return cmpTurretable && cmpTurretable.HolderID() != INVALID_ENTITY;
|
||||
};
|
||||
|
||||
UnitAI.prototype.IsFormationController = function()
|
||||
@ -4170,6 +4191,12 @@ UnitAI.prototype.BackToWork = function()
|
||||
|
||||
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);
|
||||
if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(false))
|
||||
return false;
|
||||
@ -4795,6 +4822,20 @@ UnitAI.prototype.MoveToGarrisonRange = function(target)
|
||||
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.
|
||||
* @param iid - Interface ID (optional) implementing GetRange
|
||||
@ -4920,6 +4961,16 @@ UnitAI.prototype.CheckGarrisonRange = function(target)
|
||||
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.
|
||||
*/
|
||||
@ -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
|
||||
// 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.
|
||||
if (this.isGarrisoned && !this.IsTurret() && type != "Ungarrison")
|
||||
if (this.isGarrisoned && type != "Ungarrison")
|
||||
return;
|
||||
this.ReplaceOrder(type, data);
|
||||
}
|
||||
@ -5591,7 +5642,7 @@ UnitAI.prototype.Garrison = function(target, queued, pushFront)
|
||||
this.WalkToTarget(target, queued);
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* until the target is reached
|
||||
@ -6443,6 +6509,17 @@ UnitAI.prototype.CanRepair = function(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()
|
||||
{
|
||||
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.LoadComponentScript("interfaces/Garrisonable.js");
|
||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/Health.js");
|
||||
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
|
@ -1,5 +1,7 @@
|
||||
Engine.LoadHelperScript("Position.js");
|
||||
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/Health.js");
|
||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||
Engine.LoadComponentScript("Garrisonable.js");
|
||||
|
||||
@ -9,12 +11,15 @@ const garrisonHolderID = 1;
|
||||
const garrisonableID = 2;
|
||||
AddMock(garrisonHolderID, IID_GarrisonHolder, {
|
||||
"Garrison": () => true,
|
||||
"GetSpawnPosition": () => new Vector3D(0, 0, 0),
|
||||
"IsAllowedToGarrison": () => true,
|
||||
"OrderToRallyPoint": () => {},
|
||||
"Eject": () => true
|
||||
});
|
||||
|
||||
AddMock(garrisonHolderID, IID_Footprint, {
|
||||
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
|
||||
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
|
||||
});
|
||||
|
||||
let size = 1;
|
||||
let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", {
|
||||
"Size": size
|
||||
@ -39,7 +44,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID);
|
||||
TS_ASSERT(!cmpGarrisonable.Garrison(garrisonHolderID));
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID);
|
||||
|
||||
cmpGarrisonable.UnGarrison();
|
||||
TS_ASSERT(cmpGarrisonable.UnGarrison());
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY);
|
||||
|
||||
// Test renaming.
|
||||
|
@ -1,15 +1,14 @@
|
||||
Engine.LoadHelperScript("ValueModification.js");
|
||||
Engine.LoadHelperScript("Player.js");
|
||||
Engine.LoadHelperScript("Position.js");
|
||||
Engine.LoadComponentScript("interfaces/Garrisonable.js");
|
||||
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/Health.js");
|
||||
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/TurretHolder.js");
|
||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||
Engine.LoadComponentScript("Garrisonable.js");
|
||||
Engine.LoadComponentScript("GarrisonHolder.js");
|
||||
Engine.LoadComponentScript("TurretHolder.js");
|
||||
|
||||
const player = 1;
|
||||
const enemyPlayer = 2;
|
||||
@ -34,7 +33,6 @@ let createGarrisonCmp = entity => {
|
||||
"JumpTo": (posX, posZ) => {},
|
||||
"MoveOutOfWorld": () => {},
|
||||
"SetHeightOffset": height => {},
|
||||
"SetTurretParent": ent => {},
|
||||
"SetYRotation": angle => {}
|
||||
});
|
||||
|
||||
@ -88,7 +86,6 @@ AddMock(garrison, IID_Position, {
|
||||
"JumpTo": (posX, posZ) => {},
|
||||
"MoveOutOfWorld": () => {},
|
||||
"SetHeightOffset": height => {},
|
||||
"SetTurretParent": entity => {},
|
||||
"SetYRotation": angle => {}
|
||||
});
|
||||
|
||||
@ -143,52 +140,3 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), entities
|
||||
TS_ASSERT(cmpGarrisonHolder.UnloadAll());
|
||||
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/Treasure.js");
|
||||
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
|
||||
Engine.LoadComponentScript("interfaces/Turretable.js");
|
||||
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
|
||||
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.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(cavID, cmpTurretHolder.turretPoints[1]));
|
||||
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"],
|
||||
"modifications": [
|
||||
{ "value": "Resistance/Entity/Damage/Hack", "add": 3 },
|
||||
@ -8,5 +8,5 @@
|
||||
{ "value": "Vision/Range", "add": 20 }
|
||||
],
|
||||
"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."));
|
||||
},
|
||||
|
||||
"occupy-turret": function(player, cmd, data)
|
||||
{
|
||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
|
||||
cmpUnitAI.OccupyTurret(cmd.target, cmd.queued);
|
||||
});
|
||||
},
|
||||
|
||||
"garrison": function(player, cmd, data)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
*/
|
||||
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({
|
||||
"type": "text",
|
||||
"players": [player],
|
||||
"message": markForTranslation("Unable to ungarrison unit(s)"),
|
||||
"message": markForTranslation("Unable to unload unit(s)."),
|
||||
"translateMessage": true
|
||||
});
|
||||
}
|
||||
|
@ -133,4 +133,39 @@ PositionHelper.prototype.PredictTimeToTarget = function(firstPosition, selfSpeed
|
||||
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());
|
||||
|
@ -57,6 +57,14 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts)
|
||||
"autocontinue": i == rallyPos.length - 1
|
||||
});
|
||||
break;
|
||||
case "occupy-turret":
|
||||
ret.push({
|
||||
"type": "occupy-turret",
|
||||
"entities": spawnedEnts,
|
||||
"target": data[i].target,
|
||||
"queued": true
|
||||
});
|
||||
break;
|
||||
case "garrison":
|
||||
ret.push({
|
||||
"type": "garrison",
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="8.0"/>
|
||||
<Height>15.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>athen</Civ>
|
||||
<SpecificName>Pylai</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="8.0"/>
|
||||
<Height>18.0</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>4</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>brit</Civ>
|
||||
<SpecificName>Duoricos</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="9.0"/>
|
||||
<Height>16.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>cart</Civ>
|
||||
<SpecificName>Mijdil-šaʿar</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="8.0"/>
|
||||
<Height>12.0</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>4</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>gaul</Civ>
|
||||
<SpecificName>Duoricos</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="8.0"/>
|
||||
<Height>12.7</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>iber</Civ>
|
||||
<SpecificName>Biko Sarbide</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="9.0"/>
|
||||
<Height>12.6</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>6</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>kush</Civ>
|
||||
<SpecificName>ʿryt</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="7.5"/>
|
||||
<Height>15.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>mace</Civ>
|
||||
<SpecificName>Pylai</SpecificName>
|
||||
|
@ -9,10 +9,6 @@
|
||||
<stone>200</stone>
|
||||
</Resources>
|
||||
</Cost>
|
||||
<GarrisonHolder>
|
||||
<Max>20</Max>
|
||||
<List>Infantry+Archer</List>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max>1200</Max>
|
||||
</Health>
|
||||
|
@ -10,9 +10,6 @@
|
||||
<Square width="37.0" depth="8.0"/>
|
||||
<Height>22.0</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>4</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>maur</Civ>
|
||||
<SpecificName>Dwara</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="7.0"/>
|
||||
<Height>13.8</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>6</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>pers</Civ>
|
||||
<SpecificName>Duvarθiš</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="40.0" depth="12.0"/>
|
||||
<Height>17.8</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>10</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>ptol</Civ>
|
||||
<SpecificName>Pylai</SpecificName>
|
||||
|
@ -14,9 +14,6 @@
|
||||
<Square width="37.0" depth="7.0"/>
|
||||
<Height>12.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max op="mul">0.75</Max>
|
||||
</Health>
|
||||
|
@ -14,9 +14,6 @@
|
||||
<Square width="37.0" depth="5.0"/>
|
||||
<Height>6.7</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>7</Max>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max op="mul">0.75</Max>
|
||||
</Health>
|
||||
|
@ -19,10 +19,6 @@
|
||||
<Square width="7.0" depth="7.0"/>
|
||||
<Height>12.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>4</Max>
|
||||
<List datatype="tokens">-Support -Infantry</List>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max op="mul">0.75</Max>
|
||||
</Health>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="7.0"/>
|
||||
<Height>11.9</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>10</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>rome</Civ>
|
||||
<SpecificName>Porta</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="35.0" depth="8.0"/>
|
||||
<Height>11.6</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>5</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>sele</Civ>
|
||||
<SpecificName>Pylai</SpecificName>
|
||||
|
@ -4,9 +4,6 @@
|
||||
<Square width="37.0" depth="7.0"/>
|
||||
<Height>15.5</Height>
|
||||
</Footprint>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Identity>
|
||||
<Civ>spart</Civ>
|
||||
<SpecificName>Pylai</SpecificName>
|
||||
|
@ -20,14 +20,6 @@
|
||||
<Square width="7.0" depth="7.0"/>
|
||||
<Height>13.0</Height>
|
||||
</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>
|
||||
<Max>400</Max>
|
||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_2x2</SpawnEntityOnDeath>
|
||||
|
@ -11,14 +11,6 @@
|
||||
<Square width="6.0" depth="6.0"/>
|
||||
<Height>8.0</Height>
|
||||
</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>
|
||||
<GenericName>Wall</GenericName>
|
||||
<SelectionGroupName>template_structure_defensive_wall</SelectionGroupName>
|
||||
|
@ -9,9 +9,6 @@
|
||||
<stone>36</stone>
|
||||
</Resources>
|
||||
</Cost>
|
||||
<GarrisonHolder>
|
||||
<Max>8</Max>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max>3000</Max>
|
||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_long</SpawnEntityOnDeath>
|
||||
|
@ -9,9 +9,6 @@
|
||||
<stone>24</stone>
|
||||
</Resources>
|
||||
</Cost>
|
||||
<GarrisonHolder>
|
||||
<Max>4</Max>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max>2000</Max>
|
||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_medium</SpawnEntityOnDeath>
|
||||
|
@ -6,7 +6,6 @@
|
||||
<stone>12</stone>
|
||||
</Resources>
|
||||
</Cost>
|
||||
<GarrisonHolder disable=""/>
|
||||
<Health>
|
||||
<Max>1000</Max>
|
||||
<SpawnEntityOnDeath>decay|rubble/rubble_stone_wall_short</SpawnEntityOnDeath>
|
||||
|
@ -40,6 +40,10 @@
|
||||
<GarrisonHolder>
|
||||
<Max>2</Max>
|
||||
<List datatype="tokens">Support Infantry</List>
|
||||
<EjectHealth>0.1</EjectHealth>
|
||||
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
|
||||
<BuffHeal>0</BuffHeal>
|
||||
<LoadingRange>2</LoadingRange>
|
||||
</GarrisonHolder>
|
||||
<Health>
|
||||
<Max>4000</Max>
|
||||
|
@ -45,6 +45,7 @@
|
||||
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Turretable/>
|
||||
<UnitMotion>
|
||||
<WalkSpeed op="mul">1.2</WalkSpeed>
|
||||
</UnitMotion>
|
||||
|
@ -45,6 +45,7 @@
|
||||
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Turretable/>
|
||||
<UnitMotion>
|
||||
<WalkSpeed op="mul">1.2</WalkSpeed>
|
||||
</UnitMotion>
|
||||
|
@ -42,4 +42,5 @@
|
||||
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Turretable/>
|
||||
</Entity>
|
||||
|
@ -42,4 +42,5 @@
|
||||
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<Turretable/>
|
||||
</Entity>
|
||||
|
@ -19,4 +19,5 @@
|
||||
</Damage>
|
||||
</Entity>
|
||||
</Resistance>
|
||||
<Turretable/>
|
||||
</Entity>
|
||||
|
Loading…
Reference in New Issue
Block a user