|
|
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
local num = aim |
|
local min_bonus = self:ResolveValue("MinBonus") |
|
local min_dex = self:ResolveValue("MinDex") |
|
local dex_scale = self:ResolveValue("DexScale") |
|
local dex = attacker.Dexterity |
|
|
|
if IsKindOfClasses(weapon1, "FirearmProperties", "MeleeWeaponProperties") then |
|
min_bonus = weapon1.AimAccuracy |
|
end |
|
|
|
local modifyVal, compDef |
|
local metaText = {} |
|
|
|
|
|
modifyVal, compDef = GetComponentEffectValue(weapon1, "ReduceAimAccuracy", "cth_penalty") |
|
if modifyVal then |
|
min_bonus = Max(1, MulDivRound(min_bonus, 100 - modifyVal, 100)) |
|
metaText[#metaText + 1] = compDef.DisplayName |
|
end |
|
|
|
local bonus = num * min_bonus + MulDivRound(Max(0, dex - min_dex) * num, dex_scale, 100) |
|
|
|
|
|
if IsKindOf(target, "Unit") then |
|
local armor = target:GetItemInSlot("Torso", "Armor") |
|
if armor and armor.Camouflage then |
|
bonus = MulDivRound(bonus, Max(0, 100 - const.Combat.CamoAimPenalty), 100) |
|
metaText[#metaText + 1] = T(396692757033, "Camouflaged - aiming is less effective") |
|
end |
|
end |
|
|
|
|
|
modifyVal, compDef = GetComponentEffectValue(weapon1, "FirstAimBonusModifier", "first_aim_bonus") |
|
if modifyVal then |
|
bonus = bonus + MulDivRound(min_bonus, modifyVal, 100) |
|
metaText[#metaText + 1] = compDef.DisplayName |
|
end |
|
|
|
|
|
modifyVal, compDef = GetComponentEffectValue(weapon1, "FirstShotIncreasedAim", "min_aim") |
|
if modifyVal then |
|
local reaction = table.find_value(WeaponComponentEffects.FirstShotIncreasedAim.unit_reactions, "Event", "OnCalcMinAimActions") |
|
reaction = reaction and reaction.Handler |
|
if reaction then |
|
local result = reaction(weapon1, target, 0, attacker, target, action, weapon1) |
|
if result and result == num then |
|
metaText[#metaText + 1] = compDef.DisplayName |
|
end |
|
end |
|
end |
|
|
|
|
|
if IsFullyAimedAttack(num) then |
|
modifyVal, compDef = GetComponentEffectValue(weapon1, "BonusAccuracyWhenFullyAimed", "bonus_cth") |
|
if modifyVal then |
|
bonus = bonus + modifyVal |
|
metaText[#metaText + 1] = compDef.DisplayName |
|
end |
|
end |
|
|
|
|
|
modifyVal, compDef = GetComponentEffectValue(weapon1, "AccuracyBonusWhenAimed", "bonus_cth") |
|
if modifyVal then |
|
bonus = bonus + modifyVal |
|
metaText[#metaText + 1] = compDef.DisplayName |
|
end |
|
|
|
return num > 0, bonus, T{762331260877, "Aiming (x<aim_mod>)", aim_mod = num}, #metaText ~= 0 and metaText |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MinBonus", |
|
'Value', 2, |
|
'Tag', "<MinBonus>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MinDex", |
|
'Value', 40, |
|
'Tag', "<MinDex>", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "DexScale", |
|
'Value', 10, |
|
'Tag', "<DexScale>%", |
|
}), |
|
}, |
|
display_name = T(154175220541, "Aiming"), |
|
group = "Default", |
|
id = "Aim", |
|
param_bindings = {}, |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not attacker or not target then |
|
return false, 0 |
|
end |
|
|
|
local param |
|
local metaText = {} |
|
|
|
local extra = 0 |
|
if action.id == "BurstFire" then |
|
param = "burst_max_penalty" |
|
elseif action.id == "AutoFire" then |
|
param = "auto_max_penalty" |
|
elseif action.id == "MGBurstFire" then |
|
if g_Overwatch[attacker] and g_Overwatch[attacker].permanent then |
|
param = "mg_burst_max_penalty" |
|
else |
|
param = "mg_burst_max_held_penalty" |
|
if weapon1 and weapon1:IsCumbersome() then |
|
extra = self:ResolveValue("mg_burst_cumbersome_penalty") |
|
end |
|
end |
|
elseif action.id == "GrizzlyPerk" then |
|
param = "mg_burst_max_penalty" |
|
metaText[#metaText + 1] = GrizzlyPerk.DisplayName |
|
else |
|
return false, 0 |
|
end |
|
|
|
local penalty = self:ResolveValue("base_penalty") |
|
local pb_dist = const.Weapons.PointBlankRange * const.SlabSizeX |
|
local dist = attacker_pos:Dist(target_pos) |
|
|
|
if dist > pb_dist then |
|
|
|
local max_dist = self:ResolveValue("max_dist") * const.SlabSizeX |
|
local max_penalty = self:ResolveValue(param) + extra |
|
|
|
dist = Min(dist, max_dist) - pb_dist |
|
max_dist = max_dist - pb_dist |
|
penalty = penalty + Min(-1, MulDivRound(dist, max_penalty - penalty, max_dist)) |
|
end |
|
|
|
if penalty == 0 then |
|
return false, 0 |
|
end |
|
|
|
if action.id == "BurstFire" then |
|
return true, penalty, T(913932180355, "Burst Fire"), #metaText ~= 0 and metaText |
|
end |
|
|
|
return true, penalty, false, #metaText ~= 0 and metaText |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "max_dist", |
|
'Value', 14, |
|
'Tag', "<max_dist>", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "base_penalty", |
|
'Tag', "<base_penalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "auto_max_penalty", |
|
'Value', -50, |
|
'Tag', "<auto_max_penalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "burst_max_penalty", |
|
'Value', -20, |
|
'Tag', "<burst_max_penalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "mg_burst_max_penalty", |
|
'Value', -50, |
|
'Tag', "<mg_burst_max_penalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "mg_burst_max_held_penalty", |
|
'Value', -60, |
|
'Tag', "<mg_burst_max_held_penalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "mg_burst_cumbersome_penalty", |
|
'Value', -20, |
|
'Tag', "<mg_burst_cumbersome_penalty>%", |
|
}), |
|
}, |
|
display_name = T(520853928478, "Autofire"), |
|
group = "Default", |
|
id = "Autofire", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if attacker.stance ~= "Prone" or not weapon1 then |
|
return false, 0 |
|
end |
|
local value = GetComponentEffectValue(weapon1, "AccuracyBonusProne", "bonus_cth") |
|
return not not value, value |
|
end, |
|
display_name = T(168185551132, "Bipod"), |
|
group = "Default", |
|
id = "Bipod", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
return true, self:ResolveValue("penalty") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "penalty", |
|
'Value', -10, |
|
'Tag', "<penalty>%", |
|
}), |
|
}, |
|
RequireActionType = "Brutalize", |
|
RequireTarget = true, |
|
display_name = T(644064070045, "Brutalize"), |
|
group = "Default", |
|
id = "Brutalize", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if attacker ~= target and attacker:IsPointBlankRange(target) then |
|
return false, 0 |
|
end |
|
if IsIlluminated(target) then |
|
return false, 0 |
|
end |
|
local penalty = const.EnvEffects.DarknessCTHPenalty |
|
if IsKindOf(attacker, "Unit") and attacker:HasNightVision() then |
|
local reductionPercent = CharacterEffectDefs.NightOps:ResolveValue("night_acc_penalty_reduction") |
|
penalty = MulDivRound(penalty, Max(0, 100 - reductionPercent), 100) |
|
return true, penalty, nil, T(311005929977, "Night Vision") |
|
end |
|
return true, penalty |
|
end, |
|
Comment = "Penalty when it's Night or in Underground; aim UI indication for Thermal Scope", |
|
RequireActionType = "Any Ranged Attack", |
|
display_name = T(686385477123, "In the dark"), |
|
group = "Default", |
|
id = "Darkness", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
|
|
if true then return false, 0 end |
|
|
|
if not IsKindOf(target, "Unit") then return false, 0 end |
|
|
|
|
|
|
|
|
|
|
|
|
|
local dist = movement:Len2D() |
|
if dist == 0 then |
|
return false, 0 |
|
end |
|
local move_dir = SetLen(movement, guim) |
|
local dir = target:GetVisualPos() - attacker_pos |
|
local dp = dir:Equal2D(point20) and guim or abs(Dot2D(SetLen(dir:SetZ(0), guim), move_dir) / guim) |
|
|
|
|
|
local mod = MulDivRound(self:ResolveValue("MaxPenalty"), guim - dp, guim) |
|
|
|
local max_dist = self:ResolveValue("MaxDistTiles") * const.SlabSizeX |
|
mod = MulDivRound(mod, Min(dist, max_dist), max_dist) |
|
|
|
return mod < self:ResolveValue("MinPenalty"), mod |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MaxDistTiles", |
|
'Value', 10, |
|
'Tag', "<MaxDistTiles>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MaxPenalty", |
|
'Value', -25, |
|
'Tag', "<MaxPenalty>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MinPenalty", |
|
'Value', -5, |
|
'Tag', "<MinPenalty>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(330773997382, "Evasive Movement"), |
|
group = "Default", |
|
id = "EvasiveMovement", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not IsGameRuleActive("HeavyWounds") then |
|
return false, 0 |
|
end |
|
local wounds = attacker:GetStatusEffect("Wounded") |
|
if not wounds or wounds.stacks<=0 then |
|
return false, 0 |
|
end |
|
|
|
local max_wounds = GameRuleDefs.HeavyWounds:ResolveValue("MaxWoundsEffect") |
|
local per_wound_percent = GameRuleDefs.HeavyWounds:ResolveValue("AccuracyPenalty") |
|
local total_percent = Min(wounds.stacks,max_wounds) *per_wound_percent |
|
return true, -total_percent |
|
end, |
|
display_name = T(738595150432, "Heavy Wounds"), |
|
group = "Default", |
|
id = "GameRuleHeavyWounds", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not weapon1 then |
|
return false, 0 |
|
end |
|
|
|
local min_diff = self:ResolveValue(IsKindOf(weapon1, "MeleeWeapon") and "MeleeThreshold" or "RangeThreshold") |
|
min_diff = MulDivRound(min_diff, const.SlabSizeZ, 100) |
|
local max_diff = const.SlabSizeZ * 20 |
|
|
|
local ax, ay, az = attacker_pos:xyz() |
|
az = az or terrain.GetHeight(ax, ay) |
|
local tx, ty, tz |
|
if IsValid(target) then |
|
tx, ty, tz = target:GetPosXYZ() |
|
else |
|
tx, ty, tz = target:xyz() |
|
end |
|
tz = tz or terrain.GetHeight(tx, ty) |
|
|
|
local maxMod = self:ResolveValue("MaxModifier") |
|
local minMod = self:ResolveValue("MinModifier") |
|
local modifierCap = maxMod - minMod |
|
local diff = az - tz |
|
local higher = diff < 0 |
|
diff = abs(diff) |
|
if diff < min_diff then return false, 0 end |
|
|
|
local interp = MulDivRound(diff - min_diff, 1000, max_diff) |
|
local modifierInterp = minMod + MulDivRound(interp, modifierCap, 1000) |
|
if not higher then |
|
return true, modifierInterp |
|
end |
|
|
|
return false, 0 |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MaxModifier", |
|
'Value', 20, |
|
'Tag', "<MaxModifier>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MinModifier", |
|
'Value', 10, |
|
'Tag', "<MinModifier>", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "RangeThreshold", |
|
'Value', 300, |
|
'Tag', "<RangeThreshold>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "MeleeThreshold", |
|
'Value', 50, |
|
'Tag', "<MeleeThreshold>%", |
|
}), |
|
}, |
|
StoreAsTable = true, |
|
display_name = T(652956687180, "High Ground"), |
|
group = "Default", |
|
id = "GroundDifference", |
|
param_bindings = {}, |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if IsKindOf(weapon1, "Firearm") and target.species == "Crocodile" then |
|
local modValue = self:ResolveValue("LowProfilePenalty") |
|
return true, modValue |
|
end |
|
|
|
return false, 0 |
|
end, |
|
Comment = "target is Crocodile", |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "LowProfilePenalty", |
|
'Value', -12, |
|
'Tag', "<LowProfilePenalty>", |
|
}), |
|
}, |
|
RequireActionType = "Any Ranged Attack", |
|
RequireTarget = true, |
|
display_name = T(172166647737, "Low Profile"), |
|
group = "Default", |
|
id = "LowProfile", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not IsKindOf(weapon1, "MeleeWeapon") or not IsKindOf(target, "Unit") then |
|
return false, 0 |
|
end |
|
local target_stance = target:GetHitStance() |
|
local bonus = self:ResolveValue(target_stance .. "MeleeModifier") |
|
return not not bonus, bonus |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "CrouchMeleeModifier", |
|
'Value', 15, |
|
'Tag', "<CrouchMeleeModifier>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "ProneMeleeModifier", |
|
'Value', 20, |
|
'Tag', "<ProneMeleeModifier>", |
|
}), |
|
}, |
|
RequireActionType = "Any Melee Attack", |
|
RequireTarget = true, |
|
display_name = T(413332276489, "Enemy Stance"), |
|
group = "Default", |
|
id = "MeleeAttackTargetStance", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if action.id ~= "MarkTarget" and action.ActionType ~= "Melee Attack" then return false, 0 end |
|
if IsKindOf(target, "Unit") then |
|
local w1, w2, wl = target:GetActiveWeapons() |
|
local has_melee |
|
for _, w in ipairs(wl) do |
|
has_melee = has_melee or IsKindOf(w, "MeleeWeapon") |
|
end |
|
if w1 and not has_melee then |
|
return true, self:ResolveValue("bonus") |
|
end |
|
end |
|
|
|
return false, 0 |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "bonus", |
|
'Value', 10, |
|
'Tag', "<bonus>%", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(883060605104, "Ranged Enemy"), |
|
group = "Default", |
|
id = "MeleeRangedEnemy", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if action.id ~= "MarkTarget" and action.ActionType ~= "Melee Attack" then return false, 0 end |
|
if not attacker:HasStatusEffect("Hidden") or not IsKindOf(target, "Unit") or target:IsAware() then return false, 0 end |
|
return true, self:ResolveValue("bonus") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "bonus", |
|
'Value', 15, |
|
'Tag', "<bonus>%", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(272537328286, "Stealth Strike"), |
|
group = "Default", |
|
id = "MeleeStealthStrike", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if HasVisibilityTo(attacker.team or attacker, target) then |
|
return false, 0 |
|
end |
|
if not IsKindOf(weapon1, "Firearm") or not attacker or not target then |
|
return false, 0 |
|
end |
|
|
|
return true, self:ResolveValue("Penalty") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Penalty", |
|
'Value', -50, |
|
'Tag', "<Penalty>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(373728214326, "No Line of Sight"), |
|
group = "Default", |
|
id = "NoLineOfSight", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not opportunity_attack then |
|
return false, 0 |
|
end |
|
local max = self:ResolveValue("MaxPenalty") |
|
local min = self:ResolveValue("MinPenalty") |
|
local value = max + MulDivRound(min - max, attacker.Dexterity, 100) |
|
|
|
local metaText = false |
|
local bonus, compDef = GetComponentEffectValue(weapon1, "OpportunityAttackBonusCth", "bonus_cth") |
|
if bonus then |
|
value = value + bonus |
|
metaText = compDef.DisplayName |
|
end |
|
|
|
return true, value, false, metaText |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MaxPenalty", |
|
'Value', -20, |
|
'Tag', "<MaxPenalty>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "MinPenalty", |
|
'Value', 10, |
|
'Tag', "<MinPenalty>", |
|
}), |
|
}, |
|
display_name = T(148987105999, "Opportunity Attack"), |
|
group = "Default", |
|
id = "OpportunityAttack", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if attacker and IsKindOf(weapon1, "FirearmProperties") and weapon1:HasPointBlankBonus() and attacker:IsPointBlankRange(target) then |
|
return true, self:ResolveValue("bonus") |
|
end |
|
|
|
return false, 0 |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "bonus", |
|
'Value', 15, |
|
'Tag', "<bonus>%", |
|
}), |
|
}, |
|
display_name = T(843386513579, "Point-Blank Range"), |
|
group = "Default", |
|
id = "PointBlank", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if opportunity_attack or not IsKindOf(weapon1, "Firearm") or not IsKindOf(target, "Unit") then |
|
return false, 0 |
|
end |
|
local target_stance = target:GetHitStance() |
|
if target_stance == "Prone" then |
|
local value = self:ResolveValue("PronePenalty") |
|
return true, value, T(904752344471, "Target Prone") |
|
end |
|
|
|
local cover, any, coverage |
|
if weapon1 then |
|
local ignoreCth = weapon1:HasComponent("IgnoreCoverCtHWhenFullyAimed") and IsFullyAimedAttack(aim) |
|
if not ignoreCth and (opportunity_attack or target:IsAware()) and not target:HasStatusEffect("Exposed") then |
|
cover, any, coverage = target:GetCoverPercentage(attacker_pos, target_pos) |
|
end |
|
end |
|
|
|
|
|
local melee_attack = action and action.ActionType == "Melee Attack" |
|
cover = not target.aim_action_id and not melee_attack and cover |
|
|
|
if cover then |
|
local name = false |
|
local exposed_value = self:ResolveValue("ExposedCover") |
|
local full_value = self:ResolveValue("Cover") |
|
|
|
if CheckSightCondition(attacker, target, const.usObscured) then |
|
exposed_value = exposed_value + const.EnvEffects.DustStormCoverCTHPenalty |
|
full_value = full_value + const.EnvEffects.DustStormCoverCTHPenalty |
|
name = T(548829641491, "Behind Cover (Dust Storm)") |
|
end |
|
|
|
local value = InterpolateCoverEffect(coverage, full_value, exposed_value) |
|
local metaText = false |
|
|
|
if value < exposed_value then |
|
return true, value, name, metaText, "Cover" |
|
end |
|
end |
|
if target_stance == "Crouch" then |
|
local value = self:ResolveValue("CrouchPenalty") |
|
return true, value, T(309253003316, "Target Crouched") |
|
end |
|
return false, 0 |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Cover", |
|
'Value', -20, |
|
'Tag', "<Cover>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "ExposedCover", |
|
'Value', -5, |
|
'Tag', "<ExposedCover>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "CrouchPenalty", |
|
'Value', -5, |
|
'Tag', "<CrouchPenalty>", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "PronePenalty", |
|
'Value', -10, |
|
'Tag', "<PronePenalty>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(879243129261, "Behind Cover"), |
|
group = "Default", |
|
id = "RangeAttackTargetStanceCover", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if action.id ~= "RunAndGun" then |
|
return false, 0 |
|
end |
|
return true, -self:ResolveValue("Penalty") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Penalty", |
|
'Value', 25, |
|
'Tag', "<Penalty>", |
|
}), |
|
}, |
|
display_name = T(933407442617, "Run and Gun"), |
|
group = "Default", |
|
id = "RunAndGun", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if attacker:GetLastAttack() ~= target then |
|
return false, 0 |
|
end |
|
local value = self:ResolveValue("Bonus") |
|
local name = self.display_name |
|
|
|
if IsKindOf(weapon1, "MeleeWeapon") then |
|
return true, value, T{382638810853, "<name> (Melee)", name = name} |
|
end |
|
|
|
if IsKindOf(weapon1, "Firearm") then |
|
local metaText = false |
|
local modifyVal, compDef = GetComponentEffectValue(weapon1, "AccuracyBonusSameTarget", "bonus_cth") |
|
if modifyVal then |
|
value = value + modifyVal |
|
metaText = compDef.DisplayName |
|
end |
|
return true, value, name, metaText |
|
end |
|
|
|
return false, 0 |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Bonus", |
|
'Value', 10, |
|
'Tag', "<Bonus>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(726263349116, "Same Target"), |
|
group = "Default", |
|
id = "SameTarget", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
|
|
if not attacker or not target or VisibilityCheckAll(attacker, target, nil, const.uvVisible) then |
|
return false, 0 |
|
end |
|
|
|
|
|
if not IsKindOf(weapon1, "Firearm") then |
|
return false, 0 |
|
end |
|
|
|
|
|
if not attacker.team or not VisibilityCheckAll(attacker.team, target, nil, const.uvVisible) then |
|
return true, self:ResolveValue("BlindFirePenalty") |
|
end |
|
return true, self:ResolveValue("SpotterPenalty"), T(431888134623, "Seen by Spotter") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "SpotterPenalty", |
|
'Value', -20, |
|
'Tag', "<SpotterPenalty>%", |
|
}), |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "BlindFirePenalty", |
|
'Value', -20, |
|
'Tag', "<BlindFirePenalty>%", |
|
}), |
|
}, |
|
RequireActionType = "Any Ranged Attack", |
|
RequireTarget = true, |
|
display_name = T(213459983213, "Blind fire"), |
|
group = "Default", |
|
id = "SeenBySpotter", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if not IsKindOf(target, "Trap") then return false end |
|
|
|
return true, self:ResolveValue("Bonus") |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Bonus", |
|
'Value', 20, |
|
'Tag', "<Bonus>", |
|
}), |
|
}, |
|
display_name = T(498687848389, "Stationary Target"), |
|
group = "Default", |
|
id = "SmallTarget", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
local mod = body_part_def and body_part_def.tohit_mod or 0 |
|
if IsKindOfClasses(weapon1, "Pistol", "Revolver", "SniperRifle") then |
|
mod = MulDivRound(mod, self:ResolveValue("pistol_effect"), 100) |
|
end |
|
return mod ~= 0, mod, T{357357827656, "<display_name> Shot", body_part_def} |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "pistol_effect", |
|
'Value', 66, |
|
'Tag', "<pistol_effect>%", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(857397449322, "Targeted Shot"), |
|
group = "Default", |
|
id = "TargetedShot", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if IsKindOf(attacker, "Unit") and IsKindOf(target, "Unit") then |
|
local attackerLevel = attacker:GetLevel() |
|
local targetLevel = target:GetLevel() |
|
local hasAdvantage = (attackerLevel - targetLevel) >= self:ResolveValue("levelDiff") |
|
if hasAdvantage then |
|
return true, self:ResolveValue("bonus") |
|
end |
|
end |
|
end, |
|
Comment = "More accurate attacks against lower level targets", |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "bonus", |
|
'Value', 10, |
|
'Tag', "<bonus>%", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "levelDiff", |
|
'Value', 2, |
|
'Tag', "<levelDiff>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(713286242287, "More experienced"), |
|
group = "Default", |
|
id = "TrainingAdvantage", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if IsKindOf(attacker, "Unit") and IsKindOf(target, "Unit") then |
|
local attackerLevel = attacker:GetLevel() |
|
local targetLevel = target:GetLevel() |
|
local hasDisadvantage = (targetLevel - attackerLevel) >= self:ResolveValue("levelDiff") |
|
if hasDisadvantage then |
|
return true, -self:ResolveValue("penalty") |
|
end |
|
end |
|
end, |
|
Comment = "Less accurate attacks against higher level targets", |
|
Parameters = { |
|
PlaceObj('PresetParamPercent', { |
|
'Name', "penalty", |
|
'Value', 10, |
|
'Tag', "<penalty>%", |
|
}), |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "levelDiff", |
|
'Value', 2, |
|
'Tag', "<levelDiff>", |
|
}), |
|
}, |
|
RequireTarget = true, |
|
display_name = T(133256021230, "Less Experienced"), |
|
group = "Default", |
|
id = "TrainingDisadvantage", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if action.id ~= "DualShot" then |
|
return false, 0 |
|
end |
|
local value = self:ResolveValue("Penalty") |
|
return true, value |
|
end, |
|
Parameters = { |
|
PlaceObj('PresetParamNumber', { |
|
'Name', "Penalty", |
|
'Value', -30, |
|
'Tag', "<Penalty>", |
|
}), |
|
}, |
|
display_name = T(632609411805, "Two Weapon Fire"), |
|
group = "Default", |
|
id = "TwoWeaponFire", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
local min_condition = Min(weapon1 and weapon1:GetConditionPercent() or 100, weapon2 and weapon2:GetConditionPercent() or 100) |
|
local chance = GetWeaponConditionPenalty(min_condition) |
|
return chance > 0, -chance |
|
end, |
|
display_name = T(485217354080, "Weapon Condition"), |
|
group = "Default", |
|
id = "WeaponCondition", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
if action and action.id ~= "SingleShot" then |
|
return false |
|
end |
|
|
|
local extraCth, component = GetComponentEffectValue(weapon1, "IncreasedSingleShotAccuracy", "bonus_cth") |
|
return not not extraCth, extraCth, component and component.DisplayName |
|
end, |
|
display_name = T(539719873893, "Weapon Mod"), |
|
group = "WeaponMod", |
|
id = "IncreasedSingleShotAccuracy", |
|
}) |
|
|
|
PlaceObj('ChanceToHitModifier', { |
|
CalcValue = function (self, attacker, target, body_part_def, action, weapon1, weapon2, lof, aim, opportunity_attack, attacker_pos, target_pos) |
|
local extraCth, component = GetComponentEffectValue(weapon1, "MinorAccuracyBonus", "bonus_cth") |
|
return not not extraCth, extraCth, component and component.DisplayName |
|
end, |
|
display_name = T(846343426094, "Weapon Mod"), |
|
group = "WeaponMod", |
|
id = "MinorAccuracyBonus", |
|
}) |
|
|
|
|