Download Data/ChanceToHitModifier.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 29.2 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Data/ChanceToHitModifier.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Data/ChanceToHitModifier.lua
-
curl -L -o ChanceToHitModifier.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Data/ChanceToHitModifier.lua
29.2 kB
| -- ========== GENERATED BY ChanceToHitModifier Editor DO NOT EDIT MANUALLY! ========== | |
| 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 = {} | |
| -- Light Stock | |
| 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) | |
| -- target camo | |
| 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 | |
| -- Forward Grip | |
| modifyVal, compDef = GetComponentEffectValue(weapon1, "FirstAimBonusModifier", "first_aim_bonus") | |
| if modifyVal then | |
| bonus = bonus + MulDivRound(min_bonus, modifyVal, 100) | |
| metaText[#metaText + 1] = compDef.DisplayName | |
| end | |
| -- Quick prism | |
| 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 | |
| -- Heavy Stock | |
| if IsFullyAimedAttack(num) then | |
| modifyVal, compDef = GetComponentEffectValue(weapon1, "BonusAccuracyWhenFullyAimed", "bonus_cth") | |
| if modifyVal then | |
| bonus = bonus + modifyVal | |
| metaText[#metaText + 1] = compDef.DisplayName | |
| end | |
| end | |
| -- Improved Sight | |
| 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, --[[ChanceToHitModifier Default Aim display_name]] "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 | |
| -- scale in the distance after point-blank range to max penalty | |
| 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, --[[ChanceToHitModifier Default Autofire display_name]] "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, --[[ChanceToHitModifier Default Bipod display_name]] "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, --[[ChanceToHitModifier Default Brutalize display_name]] "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, --[[ChanceToHitModifier Default Darkness display_name]] "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) | |
| -- temporary disable | |
| if true then return false, 0 end | |
| if not IsKindOf(target, "Unit") then return false, 0 end | |
| --[[ local movement = target.last_turn_movement | |
| if not movement or weapon1 and weapon1:HasC/o\mponent("ReflexSight") 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) | |
| -- scale by dot | |
| local mod = MulDivRound(self:ResolveValue("MaxPenalty"), guim - dp, guim) | |
| -- scale by distance (up to param1 tiles) | |
| 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, --[[ChanceToHitModifier Default EvasiveMovement display_name]] "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, --[[ChanceToHitModifier Default GameRuleHeavyWounds display_name]] "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 true, -modifierInterp, T{"Low Ground"} | |
| 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, --[[ChanceToHitModifier Default GroundDifference display_name]] "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, --[[ChanceToHitModifier Default LowProfile display_name]] "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, --[[ChanceToHitModifier Default MeleeAttackTargetStance display_name]] "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 -- if not w1 the target is unarmed, which is considered as a melee weapon for this modifier | |
| 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, --[[ChanceToHitModifier Default MeleeRangedEnemy display_name]] "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, --[[ChanceToHitModifier Default MeleeStealthStrike display_name]] "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, --[[ChanceToHitModifier Default NoLineOfSight display_name]] "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, --[[ChanceToHitModifier Default OpportunityAttack display_name]] "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, --[[ChanceToHitModifier Default PointBlank display_name]] "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 | |
| -- force exposed when aiming/shooting | |
| 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, --[[ChanceToHitModifier Default RangeAttackTargetStanceCover display_name]] "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, --[[ChanceToHitModifier Default RunAndGun display_name]] "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, --[[ChanceToHitModifier Default SameTarget display_name]] "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) | |
| -- the attacker must not have normal sight to the target (los + sight radius) | |
| if not attacker or not target or VisibilityCheckAll(attacker, target, nil, const.uvVisible) then | |
| return false, 0 | |
| end | |
| -- perks that negate the effect | |
| if not IsKindOf(weapon1, "Firearm") then | |
| return false, 0 | |
| end | |
| -- check if any teammate has normal sight to distinguish between Blind Fire and Seen by Spotter | |
| 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, --[[ChanceToHitModifier Default SeenBySpotter display_name]] "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, --[[ChanceToHitModifier Default SmallTarget display_name]] "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, --[[ChanceToHitModifier Default TargetedShot display_name]] "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, --[[ChanceToHitModifier Default TrainingAdvantage display_name]] "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, --[[ChanceToHitModifier Default TrainingDisadvantage display_name]] "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, --[[ChanceToHitModifier Default TwoWeaponFire display_name]] "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, --[[ChanceToHitModifier Default WeaponCondition display_name]] "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, --[[ChanceToHitModifier WeaponMod IncreasedSingleShotAccuracy display_name]] "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, --[[ChanceToHitModifier WeaponMod MinorAccuracyBonus display_name]] "Weapon Mod"), | |
| group = "WeaponMod", | |
| id = "MinorAccuracyBonus", | |
| }) | |