--[[ | 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)