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

1765 lines
49 KiB
Lua

local meta = FindMetaTable("Player")
local P_Team = meta.Team
local DMG_TAKE_BLEED = DMG_SLASH + DMG_CLUB + DMG_BULLET + DMG_BUCKSHOT + DMG_CRUSH
function meta:ProcessDamage(dmginfo)
if not self:IsValidLivingPlayer() then return end --??? Apparently player was null sometimes on server?
local attacker, inflictor, dmgtype = dmginfo:GetAttacker(), dmginfo:GetInflictor(), dmginfo:GetDamageType()
if not GAMEMODE:PlayerShouldTakeDamage(self, attacker) then return true end
local dmgbypass = bit.band(dmgtype, DMG_DIRECT) ~= 0
if self.DamageVulnerability and not dmgbypass then
dmginfo:SetDamage(dmginfo:GetDamage() * self.DamageVulnerability)
end
if attacker.AttackerForward and attacker.AttackerForward:IsValid() then
dmginfo:SetAttacker(attacker.AttackerForward)
attacker = attacker.AttackerForward
if attacker:IsPlayer() and attacker:Team() == P_Team(self) and attacker ~= self then
dmginfo:SetDamage(0)
end
end
if attacker.PBAttacker and attacker.PBAttacker:IsValid() then
attacker = attacker.PBAttacker
end
if P_Team(self) == TEAM_UNDEAD then
if self.SpawnProtection then
dmginfo:SetDamage(0)
dmginfo:ScaleDamage(0)
dmginfo:SetDamageForce(vector_origin)
return
end
local corrosion = self.Corrosion and self.Corrosion + 2 > CurTime()
if self ~= attacker and not corrosion and not dmgbypass then
dmginfo:SetDamage(dmginfo:GetDamage() * GAMEMODE:GetZombieDamageScale(dmginfo:GetDamagePosition(), self))
end
self.ShouldFlinch = true
if attacker:IsValidLivingHuman() and inflictor:IsValid() and inflictor == attacker:GetActiveWeapon() then
local damage = dmginfo:GetDamage()
local wep = attacker:GetActiveWeapon()
local attackermaxhp = math.floor(attacker:GetMaxHealth() * (attacker:IsSkillActive(SKILL_D_FRAIL) and 0.25 or 1))
if wep.IsMelee then
if attacker:IsSkillActive(SKILL_CHEAPKNUCKLE) and math.abs(self:GetForward():Angle().yaw - attacker:GetForward():Angle().yaw) <= 90 then
self:AddLegDamage(12)
end
if attacker.MeleeDamageToBloodArmorMul and attacker.MeleeDamageToBloodArmorMul > 0 and attacker:GetBloodArmor() < attacker.MaxBloodArmor then
attacker:SetBloodArmor(math.min(attacker.MaxBloodArmor, attacker:GetBloodArmor() + math.min(damage, self:Health()) * attacker.MeleeDamageToBloodArmorMul * attacker.BloodarmorGainMul))
end
if attacker:IsSkillActive(SKILL_HEAVYSTRIKES) and not self:GetZombieClassTable().Boss and (wep.IsFistWeapon and attacker:IsSkillActive(SKILL_CRITICALKNUCKLE) or wep.MeleeKnockBack > 0) then
attacker:TakeSpecialDamage(damage * (wep.Unarmed and 1 or 0.08), DMG_SLASH, self, self:GetActiveWeapon())
end
if attacker:IsSkillActive(SKILL_BLOODLUST) and attacker:GetPhantomHealth() > 0 and attacker:Health() < attackermaxhp then
local toheal = math.min(attacker:GetPhantomHealth(), math.min(self:Health(), damage * 0.25))
attacker:SetHealth(math.min(attacker:Health() + toheal, attackermaxhp))
attacker:SetPhantomHealth(attacker:GetPhantomHealth() - toheal)
end
if attacker:HasTrinket("sharpkit") then
dmginfo:SetDamage(dmginfo:GetDamage() * (1 + self:GetFlatLegDamage()/75))
end
if wep.Culinary and attacker:IsSkillActive(SKILL_MASTERCHEF) and math.random(9) == 1 then
self.ChefMarkOwner = attacker
self.ChefMarkTime = CurTime() + 1
end
end
end
return not dmgbypass and self:CallZombieFunction1("ProcessDamage", dmginfo)
end
-- Opted for multiplicative.
if attacker == self and dmgtype ~= DMG_CRUSH and dmgtype ~= DMG_FALL and self.SelfDamageMul then
dmginfo:SetDamage(dmginfo:GetDamage() * self.SelfDamageMul)
end
if bit.band(dmgtype, DMG_ALWAYSGIB) ~= 0 and self.ExplosiveDamageTakenMul then
dmginfo:SetDamage(dmginfo:GetDamage() * self.ExplosiveDamageTakenMul)
end
if bit.band(dmgtype, DMG_BURN) ~= 0 and self.FireDamageTakenMul then
dmginfo:SetDamage(dmginfo:GetDamage() * self.FireDamageTakenMul)
end
if inflictor:IsValid() and (inflictor:IsPhysicsModel() or inflictor.IsPhysbox) and self:IsValidLivingHuman() then
local damage = dmginfo:GetDamage()
local forcedamp = self:HasTrinket("forcedamp")
local noadj = attacker:IsValidLivingZombie() and attacker:GetZombieClassTable().NoAdjustPhysDamage
if forcedamp or (attacker:IsValidLivingZombie() and not noadj) then
damage = math.max(5, (math.log10(damage) * 26)-5)
end
if self.PhysicsDamageTakenMul then
damage = damage * self.PhysicsDamageTakenMul
end
if damage >= 30 and not forcedamp and not noadj and inflictor ~= attacker then
self:KnockDown(damage * 0.05)
end
dmginfo:SetDamage(damage)
end
if attacker:IsValid() and attacker:IsPlayer() and inflictor:IsValid() and attacker:Team() == TEAM_UNDEAD then
if inflictor == attacker:GetActiveWeapon() then
local damage = dmginfo:GetDamage()
if self:IsBarricadeGhosting() then
self:SetLegDamage(21 * (self.SlowEffTakenMul or 1))
else
local scale = inflictor.SlowDownScale or 1
if damage >= 45 or scale > 1 then
local dolegdamage = true
if inflictor.SlowDownImmunityTime then
if CurTime() < (self.SlowDownImmunityTime or 0) then
dolegdamage = false
else
self.SlowDownImmunityTime = CurTime() + inflictor.SlowDownImmunityTime
end
end
if dolegdamage then
self:RawCapLegDamage(self:GetLegDamage() + CurTime() + damage * 0.04 * (inflictor.SlowDownScale or 1) * (self.SlowEffTakenMul or 1))
end
end
end
if bit.band(dmgtype, DMG_SLASH) ~= 0 or inflictor.IsMelee then
if self.BarbedArmor and self.BarbedArmor > 0 then
attacker:TakeSpecialDamage(self.BarbedArmor, DMG_SLASH, self, self)
attacker:AddArmDamage(self.BarbedArmor)
end
if self.BarbedArmorPercent and self.BarbedArmorPercent > 0 then
attacker:TakeSpecialDamage(damage * self.BarbedArmorPercent, DMG_SLASH, self, self)
end
if self:HasTrinket("reactiveflasher") and (not self.LastReactiveFlash or self.LastReactiveFlash + 75 < CurTime()) then
attacker:ScreenFade(SCREENFADE.IN, nil, 1, 1)
attacker:SetDSP(36)
attacker:GiveStatus("disorientation", 2)
self:EmitSound("weapons/flashbang/flashbang_explode2.wav")
local effectdata = EffectData()
effectdata:SetOrigin(self:GetPos())
util.Effect("HelicopterMegaBomb", effectdata)
self.LastReactiveFlash = CurTime()
self.ReactiveFlashMessage = nil
elseif self:HasTrinket("bleaksoul") and (not self.LastBleakSoul or self.LastBleakSoul + 35 < CurTime()) then
attacker:GiveStatus("dimvision", 3)
attacker:SetGroundEntity(nil)
attacker:SetLocalVelocity((attacker:GetPos() - self:GetPos()):GetNormalized() * 450 + Vector(0, 0, 140))
self:EmitSound("ambient/creatures/town_child_scream1.wav", 70, 60)
self:EmitSound("npc/stalker/go_alert2a.wav", 70, 45, 0.25)
self.LastBleakSoul = CurTime()
self.BleakSoulMessage = nil
end
if self:HasTrinket("iceburst") and (not self.LastIceBurst or self.LastIceBurst + 40 < CurTime()) then
attacker:AddLegDamageExt(17, attacker, attacker, SLOWTYPE_COLD)
local effectdata = EffectData()
effectdata:SetOrigin(self:GetPos())
util.Effect("explosion_cold", effectdata)
self.LastIceBurst = CurTime()
self.IceBurstMessage = nil
end
if self.MeleeDamageTakenMul and not dmgbypass then
dmginfo:SetDamage(dmginfo:GetDamage() * self.MeleeDamageTakenMul)
end
if self:IsSkillActive(SKILL_BACKPEDDLER) then
self:AddLegDamage(8)
end
end
if self.HasHemophilia and (damage >= 4 and dmgtype == 0 or bit.band(dmgtype, DMG_TAKE_BLEED) ~= 0) then
local bleed = self:GiveStatus("bleed")
if bleed and bleed:IsValid() then
bleed:AddDamage(damage * 0.25)
if attacker:IsValid() and attacker:IsPlayer() then
bleed.Damager = attacker
end
end
end
elseif inflictor:IsProjectile() then
if self.ProjDamageTakenMul and not dmgbypass then
dmginfo:SetDamage(dmginfo:GetDamage() * self.ProjDamageTakenMul)
end
end
end
self.NextBloodArmorRegen = CurTime() + 5
if self:GetBloodArmor() > 0 then
local damage = dmginfo:GetDamage()
if damage > 0 then
if damage >= self:GetBloodArmor() and self:IsSkillActive(SKILL_BLOODLETTER) then
local bleed = self:GiveStatus("bleed")
if bleed and bleed:IsValid() then
bleed:AddDamage(5)
bleed.Damager = self
end
end
local ratio = 0.5 + self.BloodArmorDamageReductionAdd + (self:IsSkillActive(SKILL_IRONBLOOD) and self:Health() <= self:GetMaxHealth() * 0.5 and 0.25 or 0)
local absorb = math.min(self:GetBloodArmor(), damage * ratio)
dmginfo:SetDamage(damage - absorb)
self:SetBloodArmor(self:GetBloodArmor() - absorb)
if attacker:IsValid() and attacker:IsPlayer() then
local myteam = attacker:Team()
local otherteam = P_Team(self)
attacker.DamageDealt[myteam] = attacker.DamageDealt[myteam] + absorb
if myteam == TEAM_UNDEAD and otherteam == TEAM_HUMAN then
attacker:AddLifeHumanDamage(absorb)
end
end
if damage > 20 and damage - absorb <= 0 then
self:EmitSound("physics/flesh/flesh_strider_impact_bullet3.wav", 55)
end
end
end
if self:IsSkillActive(SKILL_BLOODLUST) and attacker:IsValid() and attacker:IsPlayer() and inflictor:IsValid() and attacker:Team() == TEAM_UNDEAD then
self:SetPhantomHealth(math.min(self:GetPhantomHealth() + dmginfo:GetDamage() / 2, self:GetMaxHealth()))
end
if dmginfo:GetDamage() > 0 and not self:HasGodMode() then
self.NextRegenTrinket = CurTime() + 12
self.ShouldFlinch = true
end
end
GM.TrinketRecharges = {
reactiveflasher = {"ReactiveFlashMessage", "LastReactiveFlash", "Reactive Flasher", 75},
bleaksoul = {"BleakSoulMessage", "LastBleakSoul", "Bleak Soul", 35},
biocleanser = {"BioCleanserMessage", "LastBioCleanser", "Bio Cleanser", 20},
iceburst = {"IceBurstMessage", "LastIceBurst", "Iceburst Shield", 40}
}
function meta:CheckTrinketRecharges()
local time = CurTime()
for trinket, data in pairs(GAMEMODE.TrinketRecharges) do
local msg, last, name, delay = data[1], data[2], data[3], data[4]
if self:HasTrinket(trinket) and not self[msg] and self[last] and time > self[last] + delay then
self:CenterNotify(COLOR_ORANGE, translate.ClientFormat(self, "trinket_recharged", name))
self:SendLua("surface.PlaySound(\"buttons/button5.wav\")")
self[msg] = true
end
end
end
function meta:HasWon()
if P_Team(self) == TEAM_HUMAN and self:GetObserverMode() == OBS_MODE_ROAMING then
local target = self:GetObserverTarget()
return target and target:IsValid() and target:GetClass() == "prop_obj_exit"
end
return false
end
function meta:GetBossZombieIndex()
local bossclasses = {}
for _, classtable in pairs(GAMEMODE.ZombieClasses) do
if classtable.Boss then
table.insert(bossclasses, classtable.Index)
end
end
if #bossclasses == 0 then return -1 end
local desired = self:GetInfo("zs_bossclass") or ""
if GAMEMODE:IsBabyMode() then
desired = "Giga Gore Child"
elseif desired == "[RANDOM]" or desired == "" then
desired = "Nightmare"
end
local bossindex
for _, classindex in pairs(bossclasses) do
local classtable = GAMEMODE.ZombieClasses[classindex]
if string.lower(classtable.Name) == string.lower(desired) then
bossindex = classindex
break
end
end
return bossindex or bossclasses[1]
end
function meta:ShouldReviveFrom(dmginfo, hullzplane)
return not self.Revive
and not dmginfo:GetInflictor().IsMelee and not dmginfo:GetInflictor().NoReviveFromKills
and dmginfo:GetAttacker() ~= self and dmginfo:GetAttacker():IsPlayer()
and dmginfo:GetDamageType() ~= DMG_ALWAYSGIB and dmginfo:GetDamageType() ~= DMG_BURN
and (not hullzplane and (self:LastHitGroup() == HITGROUP_LEFTLEG or self:LastHitGroup() == HITGROUP_RIGHTLEG)
or dmginfo:GetDamagePosition().z < self:LocalToWorld(Vector(0, 0, hullzplane)).z)
end
function meta:NearestArsenalCrateOwnedByOther()
local pos = self:EyePos()
local arseents = {}
table.Add(arseents, ents.FindByClass("prop_arsenalcrate"))
table.Add(arseents, ents.FindByClass("status_arsenalpack"))
for _, ent in pairs(arseents) do
local nearest = ent:NearestPoint(pos)
local owner = ent.GetObjectOwner and ent:GetObjectOwner() or ent:GetOwner()
if owner ~= self and owner:IsValidHuman() and pos:DistToSqr(nearest) <= 10000 and (WorldVisible(pos, nearest) or self:TraceLine(100).Entity == ent) then
return ent
end
end
end
local OldLastHitGroup = meta.LastHitGroup
function meta:LastHitGroup()
return self.m_LastHitGroupUnset and CurTime() <= self.m_LastHitGroupUnset and self.m_LastHitGroup or OldLastHitGroup(self)
end
function meta:SetLastHitGroup(hitgroup)
self.m_LastHitGroup = hitgroup
self.m_LastHitGroupUnset = CurTime() + 0.2
end
function meta:WasHitInHead()
return self.m_LastHitInHead and CurTime() <= self.m_LastHitInHead
end
function meta:SetWasHitInHead()
self.m_LastHitInHead = CurTime() + 0.2
end
function meta:SetPoints(points)
self:SetDTInt(1, points)
end
function meta:SetBloodArmor(armor)
self:SetDTInt(DT_PLAYER_INT_BLOODARMOR, armor)
end
function meta:WouldDieFrom(damage, hitpos)
return self:Health() <= damage * GAMEMODE:GetZombieDamageScale(hitpos, self)
end
function meta:KnockDown(time)
if P_Team(self) == TEAM_HUMAN then
self:GiveStatus("knockdown", time or 3)
end
end
function meta:FakeDeath(sequenceid, modelscale, length, start)
for _, ent in pairs(ents.FindByClass("fakedeath")) do
if ent:GetOwner() == self then
ent:Remove()
end
end
local ent = ents.Create("fakedeath")
if ent:IsValid() then
ent:SetOwner(self)
ent:SetModel(self:GetModel())
ent:SetSkin(self:GetSkin())
ent:SetColor(self:GetColor())
ent:SetMaterial(self:GetMaterial())
ent:SetPos(self:GetPos() + Vector(0, 0, 64))
ent:Spawn()
ent:SetModelScale(modelscale or self:GetModelScale(), 0)
ent:SetDeathSequence(sequenceid or 0)
ent:SetDeathAngles(self:GetAngles())
ent:SetDeathSequenceLength(length or 1)
ent:SetDeathSequenceStart(start or 0)
self:DeleteOnRemove(ent)
end
return ent
end
function meta:TrySpawnAsGoreChild(ent)
if ent and ent:IsValid() and not ent.SpawnedOn and ent:GetSettled() then
ent.SpawnedOn = true
local ang = self:EyeAngles()
ang.roll = 0
ang.pitch = 0
self.OldDeathClass = self.DeathClass
self:SetZombieClassName(ent:GetClass() == "prop_thrownbaby" and "Gore Child" or "Shadow Child")
self.DeathClass = nil
self.DidntSpawnOnSpawnPoint = true
self:UnSpectateAndSpawn()
self.Master = ent:GetOwner()
self.DeathClass = self.OldDeathClass
self:SetPos(ent:GetPos())
self:SetEyeAngles(ang)
self:StartFeignDeath(true)
if IsValid(self.FeignDeath) then
self.FeignDeath:SetState(1)
self.FeignDeath:SetDirection(math.random(2) == 1 and DIR_FORWARD or DIR_BACK)
end
ent:Remove()
end
end
function meta:SendLifeStats()
if self.LifeStatSend and self.LifeStatSend > CurTime() then return end
self.LifeStatSend = CurTime() + 0.33
net.Start("zs_lifestats")
net.WriteUInt(math.ceil(self.LifeBarricadeDamage or 0), 16)
net.WriteUInt(math.ceil(self.LifeHumanDamage or 0), 16)
net.WriteUInt(self.LifeBrainsEaten or 0, 8)
net.Send(self)
end
function meta:AddLifeBarricadeDamage(amount)
self.LifeBarricadeDamage = self.LifeBarricadeDamage + amount
self.WaveBarricadeDamage = self.WaveBarricadeDamage + amount
if not self:Alive() and not self:GetZombieClassTable().NeverAlive then
timer.Simple(0, function() if self:IsValid() then self:SendLifeStats() end end)
end
end
function meta:AddLifeHumanDamage(amount)
self.LifeHumanDamage = self.LifeHumanDamage + amount
self.WaveHumanDamage = self.WaveHumanDamage + amount
if not self:Alive() then
timer.Simple(0, function() if self:IsValid() then self:SendLifeStats() end end)
end
end
function meta:AddLifeBrainsEaten(amount)
self.LifeBrainsEaten = self.LifeBrainsEaten + amount
if not self:Alive() then
timer.Simple(0, function() if self:IsValid() then self:SendLifeStats() end end)
end
end
function meta:RemoveEphemeralStatuses()
for _, status in pairs(ents.FindByClass("status_*")) do
if status.Ephemeral and status:IsValid() and status:GetOwner() == self then
status:Remove()
end
end
end
function meta:AddPoisonDamage(damage, attacker)
--damage = math.ceil(damage)
if damage > 0 then
local status = self:GiveStatus("poison")
if status and status:IsValid() then
status:AddDamage(damage, attacker)
end
else
local status = self:GetStatus("poison")
if status and status:IsValid() then
status:AddDamage(damage)
end
end
end
function meta:AddBleedDamage(damage, attacker)
--damage = math.ceil(damage)
if damage > 0 then
local status = self:GiveStatus("bleed")
if status and status:IsValid() then
status:AddDamage(damage, attacker)
end
else
local status = self:GiveStatus("bleed")
if status and status:IsValid() then
status:AddDamage(damage)
end
end
end
function meta:FloatingScore(victimorpos, effectname, frags, flags)
if type(victimorpos) == "Vector" then
net.Start("zs_floatscore_vec")
net.WriteVector(victimorpos)
net.WriteString(effectname)
net.WriteInt(frags, 24)
net.WriteUInt(flags, 8)
net.Send(self)
else
net.Start("zs_floatscore")
net.WriteEntity(victimorpos)
net.WriteString(effectname)
net.WriteInt(frags, 24)
net.WriteUInt(flags, 8)
net.Send(self)
end
end
function meta:CollectDamageNumberSession(dmg, lastpos, hasplayer)
self.DamageNumberTally = (self.DamageNumberTally or 0) + dmg
self.DamageNumberLastPos = self.DamageNumberLastPos and ((self.DamageNumberLastPos + lastpos)/2) or lastpos
self.DamageNumberHasPlayer = self.DamageNumberHasPlayer or hasplayer
end
function meta:StartDamageNumberSession()
self.DamageNumberTally = 0
end
function meta:HasDamageNumberSession()
return self.DamageNumberTally
end
function meta:PopDamageNumberSession()
local tally, lastpos, hasplayer =
self.DamageNumberTally,
self.DamageNumberLastPos,
self.DamageNumberHasPlayer
self.DamageNumberTally = nil
self.DamageNumberLastPos = nil
self.DamageNumberHasPlayer = nil
return tally, lastpos, hasplayer
end
function meta:MarkAsBadProfile()
self.NoProfiling = true
end
function meta:CenterNotify(...)
net.Start("zs_centernotify")
net.WriteTable({...})
net.Send(self)
end
function meta:TopNotify(...)
net.Start("zs_topnotify")
net.WriteTable({...})
net.Send(self)
end
function meta:RefreshDynamicSpawnPoint()
local target = self:GetObserverTarget()
if (GAMEMODE:GetDynamicSpawning() and self:GetObserverMode() == OBS_MODE_CHASE and target and target:IsValid()) and
(self.ZombieEscape and target:IsPlayer() and target:Team() == TEAM_UNDEAD
or not self.ZombieEscape and (target.IsCreeperNest and target:GetNestBuilt() or target.MinionSpawn and target:GetSettled())
or string.sub(target:GetClass(), 1, 12) == "info_player_") then
self.ForceDynamicSpawn = target
self.ForceSpawnAngles = self:EyeAngles()
else
self.ForceDynamicSpawn = nil
self.ForceSpawnAngles = nil
end
end
function meta:PushPackedItem(class, ...)
if self.PackedItems and ... ~= nil then
local packed = {...}
self.PackedItems[class] = self.PackedItems[class] or {}
table.insert(self.PackedItems[class], packed)
end
end
function meta:PopPackedItem(class)
if self.PackedItems and self.PackedItems[class] and self.PackedItems[class][1] ~= nil then
local index = #self.PackedItems[class]
local data = self.PackedItems[class][index]
table.remove(self.PackedItems[class], index)
return data
end
end
function meta:ChangeToCrow()
self.StartCrowing = nil
local crowclass = GAMEMODE.ZombieClasses["Crow"]
if not crowclass then return end
local curclass = self.DeathClass or self:GetZombieClass()
local crowindex = crowclass.Index
self:SetZombieClass(crowindex)
self:DoHulls(crowindex, TEAM_UNDEAD)
self.DeathClass = nil
self:UnSpectateAndSpawn()
self.DeathClass = curclass
end
function meta:SelectRandomPlayerModel()
self:SetModel(player_manager.TranslatePlayerModel(GAMEMODE.RandomPlayerModels[math.random(#GAMEMODE.RandomPlayerModels)]))
end
function meta:GiveEmptyWeapon(weptype)
if not self:HasWeapon(weptype) then
local wep = self:Give(weptype)
if wep and wep:IsValid() and wep:IsWeapon() then
wep:EmptyAll()
end
return wep
end
end
local OldGive = meta.Give
function meta:Give(weptype, noammo)
if P_Team(self) ~= TEAM_HUMAN then
return OldGive(self, weptype, noammo)
end
local weps = self:GetWeapons()
local autoswitch = #weps == 1 and weps[1]:IsValid() and weps[1].AutoSwitchFrom
local ret = OldGive(self, weptype, noammo)
if autoswitch then
self:SelectWeapon(weptype)
end
return ret
end
function meta:StartFeignDeath(force)
local feigndeath = self.FeignDeath
if feigndeath and feigndeath:IsValid() then
if CurTime() >= feigndeath:GetStateEndTime() then
feigndeath:SetState(1)
feigndeath:SetStateEndTime(CurTime() + 1.5)
end
elseif force or self:IsOnGround() and not self:IsPlayingTaunt() then
local wep = self:GetActiveWeapon()
if force or wep:IsValid() and not (wep.IsSwinging and wep:IsSwinging()) and CurTime() > wep:GetNextPrimaryFire() then
if wep:IsValid() and wep.StopMoaning then
wep:StopMoaning()
end
local status = self:GiveStatus("feigndeath")
if status and status:IsValid() then
status:SetStateEndTime(CurTime() + 1.5)
end
end
end
end
function meta:UpdateLegDamage()
net.Start("zs_legdamage")
net.WriteFloat(self.LegDamage)
net.Send(self)
end
function meta:UpdateArmDamage()
net.Start("zs_armdamage")
net.WriteFloat(self.ArmDamage)
net.Send(self)
end
function meta:CoupleWith(plheadcrab)
if self:GetZombieClassTable().Headcrab == plheadcrab:GetZombieClassTable().Name then
local status = self:GiveStatus("headcrabcouple")
if status:IsValid() then
status:SetCouple(plheadcrab)
end
end
end
function meta:FixModelAngles(velocity)
local eye = self:EyeAngles()
self:SetLocalAngles(eye)
self:SetPoseParameter("move_yaw", math.NormalizeAngle(velocity:Angle().yaw - eye.y))
end
function meta:RemoveAllStatus(bSilent, bInstant)
if bInstant then
for _, ent in pairs(ents.FindByClass("status_*")) do
if not ent.NoRemoveOnDeath and ent:GetOwner() == self then
ent:Remove()
end
end
else
for _, ent in pairs(ents.FindByClass("status_*")) do
if not ent.NoRemoveOnDeath and ent:GetOwner() == self then
ent.SilentRemove = bSilent
ent:SetDie()
end
end
end
end
function meta:RemoveStatus(sType, bSilent, bInstant, sExclude)
local removed
for _, ent in pairs(ents.FindByClass("status_"..sType)) do
if ent:GetOwner() == self and not (sExclude and ent:GetClass() == "status_"..sExclude) then
if bInstant then
ent:Remove()
else
ent.SilentRemove = bSilent
ent:SetDie()
end
removed = true
end
end
return removed
end
function meta:GetStatus(sType)
local ent = self["status_"..sType]
if ent and ent:IsValid() and ent:GetOwner() == self then return ent end
end
function meta:GiveStatus(sType, fDie)
local resistable = table.HasValue(GAMEMODE.ResistableStatuses, sType)
if resistable and self:IsSkillActive(SKILL_HAEMOSTASIS) and self:GetBloodArmor() >= 2 then
self:SetBloodArmor(self:GetBloodArmor() - 2)
return
end
if resistable and self:HasTrinket("biocleanser") and (not self.LastBioCleanser or self.LastBioCleanser + 20 < CurTime()) then
self.LastBioCleanser = CurTime()
self.BioCleanserMessage = nil
end
local cur = self:GetStatus(sType)
if cur then
if fDie then
cur:SetDie(fDie)
end
cur:SetPlayer(self, true)
return cur
else
local ent = ents.Create("status_"..sType)
if ent:IsValid() then
ent:Spawn()
if fDie then
ent:SetDie(fDie)
end
ent:SetPlayer(self)
return ent
end
end
end
function meta:UnSpectateAndSpawn()
self:UnSpectate()
self:Spawn()
end
function meta:SecondWind(pl)
if self.Gibbed or self:Alive() or P_Team(self) ~= TEAM_UNDEAD then return end
local pos = self:GetPos()
local angles = self:EyeAngles()
local lastattacker = self:GetLastAttacker()
local dclass = self.DeathClass
self.DeathClass = nil
self.Revived = true
self:UnSpectateAndSpawn()
self.Revived = nil
self.DeathClass = dclass
self:SetLastAttacker(lastattacker)
self:SetPos(pos)
self:SetHealth(self:Health() * 0.2)
self:SetEyeAngles(angles)
self:CallZombieFunction0("OnSecondWind")
end
function meta:DropAll()
self:DropAllWeapons()
self:DropAllAmmo()
self:DropAllInventoryItems()
end
local function CreateRagdoll(pl)
if pl:IsValid() then pl:OldCreateRagdoll() end
end
local function SetModel(pl, mdl)
if pl:IsValid() then
pl:SetModel(mdl)
timer.Simple(0, function() CreateRagdoll(pl) end)
end
end
meta.OldCreateRagdoll = meta.OldCreateRagdoll or meta.CreateRagdoll
function meta:CreateRagdoll()
local status = self.status_overridemodel
if status and status:IsValid() then
local mdl = status:GetModel()
timer.Simple(0, function() SetModel(self, mdl) end)
status:SetRenderMode(RENDERMODE_NONE)
else
self:OldCreateRagdoll()
end
end
function meta:DropWeaponByType(class)
if GAMEMODE.ZombieEscape then return end
local wep = self:GetWeapon(class)
if wep and wep:IsValid() and not wep.Undroppable then
local ent = ents.Create("prop_weapon")
if ent:IsValid() then
ent:SetWeaponType(class)
ent:Spawn()
if wep.AmmoIfHas then
local ammocount = wep:GetPrimaryAmmoCount()
local desiredrop = math.min(ammocount, wep.Primary.ClipSize) - wep:Clip1()
if desiredrop > 0 then
wep:TakeCombinedPrimaryAmmo(desiredrop)
wep:SetClip1(desiredrop)
end
end
ent:SetClip1(wep:Clip1())
ent:SetClip2(wep:Clip2())
ent.DroppedTime = CurTime()
self:StripWeapon(class)
self:UpdateAltSelectedWeapon()
return ent
end
end
end
function meta:DropAllWeapons()
local vPos = self:GetPos()
local vVel = self:GetVelocity()
local zmax = self:OBBMaxs().z * 0.75
for _, wep in pairs(self:GetWeapons()) do
if wep:IsValid() then
local ent = self:DropWeaponByType(wep:GetClass())
if ent and ent:IsValid() then
ent:SetPos(vPos + Vector(math.Rand(-16, 16), math.Rand(-16, 16), math.Rand(2, zmax)))
ent:SetAngles(VectorRand():Angle())
local phys = ent:GetPhysicsObject()
if phys:IsValid() then
phys:AddAngleVelocity(Vector(math.Rand(-720, 720), math.Rand(-720, 720), math.Rand(-720, 720)))
phys:ApplyForceCenter(phys:GetMass() * (math.Rand(32, 328) * VectorRand():GetNormalized() + vVel))
end
end
end
end
if GAMEMODE.ZombieEscape then
local zewep = self:GetWeapon("weapon_elite")
if zewep and zewep:IsValid() then
self:DropWeapon(zewep)
end
end
end
function meta:DropAmmoByType(ammotype, amount)
if GAMEMODE.ZombieEscape then return end
local mycount = self:GetAmmoCount(ammotype)
amount = math.min(mycount, amount or mycount)
if not amount or amount <= 0 then return end
local ent = ents.Create("prop_ammo")
if ent:IsValid() then
ent:SetAmmoType(ammotype)
ent:SetAmmo(amount)
ent:Spawn()
ent.DroppedTime = CurTime()
self:RemoveAmmo(amount, ammotype)
return ent
end
end
function meta:DropAllAmmo()
local vPos = self:GetPos()
local vVel = self:GetVelocity()
local zmax = self:OBBMaxs().z * 0.75
for ammotype in pairs(GAMEMODE.AmmoCache) do
local ent = self:DropAmmoByType(ammotype)
if ent and ent:IsValid() then
ent:SetPos(vPos + Vector(math.Rand(-16, 16), math.Rand(-16, 16), math.Rand(2, zmax)))
ent:SetAngles(VectorRand():Angle())
local phys = ent:GetPhysicsObject()
if phys:IsValid() then
phys:AddAngleVelocity(Vector(math.Rand(-720, 720), math.Rand(-720, 720), math.Rand(-720, 720)))
phys:ApplyForceCenter(phys:GetMass() * (math.Rand(32, 328) * VectorRand():GetNormalized() + vVel))
end
end
end
end
function meta:Resupply(owner, obj)
if GAMEMODE:GetWave() <= 0 then return end
local stockpiling = self:IsSkillActive(SKILL_STOCKPILE)
local stowage = self:IsSkillActive(SKILL_STOWAGE)
if (stowage and (self.StowageCaches or 0) <= 0) or (not stowage and CurTime() < (self.NextResupplyUse or 0)) then
self:CenterNotify(COLOR_RED, translate.ClientGet(self, "no_ammo_here"))
return
end
if not stowage then
self.NextResupplyUse = CurTime() + GAMEMODE.ResupplyBoxCooldown * (self.ResupplyDelayMul or 1) * (stockpiling and 2.12 or 1)
net.Start("zs_nextresupplyuse")
net.WriteFloat(self.NextResupplyUse)
net.Send(self)
else
self.StowageCaches = self.StowageCaches - 1
net.Start("zs_stowagecaches")
net.WriteInt(self.StowageCaches, 8)
net.Send(self)
end
local ammotype = self:GetResupplyAmmoType()
local amount = GAMEMODE.AmmoCache[ammotype]
for i = 1, stockpiling and not stowage and 2 or 1 do
net.Start("zs_ammopickup")
net.WriteUInt(amount, 16)
net.WriteString(ammotype)
net.Send(self)
self:GiveAmmo(amount, ammotype)
if self:IsSkillActive(SKILL_FORAGER) and math.random(4) == 1 and #GAMEMODE.Food > 0 then
self:Give(GAMEMODE.Food[math.random(#GAMEMODE.Food)])
end
if self ~= owner and owner:IsValidHuman() then
if obj:GetClass() == "prop_resupplybox" then
owner.ResupplyBoxUsedByOthers = owner.ResupplyBoxUsedByOthers + 1
end
owner:AddPoints(0.15, nil, nil, true)
net.Start("zs_commission")
net.WriteEntity(obj)
net.WriteEntity(self)
net.WriteFloat(0.15)
net.Send(owner)
end
end
return true
end
-- Lets other players know about our maximum health.
meta.OldSetMaxHealth = meta.OldSetMaxHealth or FindMetaTable("Entity").SetMaxHealth
function meta:SetMaxHealth(num)
num = math.ceil(num)
self:SetDTInt(0, num)
self:OldSetMaxHealth(num)
end
function meta:PointCashOut(ent, fmtype)
if self.PointQueue >= 1 and P_Team(self) == TEAM_HUMAN then
local points = self.PointQueue --math.floor(self.PointQueue)
self.PointQueue = self.PointQueue - points
self:AddPoints(points, ent or self.LastDamageDealtPos or vector_origin, fmtype)
end
end
function meta:AddPoints(points, floatingscoreobject, fmtype, nomul)
if gamemode.Call("IsEscapeDoorOpen") then return end
if points > 0 and not nomul and self.PointIncomeMul then
points = points * self.PointIncomeMul
end
-- This lets us add partial amounts of points (floats)
local wholepoints = math.floor(points)
local remainder = points - wholepoints
if remainder > 0 then
self.PointsRemainder = self.PointsRemainder + remainder
local carryover = math.floor(self.PointsRemainder)
wholepoints = wholepoints + carryover
self.PointsRemainder = self.PointsRemainder - carryover
end
local absorb = self.PointsToAbsorb
if absorb and absorb > 0 then
wholepoints = math.max(wholepoints - absorb, 0)
self.PointsToAbsorb = absorb - wholepoints
if wholepoints <= 0 then return end
self.PointsToAbsorb = nil
end
self:AddFrags(wholepoints)
self:SetPoints(self:GetPoints() + wholepoints)
if self.PointsVault then
self.PointsVault = self.PointsVault + wholepoints * GAMEMODE.PointSaving
end
if floatingscoreobject then
self:FloatingScore(floatingscoreobject, "floatingscore", wholepoints, fmtype or FM_NONE)
end
local xp = wholepoints
if GAMEMODE.HumanXPMulti and GAMEMODE.HumanXPMulti >= 0 then
xp = xp * GAMEMODE.HumanXPMulti
local wholexp = math.floor(xp)
local xpremainder = xp - wholexp
if xpremainder > 0 then
self.XPRemainder = self.XPRemainder + xpremainder
local xpcarryover = math.floor(self.XPRemainder)
xp = wholexp + xpcarryover
self.XPRemainder = self.XPRemainder - xpcarryover
end
end
self:AddZSXP(xp * (self.RedeemBonus and 1.15 or 1))
gamemode.Call("PlayerPointsAdded", self, wholepoints)
end
function meta:TakePoints(points)
self:SetPoints(self:GetPoints() - points)
if self.PointsVault then
self.PointsVault = self.PointsVault - points
end
end
function meta:UpdateAllZombieClasses()
for _, pl in pairs(player.GetAll()) do
if pl ~= self and pl:Team() == TEAM_UNDEAD then
local id = pl:GetZombieClass()
if id and 0 < id then
net.Start("zs_zclass")
net.WriteEntity(pl)
net.WriteUInt(id, 8)
net.Send(self)
end
end
end
end
function meta:CreateAmbience(class)
class = "status_"..class
for _, ent in pairs(ents.FindByClass(class)) do
if ent:GetOwner() == self then return end
end
local ent = ents.Create(class)
if ent:IsValid() then
ent:SetPos(self:LocalToWorld(self:OBBCenter()))
self[class] = ent
ent:SetOwner(self)
ent:SetParent(self)
ent:Spawn()
end
end
function meta:SetZombieClass(cl, onlyupdate, filter)
if onlyupdate then
net.Start("zs_zclass")
net.WriteEntity(self)
net.WriteUInt(cl, 8)
if filter then
net.Send(filter)
else
net.Broadcast()
end
return
end
self:CallZombieFunction0("SwitchedAway")
local classtab = GAMEMODE.ZombieClasses[cl]
if classtab then
self.Class = cl
if P_Team(self) == TEAM_UNDEAD then
self:DoHulls(cl)
end
self:CallZombieFunction0("SwitchedTo")
net.Start("zs_zclass")
net.WriteEntity(self)
net.WriteUInt(cl, 8)
if filter then
net.Send(filter)
else
net.Broadcast()
end
end
end
function meta:DoHulls(classid, teamid)
teamid = teamid or P_Team(self)
classid = classid or self:GetZombieClass()
if teamid == TEAM_UNDEAD then
local classtab = GAMEMODE.ZombieClasses[classid]
if classtab then
if self:Alive() then
self:SetMoveType(classtab.MoveType or MOVETYPE_WALK)
end
if classtab.ModelScale then
self:SetModelScale(classtab.ModelScale, 0)
elseif self:GetModelScale() ~= DEFAULT_MODELSCALE then
self:SetModelScale(DEFAULT_MODELSCALE, 0)
end
if not classtab.Hull or not classtab.HullDuck then
self:ResetHull()
end
if classtab.ViewOffset then
self:SetViewOffset(classtab.ViewOffset)
elseif self:GetViewOffset() ~= DEFAULT_VIEW_OFFSET then
self:SetViewOffset(DEFAULT_VIEW_OFFSET)
end
if classtab.ViewOffsetDucked then
self:SetViewOffsetDucked(classtab.ViewOffsetDucked)
elseif self:GetViewOffsetDucked() ~= DEFAULT_VIEW_OFFSET_DUCKED then
self:SetViewOffsetDucked(DEFAULT_VIEW_OFFSET_DUCKED)
end
if classtab.HullDuck then
self:SetHullDuck(classtab.HullDuck[1], classtab.HullDuck[2])
end
if classtab.Hull then
self:SetHull(classtab.Hull[1], classtab.Hull[2])
end
if classtab.StepSize then
self:SetStepSize(classtab.StepSize)
elseif self:GetStepSize() ~= DEFAULT_STEP_SIZE then
self:SetStepSize(DEFAULT_STEP_SIZE)
end
if classtab.JumpPower then
self:SetJumpPower(classtab.JumpPower)
elseif self:GetJumpPower() ~= DEFAULT_JUMP_POWER then
self:SetJumpPower(DEFAULT_JUMP_POWER)
end
if classtab.Gravity then
self:SetGravity(classtab.Gravity)
elseif self:GetGravity() ~= 1 then
self:SetGravity(1)
end
local bloodcolor = classtab.BloodColor or 0
if self:GetBloodColor() ~= bloodcolor then
self:SetBloodColor(bloodcolor)
end
self:DrawShadow(not classtab.NoShadow)
self:SetRenderMode(classtab.RenderMode or RENDERMODE_NORMAL)
self.NoCollideAll = classtab.NoCollideAll or (classtab.ModelScale or 1) ~= DEFAULT_MODELSCALE
--self.NoCollideInside = classtab.NoCollideInside or (classtab.ModelScale or 1) ~= DEFAULT_MODELSCALE
self.AllowTeamDamage = classtab.AllowTeamDamage
self.NeverAlive = classtab.NeverAlive
self.KnockbackScale = classtab.KnockbackScale
local phys = self:GetPhysicsObject()
if phys:IsValid() then
phys:SetMass(classtab.Mass or DEFAULT_MASS)
end
end
else
self:SetModelScale(DEFAULT_MODELSCALE, 0)
self:ResetHull()
self:SetViewOffset(DEFAULT_VIEW_OFFSET)
self:SetViewOffsetDucked(DEFAULT_VIEW_OFFSET_DUCKED)
self:SetStepSize(DEFAULT_STEP_SIZE)
self:SetJumpPower(DEFAULT_JUMP_POWER)
self:SetGravity(1)
self:SetBloodColor(0)
self:DrawShadow(true)
self:SetRenderMode(RENDERMODE_NORMAL)
self.NoCollideAll = nil
--self.NoCollideInside = nil
self.AllowTeamDamage = nil
self.NeverAlive = nil
self.KnockbackScale = nil
local phys = self:GetPhysicsObject()
if phys:IsValid() then
phys:SetMass(DEFAULT_MASS)
end
end
net.Start("zs_dohulls")
net.WriteEntity(self)
net.WriteUInt(classid, 8)
net.WriteBool(teamid == TEAM_UNDEAD)
net.Broadcast()
self:CollisionRulesChanged()
end
function meta:ChangeTeam(teamid)
local oldteam = P_Team(self)
if oldteam ~= teamid then
gamemode.Call("PreOnPlayerChangedTeam", self, oldteam, teamid)
end
self:SetTeam(teamid)
self:CollisionRulesChanged()
if oldteam ~= teamid then
gamemode.Call("OnPlayerChangedTeam", self, oldteam, teamid)
end
end
function meta:Redeem(silent, noequip)
if gamemode.Call("PrePlayerRedeemed", self) then return end
self:RemoveStatus("overridemodel", false, true)
self:KillSilent()
self:ChangeTeam(TEAM_HUMAN)
if not GAMEMODE.InitialVolunteers[self:UniqueID()] then
self:AddZSXP(50 * (GAMEMODE.ZombieXPMulti or 1))
self.RedeemBonus = true
end
if not noequip then self.m_PreRedeem = true end
self:Spawn()
self.m_PreRedeem = nil
self:DoHulls()
local frags = self:Frags()
if frags < 0 then
self:SetFrags(frags * 5)
else
self:SetFrags(0)
end
self:SetDeaths(0)
--[[self.DeathClass = nil
self:SetZombieClass(GAMEMODE.DefaultZombieClass)]]
self.DeathClass = GAMEMODE.DefaultZombieClass
self.SpawnedTime = CurTime()
if not silent then
net.Start("zs_playerredeemed")
net.WriteEntity(self)
net.Broadcast()
end
gamemode.Call("PostPlayerRedeemed", self)
end
function meta:RedeemNextFrame()
timer.Simple(0, function()
if IsValid(self) then
self:CheckRedeem(true)
end
end)
end
local walltrace = {mask = MASK_SOLID_BRUSHONLY, mins = Vector(-8, -8, -8), maxs = Vector(8, 8, 8)}
function meta:ShouldCrouchJumpPunish()
if not self:OnGround() and self:Crouching() and self:GetZombieClassTable().CrouchedWalkSpeed ~= 1 then
local pos = self:WorldSpaceCenter()
walltrace.start = pos
walltrace.endpos = pos + self:GetForward() * 22
return not util.TraceHull(walltrace).Hit
end
return false
end
function meta:TakeBrains(amount)
self:AddFrags(-amount)
self.BrainsEaten = self.BrainsEaten - 1
end
function meta:AddBrains(amount)
self:AddFrags(amount)
self.BrainsEaten = self.BrainsEaten + 1
self:CheckRedeem()
end
meta.GetBrains = meta.Frags
function meta:CheckRedeem(instant)
if not self:IsValid() or P_Team(self) ~= TEAM_UNDEAD
or GAMEMODE:GetRedeemBrains() <= 0 or self:GetBrains() < GAMEMODE:GetRedeemBrains()
or GAMEMODE.NoRedeeming or self.NoRedeeming or self:GetZombieClassTable().Boss then return end
if GAMEMODE:GetWave() ~= GAMEMODE:GetNumberOfWaves() or not GAMEMODE.ObjectiveMap and GAMEMODE:GetNumberOfWaves() == 1 and CurTime() < GAMEMODE:GetWaveEnd() - 300 then
if instant then
self:Redeem()
else
self:RedeemNextFrame()
end
end
end
function meta:AntiGrief(dmginfo, overridenostrict)
if GAMEMODE.GriefStrict and not overridenostrict then
dmginfo:SetDamage(0)
dmginfo:ScaleDamage(0)
return
end
dmginfo:SetDamage(dmginfo:GetDamage() * GAMEMODE.GriefForgiveness)
self:GivePenalty(math.ceil(dmginfo:GetDamage() * 0.5))
self:ReflectDamage(dmginfo:GetDamage())
end
function meta:GivePenalty(amount)
self.m_PenaltyCarry = (self.m_PenaltyCarry or 0) + amount * 0.1
local frags = math.floor(self.m_PenaltyCarry)
if frags > 0 then
self.m_PenaltyCarry = self.m_PenaltyCarry - frags
self:GivePointPenalty(frags)
end
end
function meta:GivePointPenalty(amount)
self:SetFrags(self:Frags() - amount)
net.Start("zs_penalty")
net.WriteUInt(amount, 16)
net.Send(self)
end
function meta:ReflectDamage(damage)
local frags = self:Frags()
if frags < GAMEMODE.GriefReflectThreshold then
self:TakeDamage(math.ceil(damage * frags * -0.05 * GAMEMODE.GriefDamageMultiplier))
end
end
function meta:GiveWeaponByType(weapon, plyr, ammo)
local wep = self:GetActiveWeapon()
if not wep or not wep:IsValid() then return end
if wep.NoTransfer then return end
if ammo or wep.AmmoIfHas then
if not wep.Primary then return end
local ammotype = wep:ValidPrimaryAmmo()
local ammocount = wep:GetPrimaryAmmoCount()
if ammotype and ammocount then
local desiredgive = math.min(ammocount, math.ceil((GAMEMODE.AmmoCache[ammotype] or wep.Primary.ClipSize) * (ammo and 5 or 1)))
if desiredgive >= 1 then
wep:TakeCombinedPrimaryAmmo(desiredgive)
plyr:GiveAmmo(desiredgive, ammotype)
net.Start("zs_ammogive")
net.WriteUInt(desiredgive, 16)
net.WriteString(ammotype)
net.WriteEntity(plyr)
net.Send(self)
net.Start("zs_ammogiven")
net.WriteUInt(desiredgive, 16)
net.WriteString(ammotype)
net.WriteEntity(self)
net.Send(plyr)
self:PlayGiveAmmoSound()
self:RestartGesture(ACT_GMOD_GESTURE_ITEM_GIVE)
end
end
end
local primary = wep:ValidPrimaryAmmo()
if primary and 0 < wep:Clip1() then
self:GiveAmmo(wep:Clip1(), primary, true)
wep:SetClip1(0)
end
local secondary = wep:ValidSecondaryAmmo()
if secondary and 0 < wep:Clip2() then
self:GiveAmmo(wep:Clip2(), secondary, true)
wep:SetClip2(0)
end
self:StripWeapon(weapon:GetClass())
self:UpdateAltSelectedWeapon()
local wep2 = plyr:Give(weapon:GetClass())
if wep2 and wep2:IsValid() then
if wep2.Primary then
primary = wep2:ValidPrimaryAmmo()
if primary then
wep2:SetClip1(0)
plyr:RemoveAmmo(math.max(0, wep2.Primary.DefaultClip - wep2.Primary.ClipSize), primary)
end
end
if wep2.Secondary then
secondary = wep2:ValidSecondaryAmmo()
if secondary then
wep2:SetClip2(0)
plyr:RemoveAmmo(math.max(0, wep2.Secondary.DefaultClip - wep2.Secondary.ClipSize), secondary)
end
end
end
end
function meta:Gib()
local pos = self:WorldSpaceCenter()
local effectdata = EffectData()
effectdata:SetEntity(self)
effectdata:SetOrigin(pos)
util.Effect("gib_player", effectdata, true, true)
self.Gibbed = CurTime()
timer.Simple(0, function() GAMEMODE:CreateGibs(pos, pos2) end)
end
function meta:GetLastAttacker()
local ent = self.LastAttacker
if ent and ent:IsValid() and CurTime() <= self.LastAttacked + 10 then
return ent
end
--self:SetLastAttacker()
end
function meta:SetLastAttacker(ent)
if ent then
if ent ~= self then
self.LastAttacker = ent
self.LastAttacked = CurTime()
end
else
self.LastAttacker = nil
self.LastAttacked = nil
end
end
meta.OldUnSpectate = meta.OldUnSpectate or meta.UnSpectate
function meta:UnSpectate()
if self:GetObserverMode() ~= OBS_MODE_NONE then
self:OldUnSpectate(obsm)
end
end
local function nocollidetimer(self, timername)
if self:IsValid() then
for _, e in pairs(ents.FindInBox(self:WorldSpaceAABB())) do
if e and e:IsValid() and e:IsPlayer() and e ~= self and GAMEMODE:ShouldCollide(self, e) then
return
end
end
self:SetCollisionGroup(COLLISION_GROUP_PLAYER)
end
timer.Remove(timername)
end
function meta:TemporaryNoCollide(force)
if self:GetCollisionGroup() ~= COLLISION_GROUP_PLAYER and not force then return end
for _, e in pairs(ents.FindInBox(self:WorldSpaceAABB())) do
if e and e:IsValid() and e:IsPlayer() and e ~= self and GAMEMODE:ShouldCollide(self, e) then
self:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER)
local timername = "TemporaryNoCollide"..self:UniqueID()
timer.Create(timername, 0, 0, function() nocollidetimer(self, timername) end)
return
end
end
self:SetCollisionGroup(COLLISION_GROUP_PLAYER)
end
function meta:PlayEyePainSound()
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_eyepain")
net.WriteEntity(self)
net.Send(rf)
end
function meta:PlayGiveAmmoSound()
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_giveammo")
net.WriteEntity(self)
net.Send(rf)
end
function meta:PlayDeathSound()
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_death")
net.WriteEntity(self)
net.Send(rf)
end
function meta:PlayZombieDeathSound()
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_zombiedeath")
net.WriteEntity(self)
net.Send(rf)
end
function meta:PlayPainSound()
if CurTime() < self.NextPainSound then return end
self.NextPainSound = CurTime() + 0.5
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_pain")
net.WriteEntity(self)
net.WriteUInt(math.ceil(self:Health() / 25), 4)
net.Send(rf)
end
function meta:PlayZombiePainSound()
if CurTime() < self.NextPainSound then return end
self.NextPainSound = CurTime() + 0.5
local rf = RecipientFilter()
rf:AddPAS(self:GetPos())
net.Start("voice_zombiepain")
net.WriteEntity(self)
net.Send(rf)
end
function meta:DoSigilTeleport(target, from, corrupted)
if not target then return end
if corrupted == nil then corrupted = false end
if from and not from:IsValid() or not from:IsWeapon() and self:GetPos():DistToSqr(from:GetPos()) > 16384 or from:IsWeapon() and self:GetActiveWeapon() ~= from then
return
end
if self:IsValidLivingHuman() and target:IsValid() and corrupted == target:GetSigilCorrupted() then
if CurTime() >= (self._NextSigilTeleportEffect or 0) then
self._NextSigilTeleportEffect = CurTime() + 0.25
local effect = corrupted and "corrupted_teleport" or "sigil_teleport"
local effectdata = EffectData()
effectdata:SetOrigin(self:WorldSpaceCenter())
effectdata:SetEntity(self)
util.Effect(effect, effectdata, true, true)
effectdata:SetOrigin(target:WorldSpaceCenter())
util.Effect(effect, effectdata, true, true)
end
local movepos = target:GetPos() % 2
self:SetBarricadeGhosting(true, true)
for i=1, 5 do
self:SetPos(movepos)
self:SetLocalPos(movepos)
end
hook.Add("Move", self, function(_, p, mv)
if p == self then
hook.Remove("Move", p)
mv:SetOrigin(movepos)
end
end)
for _, e in pairs(ents.FindInSphere(movepos, 64)) do
if e:IsValidLivingZombie() then
e:TemporaryNoCollide(true)
end
end
if from and from:IsValid() and from:IsWeapon() then
from:TakePrimaryAmmo(1)
if from:GetPrimaryAmmoCount() <= 0 then
self:StripWeapon(from:GetClass())
end
end
end
end
local bossdrops = {
"trinket_bleaksoul",
"trinket_spiritess"
}
function meta:MakeBossDrop()
local drop = table.Random(bossdrops)
local inv = string.sub(drop, 1, 4) ~= "weap"
local pos = self:LocalToWorld(self:OBBCenter())
local ent = ents.Create(inv and "prop_invitem" or "prop_weapon")
if ent:IsValid() then
ent:SetPos(pos)
ent:SetAngles(AngleRand())
if inv then
ent:SetInventoryItemType(drop)
else
ent:SetWeaponType(drop)
end
ent:Spawn()
local phys = ent:GetPhysicsObject()
if phys:IsValid() then
phys:Wake()
phys:SetVelocityInstantaneous(VectorRand():GetNormalized() * math.Rand(24, 100))
phys:AddAngleVelocity(VectorRand() * 200)
end
end
end
function meta:UpdateAltSelectedWeapon()
net.Start("zs_updatealtselwep")
net.Send(self)
end
function meta:SendDeployableLostMessage(deployable)
local deployableclass = deployable:GetClass()
local deployableinfo = GAMEMODE.DeployableInfo[deployableclass]
net.Start("zs_deployablelost")
net.WriteString(deployableinfo.Name)
net.WriteString(deployableinfo.WepClass)
net.Send(self)
end
function meta:SendDeployableClaimedMessage(deployable)
local deployableclass = deployable:GetClass()
local deployableinfo = GAMEMODE.DeployableInfo[deployableclass]
net.Start("zs_deployableclaim")
net.WriteString(deployableinfo.Name)
net.WriteString(deployableinfo.WepClass)
net.Send(self)
end
function meta:SendDeployableOutOfAmmoMessage(deployable)
local deployableclass = deployable:GetClass()
local deployableinfo = GAMEMODE.DeployableInfo[deployableclass]
net.Start("zs_deployableout")
net.WriteString(deployableinfo.Name)
net.WriteString(deployableinfo.WepClass)
net.Send(self)
end
function meta:GetRandomStartingItem()
local pool = {}
if self:IsSkillActive(SKILL_PREPAREDNESS) and #GAMEMODE.Food > 0 then
pool[#pool + 1] = GAMEMODE.Food[math.random(#GAMEMODE.Food)]
end
if self:IsSkillActive(SKILL_EQUIPPED) then
pool[#pool + 1] = GAMEMODE.StarterTrinkets[math.random(#GAMEMODE.StarterTrinkets)]
end
if #pool > 0 then
return pool[math.random(#pool)]
end
end
function meta:PulseResonance(attacker, inflictor)
-- Weird things happen with multishot weapons..
timer.Create("PulseResonance" .. attacker:UniqueID(), 0.06, 1, function()
if not attacker:IsValid() or not self:IsValid() then return end
attacker.AccuPulse = 0
local pos = self:WorldSpaceCenter()
pos.z = pos.z + 16
if attacker:IsValidLivingHuman() then
util.BlastDamagePlayer(inflictor, attacker, pos, 100, 75, DMG_ALWAYSGIB, 0.95)
for _, ent in pairs(util.BlastAlloc(inflictor, attacker, pos, 100 * (attacker.ExpDamageRadiusMul or 1))) do
if ent:IsValidLivingPlayer() and gamemode.Call("PlayerShouldTakeDamage", ent, attacker) then
ent:AddLegDamageExt(5, attacker, inflictor, SLOWTYPE_PULSE)
end
end
end
local effectdata = EffectData()
effectdata:SetOrigin(pos)
effectdata:SetNormal(attacker:GetShootPos())
util.Effect("explosion_shockcore", effectdata)
end)
end
function meta:CryogenicInduction(attacker, inflictor, damage)
if self:Health() > self:GetMaxHealthEx() * (damage/100) or math.random(50) > damage then return end
timer.Create("Cryogenic" .. attacker:UniqueID(), 0.06, 1, function()
if not attacker:IsValid() or not self:IsValid() then return end
local pos = self:WorldSpaceCenter()
pos.z = pos.z + 16
self:TakeSpecialDamage(self:Health() + 90, DMG_DIRECT, attacker, inflictor, pos)
if attacker:IsValidLivingHuman() then
util.BlastDamagePlayer(inflictor, attacker, pos, 100, self:GetMaxHealthEx() * 0.12, DMG_DROWN, 0.95)
for _, ent in pairs(util.BlastAlloc(inflictor, attacker, pos, 100 * (attacker.ExpDamageRadiusMul or 1))) do
if ent:IsValidLivingPlayer() and gamemode.Call("PlayerShouldTakeDamage", ent, attacker) then
ent:AddLegDamageExt(6, attacker, inflictor, SLOWTYPE_COLD)
end
end
end
local effectdata = EffectData()
effectdata:SetOrigin(pos)
effectdata:SetNormal(attacker:GetShootPos())
util.Effect("hit_ice", effectdata)
end)
end
function meta:SetPhantomHealth(amount)
self:SetDTFloat(DT_PLAYER_FLOAT_PHANTOMHEALTH, amount)
end
function meta:HasBarricadeExpert()
return self:GetZSRemortLevel() > 0
end
function meta:BarricadeExpertPrecedence(otherpl)
local mygrade, myexpert = self:GetZSRemortLevelGraded(), self:HasBarricadeExpert()
local othergrade, otherexpert = otherpl:GetZSRemortLevelGraded(), otherpl:HasBarricadeExpert()
if (myexpert and not otherexpert) or mygrade > othergrade then
return 1
elseif (not myexpert and not otherexpert) or (myexpert and mygrade == othergrade) then
return 0
end
return -1
end