1
0
forked from 0ad/0ad

Workaround TriggerHelper.js not being able to spawn units correctly because of promotion.

Avoid duplication by moving the function to Transform.js.
Add tests to Transform.js
Add tests to TurretHolder.js
Optimize slightly Trainer.js by removing some useless
Engine.QueryInterface calls.
Refs #6784 (symptoms in TriggerHelper.js are fixed underlying cause not)

`SkirmishReplacer` is called before the Modifier Manager so it does not
suffer the same fate.
Entities using `ChangeEntityTemplate` function are still affected.
Maps calling Engine.AddEntity directly are still affected.

Ideally this should be handled by hotloading components instead of
creating new entities each time. See =>
https://code.wildfiregames.com/D4991
Tested using:
908dd631d9

Differential Revision: https://code.wildfiregames.com/D4984
This was SVN commit r27636.
This commit is contained in:
Stan 2023-05-10 15:13:52 +00:00
parent 1c7e157e28
commit 91509290d6
6 changed files with 163 additions and 39 deletions

View File

@ -95,6 +95,21 @@ TriggerHelper.SetUnitFormation = function(playerID, entities, formation)
});
};
/**
* Creates a new unit taking into account possible 0 experience promotion.
* @param {number} owner Player id of the owner of the new units.
* @param {string} template Name of the template.
* @returns the created entity id.
*/
TriggerHelper.AddUpgradeTemplate = function(owner, template)
{
const upgradedTemplate = GetUpgradedTemplate(owner, template);
if (upgradedTemplate !== template)
warn(`tried to spawn template '${template}' but upgraded template '${upgradedTemplate}' will be spawned instead. You might want to create a template that is not affected by this promotion.`);
return Engine.AddEntity(upgradedTemplate);
};
/**
* Can be used to "force" a building/unit to spawn a group of entities.
*
@ -121,7 +136,8 @@ TriggerHelper.SpawnUnits = function(source, template, count, owner)
for (let i = 0; i < count; ++i)
{
let ent = Engine.AddEntity(template);
const ent = this.AddUpgradeTemplate(owner, template);
let cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpEntPosition)
{
@ -178,7 +194,7 @@ TriggerHelper.SpawnGarrisonedUnits = function(entity, template, count, owner)
for (let i = 0; i < count; ++i)
{
let ent = Engine.AddEntity(template);
const ent = this.AddUpgradeTemplate(owner, template);
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
@ -219,7 +235,7 @@ TriggerHelper.SpawnTurretedUnits = function(entity, template, count, owner)
for (let i = 0; i < count; ++i)
{
let ent = Engine.AddEntity(template);
const ent = this.AddUpgradeTemplate(owner, template);
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)

View File

@ -554,7 +554,7 @@ Trainer.prototype.CalculateEntitiesMap = function()
return entMap;
}
token = this.GetUpgradedTemplate(token);
token = GetUpgradedTemplate(cmpPlayer.GetPlayerID(), token);
entMap.set(rawToken, token);
updateAllQueuedTemplate(rawToken, token);
return entMap;
@ -563,32 +563,6 @@ Trainer.prototype.CalculateEntitiesMap = function()
this.CalculateTrainCostMultiplier();
};
/*
* Returns the upgraded template name if necessary.
*/
Trainer.prototype.GetUpgradedTemplate = function(templateName)
{
const cmpPlayer = QueryOwnerInterface(this.entity);
if (!cmpPlayer)
return templateName;
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let template = cmpTemplateManager.GetTemplate(templateName);
while (template && template.Promotion !== undefined)
{
const requiredXp = ApplyValueModificationsToTemplate(
"Promotion/RequiredXp",
+template.Promotion.RequiredXp,
cmpPlayer.GetPlayerID(),
template);
if (requiredXp > 0)
break;
templateName = template.Promotion.Entity;
template = cmpTemplateManager.GetTemplate(templateName);
}
return templateName;
};
Trainer.prototype.CalculateTrainCostMultiplier = function()
{
for (const res of Resources.GetCodes().concat(["time"]))

View File

@ -35,22 +35,21 @@ class TurretHolder
*/
CreateSubunit(turretPointName)
{
let turretPoint = this.TurretPointByName(turretPointName);
const turretPoint = this.TurretPointByName(turretPointName);
if (!turretPoint || turretPoint.entity ||
this.initTurrets?.has(turretPointName) ||
this.reservedTurrets?.has(turretPointName))
return false;
let ent = Engine.AddEntity(turretPoint.template);
const cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
{
let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
const upgradedTemplate = GetUpgradedTemplate(cmpOwnership.GetOwner(), turretPoint.template);
const ent = Engine.AddEntity(upgradedTemplate);
const cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpEntOwnership?.SetOwner(cmpOwnership.GetOwner());
}
let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
const cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
return cmpTurretable?.OccupyTurret(this.entity, turretPoint.name, turretPoint.ejectable) || Engine.DestroyEntity(ent);
}
@ -389,6 +388,7 @@ class TurretHolder
this.EjectOrKill(this.GetEntities());
return;
}
for (let point of this.turretPoints)
{
// If we were created, create any subunits now.

View File

@ -55,6 +55,16 @@ AddMock(entityID, IID_Identity, {
"GetCiv": () => "iber"
});
let GetUpgradedTemplate = (_, template) => template === "units/iber/cavalry_javelineer_b" ? "units/iber/cavalry_javelineer_a" : template;
Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate);
cmpTrainer.CalculateEntitiesMap();
TS_ASSERT_UNEVAL_EQUALS(
cmpTrainer.GetEntitiesList(),
["units/iber/cavalry_javelineer_a", "units/iber/infantry_swordsman_b", "units/iber/support_female_citizen"]
);
GetUpgradedTemplate = (_, template) => template;
Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate);
cmpTrainer.CalculateEntitiesMap();
TS_ASSERT_UNEVAL_EQUALS(
cmpTrainer.GetEntitiesList(),

View File

@ -1,7 +1,9 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("interfaces/Turretable.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("TurretHolder.js");
Engine.LoadComponentScript("Turretable.js");
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id
@ -122,3 +124,100 @@ TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[
TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(archerID));
TS_ASSERT(!cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[1]));
TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[2]));
// Incremental Turret creation.
cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", {
"TurretPoints": {
"Turret": {
"X": "15.0",
"Y": "5.0",
"Z": "6.0",
"Template": "units/iber/cavalry_javelineer_c"
}
}
});
let spawned = 100;
Engine.AddEntity = function() {
++spawned;
if(spawned > 101)
{
ConstructComponent(spawned, "Turretable", {});
}
if(spawned > 102)
{
AddMock(spawned, IID_Ownership, {
"GetOwner": () => player,
"SetOwner": () => {}
});
}
if(spawned > 103)
{
AddMock(spawned, IID_Position, {
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"SetTurretParent": () => {},
"IsInWorld": () => true
});
}
return spawned;
}
const GetUpgradedTemplate = (_, template) => template === "units/iber/cavalry_javelineer_b" ? "units/iber/cavalry_javelineer_a" : template;
Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate);
cmpTurretHolder.OnOwnershipChanged({
"to": 1,
"from": INVALID_PLAYER
});
TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned));
cmpTurretHolder.OnOwnershipChanged({
"to": 1,
"from": INVALID_PLAYER
});
TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned));
cmpTurretHolder.OnOwnershipChanged({
"to": 1,
"from": INVALID_PLAYER
});
TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned));
cmpTurretHolder.OnOwnershipChanged({
"to": 1,
"from": INVALID_PLAYER
});
TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned));
// Normal turret creation.
Engine.AddEntity = function(t) {
++spawned;
// Check that we're using the upgraded template.
TS_ASSERT(t, "units/iber/cavalry_javelineer_a");
ConstructComponent(spawned, "Turretable", {});
AddMock(spawned, IID_Ownership, {
"GetOwner": () => player,
"SetOwner": () => {}
});
AddMock(spawned, IID_Position, {
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"SetTurretParent": () => {},
"IsInWorld": () => true
});
return spawned;
}
cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", {
"TurretPoints": {
"Turret": {
"X": "15.0",
"Y": "5.0",
"Z": "6.0",
"Template": "units/iber/cavalry_javelineer_b"
}
}
});
cmpTurretHolder.OnOwnershipChanged({
"to": 1,
"from": INVALID_PLAYER
});
TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned));

View File

@ -294,5 +294,30 @@ function TransferGarrisonedUnits(oldEnt, newEnt)
}
}
/**
* @param {number} playerId - the player id to check technologies for.
* @param {string} templateName - the original template name.
* @returns the upgraded template name if it exists else the original template.
*/
function GetUpgradedTemplate(playerId, templateName)
{
const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let template = cmpTemplateManager.GetTemplate(templateName);
while (template && template.Promotion !== undefined)
{
const requiredXp = ApplyValueModificationsToTemplate(
"Promotion/RequiredXp",
+template.Promotion.RequiredXp,
playerId,
template);
if (requiredXp > 0)
break;
templateName = template.Promotion.Entity;
template = cmpTemplateManager.GetTemplate(templateName);
}
return templateName;
};
Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate);
Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);