0ad/binaries/data/mods/public/simulation/helpers/Transform.js
wraitii 4fd3f071a5 Fix SetGarrisoned() call when transforming an entity
Before cc1ea7cca0, the promotion code would carry the UnitAI
IsGarrisoned flag, which the Transform helper incorrectly didn't do.
However, this was missed when merging the two code paths.

This fixes an issue for example when units are promoted while atop
walls.

Reported By: faction02
Patch By: Freagarach
Differential Revision: https://code.wildfiregames.com/D2303
This was SVN commit r22929.
2019-09-18 19:07:16 +00:00

261 lines
9.4 KiB
JavaScript

// 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;
}
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());
}
var cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
var cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
if (cmpOwnership && cmpNewOwnership)
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
// Copy control groups
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 newCp = cmpCapturable.GetCapturePoints().map(v => v / scale);
cmpNewCapturable.SetCapturePoints(newCp);
}
// 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);
}
var cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
if (cmpUnitAI && cmpNewUnitAI)
{
let pos = cmpUnitAI.GetHeldPosition();
if (pos)
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
if (cmpUnitAI.GetStanceName())
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
if (cmpUnitAI.IsGuardOf())
{
let guarded = cmpUnitAI.IsGuardOf();
let cmpGuard = Engine.QueryInterface(guarded, IID_Guard);
if (cmpGuard)
{
cmpGuard.RenameGuard(oldEnt, newEnt);
cmpNewUnitAI.SetGuardOf(guarded);
}
}
if (cmpUnitAI.IsGarrisoned())
cmpNewUnitAI.SetGarrisoned();
}
let cmpPromotion = Engine.QueryInterface(oldEnt, IID_Promotion);
let cmpNewPromotion = Engine.QueryInterface(newEnt, IID_Promotion);
if (cmpPromotion && cmpNewPromotion)
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);
}
// 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);
}
}
}
TransferGarrisonedUnits(oldEnt, newEnt);
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
if (cmpPosition && cmpPosition.IsInWorld())
cmpPosition.MoveOutOfWorld();
Engine.DestroyEntity(oldEnt);
return newEnt;
}
function CanGarrisonedChangeTemplate(ent, template)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var unitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpPosition && !cmpPosition.IsInWorld() && unitAI && unitAI.IsGarrisoned())
{
// We're a garrisoned unit, assume impossibility as I've been unable to find a way to get the holder ID.
// TODO: change this if that ever becomes possibles
return false;
}
return true;
}
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.Eject(ent);
if (!cmpNewGarrison)
continue;
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI)
continue;
cmpUnitAI.Autogarrison(newEnt);
cmpNewGarrison.Garrison(ent);
}
}
Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate);
Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);