1
0
forked from mirrors/0ad

Fix renamed entities on TurretPoint

Some units would retain an undue HeightOffset after renaming
while being on a TurretPoint.

Fixes #8651
Fixes #2004
This commit is contained in:
Atrik
2026-01-08 13:08:35 +01:00
committed by Vantha
parent 87ed9c8092
commit fb1c0d2a82
6 changed files with 93 additions and 3 deletions
@@ -74,12 +74,14 @@ class TurretHolder
/**
* @param {number} entity - The entity to check for.
* @param {Object} turretPoint - The turret point to use.
* @param {boolean} [forReplacement=false] - Whether this check is for replacement
* (if true, occupied turret points are allowed).
*
* @return {boolean} - Whether the entity is allowed to occupy the specified turret point.
*/
AllowedToOccupyTurretPoint(entity, turretPoint)
AllowedToOccupyTurretPoint(entity, turretPoint, forReplacement = false)
{
if (!turretPoint || turretPoint.entity)
if (!turretPoint || turretPoint.entity && !forReplacement)
return false;
if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
@@ -18,6 +18,14 @@ Turretable.prototype.GetRange = function(type, target)
return cmpTurretHolder ? cmpTurretHolder.LoadingRange() : { "min": 0, "max": 1 };
};
/**
* @return {number} - The turret point name of the entity this entity is turreted on.
*/
Turretable.prototype.GetTurretPointName = function()
{
return this.turretPointName || "";
};
/**
* @return {number} - The entity ID of the entity this entity is turreted on.
*/
@@ -73,6 +81,7 @@ Turretable.prototype.OccupyTurret = function(target, turretPointName = "", eject
this.holder = target;
this.ejectable = ejectable;
this.turretPointName = turretPointName;
const cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (cmpUnitAI)
@@ -19,6 +19,8 @@ Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("interfaces/Turretable.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("AutoBuildable.js");
Engine.LoadComponentScript("Foundation.js");
Engine.LoadComponentScript("Timer.js");
@@ -101,6 +103,12 @@ function testFoundation(...mocks)
"MoveOutOfWorld": () => {}
});
AddMock(foundationEnt, IID_Turretable, {
"IsTurreted": () => false,
"HolderID": () => 0,
"LeaveTurret": () => true
});
AddMock(previewEnt, IID_Ownership, {
"SetOwner": owner => { TS_ASSERT_EQUALS(owner, player); },
});
@@ -140,6 +148,13 @@ function testFoundation(...mocks)
"SetHeightOffset": () => {}
});
AddMock(newEnt, IID_Turretable, {
"IsTurreted": () => false,
"HolderID": () => 0,
"CanOccupy": (target) => true,
"LeaveTurret": () => true
});
for (const mock of mocks)
AddMock(...mock);
@@ -263,6 +278,12 @@ AddMock(foundationEnt2, IID_Health, {
},
});
AddMock(foundationEnt2, IID_Turretable, {
"IsTurreted": () => false,
"HolderID": () => 0,
"LeaveTurret": () => true
});
const cmpBuildableAuto = ConstructComponent(foundationEnt2, "AutoBuildable", {
"Rate": "1.0"
});
@@ -17,6 +17,8 @@ Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("interfaces/Turretable.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("Pack.js");
Engine.RegisterGlobal("MT_EntityRenamed", "entityRenamed");
@@ -45,10 +47,23 @@ AddMock(SYSTEM_ENTITY, IID_Timer, {
"SetInterval": (entity, iid, funcname, time, repeattime, data) => { timerActivated = true; return 7; }
});
AddMock(ent, IID_Turretable, {
"IsTurreted": () => false,
"HolderID": () => 0,
"LeaveTurret": () => true
});
AddMock(newEnt, IID_Turretable, {
"IsTurreted": () => false,
"HolderID": () => 0,
"CanOccupy": (target) => true,
"LeaveTurret": () => true
});
Engine.AddEntity = function(template)
{
TS_ASSERT_EQUALS(template, "finalTemplate");
return true;
return newEnt;
};
// Test Packing
@@ -105,6 +105,28 @@ TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHold
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1]), false);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2]), false);
// Test forReplacement parameter - empty turret points should allow both
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], false), true);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], true), true);
// Now occupy some turret points to test replacement logic
TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(archerID, cmpTurretHolder.turretPoints[0]));
TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[2]));
// Test that occupied turrets block normal occupation but allow replacement
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], false), false);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], true), true);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2], false), false);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2], true), false); // Still false due to class restriction
// Test that class restrictions still apply even for replacement
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1], false), false);
TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1], true), false); // Still false due to class restriction
// Clean up for subsequent tests
TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(archerID));
TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(cavID));
// Test that one cannot leave a turret that is not occupied.
TS_ASSERT(!cmpTurretHolder.LeaveTurretPoint(archerID));
@@ -24,6 +24,27 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
if (cmpVisual && cmpNewVisual)
cmpNewVisual.SetActorSeed(cmpVisual.GetActorSeed());
const cmpOldTurretable = Engine.QueryInterface(oldEnt, IID_Turretable);
// If the old entity is turreted, we need to handle it before copying position
if (cmpOldTurretable && cmpOldTurretable.IsTurreted())
{
const cmpNewTurretable = Engine.QueryInterface(newEnt, IID_Turretable);
// Check if new entity doesn't have Turretable component
if (!cmpNewTurretable)
cmpOldTurretable.LeaveTurret(true);
else
{
// Check if it's allowed to occupy the turret point
const cmpTurretHolderOfOldEnt = Engine.QueryInterface(cmpOldTurretable.HolderID(), IID_TurretHolder);
if (cmpTurretHolderOfNewEnt &&
!cmpTurretHolderOfOldEnt.AllowedToOccupyTurretPoint(newEnt, cmpOldTurretable.GetTurretPointName(), true))
cmpOldTurretable.LeaveTurret(true);
}
}
var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
if (cmpPosition && cmpNewPosition)