/**
* @class
*/
// Helper functions to change an entity's template and check if the transformation is possible
// returns the ID of the new entity or INVALID_ENTITY.
function ChangeEntityTemplate(oldEnt, newTemplate)
{
// Done un/packing, copy our parameters to the final entity
var newEnt = Engine.AddEntity(newTemplate);
if (newEnt == INVALID_ENTITY)
{
error("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'");
return INVALID_ENTITY;
}
Engine.ProfileStart("Transform");
var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
if (cmpPosition && cmpNewPosition)
{
if (cmpPosition.IsInWorld())
{
let pos = cmpPosition.GetPosition2D();
cmpNewPosition.JumpTo(pos.x, pos.y);
}
let rot = cmpPosition.GetRotation();
cmpNewPosition.SetYRotation(rot.y);
cmpNewPosition.SetXZRotation(rot.x, rot.z);
cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
}
// Prevent spawning subunits on occupied positions.
let cmpTurretHolder = Engine.QueryInterface(oldEnt, IID_TurretHolder);
let cmpNewTurretHolder = Engine.QueryInterface(newEnt, IID_TurretHolder);
if (cmpTurretHolder && cmpNewTurretHolder)
for (let entity of cmpTurretHolder.GetEntities())
cmpNewTurretHolder.SetReservedTurretPoint(cmpTurretHolder.GetOccupiedTurretPointName(entity));
let owner;
let cmpTerritoryDecay = Engine.QueryInterface(newEnt, IID_TerritoryDecay);
if (cmpTerritoryDecay && cmpTerritoryDecay.HasTerritoryOwnership() && cmpNewPosition)
{
let pos = cmpNewPosition.GetPosition2D();
let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
owner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
}
else
{
let cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
}
let cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
if (cmpNewOwnership)
cmpNewOwnership.SetOwner(owner);
CopyControlGroups(oldEnt, newEnt);
// Rescale capture points
var cmpCapturable = Engine.QueryInterface(oldEnt, IID_Capturable);
var cmpNewCapturable = Engine.QueryInterface(newEnt, IID_Capturable);
if (cmpCapturable && cmpNewCapturable)
{
let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
let newCapturePoints = cmpCapturable.GetCapturePoints().map(v => v / scale);
cmpNewCapturable.SetCapturePoints(newCapturePoints);
}
// Maintain current health level
var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health);
var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health);
if (cmpHealth && cmpNewHealth)
{
var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
cmpNewHealth.SetHitpoints(cmpNewHealth.GetMaxHitpoints() * healthLevel);
}
let cmpPromotion = Engine.QueryInterface(oldEnt, IID_Promotion);
let cmpNewPromotion = Engine.QueryInterface(newEnt, IID_Promotion);
if (cmpPromotion && cmpNewPromotion)
{
cmpPromotion.SetPromotedEntity(newEnt);
cmpNewPromotion.IncreaseXp(cmpPromotion.GetCurrentXp());
}
let cmpResGatherer = Engine.QueryInterface(oldEnt, IID_ResourceGatherer);
let cmpNewResGatherer = Engine.QueryInterface(newEnt, IID_ResourceGatherer);
if (cmpResGatherer && cmpNewResGatherer)
{
let carriedResources = cmpResGatherer.GetCarryingStatus();
cmpNewResGatherer.GiveResources(carriedResources);
cmpNewResGatherer.SetLastCarriedType(cmpResGatherer.GetLastCarriedType());
}
// Maintain the list of guards
let cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard);
let cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard);
if (cmpGuard && cmpNewGuard)
{
let entities = cmpGuard.GetEntities();
if (entities.length)
{
cmpNewGuard.SetEntities(entities);
for (let ent of entities)
{
let cmpEntUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpEntUnitAI)
cmpEntUnitAI.SetGuardOf(newEnt);
}
}
}
let cmpStatusEffectsReceiver = Engine.QueryInterface(oldEnt, IID_StatusEffectsReceiver);
let cmpNewStatusEffectsReceiver = Engine.QueryInterface(newEnt, IID_StatusEffectsReceiver);
if (cmpStatusEffectsReceiver && cmpNewStatusEffectsReceiver)
{
let activeStatus = cmpStatusEffectsReceiver.GetActiveStatuses();
for (let status in activeStatus)
{
let newStatus = activeStatus[status];
if (newStatus.Duration)
newStatus.Duration -= newStatus._timeElapsed;
cmpNewStatusEffectsReceiver.ApplyStatus({ [status]: newStatus }, newStatus.source.entity, newStatus.source.owner);
}
}
TransferGarrisonedUnits(oldEnt, newEnt);
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
// UnitAI generally needs other components to be properly initialised.
let cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
let cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
if (cmpUnitAI && cmpNewUnitAI)
{
let pos = cmpUnitAI.GetHeldPosition();
if (pos)
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
let guarded = cmpUnitAI.IsGuardOf();
if (guarded)
{
let cmpGuarded = Engine.QueryInterface(guarded, IID_Guard);
if (cmpGuarded)
{
cmpGuarded.RenameGuard(oldEnt, newEnt);
cmpNewUnitAI.SetGuardOf(guarded);
}
}
}
if (cmpPosition && cmpPosition.IsInWorld())
cmpPosition.MoveOutOfWorld();
Engine.ProfileStop();
Engine.DestroyEntity(oldEnt);
return newEnt;
}
/**
* Copy over the obstruction control group IDs.
* This is needed to ensure that when a group of structures with the same
* control groups is replaced by a new entity, they remains in the same control group(s).
* This is the mechanism that is used to e.g. enable wall pieces to be built closely
* together, ignoring their mutual obstruction shapes (since they would
* otherwise be prevented from being built so closely together).
*/
function CopyControlGroups(oldEnt, newEnt)
{
let cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction);
let cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction);
if (cmpObstruction && cmpNewObstruction)
{
cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
}
}
function ObstructionsBlockingTemplateChange(ent, templateArg)
{
var previewEntity = Engine.AddEntity("preview|"+templateArg);
if (previewEntity == INVALID_ENTITY)
return true;
CopyControlGroups(ent, previewEntity);
var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
// Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better
// Plus there are no real entities without owners currently.
if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership)
return DeleteEntityAndReturn(previewEntity, cmpPosition, null, null, cmpNewPosition, false);
var pos = cmpPosition.GetPosition2D();
var angle = cmpPosition.GetRotation();
// move us away to prevent our own obstruction from blocking the upgrade.
cmpPosition.MoveOutOfWorld();
cmpNewPosition.JumpTo(pos.x, pos.y);
cmpNewPosition.SetYRotation(angle.y);
var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
var checkPlacement = cmpBuildRestrictions.CheckPlacement();
if (checkPlacement && !checkPlacement.success)
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName(ent));
var newTemplate = cmpTemplateManager.GetTemplate(templateArg);
// Check if units are blocking our template change
if (template.Obstruction && newTemplate.Obstruction)
{
// This only needs to be done if the new template is strictly bigger than the old one
// "Obstructions" are annoying to test so just check.
if (newTemplate.Obstruction.Obstructions ||
newTemplate.Obstruction.Static && template.Obstruction.Static &&
(newTemplate.Obstruction.Static["@width"] > template.Obstruction.Static["@width"] ||
newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Static["@depth"]) ||
newTemplate.Obstruction.Static && template.Obstruction.Unit &&
(newTemplate.Obstruction.Static["@width"] > template.Obstruction.Unit["@radius"] ||
newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Unit["@radius"]) ||
newTemplate.Obstruction.Unit && template.Obstruction.Unit &&
newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Unit["@radius"] ||
newTemplate.Obstruction.Unit && template.Obstruction.Static &&
(newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@width"] ||
newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@depth"]))
{
var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
{
// Remove all obstructions at the new entity, especially animal corpses
for (let ent of cmpNewObstruction.GetEntitiesDeletedUponConstruction())
Engine.DestroyEntity(ent);
let collisions = cmpNewObstruction.GetEntitiesBlockingConstruction();
if (collisions.length)
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
}
}
}
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
}
function DeleteEntityAndReturn(ent, cmpPosition, position, angle, cmpNewPosition, ret)
{
// prevent preview from interfering in the world
cmpNewPosition.MoveOutOfWorld();
if (position !== null)
{
cmpPosition.JumpTo(position.x, position.y);
cmpPosition.SetYRotation(angle.y);
}
Engine.DestroyEntity(ent);
return ret;
}
function TransferGarrisonedUnits(oldEnt, newEnt)
{
// Transfer garrisoned units if possible, or unload them
let cmpOldGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
if (!cmpOldGarrison || !cmpOldGarrison.GetEntities().length)
return;
let cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
let entities = cmpOldGarrison.GetEntities().slice();
for (let ent of entities)
{
cmpOldGarrison.Unload(ent);
if (!cmpNewGarrison)
continue;
let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
if (!cmpGarrisonable)
continue;
cmpGarrisonable.Garrison(newEnt);
}
}
Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);