Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/TurretHolder.js
/** | /** | ||||
* This class holds the functions regarding entities being visible on | * This class holds the functions regarding entities being visible on | ||||
* another entity, but tied to their parents location. | * another entity, but tied to their parents location. | ||||
* Currently renaming and changing ownership are still managed by GarrisonHolder.js, | |||||
* but in the future these components should be independent. | |||||
*/ | */ | ||||
class TurretHolder | class TurretHolder | ||||
{ | { | ||||
Init() | Init() | ||||
{ | { | ||||
this.turretPoints = []; | this.turretPoints = []; | ||||
let points = this.template.TurretPoints; | let points = this.template.TurretPoints; | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | OccupyTurret(entity, requestedTurretPoint) | ||||
} | } | ||||
else | else | ||||
turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurret(entity, turret)); | turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurret(entity, turret)); | ||||
if (!turretPoint) | if (!turretPoint) | ||||
return false; | return false; | ||||
turretPoint.entity = entity; | turretPoint.entity = entity; | ||||
// Angle of turrets: | // Angle of turrets: | ||||
// Renamed entities (turretPoint != undefined) should keep their angle. | // Renamed entities (turretPoint != undefined) should keep their angle. | ||||
// Otherwise if an angle is given in the turretPoint, use it. | // Otherwise if an angle is given in the turretPoint, use it. | ||||
// If no such angle given (usually walls for which outside/inside not well defined), we keep | // 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 | // 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. | // outside, except when garrisoning from outWorld where we take as default PI. | ||||
if (!turretPoint && turretPoint.angle != null) | if (!turretPoint && turretPoint.angle != null) | ||||
cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle); | cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle); | ||||
Show All 35 Lines | class TurretHolder | ||||
/** | /** | ||||
* Remove the entity from a turret. | * Remove the entity from a turret. | ||||
* @param {number} entity - The specific entity to eject. | * @param {number} entity - The specific entity to eject. | ||||
* @param {Object} turret - Optionally the turret to abandon. | * @param {Object} turret - Optionally the turret to abandon. | ||||
* | * | ||||
* @return {boolean} - Whether the entity was occupying a/the turret before. | * @return {boolean} - Whether the entity was occupying a/the turret before. | ||||
*/ | */ | ||||
LeaveTurret(entity, requestedTurretPoint) | LeaveTurret(entity, forced, requestedTurretPoint) | ||||
{ | { | ||||
let turretPoint; | let turretPoint; | ||||
if (requestedTurretPoint) | if (requestedTurretPoint) | ||||
{ | { | ||||
if (requestedTurretPoint.entity == entity) | if (requestedTurretPoint.entity == entity) | ||||
turretPoint = requestedTurretPoint; | turretPoint = requestedTurretPoint; | ||||
} | } | ||||
else | else | ||||
turretPoint = this.turretPoints.find(turret => turret.entity == entity); | turretPoint = this.turretPoints.find(turret => turret.entity == entity); | ||||
if (!turretPoint) | if (!turretPoint) | ||||
return false; | return false; | ||||
// Find spawning location (copied from GarrisonHolder). | |||||
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 TurretHolder 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) | |||||
wraitii: Does that mean "failure" in the above function? Weird API. | |||||
Done Inline ActionsFreagarach: #5811 | |||||
{ | |||||
if (!forced) | |||||
return false; | |||||
// If ejection is forced, we need to continue, so use center of the TurretHolder. | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
pos = cmpPosition.GetPosition(); | |||||
} | |||||
turretPoint.entity = null; | |||||
Done Inline ActionsWould probably be better in the Position helper if the code is identical enough wraitii: Would probably be better in the Position helper if the code is identical enough | |||||
Done Inline ActionsYeah, I reckoned that :) Freagarach: Yeah, I reckoned that :) | |||||
let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position); | let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position); | ||||
cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D()); | cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D()); | ||||
let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion); | let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion); | ||||
if (cmpUnitMotionEntity) | if (cmpUnitMotionEntity) | ||||
cmpUnitMotionEntity.SetFacePointAfterMove(true); | cmpUnitMotionEntity.SetFacePointAfterMove(true); | ||||
let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI); | let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI); | ||||
if (cmpUnitAIEntity) | if (cmpUnitAIEntity) | ||||
cmpUnitAIEntity.ResetTurretStance(); | cmpUnitAIEntity.ResetTurretStance(); | ||||
turretPoint.entity = null; | let cmpEntPosition = Engine.QueryInterface(entity, IID_Position); | ||||
if (cmpEntPosition) | |||||
{ | |||||
cmpEntPosition.JumpTo(pos.x, pos.z); | |||||
cmpEntPosition.SetHeightOffset(0); | |||||
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | |||||
if (cmpPosition) | |||||
cmpEntPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos)); | |||||
} | |||||
// 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); | ||||
Engine.PostMessage(this.entity, MT_TurretsChanged, { | Engine.PostMessage(this.entity, MT_TurretsChanged, { | ||||
"added": [], | "added": [], | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | GetEntities() | ||||
let entities = []; | let entities = []; | ||||
for (let turretPoint of this.turretPoints) | for (let turretPoint of this.turretPoints) | ||||
if (turretPoint.entity) | if (turretPoint.entity) | ||||
entities.push(turretPoint.entity); | entities.push(turretPoint.entity); | ||||
return entities; | return entities; | ||||
} | } | ||||
/** | /** | ||||
* @return {boolean} - Whether all the turret points are occupied. | |||||
*/ | |||||
IsFull() | |||||
{ | |||||
return !!this.turretPoints.find(turretPoint => turretPoint.entity == null); | |||||
} | |||||
/** | |||||
* @return {Object} - Max and min ranges at which entities can occupy any turret. | |||||
*/ | |||||
GetLoadingRange() | |||||
{ | |||||
return { "max": +(this.template.LoadingRange || 2), "min": 0 }; | |||||
} | |||||
/** | |||||
* @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); | |||||
} | |||||
/** | |||||
* Sets an init turret, present from game start. (E.g. set in Atlas.) | * Sets an init turret, present from game start. (E.g. set in Atlas.) | ||||
* @param {String} turretName - The name of the turret point to be used. | * @param {String} turretName - The name of the turret point to be used. | ||||
* @param {number} entity - The entity-ID to be placed. | * @param {number} entity - The entity-ID to be placed. | ||||
*/ | */ | ||||
SetInitEntity(turretName, entity) | SetInitEntity(turretName, entity) | ||||
{ | { | ||||
if (!this.initTurrets) | if (!this.initTurrets) | ||||
this.initTurrets = new Map(); | this.initTurrets = new Map(); | ||||
if (this.initTurrets.has(turretName)) | if (this.initTurrets.has(turretName)) | ||||
warn("The turret position " + turretName + " of entity " + | warn("The turret position " + turretName + " of entity " + | ||||
this.entity + " is already set! Overwriting."); | this.entity + " is already set! Overwriting."); | ||||
this.initTurrets.set(turretName, entity); | this.initTurrets.set(turretName, entity); | ||||
} | } | ||||
/** | /** | ||||
* We process EntityRenamed here because we need to be sure that we receive | |||||
* it after it is processed by GarrisonHolder.js. | |||||
* ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder. | |||||
* That means an entity with TurretHolder should not need a GarrisonHolder | |||||
* for e.g. the garrisoning logic. | |||||
* | |||||
* @param {number} from - The entity to substitute. | * @param {number} from - The entity to substitute. | ||||
* @param {number} to - The entity to subtitute with. | * @param {number} to - The entity to subtitute with. | ||||
*/ | */ | ||||
SwapEntities(from, to) | SwapEntities(from, to) | ||||
{ | { | ||||
let turretPoint = this.GetOccupiedTurret(from); | let turretPoint = this.GetOccupiedTurret(from); | ||||
if (turretPoint) | if (turretPoint) | ||||
this.LeaveTurret(from, turretPoint); | { | ||||
this.LeaveTurret(from, true, turretPoint); | |||||
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); | |||||
if (cmpGarrisonHolder && cmpGarrisonHolder.IsGarrisoned(to)) | |||||
this.OccupyTurret(to, turretPoint); | this.OccupyTurret(to, turretPoint); | ||||
} | } | ||||
} | |||||
OnGarrisonedUnitsChanged(msg) | /** | ||||
* Update list of turreted entities if one gets renamed (e.g. by promotion). | |||||
*/ | |||||
OnGlobalEntityRenamed(msg) | |||||
{ | { | ||||
// Ignore renaming for that is handled seperately | this.SwapEntities(msg.entity, msg.newentity); | ||||
// (i.e. called directly from GarrisonHolder.js). | |||||
if (msg.renamed) | if (!this.initTurrets) | ||||
return; | return; | ||||
for (let entity of msg.removed) | // Update the pre-game turrets because of SkirmishReplacement. | ||||
this.LeaveTurret(entity); | if (msg.entity == this.entity) | ||||
for (let entity of msg.added) | { | ||||
this.OccupyTurret(entity); | let cmpTurretHolder = Engine.QueryInterface(msg.newentity, IID_TurretHolder); | ||||
if (cmpTurretHolder) | |||||
cmpTurretHolder.initTurrets = this.initTurrets; | |||||
} | |||||
else | |||||
{ | |||||
if (this.initTurrets.has(msg.entity)) | |||||
{ | |||||
this.initTurrets.set(msg.newentity, this.initTurrets.get(msg.entity)); | |||||
this.initTurrets.delete(msg.entity); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* Update list of turreted entities if ownership changes. | |||||
*/ | |||||
OnGlobalOwnershipChanged(msg) | |||||
{ | |||||
if (msg.entity == this.entity) | |||||
{ | |||||
for (let entity of this.GetEntities()) | |||||
if (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, entity)) | |||||
this.LeaveTurret(entity, true); | |||||
return; | |||||
} | |||||
if (this.OccupiesTurret(msg.entity) && | |||||
(msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity))) | |||||
this.LeaveTurret(msg.entity); | |||||
} | } | ||||
/** | /** | ||||
* Initialise the turreted units. | * 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. | |||||
*/ | */ | ||||
OnGlobalInitGame(msg) | OnGlobalInitGame(msg) | ||||
{ | { | ||||
if (!this.initTurrets) | if (!this.initTurrets) | ||||
return; | return; | ||||
for (let [turretPointName, entity] of this.initTurrets) | for (let [turretPointName, entity] of this.initTurrets) | ||||
{ | |||||
if (this.OccupiesTurret(entity)) | |||||
this.LeaveTurret(entity); | |||||
if (!this.OccupyNamedTurret(entity, turretPointName)) | if (!this.OccupyNamedTurret(entity, turretPointName)) | ||||
warn("Entity " + entity + " could not occupy the turret point " + | warn("Entity " + entity + " could not occupy the turret point " + | ||||
turretPointName + " of turret holder " + this.entity + "."); | turretPointName + " of turret holder " + this.entity + "."); | ||||
} | |||||
delete this.initTurrets; | delete this.initTurrets; | ||||
} | } | ||||
} | } | ||||
TurretHolder.prototype.Schema = | TurretHolder.prototype.Schema = | ||||
"<element name='TurretPoints' a:help='Points that will be used to visibly garrison a unit.'>" + | "<element name='TurretPoints' a:help='Points that will be used to visibly garrison a unit.'>" + | ||||
"<oneOrMore>" + | "<oneOrMore>" + | ||||
Show All 20 Lines | "<oneOrMore>" + | ||||
"<optional>" + | "<optional>" + | ||||
"<element name='Angle' a:help='Angle in degrees relative to the turretHolder direction.'>" + | "<element name='Angle' a:help='Angle in degrees relative to the turretHolder direction.'>" + | ||||
"<data type='decimal'/>" + | "<data type='decimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | "</optional>" + | ||||
"</interleave>" + | "</interleave>" + | ||||
"</element>" + | "</element>" + | ||||
"</oneOrMore>" + | "</oneOrMore>" + | ||||
"</element>"; | "</element>" + | ||||
"<optional>" + | |||||
"<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to occupy a turret point. Should be about 2.0 for land entities and preferably greater for ships.'>" + | |||||
"<ref name='nonNegativeDecimal'/>" + | |||||
"</element>" + | |||||
"</optional>" | |||||
"<optional>" + | |||||
"<element name='Pickup' a:help='This entity will try to move to pick up units to be turreted.'>" + | |||||
"<data type='boolean'/>" + | |||||
"</element>" + | |||||
"</optional>"; | |||||
Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); | Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); |
Wildfire Games · Phabricator
Does that mean "failure" in the above function? Weird API.