File size: 11,255 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
DefineClass.MeleeWeapon = {
__parents = { "InventoryItem", "MeleeWeaponProperties", "BaseWeapon", "BobbyRayShopMeleeWeaponProperties" },
WeaponType = "MeleeWeapon",
ImpactForce = 2,
base_skill = "Dexterity",
base_action = "MeleeAttack",
ComponentSlots = {},
Color = "Default",
components = {},
neck_attack_descriptions = {
["choke"] = T(545528819211, "<newline><newline>Unarmed: Inflicts <em>Choking</em> on hit."),
["bleed"] = T(251225177855, "<newline><newline>Knife: Inflicts <em>Bleeding</em> on hit."),
["lethal"] = T(775626326541, "<newline><newline>Machete: Chance for a lethal attack based on your Strength."),
},
}
function MeleeWeapon:GetRolloverType()
return self.ItemType or "MeleeWeapon"
end
function MeleeWeapon:GetAccuracy(dist, unit, action, ranged)
if not ranged then
return self.BaseChanceToHit
end
return GetRangeAccuracy(self, dist, unit, action)
end
function MeleeWeapon:GetBaseAttack(unit, force)
return self.base_action
end
function MeleeWeapon:GetCustomNeckAttackDescription()
return self.neck_attack_descriptions[self.NeckAttackType]
end
function MeleeWeapon:PrecalcDamageAndStatusEffects(attacker, target, attack_pos, damage, hit, effect, attack_args, record_breakdown, action, prediction)
local effects = EffectsTable(effect)
local strMod = MulDivRound(attacker.Strength, self.DamageMultiplier, 100)
if record_breakdown then record_breakdown[#record_breakdown + 1] = { name = T(162618960967, "Strength"), value = strMod } end
local mod = 100 + strMod
mod = mod + (hit.damage_bonus or 0)
local actionType = hit.actionType
if actionType == "Melee Attack" then
if IsKindOf(target, "Unit") then
if target.species == "Human" and target.stance == "Prone" then
local value = const.Combat.MeleeAttackProneMod
mod = mod + value
if record_breakdown then record_breakdown[#record_breakdown + 1] = { name = T(848625832174, "Prone Target"), value = value } end
end
end
end
damage = MulDivRound(damage, mod, 100)
BaseWeapon.PrecalcDamageAndStatusEffects(self, attacker, target, attack_pos, damage, hit, effects, attack_args, record_breakdown, action, prediction)
end
function MeleeWeapon:GetAttackResults(action, attack_args)
-- unpack some params & init default values
local attacker = attack_args.obj
local attack_pos = attack_args.step_pos
local target = attack_args.target or attack_args.target_pos
local prediction = attack_args.prediction
local stealth_kill_chance = attack_args.stealth_kill_chance or 0
local stealth_crit_chance = attack_args.stealth_bonus_crit_chance or 0
-- attack/crit rolls
local attack_results = {}
attack_results.crit_chance = attacker:CalcCritChance(self, target, attack_args, attack_pos)
if action.AlwaysHits then
attack_results.chance_to_hit = 100
elseif attack_args.cth_breakdown then
local cth, baseCth, modifiers = attacker:CalcChanceToHit(target, action, attack_args)
attack_results.chance_to_hit = cth
attack_results.chance_to_hit_modifiers = modifiers
else
attack_results.chance_to_hit = attacker:CalcChanceToHit(target, action, attack_args, "chance_only")
end
if IsKindOf(target, "Unit") and action.id == "UnarmedAttack" then
attack_results.knockdown_chance = Max(0, 20 + attacker.Strength - target.Agility)
else
attack_results.knockdown_chance = 0
end
if attack_args.chance_only and not attack_args.damage_breakdown then return attack_results end
if prediction then
attack_results.attack_roll = -1
attack_results.knockdown_roll = 101
attack_results.crit_roll = 101
if stealth_kill_chance > 0 then
attack_args.stealth_kill_roll = 101
end
else
attack_results.attack_roll = attack_args.attack_roll or attacker:Random(100) -- todo: remove the random, assert there's a valid roll
attack_results.crit_roll = attack_args.crit_roll or attacker:Random(100)
if stealth_kill_chance > 0 then
attack_args.stealth_kill_roll = attack_args.stealth_kill_roll or attacker:Random(100)
end
if attack_results.knockdown_chance > 0 then
attack_results.knockdown_roll = attacker:Random(100)
else
attack_results.knockdown_roll = 100
end
end
local miss = attack_results.attack_roll >= attack_results.chance_to_hit
local crit = attack_results.crit_roll < attack_results.crit_chance
local knockdown = attack_results.knockdown_roll < attack_results.knockdown_chance
local kill
if not miss and stealth_kill_chance > 0 then
kill = attack_args.stealth_kill_roll < stealth_kill_chance
end
attack_results.weapon = self
attack_results.crit = crit
attack_results.stealth_attack = attack_args.stealth_attack
attack_results.stealth_kill_chance = stealth_kill_chance
attack_results.stealth_kill = kill
attack_results.num_hits = miss and 0 or 1
attack_results.friendly_fire_dmg = 0
attack_results.killed_units = false
attack_results.attack_pos = attack_pos
attack_results.hit_objs = {}
attack_results.aim = attack_args.aim
attack_results.dmg_breakdown = attack_args.damage_breakdown and {} or false
attack_results.lof = attack_args.lof
local target_grazing_hit, stuck
if action.ActionType == "Ranged Attack" then -- throw
-- create trajectory and store in attack_results.trajectory
local lof_params = {
obj = attacker,
output_collisions = true,
range = range,
max_pierced_objects = 0,
target_spot_group = "Torso",
action_id = action.id,
seed = prediction and 0 or attacker:Random(),
step_pos = attack_args.step_pos or nil,
}
local attack_data = GetLoFData(attacker, target, lof_params)
assert(attack_data)
local lof_idx = table.find(attack_data.lof, "target_spot_group", attack_data.target_spot_group)
local lof_data = attack_data.lof[lof_idx or 1]
if not lof_data or lof_data.stuck then
attack_results.chance_to_hit = 0
stuck = true
local mods = attack_results.chance_to_hit_modifiers or {}
mods[#mods + 1] = {
{
id = "NoLineOfFire",
name = T(604792341662, "No Line of Fire"),
value = 0
}
}
end
local attack_pos = lof_data.attack_pos
local hit_pos = lof_data.target_pos
target_grazing_hit = not lof_data.stuck and lof_data.target_grazing_hit
if miss and not prediction then
local dispersion = Firearm:GetMaxDispersion(attacker:GetDist(target))
local misses = Firearm:CalcMissVectors(attacker, action.id, target, attack_pos, hit_pos, dispersion, 10*guic)
local main, backup = misses.clear, misses.obstructed
local tbl = #main > 0 and main or backup
assert(#tbl > 0)
-- overwrite hit_pos to create a different first part of the trajectory
hit_pos = table.interaction_rand(tbl, "Combat")
end
-- create the first part of the trajectory (attack_pos -> hit_pos)
local throw_velocity = const.Combat.KnifeThrowVelocity
local dist = attack_pos:Dist(hit_pos)
local tth = MulDivRound(dist, 1000, throw_velocity)
attack_results.trajectory = {
{ pos = attack_pos, t = 0 },
{ pos = hit_pos, t = tth },
}
-- add parabolic bounce movement on miss
if miss and hit_pos:IsValidZ() and hit_pos:z() > terrain.GetHeight(hit_pos) then
local throw_vector = hit_pos - attack_pos
if throw_vector:Len() == 0 then
throw_vector = Rotate(point(guim, 0, 0), attacker:GetAngle())
end
local bounce_diminish = const.Combat.KnifeBounceVelocityLoss
local trajectory = CalcBounceParabolaTrajectory(hit_pos, SetLen(throw_vector, throw_velocity), const.Combat.Gravity, 10000, 20, 0, bounce_diminish)
for _, step in ipairs(trajectory) do
if step.t > 0 then -- skip starting position as it is already in attack_results.trajectory
step.t = step.t + tth
attack_results.trajectory[#attack_results.trajectory + 1] = step
end
end
end
miss = miss or not lof_data or lof_data.stuck
else -- not a throw
attack_results.melee_attack = true
end
local total_damage = 0
if not miss then
local hit = {
obj = target,
stealth_kill = kill,
stealth_crit = crit and (stealth_crit_chance > 0),
weapon = self,
critical = crit,
spot_group = attack_args.target_spot_group,
actionType = action.ActionType,
damage_bonus = attack_args.damage_bonus,
impact_force = self:GetImpactForce(),
melee_attack = attack_results.melee_attack,
grazing = target_grazing_hit,
}
local record_breakdown = attack_results.dmg_breakdown
if record_breakdown and attack_args.damage_bonus then
record_breakdown[#record_breakdown + 1] = { name = action and action.DisplayName or T(328963668848, "Base"), value = attack_args.damage_bonus }
end
local damage = attacker:GetBaseDamage(self, nil, attack_results.dmg_breakdown)
if not prediction then
damage = RandomizeWeaponDamage(damage)
end
local effects = attack_args.applied_status
if knockdown then
effects = EffectsTable(effects)
EffectTableAdd(effects, "KnockDown")
end
if attack_args.target_spot_group == "Neck" then
if self.NeckAttackType == "choke" then
effects = EffectsTable(effects)
EffectTableAdd(effects, "Choking")
elseif self.NeckAttackType == "bleed" then
effects = EffectsTable(effects)
EffectTableAdd(effects, "Bleeding")
elseif self.NeckAttackType == "lethal" and kill then
attack_results.decapitate = true
end
end
self:PrecalcDamageAndStatusEffects(attacker, target, attack_pos, damage, hit, effects, attack_args, record_breakdown, action, prediction)
total_damage = total_damage + hit.damage
if kill then
hit.damage = MulDivRound(target:GetTotalHitPoints(), 125, 100)
end
attack_results.hits = { hit }
attack_results[1] = hit
attack_results.hit_objs[#attack_results.hit_objs + 1] = target
attack_results.hit_objs[target] = true
attack_results.unit_damage = { [target] = hit.damage }
if IsKindOf(target, "Unit") and not target:IsDead() and hit.damage >= target:GetTotalHitPoints() then
attack_results.killed_units = {target}
end
elseif stuck then
local hit = {
obj = target,
weapon = self,
damage = 0,
spot_group = attack_args.target_spot_group,
actionType = action.ActionType,
damage_bonus = attack_args.damage_bonus,
impact_force = self:GetImpactForce(),
melee_attack = attack_results.melee_attack,
stuck = true,
effects = {},
}
attack_results.hits = { hit }
attack_results[1] = hit
end
attack_results.total_damage = total_damage
attack_results.miss = miss
attack_results.target_hit = not miss
return attack_results
end
function MeleeWeapon:CreateVisualObj(owner)
return self:CreateVisualObjEntity(owner, IsValidEntity(self.Entity) and self.Entity or "Weapon_FC_AMZ_Knife_01")
end
DefineClass.StackableMeleeWeapon = {
__parents = { "MeleeWeapon", "InventoryStack" },
properties = {
--strip condition related stuff
{ id = "Condition" },
{ id = "RepairCost" },
{ id = "Repairable" },
{ category = "Scrap", id = "ScrapParts", name = "Scrap Parts", help = "The number for Parts that are given to the player when its scraped",
editor = "number", default = 0, template = true, min = 0, max = 1000, },
}
}
DefineClass.UnarmedWeapon = {
__parents = { "MeleeWeapon" },
base_action = "UnarmedAttack",
}
DefineClass.CrocodileWeapon = {
__parents = { "MeleeWeapon" },
base_action = "CrocodileBite",
}
DefineClass.HyenaWeapon = {
__parents = { "MeleeWeapon" },
base_action = "HyenaBite",
}
|