53743b1aee
Random starting loadouts now include things that aren't weapons (ammo, traits, etc.). Added an option to disable the automatic suicide when changing zombie classes. Zombies will no longer receive damage resistance when only one human remains. Players can no longer claim the arsenal crate that gets spawned in the human spawn when nobody has one. Any player can pack it up though. The Ghoul's ghoul touch special ability has been changed. It will no longer slow targets but will debuff them for the next 10 seconds. They will take 40% more damage (the extra damage is attributed to the person who ghoul touched them last) for the next 10 seconds as well as slightly disorienting them. The Ghoul movement speed has been reduced from 185 to 170. Added crafting recipe: 'Waraxe' Handgun. Combine two 'Battleaxe' Handguns to craft this. Slightly better version of the Owens. The Flesh Creeper model has been changed to one that doesn't have severely awkward hitboxes. The Flesh Creeper can no longer jump and attack at the same time. The Lead Pipe special trait has been changed from a disorientation to a severe view punch/snap on a global cooldown of 1.5 seconds. The Regenerative trait is now 1 health every 6 seconds under 50% health instead of 1 health every 5 seconds under 50% health. Fast Zombie Legs have been changed to be a slightly faster but slightly weaker version of Zombie Legs. Zombies that have just spawned or enter zombie gas will now have a temporary buff which gives 25% extra speed and 40% damage resistance. This buff lasts for 3 seconds and is refreshed by entering the gas. Gas will no longer heal. Zombies with this buff on will strobe dark green. Added crafting recipe: Bladehack. Combine a saw blade with a manhack to get this. Slower but has more health, does more damage, and has less of a knockback when hitting something. Resupply Boxes now award the owner a point for every 2 people who use their box instead of every single person (so half as many points). Fixed Fast Zombie Legs spawning much more abundantly than intended. Fixed Flesh Creepers not being able to jump over obstacles due to their insanely low jump height. Fixed zombies taking themselves in to account when calculating horde damage resistance (bosses always had full resistance because of this). Fixed allowing people to use worth menu after redeeming.
907 lines
24 KiB
Lua
907 lines
24 KiB
Lua
local meta = FindMetaTable("Player")
|
|
if not meta then return end
|
|
|
|
function meta:FakeDeath(sequenceid, modelscale, length)
|
|
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)
|
|
|
|
self:DeleteOnRemove(ent)
|
|
end
|
|
|
|
return ent
|
|
end
|
|
|
|
local MuscularBones = {
|
|
["ValveBiped.Bip01_R_Upperarm"] = Vector(1, 2, 2),
|
|
["ValveBiped.Bip01_R_Forearm"] = Vector(1, 2, 2),
|
|
["ValveBiped.Bip01_L_Upperarm"] = Vector(1, 2, 2),
|
|
["ValveBiped.Bip01_L_Forearm"] = Vector(1, 2, 2)
|
|
}
|
|
function meta:DoMuscularBones()
|
|
if self.BuffMuscular and self:Team() == TEAM_HUMAN then
|
|
self.MuscularBones = {}
|
|
|
|
for bonename, newscale in pairs(MuscularBones) do
|
|
local boneid = self:LookupBone(bonename)
|
|
if boneid and boneid > 0 then
|
|
table.insert(self.MuscularBones, boneid)
|
|
self:ManipulateBoneScale(boneid, newscale)
|
|
end
|
|
end
|
|
elseif self.MuscularBones then
|
|
for _, boneid in pairs(self.MuscularBones) do
|
|
self:ManipulateBoneScale(boneid, Vector(1, 1, 1))
|
|
end
|
|
self.MuscularBones = nil
|
|
end
|
|
end
|
|
|
|
local NoodleArmBones = {
|
|
["ValveBiped.Bip01_R_Upperarm"] = Vector(1, 0.4, 0.4),
|
|
["ValveBiped.Bip01_R_Forearm"] = Vector(1, 0.4, 0.4),
|
|
["ValveBiped.Bip01_L_Upperarm"] = Vector(1, 0.4, 0.4),
|
|
["ValveBiped.Bip01_L_Forearm"] = Vector(1, 0.4, 0.4)
|
|
}
|
|
function meta:DoNoodleArmBones()
|
|
if self.NoObjectPickup and self:Team() == TEAM_HUMAN then
|
|
self.NoodleArmBones = {}
|
|
|
|
for bonename, newscale in pairs(NoodleArmBones) do
|
|
local boneid = self:LookupBone(bonename)
|
|
if boneid and boneid > 0 then
|
|
table.insert(self.NoodleArmBones, boneid)
|
|
self:ManipulateBoneScale(boneid, newscale)
|
|
end
|
|
end
|
|
elseif self.NoodleArmBones then
|
|
for _, boneid in pairs(self.NoodleArmBones) do
|
|
self:ManipulateBoneScale(boneid, Vector(1, 1, 1))
|
|
end
|
|
self.NoodleArmBones = nil
|
|
end
|
|
end
|
|
|
|
function meta:ChangeTeam(teamid)
|
|
local oldteam = self:Team()
|
|
self:SetTeam(teamid)
|
|
if oldteam ~= teamid then
|
|
gamemode.Call("OnPlayerChangedTeam", self, oldteam, teamid)
|
|
end
|
|
|
|
self:CollisionRulesChanged()
|
|
end
|
|
|
|
function meta:TrySpawnAsGoreChild()
|
|
for _, ent in pairs(ents.FindByClass("prop_thrownbaby")) do
|
|
if not ent.SpawnedOn then
|
|
ent.SpawnedOn = true
|
|
|
|
local ang = self:EyeAngles()
|
|
ang.roll = 0
|
|
ang.pitch = 0
|
|
|
|
local deathclass = self.DeathClass
|
|
self:SetZombieClassName("Gore Child")
|
|
self.DeathClass = nil
|
|
self.DidntSpawnOnSpawnPoint = true
|
|
self:UnSpectateAndSpawn()
|
|
self.DeathClass = deathclass
|
|
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()
|
|
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function meta:AddLifeBarricadeDamage(amount)
|
|
self.LifeBarricadeDamage = self.LifeBarricadeDamage + amount
|
|
|
|
if not self:Alive() and not self:GetZombieClassTable().NeverAlive then
|
|
net.Start("zs_lifestatsbd")
|
|
net.WriteUInt(self.LifeBarricadeDamage, 24)
|
|
net.Send(self)
|
|
end
|
|
end
|
|
|
|
function meta:AddLifeHumanDamage(amount)
|
|
self.LifeHumanDamage = self.LifeHumanDamage + amount
|
|
|
|
if not self:Alive() then
|
|
net.Start("zs_lifestatshd")
|
|
net.WriteUInt(math.ceil(self.LifeHumanDamage), 24)
|
|
net.Send(self)
|
|
end
|
|
end
|
|
|
|
function meta:AddLifeBrainsEaten(amount)
|
|
self.LifeBrainsEaten = self.LifeBrainsEaten + amount
|
|
|
|
if not self:Alive() then
|
|
net.Start("zs_lifestatsbe")
|
|
net.WriteUInt(self.LifeBrainsEaten, 16)
|
|
net.Send(self)
|
|
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: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:GetClass() == "prop_creepernest" and target:GetNestBuilt()
|
|
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:IsValid() and wep:IsWeapon() then
|
|
wep:EmptyAll()
|
|
end
|
|
|
|
return wep
|
|
end
|
|
end
|
|
|
|
-- Here for when garry makes weapons use 357 ammo like he does every other update.
|
|
--[[local oldgive = meta.Give
|
|
function meta:Give(...)
|
|
local wep = oldgive(self, ...)
|
|
if wep:IsValid() then
|
|
if wep.Primary and wep.Primary.Ammo and wep.Primary.Ammo ~= "none" then
|
|
self:RemoveAmmo(wep.Primary.DefaultClip - wep.Primary.ClipSize, "357")
|
|
wep:SetClip1(0)
|
|
|
|
if wep.Primary.DefaultClip > wep.Primary.ClipSize then
|
|
self:GiveAmmo(wep.Primary.DefaultClip, wep.Primary.Ammo, true)
|
|
end
|
|
wep:SetClip1(wep.Primary.ClipSize)
|
|
self:RemoveAmmo(wep.Primary.ClipSize, wep.Primary.Ammo)
|
|
end
|
|
if wep.Secondary and wep.Secondary.Ammo and wep.Secondary.Ammo ~= "none" then
|
|
self:RemoveAmmo(wep.Secondary.DefaultClip - wep.Secondary.ClipSize, "357")
|
|
wep:SetClip2(0)
|
|
|
|
if wep.Secondary.DefaultClip > wep.Secondary.ClipSize then
|
|
self:GiveAmmo(wep.Secondary.DefaultClip, wep.Secondary.Ammo, true)
|
|
end
|
|
wep:SetClip2(wep.Secondary.ClipSize)
|
|
self:RemoveAmmo(wep.Secondary.ClipSize, wep.Secondary.Ammo)
|
|
end
|
|
end
|
|
return wep
|
|
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 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:SendHint()
|
|
end
|
|
|
|
local function RemoveSkyCade(groundent, timername)
|
|
if not groundent:IsValid() or not (groundent.IsBarricadeObject or groundent:IsNailedToWorldHierarchy()) then
|
|
timer.Destroy(timername)
|
|
return
|
|
end
|
|
|
|
for _, pl in pairs(player.GetAll()) do
|
|
if pl:Alive() and pl:GetGroundEntity() == groundent then
|
|
groundent:TakeDamage(3, groundent, groundent)
|
|
pl:ViewPunch(Angle(math.Rand(-25, 25), math.Rand(-25, 25), math.Rand(-25, 25)))
|
|
if math.random(9) == 1 then
|
|
groundent:EmitSound("npc/strider/creak"..math.random(4)..".wav", 65, math.random(95, 105))
|
|
end
|
|
|
|
return
|
|
end
|
|
end
|
|
|
|
timer.Destroy(timername)
|
|
end
|
|
local checkoffset = Vector(0, 0, -128)
|
|
function meta:PreventSkyCade()
|
|
local groundent = self:GetGroundEntity()
|
|
if groundent:IsValid() then
|
|
if groundent:IsNailedToWorldHierarchy() then
|
|
local phys = groundent:GetPhysicsObject()
|
|
if phys:IsValid() and phys:GetMass() <= CARRY_DRAG_MASS then
|
|
local timername = "RemoveSkyCade"..tostring(groundent)
|
|
local start = groundent:WorldSpaceCenter()
|
|
if not timer.Exists(timername) and not util.TraceHull({start = start,
|
|
endpos = start + checkoffset,
|
|
mins = groundent:OBBMins() * 0.85, maxs = groundent:OBBMaxs() * 0.85,
|
|
mask = MASK_SOLID_BRUSHONLY}).Hit then
|
|
self:MarkAsBadProfile()
|
|
timer.Create(timername, 0.25, 0, function() RemoveSkyCade(groundent, timername) end) -- Oh dear.
|
|
end
|
|
end
|
|
elseif groundent.IsBarricadeObject then
|
|
local timername = "RemoveSkyCade"..tostring(groundent)
|
|
local start = groundent:WorldSpaceCenter()
|
|
if not timer.Exists(timername) and not util.TraceHull({start = start,
|
|
endpos = start + checkoffset,
|
|
mins = groundent:OBBMins() * 0.85, maxs = groundent:OBBMaxs() * 0.85,
|
|
mask = MASK_SOLID_BRUSHONLY}).Hit then
|
|
self:MarkAsBadProfile()
|
|
timer.Create(timername, 0.25, 0, function() RemoveSkyCade(groundent, timername) end) -- Oh dear.
|
|
end
|
|
end
|
|
end
|
|
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.Owner == self then return ent end
|
|
end
|
|
|
|
function meta:GiveStatus(sType, fDie)
|
|
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 self:Team() ~= 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:CallZombieFunction("OnSecondWind")
|
|
end
|
|
|
|
function meta:DropAll()
|
|
self:DropAllAmmo()
|
|
self:DropAllWeapons()
|
|
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.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()
|
|
ent:SetClip1(wep:Clip1())
|
|
ent:SetClip2(wep:Clip2())
|
|
|
|
self:StripWeapon(class)
|
|
|
|
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
|
|
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
|
|
|
|
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()
|
|
|
|
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
|
|
|
|
-- Lets other players know about our maximum health.
|
|
meta.OldSetMaxHealth = 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.m_PointQueue >= 1 and self:Team() == TEAM_HUMAN then
|
|
local points = math.floor(self.m_PointQueue)
|
|
self.m_PointQueue = self.m_PointQueue - points
|
|
|
|
self:AddPoints(points)
|
|
self:FloatingScore(ent or self.m_LastDamageDealtPosition or vector_origin, "floatingscore", points, fmtype or FM_NONE)
|
|
end
|
|
end
|
|
|
|
function meta:AddPoints(points)
|
|
self:AddFrags(points)
|
|
self:SetPoints(self:GetPoints() + points)
|
|
|
|
gamemode.Call("PlayerPointsAdded", self, points)
|
|
end
|
|
|
|
function meta:TakePoints(points)
|
|
self:SetPoints(self:GetPoints() - points)
|
|
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:CallZombieFunction("SwitchedAway")
|
|
|
|
local classtab = GAMEMODE.ZombieClasses[cl]
|
|
if classtab then
|
|
self.Class = cl
|
|
if self:Team() == TEAM_UNDEAD then
|
|
self:DoHulls(cl)
|
|
end
|
|
self:CallZombieFunction("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 self:Team()
|
|
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
|
|
|
|
self:DrawShadow(not classtab.NoShadow)
|
|
|
|
self.NoCollideAll = classtab.NoCollideAll
|
|
self.AllowTeamDamage = classtab.AllowTeamDamage
|
|
self.NeverAlive = classtab.NeverAlive
|
|
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:DrawShadow(true)
|
|
|
|
self.NoCollideAll = nil
|
|
self.AllowTeamDamage = nil
|
|
self.NeverAlive = 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, 16)
|
|
net.WriteUInt(teamid, 16)
|
|
net.Broadcast()
|
|
|
|
self:CollisionRulesChanged()
|
|
end
|
|
|
|
function meta:Redeem()
|
|
gamemode.Call("PlayerRedeemed", self)
|
|
end
|
|
|
|
function meta:RedeemNextFrame()
|
|
timer.Simple(0, function()
|
|
if IsValid(self) then
|
|
self:CheckRedeem(true)
|
|
end
|
|
end)
|
|
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
|
|
|
|
function meta:CheckRedeem(instant)
|
|
if self:IsValid() and self:Team() == TEAM_UNDEAD and GAMEMODE:GetRedeemBrains() > 0 and GAMEMODE:GetRedeemBrains() <= self:Frags() and GAMEMODE:GetWave() ~= GAMEMODE:GetNumberOfWaves() and not self.NoRedeeming and not self:GetZombieClassTable().Boss 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)
|
|
if ammo then
|
|
local wep = self:GetActiveWeapon()
|
|
if not wep or not wep:IsValid() or 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) * 5))
|
|
if desiredgive >= 1 then
|
|
wep:TakeCombinedPrimaryAmmo(desiredgive)
|
|
plyr:GiveAmmo(desiredgive, ammotype)
|
|
|
|
self:PlayGiveAmmoSound()
|
|
self:RestartGesture(ACT_GMOD_GESTURE_ITEM_GIVE)
|
|
end
|
|
end
|
|
end
|
|
|
|
local wep = self:GetActiveWeapon()
|
|
if wep:IsValid() then
|
|
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())
|
|
|
|
local wep2 = plyr:Give(weapon:GetClass())
|
|
if wep2 and wep2:IsValid() then
|
|
if wep2.Primary then
|
|
local 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
|
|
local 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
|
|
end
|
|
|
|
function meta:Gib()
|
|
local pos = self:WorldSpaceCenter()
|
|
local headoffset = self:LocalToWorld(self:OBBMaxs()).z - pos.z
|
|
|
|
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 ent:Team() ~= self:Team() 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.UnSpectate
|
|
function meta:UnSpectate()
|
|
if self:GetObserverMode() ~= OBS_MODE_NONE then
|
|
self:OldUnSpectate(obsm)
|
|
end
|
|
end
|