Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/components/AlertRaiser.js
function AlertRaiser() {} | function AlertRaiser() {} | ||||
AlertRaiser.prototype.Schema = | AlertRaiser.prototype.Schema = | ||||
"<element name='MaximumLevel'><data type='nonNegativeInteger'/></element>" + | "<element name='MaximumLevel'><data type='nonNegativeInteger'/></element>" + | ||||
"<element name='Range'><data type='nonNegativeInteger'/></element>"; | "<element name='Range'><data type='nonNegativeInteger'/></element>"; | ||||
AlertRaiser.prototype.Init = function() | AlertRaiser.prototype.Init = function() | ||||
{ | { | ||||
this.level = 0; | this.level = 0; | ||||
this.garrisonedUnits = []; | |||||
this.prodBuildings = []; | // Range at which units will search for garrison holders | ||||
this.walkingUnits = []; | this.searchRange = 100; | ||||
// Extra allowance for units to garrison in buildings outside the alert range | |||||
this.bufferRange = 50; | |||||
}; | }; | ||||
AlertRaiser.prototype.GetLevel = function() | AlertRaiser.prototype.GetLevel = function() | ||||
{ | { | ||||
return this.level; | return this.level; | ||||
}; | }; | ||||
AlertRaiser.prototype.HasRaisedAlert = function() | AlertRaiser.prototype.HasRaisedAlert = function() | ||||
{ | { | ||||
return this.level > 0; | return this.level > 0; | ||||
}; | }; | ||||
AlertRaiser.prototype.CanIncreaseLevel = function() | AlertRaiser.prototype.CanIncreaseLevel = function() | ||||
{ | { | ||||
return this.template.MaximumLevel > this.level; | return this.template.MaximumLevel > this.level; | ||||
}; | }; | ||||
AlertRaiser.prototype.SoundAlert = function() | AlertRaiser.prototype.SoundAlert = function() | ||||
{ | { | ||||
PlaySound("alert" + this.level, this.entity); | PlaySound("alert" + this.level, this.entity); | ||||
}; | }; | ||||
/** | |||||
* Used when units are spawned and need to follow alert orders. | |||||
* @param {number[]} units - Entity IDs of spawned units. | |||||
*/ | |||||
AlertRaiser.prototype.UpdateUnits = function(units) | |||||
{ | |||||
for (let unit of units) | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | |||||
if (!cmpUnitAI || !cmpUnitAI.ReactsToAlert(this.level)) | |||||
continue; | |||||
cmpUnitAI.ReplaceOrder("Alert", { "raiser": this.entity, "force": true }); | |||||
this.walkingUnits.push(unit); | |||||
} | |||||
}; | |||||
AlertRaiser.prototype.IncreaseAlertLevel = function() | AlertRaiser.prototype.IncreaseAlertLevel = function() | ||||
{ | { | ||||
if (!this.CanIncreaseLevel()) | if (!this.CanIncreaseLevel()) | ||||
return false; | return false; | ||||
++this.level; | ++this.level; | ||||
this.SoundAlert(); | this.SoundAlert(); | ||||
// Find buildings/units owned by this unit's player | // Find buildings/units owned by this unit's player | ||||
let players = []; | |||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
if (cmpOwnership) | if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER) | ||||
players = [cmpOwnership.GetOwner()]; | return false; | ||||
// Select production buildings to put "under alert", including the raiser itself if possible | let players = [cmpOwnership.GetOwner()]; | ||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
let buildings = cmpRangeManager.ExecuteQuery(this.entity, 0, this.template.Range, players, IID_ProductionQueue); | |||||
if (Engine.QueryInterface(this.entity, IID_ProductionQueue)) | |||||
buildings.push(this.entity); | |||||
for (let building of buildings) | |||||
{ | |||||
let cmpProductionQueue = Engine.QueryInterface(building, IID_ProductionQueue); | |||||
cmpProductionQueue.PutUnderAlert(this.entity); | |||||
this.prodBuildings.push(building); | |||||
} | |||||
// Select units to put under alert, according to their reaction to this level | // Select units to put under alert, according to their reaction to this level | ||||
let units = cmpRangeManager.ExecuteQuery(this.entity, 0, this.template.Range, players, IID_UnitAI).filter(ent => { | let units = cmpRangeManager.ExecuteQuery(this.entity, 0, this.template.Range, players, IID_UnitAI).filter(ent => | ||||
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | Engine.QueryInterface(ent, IID_UnitAI).ReactsToAlert(this.level) | ||||
return !cmpUnitAI.IsUnderAlert() && cmpUnitAI.ReactsToAlert(this.level); | ); | ||||
}); | |||||
// Store the number of available garrison spots, so that units don't try to garrison in buildings that will be full | |||||
let reserved = new Map(); | |||||
for (let unit of units) | for (let unit of units) | ||||
{ | { | ||||
let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | let holder = cmpRangeManager.ExecuteQuery(unit, 0, this.searchRange, players, IID_GarrisonHolder).find(ent => { | ||||
cmpUnitAI.ReplaceOrder("Alert", { "raiser": this.entity, "force": true }); | // Ignore moving garrison holders | ||||
this.walkingUnits.push(unit); | if (Engine.QueryInterface(ent, IID_UnitAI)) | ||||
} | return false; | ||||
return true; | // Ensure that the garrison holder is within range of the alert raiser | ||||
}; | if (DistanceBetweenEntities(this.entity, ent) > +this.template.Range + this.bufferRange) | ||||
return false; | |||||
AlertRaiser.prototype.OnUnitGarrisonedAfterAlert = function(msg) | let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); | ||||
{ | if (!reserved.has(ent)) | ||||
this.garrisonedUnits.push({ "holder": msg.holder, "unit": msg.unit }); | reserved.set(ent, cmpGarrisonHolder.GetCapacity() - cmpGarrisonHolder.GetGarrisonedEntitiesCount()); | ||||
return cmpGarrisonHolder.IsAllowedToGarrison(unit) && reserved.get(ent); | |||||
}); | |||||
let index = this.walkingUnits.indexOf(msg.unit); | let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | ||||
if (index != -1) | if (holder) | ||||
this.walkingUnits.splice(index, 1); | { | ||||
reserved.set(holder, reserved.get(holder) - 1); | |||||
cmpUnitAI.ReplaceOrder("Garrison", { "target": holder, "force": true }); | |||||
} | |||||
else | |||||
// If no available spots, move to the alert raiser | |||||
cmpUnitAI.ReplaceOrder("WalkToTarget", { "target": this.entity, "force": true }); | |||||
} | |||||
return true; | |||||
}; | }; | ||||
AlertRaiser.prototype.EndOfAlert = function() | AlertRaiser.prototype.EndOfAlert = function() | ||||
{ | { | ||||
this.level = 0; | |||||
this.SoundAlert(); | this.SoundAlert(); | ||||
// First, handle units not yet garrisoned | let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
for (let unit of this.walkingUnits) | if (!cmpOwnership || cmpOwnership.GetOwner() == INVALID_PLAYER) | ||||
{ | return false; | ||||
let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | |||||
if (!cmpUnitAI) | |||||
continue; | |||||
cmpUnitAI.ResetAlert(); | let players = [cmpOwnership.GetOwner()]; | ||||
if (cmpUnitAI.HasWorkOrders()) | // Units that are not garrisoned should stop and go back to work | ||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||
let units = cmpRangeManager.ExecuteQuery(this.entity, 0, +this.template.Range + this.bufferRange, players, IID_UnitAI).filter(ent => | |||||
Engine.QueryInterface(ent, IID_UnitAI).ReactsToAlert(this.level) | |||||
); | |||||
for (let unit of units) | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | |||||
if (cmpUnitAI.HasWorkOrders() && (cmpUnitAI.HasGarrisonOrder() || cmpUnitAI.IsIdle())) | |||||
cmpUnitAI.BackToWork(); | cmpUnitAI.BackToWork(); | ||||
else | else if (cmpUnitAI.HasGarrisonOrder()) | ||||
// Stop rather than continue to try to garrison | |||||
cmpUnitAI.ReplaceOrder("Stop", undefined); | cmpUnitAI.ReplaceOrder("Stop", undefined); | ||||
} | } | ||||
this.walkingUnits = []; | |||||
// Then, eject garrisoned units | // Units that are garrisoned should ungarrison and go back to work | ||||
for (let slot of this.garrisonedUnits) | let holders = cmpRangeManager.ExecuteQuery(this.entity, 0, +this.template.Range + this.bufferRange, players, IID_GarrisonHolder); | ||||
if (Engine.QueryInterface(this.entity, IID_GarrisonHolder)) | |||||
holders.push(this.entity); | |||||
for (let holder of holders) | |||||
{ | { | ||||
let cmpGarrisonHolder = Engine.QueryInterface(slot.holder, IID_GarrisonHolder); | if (Engine.QueryInterface(holder, IID_UnitAI)) | ||||
let cmpUnitAI = Engine.QueryInterface(slot.unit, IID_UnitAI); | |||||
if (!cmpUnitAI) | |||||
continue; | continue; | ||||
// If the garrison building was destroyed, the unit is already ejected | let cmpGarrisonHolder = Engine.QueryInterface(holder, IID_GarrisonHolder); | ||||
if (!cmpGarrisonHolder || cmpGarrisonHolder.PerformEject([slot.unit], true)) | let units = cmpGarrisonHolder.GetEntities().filter(ent => | ||||
Engine.QueryInterface(ent, IID_UnitAI).ReactsToAlert(this.level) && | |||||
Engine.QueryInterface(ent, IID_Ownership).GetOwner() == players[0] | |||||
); | |||||
for (let unit of units) | |||||
if (cmpGarrisonHolder.PerformEject([unit], false)) | |||||
{ | { | ||||
cmpUnitAI.ResetAlert(); | let cmpUnitAI = Engine.QueryInterface(unit, IID_UnitAI); | ||||
if (cmpUnitAI.HasWorkOrders()) | if (cmpUnitAI.HasWorkOrders()) | ||||
cmpUnitAI.BackToWork(); | cmpUnitAI.BackToWork(); | ||||
else | |||||
// Stop rather than walk to the rally point | |||||
cmpUnitAI.ReplaceOrder("Stop", undefined); | |||||
} | } | ||||
} | } | ||||
this.garrisonedUnits = []; | |||||
// Finally, reset production buildings state | |||||
for (let building of this.prodBuildings) | |||||
{ | |||||
let cmpProductionQueue = Engine.QueryInterface(building, IID_ProductionQueue); | |||||
if (cmpProductionQueue) | |||||
cmpProductionQueue.ResetAlert(); | |||||
} | |||||
this.prodBuildings = []; | |||||
this.level = 0; | |||||
return true; | return true; | ||||
}; | }; | ||||
Engine.RegisterComponentType(IID_AlertRaiser, "AlertRaiser", AlertRaiser); | Engine.RegisterComponentType(IID_AlertRaiser, "AlertRaiser", AlertRaiser); |
Wildfire Games · Phabricator