2014-10-02 08:49:54 +08:00
|
|
|
local meta = FindMetaTable("Entity")
|
2018-05-02 06:32:59 +08:00
|
|
|
|
|
|
|
local vector_origin = vector_origin
|
|
|
|
local util_SharedRandom = util.SharedRandom
|
|
|
|
local util_TraceHull = util.TraceHull
|
|
|
|
local util_TraceLine = util.TraceLine
|
|
|
|
local TEAM_HUMAN = TEAM_HUMAN
|
|
|
|
local TEAM_UNDEAD = TEAM_UNDEAD
|
|
|
|
local HITGROUP_HEAD = HITGROUP_HEAD
|
|
|
|
local MASK_SHOT = MASK_SHOT
|
|
|
|
local pairs = pairs
|
|
|
|
|
|
|
|
local M_Player = FindMetaTable("Player")
|
|
|
|
local P_Team = M_Player.Team
|
|
|
|
local E_IsValid = meta.IsValid
|
|
|
|
|
|
|
|
-- Completely Lua-side bullets
|
|
|
|
local CONTENTS_LIQUID = bit.bor(CONTENTS_WATER, CONTENTS_SLIME)
|
|
|
|
local MASK_SHOT_HIT_WATER = bit.bor(MASK_SHOT, CONTENTS_LIQUID)
|
|
|
|
|
|
|
|
local bullet_tr = {}
|
|
|
|
local bullet_water_tr = {}
|
|
|
|
local temp_angle = Angle(0, 0, 0)
|
|
|
|
local temp_ignore_team
|
|
|
|
local temp_has_spread
|
|
|
|
local method_to_use, base_ang
|
|
|
|
local bullet_trace = {mask = MASK_SHOT, output = bullet_tr}
|
|
|
|
local temp_shooter = NULL
|
|
|
|
local temp_attacker = NULL
|
|
|
|
local attacker_player, inflictor_weapon
|
|
|
|
local temp_vel_ents = {}
|
|
|
|
local function BaseBulletFilter(ent)
|
|
|
|
if ent == temp_shooter or ent == temp_attacker or ent.NeverAlive or ent.SpawnProtection or ent.IgnoreBullets or ent:IsPlayer() and P_Team(ent) == temp_ignore_team then
|
|
|
|
return false
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
2018-05-02 06:32:59 +08:00
|
|
|
|
|
|
|
if ent.AlwaysImpactBullets then
|
|
|
|
return true
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
return true
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
local function HandleShotImpactingWater(damage)
|
|
|
|
-- Trace again with water enabled
|
|
|
|
bullet_trace.mask = MASK_SHOT_HIT_WATER
|
|
|
|
bullet_trace.output = bullet_water_tr
|
|
|
|
util_TraceLine(bullet_trace)
|
|
|
|
bullet_trace.output = bullet_tr
|
|
|
|
bullet_trace.mask = MASK_SHOT
|
|
|
|
|
|
|
|
if bullet_water_tr.AllSolid then return false end
|
|
|
|
|
|
|
|
local contents = util.PointContents(bullet_water_tr.HitPos - bullet_water_tr.HitNormal * 0.1)
|
|
|
|
if bit.band(contents, CONTENTS_LIQUID) == 0 then return false end
|
|
|
|
|
|
|
|
if IsFirstTimePredicted() then
|
|
|
|
local effectdata = EffectData()
|
|
|
|
effectdata:SetOrigin(bullet_water_tr.HitPos)
|
|
|
|
effectdata:SetNormal(bullet_water_tr.HitNormal)
|
|
|
|
effectdata:SetScale(math.Clamp(damage * 0.25, 5, 30))
|
|
|
|
effectdata:SetFlags(bit.band(contents, CONTENTS_SLIME) ~= 0 and 1 or 0)
|
|
|
|
util.Effect("gunshotsplash", effectdata)
|
2015-10-22 15:56:21 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
local wspawn = Entity(0)
|
|
|
|
local function CheckFHB(tr)
|
|
|
|
local ent = tr.Entity
|
|
|
|
if ent == wspawn then return end
|
2015-10-22 16:01:26 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
if ent.FHB and E_IsValid(ent) then
|
|
|
|
tr.Entity = ent:GetParent()
|
|
|
|
end
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
function meta:FireBulletsLua(src, dir, spread, num, damage, attacker, force_mul, tracer, callback, hull_size, hit_own_team, max_distance, filter, inflictor)
|
|
|
|
max_distance = max_distance or 56756
|
|
|
|
attacker = attacker or self
|
|
|
|
if not E_IsValid(attacker) then attacker = self end
|
|
|
|
force_mul = force_mul or 1
|
|
|
|
|
|
|
|
temp_shooter = self
|
|
|
|
temp_attacker = attacker
|
|
|
|
attacker_player = attacker:IsPlayer()
|
|
|
|
inflictor_weapon = inflictor and inflictor:IsWeapon()
|
|
|
|
|
|
|
|
bullet_trace.start = src
|
|
|
|
if filter then
|
|
|
|
bullet_trace.filter = filter
|
|
|
|
else
|
|
|
|
bullet_trace.filter = BaseBulletFilter
|
|
|
|
if not hit_own_team and attacker_player then
|
|
|
|
temp_ignore_team = P_Team(attacker)
|
|
|
|
else
|
|
|
|
temp_ignore_team = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if hull_size then
|
|
|
|
bullet_trace.maxs = Vector(hull_size, hull_size, hull_size) * 0.5
|
|
|
|
bullet_trace.mins = bullet_trace.maxs * -1
|
|
|
|
method_to_use = util_TraceHull
|
|
|
|
else
|
|
|
|
method_to_use = util_TraceLine
|
|
|
|
end
|
|
|
|
|
|
|
|
base_ang = dir:Angle()
|
|
|
|
temp_has_spread = spread > 0
|
|
|
|
|
|
|
|
if SERVER and num > 1 and attacker_player then attacker:StartDamageNumberSession() end
|
|
|
|
|
|
|
|
for i=0, num - 1 do
|
|
|
|
if temp_has_spread then
|
|
|
|
temp_angle:Set(base_ang)
|
|
|
|
temp_angle:RotateAroundAxis(
|
|
|
|
temp_angle:Forward(),
|
|
|
|
inflictor_weapon and util_SharedRandom("bulletrotate" .. i, 0, 360) or math.Rand(0, 360)
|
|
|
|
)
|
|
|
|
temp_angle:RotateAroundAxis(
|
|
|
|
temp_angle:Up(),
|
|
|
|
inflictor_weapon and util_SharedRandom("bulletangle" .. i, -spread, spread) or math.Rand(-spread, spread)
|
|
|
|
)
|
|
|
|
|
|
|
|
dir = temp_angle:Forward()
|
|
|
|
end
|
|
|
|
|
|
|
|
bullet_trace.endpos = src + dir * max_distance
|
|
|
|
bullet_tr = method_to_use(bullet_trace)
|
|
|
|
|
|
|
|
CheckFHB(bullet_tr)
|
|
|
|
|
|
|
|
local hitwater
|
|
|
|
if bit.band(util.PointContents(bullet_tr.HitPos), CONTENTS_LIQUID) ~= 0 then
|
|
|
|
hitwater = HandleShotImpactingWater(damage)
|
|
|
|
end
|
|
|
|
|
|
|
|
local damageinfo = DamageInfo()
|
|
|
|
damageinfo:SetDamageType(DMG_BULLET)
|
|
|
|
damageinfo:SetDamage(damage)
|
|
|
|
damageinfo:SetDamagePosition(bullet_tr.HitPos)
|
|
|
|
damageinfo:SetAttacker(attacker)
|
|
|
|
damageinfo:SetInflictor(inflictor or self)
|
|
|
|
if force_mul > 0 then
|
|
|
|
damageinfo:SetDamageForce(force_mul * damage * 70 * dir:GetNormalized())
|
|
|
|
else
|
|
|
|
damageinfo:SetDamageForce(Vector(0, 0, 1))
|
|
|
|
end
|
|
|
|
|
|
|
|
local use_tracer = true
|
|
|
|
local use_impact = true
|
|
|
|
local use_ragdoll_impact = true
|
|
|
|
local use_damage = true
|
|
|
|
|
|
|
|
if callback then
|
|
|
|
local ret = callback(attacker, bullet_tr, damageinfo)
|
|
|
|
if ret then
|
|
|
|
if ret.donothing then continue end
|
|
|
|
|
|
|
|
if ret.tracer ~= nil then use_tracer = ret.tracer end
|
|
|
|
if ret.impact ~= nil then use_impact = ret.impact end
|
|
|
|
if ret.ragdoll_impact ~= nil then use_ragdoll_impact = ret.ragdoll_impact end
|
|
|
|
if ret.damage ~= nil then use_damage = ret.damage end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local ent = bullet_tr.Entity
|
|
|
|
if E_IsValid(ent) and use_damage then
|
|
|
|
if ent:IsPlayer() then
|
|
|
|
temp_vel_ents[ent] = temp_vel_ents[ent] or ent:GetVelocity()
|
|
|
|
if SERVER then
|
|
|
|
ent:SetLastHitGroup(bullet_tr.HitGroup)
|
|
|
|
if bullet_tr.HitGroup == HITGROUP_HEAD then
|
|
|
|
ent:SetWasHitInHead()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif attacker:IsValidPlayer() then
|
|
|
|
local phys = ent:GetPhysicsObject()
|
|
|
|
if ent:GetMoveType() == MOVETYPE_VPHYSICS and phys:IsValid() and phys:IsMoveable() then
|
|
|
|
ent:SetPhysicsAttacker(attacker)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ent:DispatchTraceAttack(damageinfo, bullet_tr, dir)
|
|
|
|
end
|
|
|
|
|
|
|
|
if SERVER and num > 1 and i == num - 1 and attacker_player then
|
|
|
|
local dmg, dmgpos, haspl = attacker:PopDamageNumberSession()
|
|
|
|
|
|
|
|
if dmg > 0 and dmgpos then
|
|
|
|
GAMEMODE:DamageFloater(attacker, ent, dmgpos, dmg, haspl)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if IsFirstTimePredicted() then
|
|
|
|
local effectdata = EffectData()
|
|
|
|
effectdata:SetOrigin(bullet_tr.HitPos)
|
|
|
|
effectdata:SetStart(src)
|
|
|
|
effectdata:SetNormal(bullet_tr.HitNormal)
|
|
|
|
|
|
|
|
if hitwater then
|
|
|
|
-- We may not impact, but we DO need to affect ragdolls on the client
|
|
|
|
if use_ragdoll_impact then
|
|
|
|
util.Effect("RagdollImpact", effectdata)
|
|
|
|
end
|
|
|
|
elseif use_impact and not bullet_tr.HitSky and bullet_tr.Fraction < 1 then
|
|
|
|
effectdata:SetSurfaceProp(bullet_tr.SurfaceProps)
|
|
|
|
effectdata:SetDamageType(DMG_BULLET)
|
|
|
|
effectdata:SetHitBox(bullet_tr.HitBox)
|
|
|
|
effectdata:SetEntity(ent)
|
|
|
|
util.Effect("Impact", effectdata)
|
|
|
|
end
|
|
|
|
|
|
|
|
if use_tracer then
|
|
|
|
if self:IsPlayer() and E_IsValid(self:GetActiveWeapon()) then
|
|
|
|
effectdata:SetFlags( 0x0003 ) --TRACER_FLAG_USEATTACHMENT + TRACER_FLAG_WHIZ
|
|
|
|
effectdata:SetEntity(self:GetActiveWeapon())
|
|
|
|
effectdata:SetAttachment(1)
|
|
|
|
else
|
|
|
|
effectdata:SetEntity(self)
|
|
|
|
effectdata:SetFlags( 0x0001 ) -- TRACER_FLAG_WHIZ
|
|
|
|
end
|
|
|
|
effectdata:SetScale(5000) -- Tracer travel speed
|
|
|
|
util.Effect(tracer or "Tracer", effectdata)
|
|
|
|
end
|
|
|
|
end
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
-- No knockback vs. players. Do this ONLY once to migitate lag compensation issues instead of per bullet. Might just disable lag comp here.
|
|
|
|
for ent, vel in pairs(temp_vel_ents) do
|
|
|
|
ent:SetLocalVelocity(vel)
|
|
|
|
end
|
|
|
|
table.Empty(temp_vel_ents)
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidPlayer()
|
|
|
|
return E_IsValid(self) and self:IsPlayer()
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidHuman()
|
|
|
|
return E_IsValid(self) and self:IsPlayer() and P_Team(self) == TEAM_HUMAN
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidZombie()
|
|
|
|
return E_IsValid(self) and self:IsPlayer() and P_Team(self) == TEAM_UNDEAD
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsHuman()
|
|
|
|
return self:IsPlayer() and P_Team(self) == TEAM_HUMAN
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsZombie()
|
|
|
|
return self:IsPlayer() and P_Team(self) == TEAM_UNDEAD
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidLivingPlayer()
|
|
|
|
return self:IsValidPlayer() and self:Alive()
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidLivingHuman()
|
|
|
|
return self:IsValidHuman() and self:Alive()
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsValidLivingZombie()
|
|
|
|
return self:IsValidZombie() and self:Alive()
|
2015-10-22 15:52:29 +08:00
|
|
|
end
|
|
|
|
|
2014-10-02 08:49:54 +08:00
|
|
|
function meta:ApplyPlayerProperties(ply)
|
|
|
|
self.GetPlayerColor = function() return ply:GetPlayerColor() end
|
|
|
|
self:SetBodygroup( ply:GetBodygroup(1), 1 )
|
|
|
|
self:SetMaterial( ply:GetMaterial() )
|
|
|
|
self:SetSkin( ply:GetSkin() or 1 )
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetVolume()
|
|
|
|
local mins, maxs = self:OBBMins(), self:OBBMaxs()
|
|
|
|
return (maxs.x - mins.x) + (maxs.y - mins.y) + (maxs.z - mins.z)
|
|
|
|
end
|
|
|
|
|
2014-11-10 11:51:43 +08:00
|
|
|
function meta:TakeSpecialDamage(damage, damagetype, attacker, inflictor, hitpos, damageforce)
|
2018-05-02 06:32:59 +08:00
|
|
|
if self:IsPlayer() and not self:Alive() then return end
|
|
|
|
if not attacker or not E_IsValid(attacker) then attacker = game.GetWorld() end
|
|
|
|
if not inflictor or not E_IsValid(inflictor) then inflictor = attacker end
|
2014-11-10 11:51:43 +08:00
|
|
|
|
2014-10-02 08:49:54 +08:00
|
|
|
local dmginfo = DamageInfo()
|
|
|
|
dmginfo:SetDamage(damage)
|
2018-05-02 06:32:59 +08:00
|
|
|
if attacker then
|
|
|
|
dmginfo:SetAttacker(attacker)
|
2014-11-10 11:51:43 +08:00
|
|
|
end
|
2018-05-02 06:32:59 +08:00
|
|
|
|
|
|
|
local nearestpos = self:NearestPoint(inflictor:NearestPoint(self:LocalToWorld(self:OBBCenter())))
|
|
|
|
|
|
|
|
if inflictor then
|
|
|
|
dmginfo:SetInflictor(inflictor)
|
|
|
|
if hitpos then
|
|
|
|
dmginfo:SetDamagePosition(hitpos)
|
|
|
|
elseif E_IsValid(inflictor) then
|
|
|
|
dmginfo:SetDamagePosition(nearestpos)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
dmginfo:SetDamagePosition(hitpos or nearestpos)
|
|
|
|
end
|
|
|
|
dmginfo:SetDamageType(damagetype)
|
|
|
|
|
|
|
|
local vel = self:GetVelocity()
|
2014-10-02 08:49:54 +08:00
|
|
|
self:TakeDamageInfo(dmginfo)
|
2018-05-02 06:32:59 +08:00
|
|
|
if self:IsPlayer() and attacker ~= self then
|
|
|
|
self:SetLocalVelocity(vel)
|
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
|
|
|
return dmginfo
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:NearestBone(pos)
|
|
|
|
local count = self:GetBoneCount()
|
|
|
|
if count == 0 then return end
|
|
|
|
|
|
|
|
local nearest
|
|
|
|
local nearestdist
|
|
|
|
|
|
|
|
for boneid = 1, count - 1 do
|
2018-05-02 06:32:59 +08:00
|
|
|
local bonepos = self:GetBonePositionMatrixed(boneid)
|
|
|
|
local dist = bonepos:DistToSqr(pos)
|
2014-10-02 08:49:54 +08:00
|
|
|
|
|
|
|
if not nearest or dist < nearestdist then
|
|
|
|
nearest = boneid
|
|
|
|
nearestdist = dist
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return nearest
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsProjectile()
|
2018-05-02 06:32:59 +08:00
|
|
|
return self:GetCollisionGroup() == COLLISION_GROUP_PROJECTILE or self.AlwaysProjectile
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:SetupGenericProjectile(gravity)
|
|
|
|
self:SetCustomCollisionCheck(true)
|
|
|
|
self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE)
|
|
|
|
|
|
|
|
local phys = self:GetPhysicsObject()
|
|
|
|
if phys:IsValid() then
|
|
|
|
phys:SetMass(1)
|
|
|
|
phys:SetBuoyancyRatio(0.0001)
|
|
|
|
phys:EnableMotion(true)
|
|
|
|
if not gravity then phys:EnableGravity(gravity) end
|
|
|
|
phys:EnableDrag(false)
|
|
|
|
phys:Wake()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:ProjectileDamageSource()
|
|
|
|
return (self.ProjSource and E_IsValid(self.ProjSource)) and self.ProjSource or self
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
function meta:ResetBones(onlyscale)
|
|
|
|
local v = Vector(1, 1, 1)
|
2018-05-02 06:32:59 +08:00
|
|
|
local bcount = self.BuildingBones or self:GetBoneCount() - 1
|
2014-10-02 08:49:54 +08:00
|
|
|
if onlyscale then
|
2018-05-02 06:32:59 +08:00
|
|
|
for i=0, bcount do
|
2014-10-02 08:49:54 +08:00
|
|
|
self:ManipulateBoneScale(i, v)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local a = Angle(0, 0, 0)
|
2018-05-02 06:32:59 +08:00
|
|
|
for i=0, bcount do
|
2014-10-02 08:49:54 +08:00
|
|
|
self:ManipulateBoneScale(i, v)
|
|
|
|
self:ManipulateBoneAngles(i, a)
|
|
|
|
self:ManipulateBonePosition(i, vector_origin)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetBarricadeHealth()
|
|
|
|
return self:GetDTFloat(1)
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetMaxBarricadeHealth()
|
|
|
|
return self:GetDTFloat(2)
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetBarricadeRepairs()
|
|
|
|
return self:GetDTFloat(3)
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetMaxBarricadeRepairs()
|
2018-05-02 06:32:59 +08:00
|
|
|
return self:GetMaxBarricadeHealth() * GAMEMODE.BarricadeRepairCapacity
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetBonePositionMatrixed(index)
|
|
|
|
local matrix = self:GetBoneMatrix(index)
|
|
|
|
if matrix then
|
|
|
|
return matrix:GetTranslation(), matrix:GetAngles()
|
|
|
|
end
|
|
|
|
|
|
|
|
return self:GetPos(), self:GetAngles()
|
|
|
|
end
|
|
|
|
|
|
|
|
-- This needs to be done otherwise the physics might crash.
|
|
|
|
function meta:CollisionRulesChanged()
|
|
|
|
if not self.m_OldCollisionGroup then self.m_OldCollisionGroup = self:GetCollisionGroup() end
|
|
|
|
self:SetCollisionGroup(self.m_OldCollisionGroup == COLLISION_GROUP_DEBRIS and COLLISION_GROUP_WORLD or COLLISION_GROUP_DEBRIS)
|
|
|
|
self:SetCollisionGroup(self.m_OldCollisionGroup)
|
|
|
|
self.m_OldCollisionGroup = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetAlpha()
|
|
|
|
return self:GetColor().a
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:SetAlpha(a)
|
|
|
|
local col = self:GetColor()
|
|
|
|
col.a = a
|
|
|
|
self:SetColor(col)
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:IsBarricadeProp()
|
|
|
|
return self.IsBarricadeObject or self:IsNailed()
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:GetHolder()
|
|
|
|
for _, ent in pairs(ents.FindByClass("status_human_holding")) do
|
|
|
|
if ent:GetObject() == self then
|
|
|
|
local owner = ent:GetOwner()
|
|
|
|
if owner:IsPlayer() and owner:Alive() then return owner, ent end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:ThrowFromPosition(pos, force, noknockdown)
|
|
|
|
if force == 0 or self:IsProjectile() or self.NoThrowFromPosition then return false end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
if self:IsPlayer() then
|
|
|
|
if self:ActiveBarricadeGhosting() then return false end
|
|
|
|
|
|
|
|
force = force * (self.KnockbackScale or 1)
|
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
|
|
|
if self:GetMoveType() == MOVETYPE_VPHYSICS then
|
|
|
|
local phys = self:GetPhysicsObject()
|
|
|
|
if phys:IsValid() and phys:IsMoveable() then
|
|
|
|
local nearest = self:NearestPoint(pos)
|
|
|
|
phys:ApplyForceOffset(force * 50 * (nearest - pos):GetNormalized(), nearest)
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
elseif self:GetMoveType() >= MOVETYPE_WALK and self:GetMoveType() < MOVETYPE_PUSH then
|
|
|
|
self:SetGroundEntity(NULL)
|
|
|
|
if SERVER and not noknockdown and self:IsPlayer() then
|
|
|
|
local absforce = math.abs(force)
|
2018-05-02 06:32:59 +08:00
|
|
|
if absforce >= 512 or self.IsClumsy and P_Team(self) == TEAM_HUMAN and absforce >= 32 then
|
2014-10-02 08:49:54 +08:00
|
|
|
self:KnockDown()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:SetVelocity(force * (self:LocalToWorld(self:OBBCenter()) - pos):GetNormalized())
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function meta:ThrowFromPositionSetZ(pos, force, zmul, noknockdown)
|
|
|
|
if force == 0 or self:IsProjectile() or self.NoThrowFromPosition then return false end
|
|
|
|
zmul = zmul or 0.7
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
if self:IsPlayer() then
|
|
|
|
if self:ActiveBarricadeGhosting() or self.SpawnProtection then return false end
|
|
|
|
|
|
|
|
force = force * (self.KnockbackScale or 1)
|
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
|
|
|
if self:GetMoveType() == MOVETYPE_VPHYSICS then
|
|
|
|
local phys = self:GetPhysicsObject()
|
|
|
|
if phys:IsValid() and phys:IsMoveable() then
|
|
|
|
local nearest = self:NearestPoint(pos)
|
|
|
|
local dir = nearest - pos
|
|
|
|
dir.z = 0
|
|
|
|
dir:Normalize()
|
|
|
|
dir.z = zmul
|
|
|
|
phys:ApplyForceOffset(force * 50 * dir, nearest)
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
elseif self:GetMoveType() >= MOVETYPE_WALK and self:GetMoveType() < MOVETYPE_PUSH then
|
|
|
|
self:SetGroundEntity(NULL)
|
|
|
|
if SERVER and not noknockdown and self:IsPlayer() then
|
|
|
|
local absforce = math.max(math.abs(force) * math.abs(zmul), math.abs(force))
|
2018-05-02 06:32:59 +08:00
|
|
|
if absforce >= 512 or self.IsClumsy and P_Team(self) == TEAM_HUMAN and absforce >= 32 then
|
2014-10-02 08:49:54 +08:00
|
|
|
self:KnockDown()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local dir = self:LocalToWorld(self:OBBCenter()) - pos
|
|
|
|
dir.z = 0
|
|
|
|
dir:Normalize()
|
|
|
|
dir.z = zmul
|
|
|
|
self:SetVelocity(force * dir)
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
util.PrecacheSound("player/pl_pain5.wav")
|
|
|
|
util.PrecacheSound("player/pl_pain6.wav")
|
|
|
|
util.PrecacheSound("player/pl_pain7.wav")
|
2018-05-02 06:32:59 +08:00
|
|
|
function meta:PoisonDamage(damage, attacker, inflictor, hitpos, noreduction, instant)
|
2014-10-02 08:49:54 +08:00
|
|
|
damage = damage or 1
|
|
|
|
|
|
|
|
local dmginfo = DamageInfo()
|
|
|
|
|
|
|
|
if self:IsPlayer() then
|
2018-05-02 06:32:59 +08:00
|
|
|
if P_Team(self) ~= TEAM_HUMAN then return end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
--[[if self.BuffResistant then
|
2014-10-02 08:49:54 +08:00
|
|
|
damage = damage / 2
|
2018-05-02 06:32:59 +08:00
|
|
|
end]]
|
|
|
|
|
|
|
|
if self.PoisonDamageTakenMul then
|
|
|
|
damage = damage * self.PoisonDamageTakenMul
|
|
|
|
end
|
|
|
|
|
|
|
|
if inflictor:IsProjectile() and self.ProjDamageTakenMul then
|
|
|
|
damage = damage * self.ProjDamageTakenMul
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
self:ViewPunch(Angle(math.random(-10, 10), math.random(-10, 10), math.random(-20, 20)))
|
2018-05-02 06:32:59 +08:00
|
|
|
self:EmitSound(string.format("player/pl_pain%d.wav", math.random(5, 7)))
|
|
|
|
|
|
|
|
if not instant then
|
|
|
|
if SERVER then
|
|
|
|
local dmg = math.floor(damage * 0.5)
|
|
|
|
if dmg > 0 then
|
|
|
|
self:AddPoisonDamage(dmg, attacker)
|
|
|
|
end
|
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
damage = math.ceil(damage * 0.5)
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
dmginfo:SetDamageType(DMG_ACID)
|
|
|
|
elseif self.GetObjectHealth then
|
2014-10-02 08:49:54 +08:00
|
|
|
dmginfo:SetDamageType(DMG_ACID)
|
|
|
|
else
|
2018-05-02 06:32:59 +08:00
|
|
|
--[[if not noreduction then
|
2014-10-02 08:49:54 +08:00
|
|
|
damage = damage / 3
|
2018-05-02 06:32:59 +08:00
|
|
|
end]]
|
2014-10-02 08:49:54 +08:00
|
|
|
dmginfo:SetDamageType(DMG_SLASH) -- Fixes not doing damage to props.
|
|
|
|
end
|
|
|
|
|
|
|
|
attacker = attacker or self
|
|
|
|
inflictor = inflictor or attacker
|
|
|
|
|
|
|
|
dmginfo:SetDamagePosition(hitpos or self:NearestPoint(inflictor:NearestPoint(self:LocalToWorld(self:OBBCenter()))))
|
|
|
|
dmginfo:SetDamage(damage)
|
|
|
|
dmginfo:SetAttacker(attacker)
|
|
|
|
dmginfo:SetInflictor(inflictor)
|
|
|
|
self:TakeDamageInfo(dmginfo)
|
|
|
|
end
|
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
-- Fix sequence duration being able to be nil
|
|
|
|
local OldSequenceDuration = meta.SequenceDuration
|
|
|
|
function meta:SequenceDuration(seqid)
|
|
|
|
return OldSequenceDuration(self, seqid) or 0
|
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
-- Workaround for DispatchTraceAttack not interacting with some entities. Replace me after appropriate binding exists.
|
|
|
|
local OldDispatch = meta.DispatchTraceAttack
|
|
|
|
function meta:DispatchTraceAttack(dmginfo, tr, dir)
|
|
|
|
if self.NoTraceAttack then
|
|
|
|
self:TakeDamageInfo(dmginfo)
|
|
|
|
else
|
|
|
|
OldDispatch(self, dmginfo, tr, dir)
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
2018-05-02 06:32:59 +08:00
|
|
|
end
|
2014-10-02 08:49:54 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
function meta:IsPhysicsModel(mdl)
|
|
|
|
return string.sub(self:GetClass(), 1, 12) == "prop_physics" and (not mdl or string.lower(self:GetModel()) == string.lower(mdl))
|
2014-10-02 08:49:54 +08:00
|
|
|
end
|
2015-06-04 04:53:02 +08:00
|
|
|
|
2018-05-02 06:32:59 +08:00
|
|
|
function meta:IsWeaponType(class)
|
|
|
|
return self:GetClass() == "prop_weapon" and self:GetWeaponType() == class or (self:IsWeapon() and self:GetClass() == class)
|
2015-06-04 04:53:02 +08:00
|
|
|
end
|