Download Lua/Tactical/MeleeWeapon.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/MeleeWeapon.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Lua/Tactical/MeleeWeapon.lua
-
curl -L -o MeleeWeapon.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/MeleeWeapon.lua
11.3 kB
| 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", | |
| } | |