zombiesurvival-evolved/gamemodes/zombiesurvival/gamemode/obj_entity_extend.lua

584 lines
16 KiB
Lua
Raw Normal View History

2014-10-02 08:49:54 +08:00
local meta = FindMetaTable("Entity")
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
end
if ent.AlwaysImpactBullets then
return true
end
return true
end
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
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
if ent.FHB and E_IsValid(ent) then
tr.Entity = ent:GetParent()
end
end
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
end
-- 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()
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
function meta:TakeSpecialDamage(damage, damagetype, attacker, inflictor, hitpos, damageforce)
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-10-02 08:49:54 +08:00
local dmginfo = DamageInfo()
dmginfo:SetDamage(damage)
if attacker then
dmginfo:SetAttacker(attacker)
end
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)
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
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()
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)
local bcount = self.BuildingBones or self:GetBoneCount() - 1
2014-10-02 08:49:54 +08:00
if onlyscale then
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)
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()
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
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)
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
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))
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")
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
if P_Team(self) ~= TEAM_HUMAN then return end
2014-10-02 08:49:54 +08:00
--[[if self.BuffResistant then
2014-10-02 08:49:54 +08:00
damage = damage / 2
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)))
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
damage = math.ceil(damage * 0.5)
2014-10-02 08:49:54 +08:00
end
dmginfo:SetDamageType(DMG_ACID)
elseif self.GetObjectHealth then
2014-10-02 08:49:54 +08:00
dmginfo:SetDamageType(DMG_ACID)
else
--[[if not noreduction then
2014-10-02 08:49:54 +08:00
damage = damage / 3
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
-- 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
-- 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
end
2014-10-02 08:49:54 +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
function meta:IsWeaponType(class)
return self:GetClass() == "prop_weapon" and self:GetWeaponType() == class or (self:IsWeapon() and self:GetClass() == class)
end