Differential D2367 Diff 12812 ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js
Show All 25 Lines | "<optional>" + | ||||
"<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" + | "<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" + | ||||
"<ref name='nonNegativeDecimal'/>" + | "<ref name='nonNegativeDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | "</optional>" + | ||||
"<optional>" + | "<optional>" + | ||||
"<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" + | "<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" + | ||||
"<data type='boolean'/>" + | "<data type='boolean'/>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | |||||
"<optional>" + | |||||
"<element name='VisibleGarrisonPoints' a:help='Points that will be used to visibly garrison a unit'>" + | |||||
"<zeroOrMore>" + | |||||
"<element a:help='Element containing the offset coordinates'>" + | |||||
"<anyName/>" + | |||||
"<interleave>" + | |||||
"<element name='X'>" + | |||||
"<data type='decimal'/>" + | |||||
"</element>" + | |||||
"<element name='Y'>" + | |||||
"<data type='decimal'/>" + | |||||
"</element>" + | |||||
"<element name='Z'>" + | |||||
"<data type='decimal'/>" + | |||||
"</element>" + | |||||
"<optional>" + | |||||
"<element name='AllowedClasses' a:help='If specified, only entities matching the given classes will be able to use this visible garrison point.'>" + | |||||
"<attribute name='datatype'>" + | |||||
"<value>tokens</value>" + | |||||
"</attribute>" + | |||||
"<text/>" + | |||||
"</element>" + | |||||
"</optional>"+ | |||||
"<optional>" + | |||||
"<element name='Angle' a:help='Angle in degrees relative to the garrisonHolder direction'>" + | |||||
"<data type='decimal'/>" + | |||||
"</element>" + | |||||
"</optional>" + | |||||
"</interleave>" + | |||||
"</element>" + | |||||
"</zeroOrMore>" + | |||||
"</element>" + | |||||
"</optional>"; | "</optional>"; | ||||
/** | /** | ||||
* Initialize GarrisonHolder Component | * Initialize GarrisonHolder Component | ||||
* Garrisoning when loading a map is set in the script of the map, by setting initGarrison | * Garrisoning when loading a map is set in the script of the map, by setting initGarrison | ||||
* which should contain the array of garrisoned entities. | * which should contain the array of garrisoned entities. | ||||
*/ | */ | ||||
GarrisonHolder.prototype.Init = function() | GarrisonHolder.prototype.Init = function() | ||||
{ | { | ||||
// Garrisoned Units | // Garrisoned Units | ||||
this.entities = []; | this.entities = []; | ||||
this.timer = undefined; | this.timer = undefined; | ||||
this.allowGarrisoning = new Map(); | this.allowGarrisoning = new Map(); | ||||
this.visibleGarrisonPoints = []; | }; | ||||
if (!this.template.VisibleGarrisonPoints) | |||||
return; | |||||
let points = this.template.VisibleGarrisonPoints; | /** | ||||
for (let point in points) | * @param {number} entity - The entity to verify. | ||||
this.visibleGarrisonPoints.push({ | * @return {boolean} - Whether the given entity is garrisoned in this GarrisonHolder. | ||||
"offset": { | */ | ||||
"x": +points[point].X, | GarrisonHolder.prototype.IsGarrisoned = function(entity) | ||||
"y": +points[point].Y, | { | ||||
"z": +points[point].Z | return this.entities.indexOf(entity) != -1; | ||||
}, | |||||
"allowedClasses": points[point].AllowedClasses, | |||||
"angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null, | |||||
"entity": null | |||||
}); | |||||
}; | }; | ||||
/** | /** | ||||
* @return {Object} max and min range at which entities can garrison the holder. | * @return {Object} max and min range at which entities can garrison the holder. | ||||
*/ | */ | ||||
GarrisonHolder.prototype.GetLoadingRange = function() | GarrisonHolder.prototype.GetLoadingRange = function() | ||||
{ | { | ||||
return { "max": +this.template.LoadingRange, "min": 0 }; | return { "max": +this.template.LoadingRange, "min": 0 }; | ||||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | GarrisonHolder.prototype.IsAllowedToGarrison = function(entity) | ||||
if (!cmpIdentity) | if (!cmpIdentity) | ||||
return false; | return false; | ||||
let entityClasses = cmpIdentity.GetClassesList(); | let entityClasses = cmpIdentity.GetClassesList(); | ||||
return MatchesClassList(entityClasses, this.template.List._string) && !!Engine.QueryInterface(entity, IID_Garrisonable); | return MatchesClassList(entityClasses, this.template.List._string) && !!Engine.QueryInterface(entity, IID_Garrisonable); | ||||
}; | }; | ||||
/** | /** | ||||
* @param {number} entity - The entity's id. | * @param {number} entity - The entityID to garrison. | ||||
* @param {Object|undefined} visibleGarrisonPoint - The vgp object. | * @param {boolean} renamed - Whether the entity was renamed. | ||||
* @return {boolean} - Whether the unit is allowed be visible on that garrison point. | * | ||||
*/ | |||||
GarrisonHolder.prototype.AllowedToVisibleGarrisoning = function(entity, visibleGarrisonPoint) | |||||
{ | |||||
if (!visibleGarrisonPoint) | |||||
return false; | |||||
if (!visibleGarrisonPoint.allowedClasses) | |||||
return true; | |||||
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); | |||||
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), visibleGarrisonPoint.allowedClasses._string); | |||||
}; | |||||
/** | |||||
* Garrison a unit inside. The timer for AutoHeal is started here. | |||||
* @param {number} vgpEntity - The visual garrison point that will be used. | |||||
* If vgpEntity is given, this visualGarrisonPoint will be used for the entity. | |||||
* @return {boolean} - Whether the entity was garrisoned. | * @return {boolean} - Whether the entity was garrisoned. | ||||
*/ | */ | ||||
GarrisonHolder.prototype.Garrison = function(entity, vgpEntity) | GarrisonHolder.prototype.Garrison = function(entity, renamed = false) | ||||
{ | { | ||||
if (!this.IsAllowedToGarrison(entity)) | if (!this.IsAllowedToGarrison(entity)) | ||||
return false; | return false; | ||||
if (!this.HasEnoughHealth()) | if (!this.HasEnoughHealth()) | ||||
return false; | return false; | ||||
let cmpPosition = Engine.QueryInterface(entity, IID_Position); | |||||
if (!cmpPosition) | |||||
return false; | |||||
if (!this.timer && this.GetHealRate() > 0) | if (!this.timer && this.GetHealRate() > 0) | ||||
{ | { | ||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | ||||
this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); | this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); | ||||
} | } | ||||
this.entities.push(entity); | this.entities.push(entity); | ||||
this.UpdateGarrisonFlag(); | this.UpdateGarrisonFlag(); | ||||
let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); | let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); | ||||
if (cmpProductionQueue) | if (cmpProductionQueue) | ||||
cmpProductionQueue.PauseProduction(); | cmpProductionQueue.PauseProduction(); | ||||
let cmpAura = Engine.QueryInterface(entity, IID_Auras); | let cmpAura = Engine.QueryInterface(entity, IID_Auras); | ||||
if (cmpAura && cmpAura.HasGarrisonAura()) | if (cmpAura && cmpAura.HasGarrisonAura()) | ||||
cmpAura.ApplyGarrisonAura(this.entity); | cmpAura.ApplyGarrisonAura(this.entity); | ||||
let visibleGarrisonPoint; | let cmpPosition = Engine.QueryInterface(entity, IID_Position); | ||||
if (vgpEntity && this.AllowedToVisibleGarrisoning(entity, vgpEntity)) | if (cmpPosition) | ||||
visibleGarrisonPoint = vgpEntity; | |||||
if (!visibleGarrisonPoint) | |||||
visibleGarrisonPoint = this.visibleGarrisonPoints.find(vgp => !vgp.entity && this.AllowedToVisibleGarrisoning(entity, vgp)); | |||||
let isVisiblyGarrisoned = false; | |||||
if (visibleGarrisonPoint) | |||||
{ | |||||
visibleGarrisonPoint.entity = entity; | |||||
// Angle of turrets: | |||||
// Renamed entities (vgpEntity != undefined) should keep their angle. | |||||
// Otherwise if an angle is given in the visibleGarrisonPoint, use it. | |||||
// If no such angle given (usually walls for which outside/inside not well defined), we keep | |||||
// the current angle as it was used for garrisoning and thus quite often was from inside to | |||||
// outside, except when garrisoning from outWorld where we take as default PI. | |||||
let cmpTurretPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (!vgpEntity && visibleGarrisonPoint.angle != null) | |||||
cmpPosition.SetYRotation(cmpTurretPosition.GetRotation().y + visibleGarrisonPoint.angle); | |||||
else if (!vgpEntity && !cmpPosition.IsInWorld()) | |||||
cmpPosition.SetYRotation(cmpTurretPosition.GetRotation().y + Math.PI); | |||||
let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); | |||||
if (cmpUnitMotion) | |||||
cmpUnitMotion.SetFacePointAfterMove(false); | |||||
cmpPosition.SetTurretParent(this.entity, visibleGarrisonPoint.offset); | |||||
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); | |||||
isVisiblyGarrisoned = true; | |||||
} | |||||
else | |||||
cmpPosition.MoveOutOfWorld(); | cmpPosition.MoveOutOfWorld(); | ||||
// Should only be called after the garrison has been performed else the visible Garrison Points are not updated yet. | |||||
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | ||||
"added": [entity], | "added": [entity], | ||||
"removed": [], | "removed": [], | ||||
"visible": { | "renamed": renamed | ||||
[entity]: isVisiblyGarrisoned, | |||||
} | |||||
}); | }); | ||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* Simply eject the unit from the garrisoning entity without moving it | * Simply eject the unit from the garrisoning entity without moving it | ||||
* @param {number} entity - Id of the entity to be ejected. | * @param {number} entity - Id of the entity to be ejected. | ||||
* @param {boolean} forced - Whether eject is forced (i.e. if building is destroyed). | * @param {boolean} forced - Whether eject is forced (i.e. if building is destroyed). | ||||
* @param {boolean} renamed - Whether eject was due to entity renaming. | |||||
* | |||||
* @return {boolean} Whether the entity was ejected. | * @return {boolean} Whether the entity was ejected. | ||||
*/ | */ | ||||
GarrisonHolder.prototype.Eject = function(entity, forced) | GarrisonHolder.prototype.Eject = function(entity, forced, renamed = false) | ||||
{ | { | ||||
let entityIndex = this.entities.indexOf(entity); | let entityIndex = this.entities.indexOf(entity); | ||||
// Error: invalid entity ID, usually it's already been ejected | // Error: invalid entity ID, usually it's already been ejected | ||||
if (entityIndex == -1) | if (entityIndex == -1) | ||||
return false; | return false; | ||||
// Find spawning location | // Find spawning location | ||||
let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); | let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); | ||||
Show All 15 Lines | if (!forced) | ||||
return false; | return false; | ||||
// If ejection is forced, we need to continue, so use center of the building | // If ejection is forced, we need to continue, so use center of the building | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
pos = cmpPosition.GetPosition(); | pos = cmpPosition.GetPosition(); | ||||
} | } | ||||
this.entities.splice(entityIndex, 1); | this.entities.splice(entityIndex, 1); | ||||
let cmpEntPosition = Engine.QueryInterface(entity, IID_Position); | |||||
let cmpEntUnitAI = Engine.QueryInterface(entity, IID_UnitAI); | |||||
// Needs to be set before the visible garrison points are cleared. | |||||
let visible = { | |||||
[entity]: this.IsVisiblyGarrisoned(entity) | |||||
}; | |||||
for (let vgp of this.visibleGarrisonPoints) | |||||
{ | |||||
if (vgp.entity != entity) | |||||
continue; | |||||
cmpEntPosition.SetTurretParent(INVALID_ENTITY, new Vector3D()); | |||||
let cmpEntUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); | |||||
if (cmpEntUnitMotion) | |||||
cmpEntUnitMotion.SetFacePointAfterMove(true); | |||||
if (cmpEntUnitAI) | |||||
cmpEntUnitAI.ResetTurretStance(); | |||||
vgp.entity = null; | |||||
break; | |||||
} | |||||
// Reset the obstruction flags to template defaults. | // Reset the obstruction flags to template defaults. | ||||
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); | let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); | ||||
if (cmpObstruction) | if (cmpObstruction) | ||||
cmpObstruction.SetActive(true); | cmpObstruction.SetActive(true); | ||||
let cmpEntUnitAI = Engine.QueryInterface(entity, IID_UnitAI); | |||||
if (cmpEntUnitAI) | if (cmpEntUnitAI) | ||||
cmpEntUnitAI.Ungarrison(); | cmpEntUnitAI.Ungarrison(); | ||||
let cmpEntProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); | let cmpEntProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); | ||||
if (cmpEntProductionQueue) | if (cmpEntProductionQueue) | ||||
cmpEntProductionQueue.UnpauseProduction(); | cmpEntProductionQueue.UnpauseProduction(); | ||||
let cmpEntAura = Engine.QueryInterface(entity, IID_Auras); | let cmpEntAura = Engine.QueryInterface(entity, IID_Auras); | ||||
if (cmpEntAura && cmpEntAura.HasGarrisonAura()) | if (cmpEntAura && cmpEntAura.HasGarrisonAura()) | ||||
cmpEntAura.RemoveGarrisonAura(this.entity); | cmpEntAura.RemoveGarrisonAura(this.entity); | ||||
let cmpEntPosition = Engine.QueryInterface(entity, IID_Position); | |||||
if (cmpEntPosition) | |||||
{ | |||||
cmpEntPosition.JumpTo(pos.x, pos.z); | cmpEntPosition.JumpTo(pos.x, pos.z); | ||||
cmpEntPosition.SetHeightOffset(0); | cmpEntPosition.SetHeightOffset(0); | ||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
if (cmpPosition) | if (cmpPosition) | ||||
cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); | cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); | ||||
} | |||||
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | ||||
"added": [], | "added": [], | ||||
"removed": [entity], | "removed": [entity], | ||||
"visible": visible | "renamed": renamed | ||||
}); | }); | ||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* Ejects units and orders them to move to the rally point. If an ejection | * Ejects units and orders them to move to the rally point. If an ejection | ||||
* with a given obstruction radius has failed, we won't try anymore to eject | * with a given obstruction radius has failed, we won't try anymore to eject | ||||
▲ Show 20 Lines • Show All 226 Lines • ▼ Show 20 Lines | GarrisonHolder.prototype.OnGlobalOwnershipChanged = function(msg) | ||||
{ | { | ||||
// If the entity is dead, remove it directly instead of ejecting the corpse | // If the entity is dead, remove it directly instead of ejecting the corpse | ||||
let cmpHealth = Engine.QueryInterface(msg.entity, IID_Health); | let cmpHealth = Engine.QueryInterface(msg.entity, IID_Health); | ||||
if (cmpHealth && cmpHealth.GetHitpoints() == 0) | if (cmpHealth && cmpHealth.GetHitpoints() == 0) | ||||
{ | { | ||||
this.entities.splice(entityIndex, 1); | this.entities.splice(entityIndex, 1); | ||||
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | ||||
"added": [], | "added": [], | ||||
"removed": [msg.entity], | "removed": [msg.entity] | ||||
"visible": { | |||||
[msg.entity]: this.IsVisiblyGarrisoned(msg.entity) | |||||
} | |||||
}); | }); | ||||
this.UpdateGarrisonFlag(); | this.UpdateGarrisonFlag(); | ||||
for (let point of this.visibleGarrisonPoints) | |||||
if (point.entity == msg.entity) | |||||
point.entity = null; | |||||
} | } | ||||
else if (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)) | else if (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)) | ||||
this.EjectOrKill([msg.entity]); | this.EjectOrKill([msg.entity]); | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* Update list of garrisoned entities if one gets renamed (e.g. by promotion). | * Update list of garrisoned entities if one gets renamed (e.g. by promotion). | ||||
*/ | */ | ||||
GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg) | GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg) | ||||
{ | { | ||||
let entityIndex = this.entities.indexOf(msg.entity); | let entityIndex = this.entities.indexOf(msg.entity); | ||||
if (entityIndex != -1) | if (entityIndex != -1) | ||||
{ | { | ||||
let vgpRenamed; | this.Eject(msg.entity, true, true); | ||||
for (let vgp of this.visibleGarrisonPoints) | this.Garrison(msg.newentity, true); | ||||
{ | |||||
if (vgp.entity != msg.entity) | // TurretHolder is not subscribed to GarrisonChanged, so we must inform it explicitly. | ||||
continue; | // Otherwise a renaming entity may re-occupy another turret instead of its previous one, | ||||
vgpRenamed = vgp; | // since the message does not know what turret point whas used, which is not wanted. | ||||
break; | // Also ensure the TurretHolder receives the message after we process it. | ||||
} | // If it processes it before us we garrison a turret and subsequently | ||||
this.Eject(msg.entity, true); | // are hidden by GarrisonHolder again. | ||||
this.Garrison(msg.newentity, vgpRenamed); | // This could be fixed by not requiring a turret to be 'garrisoned'. | ||||
let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); | |||||
if (cmpTurretHolder) | |||||
cmpTurretHolder.SwapEntities(msg.entity, msg.newentity); | |||||
} | } | ||||
if (!this.initGarrison) | if (!this.initGarrison) | ||||
return; | return; | ||||
// Update the pre-game garrison because of SkirmishReplacement | // Update the pre-game garrison because of SkirmishReplacement | ||||
if (msg.entity == this.entity) | if (msg.entity == this.entity) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | for (let entity of entities) | ||||
if (cmpHealth) | if (cmpHealth) | ||||
cmpHealth.Kill(); | cmpHealth.Kill(); | ||||
this.entities.splice(entityIndex, 1); | this.entities.splice(entityIndex, 1); | ||||
killedEntities.push(entity); | killedEntities.push(entity); | ||||
} | } | ||||
if (killedEntities.length) | if (killedEntities.length) | ||||
{ | { | ||||
let visibleEntitiesIds = {}; | |||||
for (let ent of killedEntities) | |||||
visibleEntitiesIds[ent] = this.IsVisiblyGarrisoned(ent); | |||||
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { | ||||
"added": [], | "added": [], | ||||
"removed": killedEntities, | "removed": killedEntities | ||||
"visible": visibleEntitiesIds | |||||
}); | }); | ||||
} | |||||
this.UpdateGarrisonFlag(); | this.UpdateGarrisonFlag(); | ||||
}; | } | ||||
/** | |||||
* Gives insight about the unit type of garrisoning. | |||||
* @param {number} entity - The entity's id. | |||||
* @return {boolean} - Whether the entity is visible on the garrison holder. | |||||
*/ | |||||
GarrisonHolder.prototype.IsVisiblyGarrisoned = function(entity) | |||||
{ | |||||
return this.visibleGarrisonPoints.some(point => point.entity == entity); | |||||
}; | }; | ||||
/** | /** | ||||
* Whether an entity is ejectable. | * Whether an entity is ejectable. | ||||
* @param {number} entity - The entity-ID to be tested. | * @param {number} entity - The entity-ID to be tested. | ||||
* @return {boolean} - Whether the entity is ejectable. | * @return {boolean} - Whether the entity is ejectable. | ||||
*/ | */ | ||||
GarrisonHolder.prototype.IsEjectable = function(entity) | GarrisonHolder.prototype.IsEjectable = function(entity) | ||||
▲ Show 20 Lines • Show All 57 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator