Files
wnsrc/lua/weapons/tfa_melee_base/shared.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

1019 lines
29 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
-- Copyright (c) 2018-2020 TFA Base Devs
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
local vector_origin = Vector()
DEFINE_BASECLASS("tfa_bash_base")
SWEP.DrawCrosshair = true
SWEP.SlotPos = 72
SWEP.Slot = 0
SWEP.WeaponLength = 8
SWEP.Secondary.IronSightsEnabled = false
SWEP.Secondary.DisplaySpread = false
SWEP.Primary.Directional = false
SWEP.Primary.Attacks = {}
--[[{
{
["act"] = ACT_VM_HITLEFT, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence
["len"] = 8 * 4.5, -- Trace distance
["src"] = Vector(20,10,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dir"] = Vector(-40,30,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dmg"] = 60, --Damage
["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc.
["delay"] = 0.2, --Delay
["spr"] = true, --Allow attack while sprinting?
["snd"] = "Swing.Sound", -- Sound ID
["viewpunch"] = Angle(1,-10,0), --viewpunch angle
["end"] = 1, --time before next attack
["hull"] = 10, --Hullsize
["direction"] = "L", --Swing direction
["combotime"] = 0.2 --If you hold attack down, attack this much earlier
},
{
["act"] = ACT_VM_HITRIGHT, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence
["len"] = 8 * 4.5, -- Trace distance
["src"] = Vector(-10,10,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dir"] = Vector(40,30,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dmg"] = 60, --Damage
["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc.
["delay"] = 0.2, --Delay
["spr"] = true, --Allow attack while sprinting?
["snd"] = "Swing.Sound", -- Sound ID
["viewpunch"] = Angle(1,10,0), --viewpunch angle
["end"] = 1, --time before next attack
["hull"] = 10, --Hullsize
["direction"] = "R", --Swing direction
["combotime"] = 0.2 --If you hold attack down, attack this much earlier
}
}
SWEP.Secondary.Attacks = {
{
["act"] = ACT_VM_MISSCENTER, -- Animation; ACT_VM_THINGY, ideally something unique per-sequence
["src"] = Vector(0,5,0), -- Trace source; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dir"] = Vector(0,50,0), -- Trace direction/length; X ( +right, -left ), Y ( +forward, -back ), Z ( +up, -down )
["dmg"] = 60, --Damage
["dmgtype"] = DMG_SLASH, --DMG_SLASH,DMG_CRUSH, etc.
["delay"] = 0.2, --Delay
["spr"] = true, --Allow attack while sprinting?
["snd"] = "Swing.Sound", -- Sound ID
["viewpunch"] = Angle(5,0,0), --viewpunch angle
["end"] = 1, --time before next attack
["callback"] = function(tbl,wep,tr) end,
["kickback"] = nil--Recoil if u hit something with this activity
}
}
]]
--
SWEP.IsMelee = true
SWEP.Precision = 9 --Traces to use per attack
local l_CT = CurTime
SWEP.Primary.MaxCombo = 3 --Max amount of times you'll attack by simply holding down the mouse; -1 to unlimit
SWEP.Secondary.MaxCombo = 3 --Max amount of times you'll attack by simply holding down the mouse; -1 to unlimit
SWEP.CanBlock = false
SWEP.BlockAnimation = {
["in"] = {
["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act
["value"] = ACT_VM_DEPLOY, --Number for act, String/Number for sequence
["transition"] = true
},
--Inward transition
["loop"] = {
["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act
["value"] = ACT_VM_IDLE_DEPLOYED, --Number for act, String/Number for sequence
["is_idle"] = true
},
--looping animation
["hit"] = {
["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act
["value"] = ACT_VM_RELOAD_DEPLOYED, --Number for act, String/Number for sequence
["is_idle"] = true
},
--when you get hit and block it
["out"] = {
["type"] = TFA.Enum.ANIMATION_ACT, --Sequence or act
["value"] = ACT_VM_UNDEPLOY, --Number for act, String/Number for sequence
["transition"] = true
}
}
--Outward transition
SWEP.BlockDamageTypes = {DMG_SLASH, DMG_CLUB}
SWEP.BlockCone = 135 --Think of the player's view direction as being the middle of a sector, with the sector's angle being this
SWEP.BlockDamageMaximum = 0.1 --Multiply damage by this for a maximumly effective block
SWEP.BlockDamageMinimum = 0.4 --Multiply damage by this for a minimumly effective block
SWEP.BlockTimeWindow = 0.5 --Time to absorb maximum damage
SWEP.BlockTimeFade = 1 --Time for blocking to do minimum damage. Does not include block window
SWEP.BlockDamageCap = 100
SWEP.BlockSound = ""
SWEP.BlockFadeOut = nil --Override the length of the ["out"] block animation easily
SWEP.BlockFadeOutEnd = 0.2 --In absense of BlockFadeOut, shave this length off of the animation time
SWEP.BlockHoldType = "magic"
SWEP.BlockCanDeflect = true --Can "bounce" bullets off a perfect parry?
SWEP.Secondary.Directional = true
SWEP.Primary.Automatic = true
SWEP.Secondary.Automatic = true
SWEP.ImpactDecal = "ManhackCut"
SWEP.Secondary.CanBash = false
SWEP.DefaultComboTime = 0.2
SWEP.AllowSprintAttack = true
--[[ START OF BASE CODE ]]
--
SWEP.Primary.ClipSize = -1
SWEP.Primary.Ammo = ""
SWEP.Seed = 0
SWEP.AttackSoundTime = -1
SWEP.VoxSoundTime = -1
SWEP.Primary.DisplayFalloff = false
SWEP.CrosshairConeRecoilOverride = .05
function SWEP:SetupDataTables()
BaseClass.SetupDataTables(self)
self:NetworkVarTFA("Bool", "VP")
self:NetworkVarTFA("Bool", "BashImpulse")
self:NetworkVarTFA("Float", "VPTime")
self:NetworkVarTFA("Float", "VPPitch")
self:NetworkVarTFA("Float", "VPYaw")
self:NetworkVarTFA("Float", "VPRoll")
self:NetworkVarTFA("Int", "ComboCount")
self:NetworkVarTFA("Int", "MelAttackID")
self:SetMelAttackID(1)
self:SetVP(false)
self:SetVPPitch(0)
self:SetVPYaw(0)
self:SetVPRoll(0)
self:SetVPTime(-1)
self:SetComboCount(0)
end
function SWEP:Deploy()
self:SetMelAttackID(1)
self:SetVP(false)
self:SetVPPitch(0)
self:SetVPYaw(0)
self:SetVPRoll(0)
self:SetVPTime(-1)
self.up_hat = false
self:SetComboCount(0)
self:AddNZAnimations()
return BaseClass.Deploy(self)
end
function SWEP:AddNZAnimations()
if self.Primary.Attacks then
for _, v in pairs(self.Primary.Attacks) do
if v.act then
self.DTapActivities[v.act] = true
end
end
end
if self.Secondary.Attacks then
for _, v in pairs(self.Secondary.Attacks) do
if v.act then
self.DTapActivities[v.act] = true
end
end
end
end
function SWEP:CanInterruptShooting()
return false
end
local att = {}
local attack
local ind
local tr = {}
local traceres = {}
local pos, ang, mdl, ski, prop
local fwd, eang, scl, dirv
local strikedir = Vector()
local srctbl
SWEP.hpf = false
SWEP.hpw = false
local lim_up_vec = Vector(1, 1, 0.05)
function SWEP:ApplyForce(ent, force, posv)
if not IsValid(ent) or not ent.GetPhysicsObjectNum then return end
if hook.Run("TFAMeleeApplyForce", ent) ~= false then return end
if ent.GetRagdollEntity and IsValid(ent:GetRagdollEntity()) and ent ~= ent:GetRagdollEntity() then
ent = ent:GetRagdollEntity()
timer.Simple(0, function()
if IsValid(self) and self:OwnerIsValid() and IsValid(ent) then
self:ApplyForce(ent, force, posv, false)
end
end)
return
end
if not IsValid(ent) then return end
if ent:IsPlayer() or ent:IsNPC() then
ent:SetVelocity(force * 0.1 * lim_up_vec)
end
if ent:GetPhysicsObjectCount() > 1 then
for i = 0, ent:GetPhysicsObjectCount() - 1 do
local phys = ent:GetPhysicsObjectNum(i)
if IsValid(phys) then
phys:ApplyForceOffset(force / ent:GetPhysicsObjectCount(), posv)
end
end
else
local phys = ent:GetPhysicsObjectNum(0)
if IsValid(phys) then
phys:ApplyForceOffset(force, posv)
end
end
end
function SWEP:ApplyDamage(trace, dmginfo, attk)
local dam, force = dmginfo:GetBaseDamage(), dmginfo:GetDamageForce()
dmginfo:SetDamagePosition(trace.HitPos)
dmginfo:SetReportedPosition(trace.StartPos)
trace.Entity:DispatchTraceAttack(dmginfo, trace, fwd)
dmginfo:SetDamage(dam)
dmginfo:SetDamageForce(force)
-- dmginfo:SetAttacker( self:GetOwner() )
self:ApplyForce(trace.Entity, dmginfo:GetDamageForce(), trace.HitPos)
dmginfo:SetDamage(dam)
dmginfo:SetDamageForce(force)
-- dmginfo:SetAttacker( self:GetOwner() )
end
function SWEP:SmackEffect(trace, dmg)
local vSrc = trace.StartPos
local bFirstTimePredicted = IsFirstTimePredicted()
local bHitWater = bit.band(util.PointContents(vSrc), MASK_WATER) ~= 0
local bEndNotWater = bit.band(util.PointContents(trace.HitPos), MASK_WATER) == 0
local trSplash = bHitWater and bEndNotWater and util.TraceLine({
start = trace.HitPos,
endpos = vSrc,
mask = MASK_WATER
}) or not (bHitWater or bEndNotWater) and util.TraceLine({
start = vSrc,
endpos = trace.HitPos,
mask = MASK_WATER
})
if (trSplash and bFirstTimePredicted) then
local data = EffectData()
data:SetOrigin(trSplash.HitPos)
data:SetScale(1)
if (bit.band(util.PointContents(trSplash.HitPos), CONTENTS_SLIME) ~= 0) then
data:SetFlags(1) --FX_WATER_IN_SLIME
end
util.Effect("watersplash", data)
end
local dam, force, dt = dmg:GetBaseDamage(), dmg:GetDamageForce(), dmg:GetDamageType()
if (trace.Hit and bFirstTimePredicted and (not trSplash) and self:DoImpactEffect(trace, dt) ~= true) then
local data = EffectData()
data:SetOrigin(trace.HitPos)
data:SetStart(vSrc)
data:SetSurfaceProp(trace.SurfaceProps)
data:SetDamageType(dt)
data:SetHitBox(trace.HitBox)
data:SetEntity(trace.Entity)
util.Effect("Impact", data)
end
dmg:SetDamage(dam)
dmg:SetDamageForce(force)
-- dmg:SetAttacker( self:GetOwner() )
end
function SWEP:MakeDoor(ent, dmginfo)
pos = ent:GetPos()
ang = ent:GetAngles()
mdl = ent:GetModel()
ski = ent:GetSkin()
ent:SetNotSolid(true)
ent:SetNoDraw(true)
prop = ents.Create("prop_physics")
prop:SetPos(pos)
prop:SetAngles(ang)
prop:SetModel(mdl)
prop:SetSkin(ski or 0)
prop:Spawn()
prop:SetVelocity(dmginfo:GetDamageForce() * 48)
prop:GetPhysicsObject():ApplyForceOffset(dmginfo:GetDamageForce() * 48, dmginfo:GetDamagePosition())
if IsValid(dmginfo:GetAttacker()) then
prop:SetPhysicsAttacker(dmginfo:GetAttacker())
end
prop:EmitSound("physics/wood/wood_furniture_break" .. tostring(math.random(1, 2)) .. ".wav", 110, math.random(90, 110))
end
local cv_doordestruction = GetConVar("sv_tfa_melee_doordestruction")
function SWEP:BurstDoor(ent, dmginfo)
if not ents.Create then return end
if not cv_doordestruction:GetBool() then return end
if dmginfo:GetDamage() > 60 and (dmginfo:IsDamageType(DMG_CRUSH) or dmginfo:IsDamageType(DMG_CLUB)) and (ent:GetClass() == "func_door_rotating" or ent:GetClass() == "prop_door_rotating") then
if dmginfo:GetDamage() > 150 then
local ply = self:GetOwner()
self:MakeDoor(ent, dmginfo)
ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110))
else
local ply = self:GetOwner()
ply:EmitSound("ambient/materials/door_hit1.wav", 100, math.random(90, 110))
ply.oldname = ply:GetName()
ply:SetName("bashingpl" .. ply:EntIndex())
ent:SetKeyValue("Speed", "500")
ent:SetKeyValue("Open Direction", "Both directions")
ent:SetKeyValue("opendir", "0")
ent:Fire("unlock", "", .01)
ent:Fire("openawayfrom", "bashingpl" .. ply:EntIndex(), .01)
timer.Simple(0.02, function()
if IsValid(ply) then
ply:SetName(ply.oldname)
end
end)
timer.Simple(0.3, function()
if IsValid(ent) then
ent:SetKeyValue("Speed", "100")
end
end)
end
end
end
function SWEP:ThinkNPC()
local ow = self:GetOwner()
if ow:IsCurrentSchedule(SCHED_CHASE_ENEMY) then return end
if ow:IsCurrentSchedule(SCHED_MELEE_ATTACK1) then return end
if not self.Range then
local t = table.Random( self.Primary_TFA.Attacks )
if t and t.range then
self.Range = t.src:Length() + t.dir:Length()
else
self.Range = 80
end
end
local en = ow:GetEnemy()
if IsValid(en) and en:GetPos():Distance(self:GetPos()) <= self.Range and CurTime() > self:GetNextPrimaryFire() then
self:PrimaryAttack()
else
self:GetOwner():SetSchedule( SCHED_CHASE_ENEMY )
end
end
function SWEP:Think2(...)
if not self:VMIV() then return end
if (not self:GetOwner():KeyDown(IN_ATTACK)) and (not self:GetOwner():KeyDown(IN_ATTACK2)) then
self:SetComboCount(0)
end
if self:GetVP() and CurTime() > self:GetVPTime() then
self:SetVP(false)
self:SetVPTime(-1)
self:GetOwner():ViewPunch(Angle(self:GetVPPitch(), self:GetVPYaw(), self:GetVPRoll()))
end
if self.CanBlock then
local stat = self:GetStatus()
if self:GetBashImpulse() and TFA.Enum.ReadyStatus[stat] and not self:GetOwner():KeyDown(IN_USE) then
self:SetStatus(TFA.Enum.STATUS_BLOCKING, math.huge)
if self.BlockAnimation["in"] then
self:PlayAnimation(self.BlockAnimation["in"])
elseif self.BlockAnimation["loop"] then
self:PlayAnimation(self.BlockAnimation["loop"])
end
self.BlockStart = CurTime()
elseif stat == TFA.Enum.STATUS_BLOCKING and not self:GetBashImpulse() then
local _, tanim, ttype
if self.BlockAnimation["out"] then
_, tanim, ttype = self:PlayAnimation(self.BlockAnimation["out"])
else
_, tanim, ttype = self:ChooseIdleAnim()
end
self:ScheduleStatus(TFA.Enum.STATUS_BLOCKING_END, self.BlockFadeOut or (self:GetActivityLength(tanim, false, ttype) - self.BlockFadeOutEnd))
elseif stat == TFA.Enum.STATUS_BLOCKING and CurTime() > self:GetNextIdleAnim() then
self:ChooseIdleAnim()
end
end
self:StrikeThink()
BaseClass.Think2(self, ...)
end
function SWEP:ProcessHoldType(...)
if self:GetStatus() == TFA.Enum.STATUS_BLOCKING then
self:SetHoldType(self.BlockHoldType or "magic")
return self.BlockHoldType or "magic"
else
return BaseClass.ProcessHoldType(self, ...)
end
end
function SWEP:GetBlockStart()
return self.BlockStart or -1
end
function SWEP:ChooseBlockAnimation()
if self.BlockAnimation["hit"] then
self:PlayAnimation(self.BlockAnimation["hit"])
elseif self.BlockAnimation["in"] then
self:PlayAnimation(self.BlockAnimation["in"])
end
end
function SWEP:ChooseIdleAnim(...)
if self.CanBlock and self:GetStatus() == TFA.Enum.STATUS_BLOCKING and self.BlockAnimation["loop"] then
return self:PlayAnimation(self.BlockAnimation["loop"])
else
return BaseClass.ChooseIdleAnim(self, ...)
end
end
function SWEP:StrikeThink()
if self:GetSprinting() and not self:GetStatL("AllowSprintAttack", false) then
self:SetComboCount(0)
--return
end
if self:IsSafety() then
self:SetComboCount(0)
return
end
if not IsFirstTimePredicted() then return end
if self:GetStatus() ~= TFA.Enum.STATUS_SHOOTING then return end
if self.up_hat then return end
if self.AttackSoundTime ~= -1 and CurTime() > self.AttackSoundTime then
ind = self:GetMelAttackID() or 1
srctbl = (ind < 0) and self.Secondary_TFA.Attacks or self.Primary_TFA.Attacks
attack = srctbl[math.abs(ind)]
self:EmitSound(attack.snd)
if self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
self.AttackSoundTime = -1
end
if self:GetOwner().Vox and self.VoxSoundTime ~= -1 and CurTime() > self.VoxSoundTime - self:GetOwner():Ping() * 0.001 then
if self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
self.VoxSoundTime = -1
end
if CurTime() > self:GetStatusEnd() then
ind = self:GetMelAttackID() or 1
srctbl = (ind < 0) and self.Secondary_TFA.Attacks or self.Primary_TFA.Attacks
attack = srctbl[math.abs(ind)]
self.DamageType = attack.dmgtype
--Just attacked, so don't do it again
self.up_hat = true
self:SetStatus(TFA.Enum.STATUS_IDLE, math.huge)
if self:GetComboCount() > 0 then
self:SetNextPrimaryFire(self:GetNextPrimaryFire() - (attack.combotime or 0))
self:SetNextSecondaryFire(self:GetNextSecondaryFire() - (attack.combotime or 0))
end
self:Strike(attack, self.Precision)
end
end
local totalResults = {}
local function TraceHitFlesh(b)
return b.MatType == MAT_FLESH or b.MatType == MAT_ALIENFLESH or (IsValid(b.Entity) and b.Entity.IsNPC and (b.Entity:IsNPC() or b.Entity:IsPlayer() or b.Entity:IsRagdoll()))
end
function SWEP:Strike(attk, precision)
local hitWorld, hitNonWorld, hitFlesh, needsCB
local distance, direction, maxhull
local ow = self:GetOwner()
if not IsValid(ow) then return end
distance = attk.len
direction = attk.dir
maxhull = attk.hull
table.Empty(totalResults)
eang = ow:EyeAngles()
fwd = ow:EyeAngles():Forward()
tr.start = ow:GetShootPos()
scl = direction:Length() / precision / 2
tr.maxs = Vector(scl, scl, scl)
tr.mins = -tr.maxs
tr.mask = MASK_SHOT
tr.filter = function(ent)
if ent == ow or ent == self then return false end
return true
end
hitWorld = false
hitNonWorld = false
hitFlesh = false
if attk.callback then
needsCB = true
else
needsCB = false
end
if maxhull then
tr.maxs.x = math.min(tr.maxs.x, maxhull / 2)
tr.maxs.y = math.min(tr.maxs.y, maxhull / 2)
tr.maxs.z = math.min(tr.maxs.z, maxhull / 2)
tr.mins = -tr.maxs
end
strikedir:Zero()
strikedir:Add(direction.x * eang:Right())
strikedir:Add(direction.y * eang:Forward())
strikedir:Add(direction.z * eang:Up())
local strikedirfull = strikedir * 1
if ow:IsPlayer() and ow:IsAdmin() and GetConVarNumber("developer") > 0 then
local spos, epos = tr.start + Vector(0, 0, -1) + fwd * distance / 2 - strikedirfull / 2, tr.start + Vector(0, 0, -1) + fwd * distance / 2 + strikedirfull / 2
debugoverlay.Line(spos, epos, 5, Color(255, 0, 0))
debugoverlay.Cross(spos, 8, 5, Color(0, 255, 0), true)
debugoverlay.Cross(epos, 4, 5, Color(0, 255, 255), true)
end
if SERVER and not game.SinglePlayer() and ow:IsPlayer() then
ow:LagCompensation(true)
end
for i = 1, precision do
dirv = LerpVector((i - 0.5) / precision, -direction / 2, direction / 2)
strikedir:Zero()
strikedir:Add(dirv.x * eang:Right())
strikedir:Add(dirv.y * eang:Forward())
strikedir:Add(dirv.z * eang:Up())
tr.endpos = tr.start + distance * fwd + strikedir
traceres = util.TraceLine(tr)
table.insert(totalResults, traceres)
end
if SERVER and not game.SinglePlayer() and ow:IsPlayer() then
ow:LagCompensation(false)
end
local forcevec = strikedirfull:GetNormalized() * (attack.force or attack.dmg / 4) * 128
local damage = DamageInfo()
damage:SetAttacker(self:GetOwner())
damage:SetInflictor(self)
damage:SetDamage(attk.dmg)
damage:SetDamageType(attk.dmgtype or DMG_SLASH)
damage:SetDamageForce(forcevec)
local fleshHits = 0
--Handle flesh
for _, v in ipairs(totalResults) do
if v.Hit and IsValid(v.Entity) and TraceHitFlesh(v) and (not v.Entity.TFA_HasMeleeHit) then
self:ApplyDamage(v, damage, attk)
self:SmackEffect(v, damage)
v.Entity.TFA_HasMeleeHit = true
fleshHits = fleshHits + 1
if fleshHits >= (attk.maxhits or 3) then break end
if attk.hitflesh and not hitFlesh then
self:EmitSoundNet(attk.hitflesh)
end
if attk.callback and needsCB then
attk.callback(attack, self, v)
needsCB = false
end
hitFlesh = true
end
--debugoverlay.Sphere( v.HitPos, 5, 5, color_white )
end
--Handle non-world
for _, v in ipairs(totalResults) do
if v.Hit and (not TraceHitFlesh(v)) and (not v.Entity.TFA_HasMeleeHit) then
self:ApplyDamage(v, damage, attk)
v.Entity.TFA_HasMeleeHit = true
if not hitNonWorld then
self:SmackEffect(v, damage)
if attk.hitworld and not hitFlesh then
self:EmitSoundNet(attk.hitworld)
end
if attk.callback and needsCB then
attk.callback(attack, self, v)
needsCB = false
end
self:BurstDoor(v.Entity, damage)
hitNonWorld = true
end
end
end
-- Handle world
if not hitNonWorld and not hitFlesh then
for _, v in ipairs(totalResults) do
if v.Hit and v.HitWorld and not hitWorld then
hitWorld = true
if attk.hitworld then
self:EmitSoundNet(attk.hitworld)
end
self:SmackEffect(v, damage)
if attk.callback and needsCB then
attk.callback(attack, self, v)
needsCB = false
end
end
end
end
--Handle empty + cleanup
for _, v in ipairs(totalResults) do
if needsCB then
attk.callback(attack, self, v)
needsCB = false
end
if IsValid(v.Entity) then
v.Entity.TFA_HasMeleeHit = false
end
end
if attack.kickback and (hitFlesh or hitNonWorld or hitWorld) then
self:SendViewModelAnim(attack.kickback)
end
end
function SWEP:PlaySwing(act)
self:SendViewModelAnim(act)
return true, act
end
local lvec, ply, targ
lvec = Vector()
function SWEP:PrimaryAttack()
local ow = self:GetOwner()
if IsValid(ow) and ow:IsNPC() then
local keys = table.GetKeys(self:GetStatL("Primary.Attacks"))
table.RemoveByValue(keys, "BaseClass")
local attk = self:GetStatL("Primary.Attacks")[table.Random(keys)]
local owv = self:GetOwner()
timer.Simple(0.5, function()
if IsValid(self) and IsValid(owv) and owv:IsCurrentSchedule(SCHED_MELEE_ATTACK1) then
attack = attk
self:Strike(attk, 5)
end
end)
self:SetNextPrimaryFire(CurTime() + attk["end"] or 1)
timer.Simple(self:GetNextPrimaryFire() - CurTime(), function()
if IsValid(owv) then
owv:ClearSchedule()
end
end)
self:GetOwner():SetSchedule(SCHED_MELEE_ATTACK1)
return
end
if self:GetSprinting() and not self:GetStatL("AllowSprintAttack", false) then return end
if self:IsSafety() then return end
if not self:VMIV() then return end
if CurTime() <= self:GetNextPrimaryFire() then return end
if not TFA.Enum.ReadyStatus[self:GetStatus()] then return end
if self:GetComboCount() >= self.Primary_TFA.MaxCombo and self.Primary_TFA.MaxCombo > 0 then return end
table.Empty(att)
local founddir = false
if self.Primary_TFA.Directional then
ply = self:GetOwner()
--lvec = WorldToLocal(ply:GetVelocity(), Angle(0, 0, 0), vector_origin, ply:EyeAngles()):GetNormalized()
lvec.x = 0
lvec.y = 0
if ply:KeyDown(IN_MOVERIGHT) then
lvec.y = lvec.y - 1
end
if ply:KeyDown(IN_MOVELEFT) then
lvec.y = lvec.y + 1
end
if ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_JUMP) then
lvec.x = lvec.x + 1
end
if ply:KeyDown(IN_BACK) or ply:KeyDown(IN_DUCK) then
lvec.x = lvec.x - 1
end
lvec.z = 0
--lvec:Normalize()
if lvec.y > 0.3 then
targ = "L"
elseif lvec.y < -0.3 then
targ = "R"
elseif lvec.x > 0.5 then
targ = "F"
elseif lvec.x < -0.1 then
targ = "B"
else
targ = ""
end
for k, v in pairs(self.Primary_TFA.Attacks) do
if (not self:GetSprinting() or v.spr) and v.direction and string.find(v.direction, targ) then
if string.find(v.direction, targ) then
founddir = true
end
table.insert(att, #att + 1, k)
end
end
end
if not self.Primary_TFA.Directional or #att <= 0 or not founddir then
for k, v in pairs(self.Primary_TFA.Attacks) do
if (not self:GetSprinting() or v.spr) and v.dmg then
table.insert(att, #att + 1, k)
end
end
end
if #att <= 0 then return end
ind = att[self:SharedRandom(1, #att, "PrimaryAttack")]
attack = self.Primary_TFA.Attacks[ind]
--We have attack isolated, begin attack logic
self:PlaySwing(attack.act)
if not attack.snd_delay or attack.snd_delay <= 0 then
if IsFirstTimePredicted() then
self:EmitSound(attack.snd)
if self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
end
self:GetOwner():ViewPunch(attack.viewpunch)
elseif attack.snd_delay then
if IsFirstTimePredicted() then
self.AttackSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)
self.VoxSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)
end
--[[
timer.Simple(attack.snd_delay, function()
if IsValid(self) and self:IsValid() and SERVER then
self:EmitSound(attack.snd)
if self:OwnerIsValid() and self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
end
end)
]]
--
self:SetVP(true)
self:SetVPPitch(attack.viewpunch.p)
self:SetVPYaw(attack.viewpunch.y)
self:SetVPRoll(attack.viewpunch.r)
self:SetVPTime(CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act))
self:GetOwner():ViewPunch(-Angle(attack.viewpunch.p / 2, attack.viewpunch.y / 2, attack.viewpunch.r / 2))
end
self.up_hat = false
self:ScheduleStatus(TFA.Enum.STATUS_SHOOTING, attack.delay / self:GetAnimationRate(attack.act))
self:SetMelAttackID(ind)
self:SetNextPrimaryFire(CurTime() + attack["end"] / self:GetAnimationRate(attack.act))
self:GetOwner():SetAnimation(PLAYER_ATTACK1)
self:SetComboCount(self:GetComboCount() + 1)
end
function SWEP:SecondaryAttack()
if self:GetSprinting() and not self:GetStatL("AllowSprintAttack", false) then return end
if self:IsSafety() then return end
if not self:VMIV() then return end
if CurTime() <= self:GetNextPrimaryFire() then return end
if not TFA.Enum.ReadyStatus[self:GetStatus()] then return end
if self:GetComboCount() >= self.Secondary_TFA.MaxCombo and self.Secondary_TFA.MaxCombo > 0 then return end
table.Empty(att)
local founddir = false
if not self.Secondary_TFA.Attacks or #self.Secondary_TFA.Attacks == 0 then
self.Secondary_TFA.Attacks = self.Primary_TFA.Attacks
end
if self.Secondary_TFA.Directional then
ply = self:GetOwner()
--lvec = WorldToLocal(ply:GetVelocity(), Angle(0, 0, 0), vector_origin, ply:EyeAngles()):GetNormalized()
lvec.x = 0
lvec.y = 0
if ply:KeyDown(IN_MOVERIGHT) then
lvec.y = lvec.y - 1
end
if ply:KeyDown(IN_MOVELEFT) then
lvec.y = lvec.y + 1
end
if ply:KeyDown(IN_FORWARD) or ply:KeyDown(IN_JUMP) then
lvec.x = lvec.x + 1
end
if ply:KeyDown(IN_BACK) or ply:KeyDown(IN_DUCK) then
lvec.x = lvec.x - 1
end
lvec.z = 0
--lvec:Normalize()
if lvec.y > 0.3 then
targ = "L"
elseif lvec.y < -0.3 then
targ = "R"
elseif lvec.x > 0.5 then
targ = "F"
elseif lvec.x < -0.1 then
targ = "B"
else
targ = ""
end
for k, v in pairs(self.Secondary_TFA.Attacks) do
if (not self:GetSprinting() or v.spr) and v.direction and string.find(v.direction, targ) then
if string.find(v.direction, targ) then
founddir = true
end
table.insert(att, #att + 1, k)
end
end
end
if not self.Secondary_TFA.Directional or #att <= 0 or not founddir then
for k, v in pairs(self.Secondary_TFA.Attacks) do
if (not self:GetSprinting() or v.spr) and v.dmg then
table.insert(att, #att + 1, k)
end
end
end
if #att <= 0 then return end
ind = att[self:SharedRandom(1, #att, "SecondaryAttack")]
attack = self.Secondary_TFA.Attacks[ind]
--We have attack isolated, begin attack logic
self:PlaySwing(attack.act)
if not attack.snd_delay or attack.snd_delay <= 0 then
if IsFirstTimePredicted() then
self:EmitSound(attack.snd)
if self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
end
self:GetOwner():ViewPunch(attack.viewpunch)
elseif attack.snd_delay then
if IsFirstTimePredicted() then
self.AttackSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)
self.VoxSoundTime = CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act)
end
--[[
timer.Simple(attack.snd_delay, function()
if IsValid(self) and self:IsValid() and SERVER then
self:EmitSound(attack.snd)
if self:OwnerIsValid() and self:GetOwner().Vox then
self:GetOwner():Vox("bash", 4)
end
end
end)
]]
--
self:SetVP(true)
self:SetVPPitch(attack.viewpunch.p)
self:SetVPYaw(attack.viewpunch.y)
self:SetVPRoll(attack.viewpunch.r)
self:SetVPTime(CurTime() + attack.snd_delay / self:GetAnimationRate(attack.act))
self:GetOwner():ViewPunch(-Angle(attack.viewpunch.p / 2, attack.viewpunch.y / 2, attack.viewpunch.r / 2))
end
self.up_hat = false
self:ScheduleStatus(TFA.Enum.STATUS_SHOOTING, attack.delay / self:GetAnimationRate(attack.act))
self:SetMelAttackID(-ind)
self:SetNextPrimaryFire(CurTime() + attack["end"] / self:GetAnimationRate(attack.act))
self:GetOwner():SetAnimation(PLAYER_ATTACK1)
self:SetComboCount(self:GetComboCount() + 1)
end
function SWEP:AltAttack()
if self.CanBlock then
if self.Secondary_TFA.CanBash and self.CanBlock and self:GetOwner():KeyDown(IN_USE) then
BaseClass.AltAttack(self)
return
end
else
if not self:VMIV() then return end
if not TFA.Enum.ReadyStatus[self:GetStatus()] then return end
if not self.Secondary_TFA.CanBash then return end
if self:IsSafety() then return end
return BaseClass.AltAttack(self)
end
end
function SWEP:Reload(released, ovr, ...)
if not self:VMIV() then return end
if ovr then return BaseClass.Reload(self, released, ...) end
if self:GetOwner().GetInfoNum and self:GetOwner():GetInfoNum("cl_tfa_keys_inspect", 0) > 0 then
return
end
if (self.SequenceEnabled[ACT_VM_FIDGET] or self.InspectionActions) and self:GetStatus() == TFA.Enum.STATUS_IDLE then
local _, tanim, ttype = self:ChooseInspectAnim()
self:ScheduleStatus(TFA.Enum.STATUS_FIDGET, self:GetActivityLength(tanim, false, ttype))
end
end
function SWEP:CycleSafety()
end
TFA.FillMissingMetaValues(SWEP)