Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/components/Builder.js
Show All 12 Lines | Builder.prototype.Schema = | ||||
"</element>" + | "</element>" + | ||||
"<element name='Entities' a:help='Space-separated list of entity template names that this unit can build. The special string \"{civ}\" will be automatically replaced by the civ code of the unit's owner, while the string \"{native}\" will be automatically replaced by the unit's civ code. This element can also be empty, in which case no new foundations may be placed by the unit, but they can still repair existing buildings.'>" + | "<element name='Entities' a:help='Space-separated list of entity template names that this unit can build. The special string \"{civ}\" will be automatically replaced by the civ code of the unit's owner, while the string \"{native}\" will be automatically replaced by the unit's civ code. This element can also be empty, in which case no new foundations may be placed by the unit, but they can still repair existing buildings.'>" + | ||||
"<attribute name='datatype'>" + | "<attribute name='datatype'>" + | ||||
"<value>tokens</value>" + | "<value>tokens</value>" + | ||||
"</attribute>" + | "</attribute>" + | ||||
"<text/>" + | "<text/>" + | ||||
"</element>"; | "</element>"; | ||||
/* | |||||
* Build interval and repeat time, in ms. | |||||
*/ | |||||
Builder.prototype.BUILD_INTERVAL = 1000; | |||||
Builder.prototype.Init = function() | Builder.prototype.Init = function() | ||||
{ | { | ||||
}; | }; | ||||
Builder.prototype.Serialize = null; // we have no dynamic state to save | |||||
Builder.prototype.GetEntitiesList = function() | Builder.prototype.GetEntitiesList = function() | ||||
{ | { | ||||
let string = this.template.Entities._string; | let string = this.template.Entities._string; | ||||
if (!string) | if (!string) | ||||
return []; | return []; | ||||
let cmpPlayer = QueryOwnerInterface(this.entity); | let cmpPlayer = QueryOwnerInterface(this.entity); | ||||
if (!cmpPlayer) | if (!cmpPlayer) | ||||
Show All 40 Lines | Builder.prototype.CanRepair = function(target) | ||||
if (!cmpFoundation && !cmpRepairable) | if (!cmpFoundation && !cmpRepairable) | ||||
return false; | return false; | ||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); | ||||
return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target); | return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target); | ||||
}; | }; | ||||
/** | /** | ||||
* Build/repair the target entity. This should only be called after a successful range check. | * @param {number} target - The target to repair. | ||||
* It should be called at a rate of once per second. | * @param {number} callerIID - The IID to notify on specific events. | ||||
* @return {boolean} - Whether we started repairing. | |||||
*/ | |||||
Builder.prototype.StartRepairing = function(target, callerIID) | |||||
{ | |||||
if (this.target) | |||||
this.StopRepairing(); | |||||
if (!this.CanRepair(target)) | |||||
return false; | |||||
let cmpBuilderList = QueryBuilderListInterface(target); | |||||
if (cmpBuilderList) | |||||
cmpBuilderList.AddBuilder(this.entity); | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (cmpVisual) | |||||
cmpVisual.SelectAnimation("build", false, 1.0); | |||||
this.target = target; | |||||
this.callerIID = callerIID; | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
this.timer = cmpTimer.SetInterval(this.entity, IID_Builder, "PerformBuilding", this.BUILD_INTERVAL, this.BUILD_INTERVAL, null); | |||||
return true; | |||||
}; | |||||
/** | |||||
* @param {string} reason - The reason why we stopped repairing. | |||||
*/ | |||||
Builder.prototype.StopRepairing = function(reason) | |||||
{ | |||||
if (this.timer) | |||||
{ | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
cmpTimer.CancelTimer(this.timer); | |||||
delete this.timer; | |||||
} | |||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); | |||||
if (cmpVisual) | |||||
cmpVisual.SelectAnimation("idle", false, 1.0); | |||||
if (this.target) | |||||
{ | |||||
let cmpBuilderList = QueryBuilderListInterface(this.target); | |||||
if (cmpBuilderList) | |||||
cmpBuilderList.RemoveBuilder(this.entity); | |||||
delete this.target; | |||||
} | |||||
// The callerIID component may start repairing again, | |||||
// replacing the callerIID, hence save that. | |||||
let callerIID = this.callerIID; | |||||
delete this.callerIID; | |||||
if (reason && callerIID) | |||||
{ | |||||
let component = Engine.QueryInterface(this.entity, callerIID); | |||||
if (component) | |||||
component.ProcessMessage(reason, null); | |||||
} | |||||
}; | |||||
/** | |||||
* Repair our target entity. | |||||
* @params - data and lateness are unused. | |||||
*/ | */ | ||||
Builder.prototype.PerformBuilding = function(target) | Builder.prototype.PerformBuilding = function(data, lateness) | ||||
{ | |||||
if (!this.CanRepair(this.target)) | |||||
{ | { | ||||
let rate = this.GetRate(); | this.StopRepairing("TargetInvalidated"); | ||||
return; | |||||
} | |||||
let cmpFoundation = Engine.QueryInterface(target, IID_Foundation); | if (!this.IsTargetInRange(this.target)) | ||||
{ | |||||
this.StopRepairing("OutOfRange"); | |||||
return; | |||||
} | |||||
// ToDo: Enable entities to keep facing a target. | |||||
Engine.QueryInterface(this.entity, IID_UnitAI)?.FaceTowardsTarget(this.target); | |||||
let cmpFoundation = Engine.QueryInterface(this.target, IID_Foundation); | |||||
if (cmpFoundation) | if (cmpFoundation) | ||||
{ | { | ||||
cmpFoundation.Build(this.entity, rate); | cmpFoundation.Build(this.entity, this.GetRate()); | ||||
return; | return; | ||||
} | } | ||||
let cmpRepairable = Engine.QueryInterface(target, IID_Repairable); | let cmpRepairable = Engine.QueryInterface(this.target, IID_Repairable); | ||||
if (cmpRepairable) | if (cmpRepairable) | ||||
{ | { | ||||
cmpRepairable.Repair(this.entity, rate); | cmpRepairable.Repair(this.entity, this.GetRate()); | ||||
return; | return; | ||||
} | } | ||||
}; | }; | ||||
/** | |||||
* @param {number} - The entity ID of the target to check. | |||||
* @return {boolean} - Whether this entity is in range of its target. | |||||
*/ | |||||
Builder.prototype.IsTargetInRange = function(target) | |||||
{ | |||||
let range = this.GetRange(); | |||||
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); | |||||
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); | |||||
}; | |||||
Builder.prototype.OnValueModification = function(msg) | Builder.prototype.OnValueModification = function(msg) | ||||
{ | { | ||||
if (msg.component != "Builder" || !msg.valueNames.some(name => name.endsWith('_string'))) | if (msg.component != "Builder" || !msg.valueNames.some(name => name.endsWith('_string'))) | ||||
return; | return; | ||||
// Token changes may require selection updates. | // Token changes may require selection updates. | ||||
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); | let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); | ||||
if (cmpPlayer) | if (cmpPlayer) | ||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID()); | Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID()); | ||||
}; | }; | ||||
Engine.RegisterComponentType(IID_Builder, "Builder", Builder); | Engine.RegisterComponentType(IID_Builder, "Builder", Builder); |
Wildfire Games · Phabricator