0ad/binaries/data/mods/public/simulation/helpers/Position.js
Ralph Sennhauser 0ce889ca6d
Use stylistic for deprecated eslint rules
During the eslint 8 cycle the formatting rules were split out [1],
deprecating the corresponding rules in core.

This replaces all rules that where moved to @stylistic/eslint-plugin [2]
and accounts for the difference in the indenting rule behaviour.

To allow the pre-commit import hack to continue to work with the
stylisitc plugin for a recent nodejs version to be used.

[1] https://eslint.org/blog/2023/10/deprecating-formatting-rules/
[2] https://eslint.style/packages/default

Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
2025-06-10 21:23:23 +02:00

171 lines
6.5 KiB
JavaScript

function PositionHelper() {}
/**
* @param {number} firstEntity - The entityID of an entity.
* @param {number} secondEntity - The entityID of an entity.
*
* @return {number} - The horizontal distance between the two given entities. Returns
* infinity when the distance cannot be calculated.
*/
PositionHelper.prototype.DistanceBetweenEntities = function(firstEntity, secondEntity)
{
const cmpFirstPosition = Engine.QueryInterface(firstEntity, IID_Position);
if (!cmpFirstPosition || !cmpFirstPosition.IsInWorld())
return Infinity;
const cmpSecondPosition = Engine.QueryInterface(secondEntity, IID_Position);
if (!cmpSecondPosition || !cmpSecondPosition.IsInWorld())
return Infinity;
return cmpFirstPosition.GetPosition2D().distanceTo(cmpSecondPosition.GetPosition2D());
};
/**
* @param {Vector2D} origin - The point to check around.
* @param {number} radius - The radius around the point to check.
* @param {number[]} players - The players of which we need to check entities.
* @param {number} iid - Interface IID that returned entities must implement. Defaults to none.
*
* @return {number[]} The id's of the entities in range of the given point.
*/
PositionHelper.prototype.EntitiesNearPoint = function(origin, radius, players, iid = 0)
{
if (!origin || !radius || !players || !players.length)
return [];
const cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, iid, true);
};
/**
* Gives the position of the given entity, taking the lateness into account.
* Note that vertical movement is ignored.
*
* @param {number} ent - Entity id of the entity we are finding the location for.
* @param {number} lateness - The time passed since the expected time to fire the function.
*
* @return {Vector3D} The interpolated location of the entity.
*/
PositionHelper.prototype.InterpolatedLocation = function(ent, lateness)
{
const cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
return undefined;
const curPos = cmpTargetPosition.GetPosition();
const prevPos = cmpTargetPosition.GetPreviousPosition();
const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
const turnLength = cmpTimer.GetLatestTurnLength();
return new Vector3D(
(curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,
0,
(curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength
);
};
/**
* Test if a point is inside an entity's footprint.
* Note that edges may be not included for square entities due to rounding.
*
* @param {number} ent - Id of the entity we are checking with.
* @param {Vector3D} point - The point we are checking with.
* @param {number} lateness - The time passed since the expected time to fire the function.
*
* @return {boolean} True if the point is inside of the entity's footprint.
*/
PositionHelper.prototype.TestCollision = function(ent, point, lateness)
{
const targetPosition = this.InterpolatedLocation(ent, lateness);
if (!targetPosition)
return false;
const cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);
if (!cmpFootprint)
return false;
const targetShape = cmpFootprint.GetShape();
if (!targetShape)
return false;
if (targetShape.type == "circle")
return targetPosition.horizDistanceToSquared(point) < targetShape.radius * targetShape.radius;
if (targetShape.type == "square")
{
const angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
const distance = Vector2D.from3D(Vector3D.sub(point, targetPosition)).rotate(-angle);
return Math.abs(distance.x) < targetShape.width / 2 && Math.abs(distance.y) < targetShape.depth / 2;
}
warn("TestCollision called with an invalid footprint shape: " + targetShape.type + ".");
return false;
};
/**
* Get the predicted time of collision between a projectile (or a chaser)
* and its target, assuming they both move in straight line at a constant speed.
* Vertical component of movement is ignored.
*
* @param {Vector3D} firstPosition - The 3D position of the projectile (or chaser).
* @param {number} selfSpeed - The horizontal speed of the projectile (or chaser).
* @param {Vector3D} targetPosition - The 3D position of the target.
* @param {Vector3D} targetVelocity - The 3D velocity vector of the target.
*
* @return {number|boolean} - The time to collision or false if the collision will not happen.
*/
PositionHelper.prototype.PredictTimeToTarget = function(firstPosition, selfSpeed, targetPosition, targetVelocity)
{
const relativePosition = new Vector3D.sub(targetPosition, firstPosition);
const a = targetVelocity.x * targetVelocity.x + targetVelocity.z * targetVelocity.z - selfSpeed * selfSpeed;
const b = relativePosition.x * targetVelocity.x + relativePosition.z * targetVelocity.z;
const c = relativePosition.x * relativePosition.x + relativePosition.z * relativePosition.z;
// The predicted time to reach the target is the smallest non negative solution
// (when it exists) of the equation a t^2 + 2 b t + c = 0.
// Using c>=0, we can straightly compute the right solution.
if (c == 0)
return 0;
const disc = b * b - a * c;
if (a < 0 || b < 0 && disc >= 0)
return c / (Math.sqrt(disc) - b);
return false;
};
/**
* @param {number} target - EntityID to find the spawn position for.
* @param {number} entity - EntityID to find the spawn position for.
* @param {boolean} forced - Optionally whether the spawning is forced.
* @return {Vector3D} - An appropriate spawning position.
*/
PositionHelper.prototype.GetSpawnPosition = function(target, entity, forced)
{
const cmpFootprint = Engine.QueryInterface(target, IID_Footprint);
const cmpHealth = Engine.QueryInterface(target, IID_Health);
const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpFootprint)
return null;
// If the spawner is a sinking ship, restrict the location to the intersection of both passabilities.
// TODO: should use passability classes to be more generic.
let pos;
if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship"))
pos = cmpFootprint.PickSpawnPointBothPass(entity);
else
pos = cmpFootprint.PickSpawnPoint(entity);
if (pos.y < 0)
{
if (!forced)
return null;
// If ejection is forced, we need to continue, so use center of the entity.
const cmpPosition = Engine.QueryInterface(target, IID_Position);
pos = cmpPosition.GetPosition();
}
return pos;
};
Engine.RegisterGlobal("PositionHelper", new PositionHelper());