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 97 Lines • ▼ Show 20 Lines | else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) | ||||||||
// Check that the unit is within the distance splash width of the line starting at the missile's | // Check that the unit is within the distance splash width of the line starting at the missile's | ||||||||
// landing point which extends in the direction of the missile for length splash radius. | // landing point which extends in the direction of the missile for length splash radius. | ||||||||
if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions | if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions | ||||||||
damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) * | damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) * | ||||||||
(1 - perpPos * perpPos / (width * width)); | (1 - perpPos * perpPos / (width * width)); | ||||||||
else | else | ||||||||
damageMultiplier = 0; | damageMultiplier = 0; | ||||||||
} | } | ||||||||
else // In case someone calls this function with an invalid shape. | else | ||||||||
{ | |||||||||
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); | this.HandleAttackEffects(ent, data.type + ".Splash", data.attackData, data.attacker, data.attackerOwner, damageMultiplier); | ||||||||
} | } | ||||||||
}; | }; | ||||||||
Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1) | Attacking.prototype.HandleAttackEffects = function(target, attackType, attackData, attacker, attackerOwner, bonusMultiplier = 1) | ||||||||
{ | { | ||||||||
bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attacker, target, attackType, attackData.Bonuses); | // We force entities to have cmpResistance for range queries. | ||||||||
let cmpResistance = Engine.QueryInterface(target, IID_Resistance); | |||||||||
let targetState = {}; | if (!cmpResistance) | ||||||||
Done Inline Actions
bb: | |||||||||
for (let effectType of g_EffectTypes) | return false; | ||||||||
{ | |||||||||
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, { | cmpResistance.HandleAttackEffects(attackType, attackData, attacker, attackerOwner, bonusMultiplier); | ||||||||
"type": attackType, | return true; | ||||||||
"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. | ||||||||
* @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. | * @param {number} interfaceID - Interface ID that returned entities must implement. Defaults to IIR_Resistance. | ||||||||
* @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, interfaceID = IID_Resistance) | ||||||||
{ | { | ||||||||
// 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, interfaceID); | ||||||||
}; | |||||||||
/** | |||||||||
* Called when a unit kills something (another unit, building, animal etc). | |||||||||
* @param {number} attacker - The entity id of the killer. | |||||||||
* @param {number} target - The entity id of the target. | |||||||||
* @param {number} attackerOwner - The player id of the attacker. | |||||||||
*/ | |||||||||
Attacking.prototype.TargetKilled = function(attacker, target, attackerOwner) | |||||||||
{ | |||||||||
let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); | |||||||||
let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != INVALID_PLAYER ? cmpAttackerOwnership.GetOwner() : attackerOwner; | |||||||||
// Add to killer statistics. | |||||||||
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