Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/helpers/Attacking.js
/** | /** | ||||||||
* Provides attack and damage-related helpers under the Attacking umbrella (to avoid name ambiguity with the component). | * Provides attack and damage-related helpers under the Attacking umbrella (to avoid name ambiguity with the component). | ||||||||
*/ | */ | ||||||||
function Attacking() {} | function Attacking() {} | ||||||||
const DirectEffectsSchema = | const DirectEffectsSchema = | ||||||||
"<element name='Damage'>" + | "<element name='Damage'>" + | ||||||||
"<oneOrMore>" + | "<oneOrMore>" + | ||||||||
"<element a:help='One or more elements describing damage types'>" + | "<element a:help='One or more elements describing damage types'>" + | ||||||||
"<anyName>" + | "<anyName/>" + | ||||||||
// Armour requires Foundation to not be a damage type. | |||||||||
"<except><name>Foundation</name></except>" + | |||||||||
"</anyName>" + | |||||||||
"<ref name='nonNegativeDecimal' />" + | "<ref name='nonNegativeDecimal' />" + | ||||||||
"</element>" + | "</element>" + | ||||||||
"</oneOrMore>" + | "</oneOrMore>" + | ||||||||
"</element>" + | "</element>" + | ||||||||
"<element name='Capture' a:help='Capture points value'>" + | "<element name='Capture' a:help='Capture points value'>" + | ||||||||
"<ref name='nonNegativeDecimal'/>" + | "<ref name='nonNegativeDecimal'/>" + | ||||||||
"</element>"; | "</element>"; | ||||||||
▲ Show 20 Lines • Show All 140 Lines • ▼ Show 20 Lines | if (modifierTemplate.Add !== undefined) | ||||||||
modifiers[modifier].Add = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Add", +modifierTemplate.Add, entity); | modifiers[modifier].Add = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Add", +modifierTemplate.Add, entity); | ||||||||
if (modifierTemplate.Multiply !== undefined) | if (modifierTemplate.Multiply !== undefined) | ||||||||
modifiers[modifier].Multiply = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Multiply", +modifierTemplate.Multiply, entity); | modifiers[modifier].Multiply = ApplyValueModificationsToEntity(valueModifRoot + "/ApplyStatus/" + effect + "/Modifiers/" + modifier + "/Multiply", +modifierTemplate.Multiply, entity); | ||||||||
if (modifierTemplate.Replace !== undefined) | if (modifierTemplate.Replace !== undefined) | ||||||||
modifiers[modifier].Replace = modifierTemplate.Replace; | modifiers[modifier].Replace = modifierTemplate.Replace; | ||||||||
} | } | ||||||||
return modifiers; | return modifiers; | ||||||||
}; | }; | ||||||||
Attacking.prototype.GetTotalAttackEffects = function(effectData, effectType, cmpResistance) | |||||||||
{ | |||||||||
let total = 0; | |||||||||
let armourStrengths = cmpResistance ? cmpResistance.GetArmourStrengths(effectType) : {}; | |||||||||
for (let type in effectData) | |||||||||
total += effectData[type] * Math.pow(0.9, armourStrengths[type] || 0); | |||||||||
return total; | |||||||||
}; | |||||||||
/** | /** | ||||||||
Freagarach: `resistanceStrengths ? resistanceStrengths[type] || 0 : 0` | |||||||||
* Gives the position of the given entity, taking the lateness into account. | * Gives the position of the given entity, taking the lateness into account. | ||||||||
* @param {number} ent - Entity id of the entity we are finding the location for. | * @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. | * @param {number} lateness - The time passed since the expected time to fire the function. | ||||||||
* @return {Vector3D} The location of the entity. | * @return {Vector3D} The location of the entity. | ||||||||
Done Inline Actions
bb: | |||||||||
*/ | */ | ||||||||
Attacking.prototype.InterpolatedLocation = function(ent, lateness) | Attacking.prototype.InterpolatedLocation = function(ent, lateness) | ||||||||
{ | { | ||||||||
let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); | let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position); | ||||||||
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly | if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly | ||||||||
return undefined; | return undefined; | ||||||||
let curPos = cmpTargetPosition.GetPosition(); | let curPos = cmpTargetPosition.GetPosition(); | ||||||||
let prevPos = cmpTargetPosition.GetPreviousPosition(); | let prevPos = cmpTargetPosition.GetPreviousPosition(); | ||||||||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Lines | else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) | ||||||||
else | else | ||||||||
damageMultiplier = 0; | damageMultiplier = 0; | ||||||||
} | } | ||||||||
else // In case someone calls this function with an invalid shape. | else // In case someone calls this function with an invalid shape. | ||||||||
{ | { | ||||||||
warn("The " + data.shape + " splash damage shape is not implemented!"); | warn("The " + data.shape + " splash damage shape is not implemented!"); | ||||||||
} | } | ||||||||
this.HandleAttackEffects(data.type + ".Splash", data.attackData, ent, data.attacker, data.attackerOwner, damageMultiplier); | let cmpResistance = Engine.QueryInterface(ent, IID_Resistance) | ||||||||
} | if (cmpResistance) | ||||||||
}; | cmpResistance.HandleAttackEffects(data.type + ".Splash", data.attackData, data.attacker, data.attackerOwner, damageMultiplier); | ||||||||
Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1) | |||||||||
{ | |||||||||
bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attacker, target, attackType, attackData.Bonuses); | |||||||||
let targetState = {}; | |||||||||
for (let effectType of g_EffectTypes) | |||||||||
{ | |||||||||
if (!attackData[effectType]) | |||||||||
continue; | |||||||||
let receiver = g_EffectReceiver[effectType]; | |||||||||
let cmpReceiver = Engine.QueryInterface(target, global[receiver.IID]); | |||||||||
if (!cmpReceiver) | |||||||||
continue; | |||||||||
Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier)); | |||||||||
} | } | ||||||||
if (!Object.keys(targetState).length) | |||||||||
return; | |||||||||
if (targetState.killed) | |||||||||
this.TargetKilled(attacker, target, attackerOwner); | |||||||||
Engine.PostMessage(target, MT_Attacked, { | |||||||||
"type": attackType, | |||||||||
"target": target, | |||||||||
"attacker": attacker, | |||||||||
"attackerOwner": attackerOwner, | |||||||||
"damage": -(targetState.HPchange || 0), | |||||||||
"capture": targetState.captureChange || 0, | |||||||||
"statusEffects": targetState.inflictedStatuses || [], | |||||||||
"fromStatusEffect": !!attackData.StatusEffect, | |||||||||
}); | |||||||||
// We do not want an entity to get XP from active Status Effects. | |||||||||
if (!!attackData.StatusEffect) | |||||||||
return; | |||||||||
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion); | |||||||||
if (cmpPromotion && targetState.xp) | |||||||||
cmpPromotion.IncreaseXp(targetState.xp); | |||||||||
}; | }; | ||||||||
/** | /** | ||||||||
* Gets entities near a give point for given players. | * Gets entities near a give point for given players. | ||||||||
* @param {Vector2D} origin - The point to check around. | * @param {Vector2D} origin - The point to check around. | ||||||||
* @param {number} radius - The radius around the point to check. | * @param {number} radius - The radius around the point to check. | ||||||||
Done Inline Actions
bb: | |||||||||
* @param {number[]} players - The players of which we need to check entities. | * @param {number[]} players - The players of which we need to check entities. | ||||||||
* @param {number} itf - Interface IID that returned entities must implement. Defaults to none. | |||||||||
* @return {number[]} The id's of the entities in range of the given point. | * @return {number[]} The id's of the entities in range of the given point. | ||||||||
*/ | */ | ||||||||
Attacking.prototype.EntitiesNearPoint = function(origin, radius, players, itf = 0) | Attacking.prototype.EntitiesNearPoint = function(origin, radius, players) | ||||||||
{ | { | ||||||||
// If there is insufficient data return an empty array. | // If there is insufficient data return an empty array. | ||||||||
if (!origin || !radius || !players || !players.length) | if (!origin || !radius || !players || !players.length) | ||||||||
return []; | return []; | ||||||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||||||
return cmpRangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, itf); | return cmpRangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_Resistance); | ||||||||
}; | }; | ||||||||
/** | /** | ||||||||
* Called when a unit kills something (another unit, building, animal etc). | * Get the predicted time of collision between a projectile (or a chaser) | ||||||||
* @param {number} attacker - The entity id of the killer. | * and its target, assuming they both move in straight line at a constant speed. | ||||||||
* @param {number} target - The entity id of the target. | * Vertical component of movement is ignored. | ||||||||
* @param {number} attackerOwner - The player id of the attacker. | * @param {Vector3D} selfPosition - the 3D position of the projectile (or chaser). | ||||||||
* @param {number} horizSpeed - 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 {Vector3D|boolean} - the 3D predicted position or false if the collision will not happen. | |||||||||
*/ | */ | ||||||||
Attacking.prototype.TargetKilled = function(attacker, target, attackerOwner) | Attacking.prototype.PredictTimeToTarget = function(selfPosition, horizSpeed, targetPosition, targetVelocity) | ||||||||
{ | { | ||||||||
let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); | let relativePosition = new Vector3D.sub(targetPosition, selfPosition); | ||||||||
let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != INVALID_PLAYER ? cmpAttackerOwnership.GetOwner() : attackerOwner; | let a = targetVelocity.x * targetVelocity.x + targetVelocity.z * targetVelocity.z - horizSpeed * horizSpeed; | ||||||||
let b = relativePosition.x * targetVelocity.x + relativePosition.z * targetVelocity.z; | |||||||||
let 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; | |||||||||
let disc = b * b - a * c; | |||||||||
if (a < 0 || b < 0 && disc >= 0) | |||||||||
return c / (Math.sqrt(disc) - b); | |||||||||
// Add to killer statistics. | return false; | ||||||||
let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(atkOwner, IID_StatisticsTracker); | |||||||||
if (cmpKillerPlayerStatisticsTracker) | |||||||||
cmpKillerPlayerStatisticsTracker.KilledEntity(target); | |||||||||
// Add to loser statistics. | |||||||||
let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(target, IID_StatisticsTracker); | |||||||||
if (cmpTargetPlayerStatisticsTracker) | |||||||||
cmpTargetPlayerStatisticsTracker.LostEntity(target); | |||||||||
// If killer can collect loot, let's try to collect it. | |||||||||
let cmpLooter = Engine.QueryInterface(attacker, IID_Looter); | |||||||||
if (cmpLooter) | |||||||||
cmpLooter.Collect(target); | |||||||||
}; | }; | ||||||||
var AttackingInstance = new Attacking(); | var AttackingInstance = new Attacking(); | ||||||||
Engine.RegisterGlobal("Attacking", AttackingInstance); | Engine.RegisterGlobal("Attacking", AttackingInstance); |
Wildfire Games · Phabricator
resistanceStrengths ? resistanceStrengths[type] || 0 : 0