Build using Builder instead of UnitAI.
Moves the building logic from UnitAI to Builder. Makes it easier for modders to change building behaviour, e.g. letting structures build. Differential revision: D3812 Comment by: @Angen This was SVN commit r25208.
This commit is contained in:
parent
f2d5603422
commit
e3695abe59
@ -18,12 +18,15 @@ Builder.prototype.Schema =
|
||||
"<text/>" +
|
||||
"</element>";
|
||||
|
||||
/*
|
||||
* Build interval and repeat time, in ms.
|
||||
*/
|
||||
Builder.prototype.BUILD_INTERVAL = 1000;
|
||||
|
||||
Builder.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
||||
Builder.prototype.Serialize = null; // we have no dynamic state to save
|
||||
|
||||
Builder.prototype.GetEntitiesList = function()
|
||||
{
|
||||
let string = this.template.Entities._string;
|
||||
@ -80,28 +83,120 @@ Builder.prototype.CanRepair = function(target)
|
||||
};
|
||||
|
||||
/**
|
||||
* Build/repair the target entity. This should only be called after a successful range check.
|
||||
* It should be called at a rate of once per second.
|
||||
* @param {number} target - The target to repair.
|
||||
* @param {number} callerIID - The IID to notify on specific events.
|
||||
* @return {boolean} - Whether we started repairing.
|
||||
*/
|
||||
Builder.prototype.PerformBuilding = function(target)
|
||||
Builder.prototype.StartRepairing = function(target, callerIID)
|
||||
{
|
||||
let rate = this.GetRate();
|
||||
if (this.target)
|
||||
this.StopRepairing();
|
||||
|
||||
let cmpFoundation = Engine.QueryInterface(target, IID_Foundation);
|
||||
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;
|
||||
}
|
||||
|
||||
if (this.target)
|
||||
{
|
||||
let cmpBuilderList = QueryBuilderListInterface(this.target);
|
||||
if (cmpBuilderList)
|
||||
cmpBuilderList.RemoveBuilder(this.entity);
|
||||
|
||||
delete this.target;
|
||||
}
|
||||
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("idle", false, 1.0);
|
||||
|
||||
// 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(data, lateness)
|
||||
{
|
||||
if (!this.CanRepair(this.target))
|
||||
{
|
||||
this.StopRepairing("TargetInvalidated");
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
cmpFoundation.Build(this.entity, rate);
|
||||
cmpFoundation.Build(this.entity, this.GetRate());
|
||||
return;
|
||||
}
|
||||
|
||||
let cmpRepairable = Engine.QueryInterface(target, IID_Repairable);
|
||||
let cmpRepairable = Engine.QueryInterface(this.target, IID_Repairable);
|
||||
if (cmpRepairable)
|
||||
{
|
||||
cmpRepairable.Repair(this.entity, rate);
|
||||
cmpRepairable.Repair(this.entity, this.GetRate());
|
||||
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)
|
||||
{
|
||||
if (msg.component != "Builder" || !msg.valueNames.some(name => name.endsWith('_string')))
|
||||
|
@ -2961,6 +2961,13 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"REPAIRING": {
|
||||
"enter": function() {
|
||||
let cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
|
||||
if (!cmpBuilder)
|
||||
{
|
||||
this.FinishOrder();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this order was forced, the player probably gave it, but now we've reached the target
|
||||
// switch to an unforced order (can be interrupted by attacks)
|
||||
if (this.order.data.force)
|
||||
@ -2968,68 +2975,43 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
this.order.data.force = false;
|
||||
|
||||
// Needed to remove the entity from the builder list when leaving this state.
|
||||
this.repairTarget = this.order.data.target;
|
||||
|
||||
if (!this.CanRepair(this.repairTarget))
|
||||
if (!this.CheckTargetRange(this.order.data.target, IID_Builder))
|
||||
{
|
||||
this.FinishOrder();
|
||||
this.ProcessMessage("OutOfRange");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.CheckTargetRange(this.repairTarget, IID_Builder))
|
||||
{
|
||||
this.SetNextState("APPROACHING");
|
||||
return true;
|
||||
}
|
||||
|
||||
let cmpHealth = Engine.QueryInterface(this.repairTarget, IID_Health);
|
||||
let cmpHealth = Engine.QueryInterface(this.order.data.target, IID_Health);
|
||||
if (cmpHealth && cmpHealth.GetHitpoints() >= cmpHealth.GetMaxHitpoints())
|
||||
{
|
||||
// The building was already finished/fully repaired before we arrived;
|
||||
// let the ConstructionFinished handler handle this.
|
||||
this.ConstructionFinished({ "entity": this.repairTarget, "newentity": this.repairTarget });
|
||||
this.ConstructionFinished({ "entity": this.order.data.target, "newentity": this.order.data.target });
|
||||
return true;
|
||||
}
|
||||
|
||||
let cmpBuilderList = QueryBuilderListInterface(this.repairTarget);
|
||||
if (cmpBuilderList)
|
||||
cmpBuilderList.AddBuilder(this.entity);
|
||||
if (!cmpBuilder.StartRepairing(this.order.data.target, IID_UnitAI))
|
||||
{
|
||||
this.ProcessMessage("TargetInvalidated");
|
||||
return true;
|
||||
}
|
||||
|
||||
this.FaceTowardsTarget(this.repairTarget);
|
||||
|
||||
this.SelectAnimation("build");
|
||||
this.StartTimer(1000, 1000);
|
||||
this.FaceTowardsTarget(this.order.data.target);
|
||||
return false;
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
let cmpBuilderList = QueryBuilderListInterface(this.repairTarget);
|
||||
if (cmpBuilderList)
|
||||
cmpBuilderList.RemoveBuilder(this.entity);
|
||||
delete this.repairTarget;
|
||||
this.StopTimer();
|
||||
this.ResetAnimation();
|
||||
let cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
|
||||
if (cmpBuilder)
|
||||
cmpBuilder.StopRepairing();
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
if (!this.CanRepair(this.repairTarget))
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
}
|
||||
"OutOfRange": function(msg) {
|
||||
this.SetNextState("APPROACHING");
|
||||
},
|
||||
|
||||
this.FaceTowardsTarget(this.repairTarget);
|
||||
|
||||
let cmpBuilder = Engine.QueryInterface(this.entity, IID_Builder);
|
||||
cmpBuilder.PerformBuilding(this.repairTarget);
|
||||
// If the building is completed, the leave() function will be called
|
||||
// by the ConstructionFinished message.
|
||||
// In that case, the repairTarget is deleted, and we can just return.
|
||||
if (!this.repairTarget)
|
||||
return;
|
||||
if (!this.CheckTargetRange(this.repairTarget, IID_Builder))
|
||||
this.SetNextState("APPROACHING");
|
||||
"TargetInvalidated": function(msg) {
|
||||
this.FinishOrder();
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -1,11 +1,21 @@
|
||||
Engine.LoadHelperScript("Player.js");
|
||||
Engine.LoadComponentScript("interfaces/Builder.js");
|
||||
Engine.LoadComponentScript("interfaces/Foundation.js");
|
||||
Engine.LoadComponentScript("interfaces/Repairable.js");
|
||||
Engine.LoadComponentScript("interfaces/Timer.js");
|
||||
Engine.LoadComponentScript("interfaces/UnitAI.js");
|
||||
Engine.LoadComponentScript("Builder.js");
|
||||
Engine.LoadComponentScript("Timer.js");
|
||||
|
||||
const builderId = 6;
|
||||
const target = 7;
|
||||
const playerId = 1;
|
||||
const playerEntityID = 2;
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||
"IsInTargetRange": () => true
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
|
||||
"TemplateExists": () => true
|
||||
});
|
||||
@ -14,7 +24,7 @@ Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oV
|
||||
|
||||
|
||||
let cmpBuilder = ConstructComponent(builderId, "Builder", {
|
||||
"Rate": 1.0,
|
||||
"Rate": "1.0",
|
||||
"Entities": { "_string": "structures/{civ}/barracks structures/{civ}/civil_centre structures/{native}/house" }
|
||||
});
|
||||
|
||||
@ -81,3 +91,30 @@ AddMock(builderId, IID_Obstruction, {
|
||||
});
|
||||
|
||||
TS_ASSERT_UNEVAL_EQUALS(cmpBuilder.GetRange(), { "max": 3, "min": 0 });
|
||||
|
||||
// Test repairing.
|
||||
AddMock(playerEntityID, IID_Player, {
|
||||
"IsAlly": (p) => p == playerId
|
||||
});
|
||||
|
||||
AddMock(target, IID_Ownership, {
|
||||
"GetOwner": () => playerId
|
||||
});
|
||||
|
||||
let increased = false;
|
||||
AddMock(target, IID_Foundation, {
|
||||
"Build": (entity, amount) => {
|
||||
increased = true;
|
||||
TS_ASSERT_EQUALS(amount, 1);
|
||||
},
|
||||
"AddBuilder": () => {}
|
||||
});
|
||||
|
||||
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
|
||||
|
||||
TS_ASSERT(cmpBuilder.StartRepairing(target));
|
||||
cmpTimer.OnUpdate({ "turnLength": 1 });
|
||||
TS_ASSERT(increased);
|
||||
increased = false;
|
||||
cmpTimer.OnUpdate({ "turnLength": 2 });
|
||||
TS_ASSERT(increased);
|
||||
|
@ -68,8 +68,14 @@ TestTargetEntityRenaming(
|
||||
);
|
||||
|
||||
TestTargetEntityRenaming(
|
||||
"INDIVIDUAL.REPAIR.REPAIRING", "INDIVIDUAL.IDLE",
|
||||
"INDIVIDUAL.REPAIR.REPAIRING", "INDIVIDUAL.REPAIR.REPAIRING",
|
||||
(unitAI, player_ent, target_ent) => {
|
||||
|
||||
AddMock(player_ent, IID_Builder, {
|
||||
"StartRepairing": () => true,
|
||||
"StopRepairing": () => {}
|
||||
});
|
||||
|
||||
QueryBuilderListInterface = () => {};
|
||||
unitAI.CheckTargetRange = () => true;
|
||||
unitAI.CanRepair = (target) => target == target_ent;
|
||||
|
Loading…
Reference in New Issue
Block a user