zombiesurvival-evolved/gamemodes/zombiesurvival/gamemode/init.lua
2019-04-11 13:57:29 -03:00

4383 lines
131 KiB
Lua

--[[
Zombie Survival
by William "JetBoom" Moodhe
williammoodhe@gmail.com -or- jetboom@noxiousnet.com
http://www.noxiousnet.com/
Further credits displayed by pressing F1 in-game.
This was my first ever gamemode. A lot of stuff is from years ago and some stuff is very recent.
]]
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
AddCSLuaFile("sh_translate.lua")
AddCSLuaFile("sh_colors.lua")
AddCSLuaFile("sh_serialization.lua")
AddCSLuaFile("sh_globals.lua")
AddCSLuaFile("sh_util.lua")
AddCSLuaFile("sh_options.lua")
AddCSLuaFile("sh_zombieclasses.lua")
AddCSLuaFile("sh_animations.lua")
AddCSLuaFile("sh_sigils.lua")
AddCSLuaFile("sh_channel.lua")
AddCSLuaFile("sh_weaponquality.lua")
AddCSLuaFile("vault/shared.lua")
AddCSLuaFile("cl_draw.lua")
AddCSLuaFile("cl_net.lua")
AddCSLuaFile("cl_util.lua")
AddCSLuaFile("cl_options.lua")
AddCSLuaFile("cl_scoreboard.lua")
AddCSLuaFile("cl_targetid.lua")
AddCSLuaFile("cl_postprocess.lua")
AddCSLuaFile("cl_deathnotice.lua")
AddCSLuaFile("cl_floatingscore.lua")
AddCSLuaFile("cl_dermaskin.lua")
AddCSLuaFile("cl_hint.lua")
AddCSLuaFile("cl_thirdperson.lua")
AddCSLuaFile("cl_voicesets.lua")
AddCSLuaFile("skillweb/sh_skillweb.lua")
AddCSLuaFile("skillweb/cl_skillweb.lua")
AddCSLuaFile("skillweb/registry.lua")
AddCSLuaFile("obj_vector_extend.lua")
AddCSLuaFile("obj_entity_extend.lua")
AddCSLuaFile("obj_entity_extend_cl.lua")
AddCSLuaFile("obj_player_extend.lua")
AddCSLuaFile("obj_player_extend_cl.lua")
AddCSLuaFile("obj_weapon_extend.lua")
AddCSLuaFile("obj_weapon_extend_cl.lua")
AddCSLuaFile("vgui/dteamcounter.lua")
AddCSLuaFile("vgui/dmodelpanelex.lua")
AddCSLuaFile("vgui/dammocounter.lua")
AddCSLuaFile("vgui/dpingmeter.lua")
AddCSLuaFile("vgui/dteamheading.lua")
AddCSLuaFile("vgui/dsidemenu.lua")
AddCSLuaFile("vgui/dspawnmenu.lua")
AddCSLuaFile("vgui/dmodelkillicon.lua")
AddCSLuaFile("vgui/dexroundedpanel.lua")
AddCSLuaFile("vgui/dexroundedframe.lua")
AddCSLuaFile("vgui/dexrotatedimage.lua")
AddCSLuaFile("vgui/dexnotificationslist.lua")
AddCSLuaFile("vgui/dexchanginglabel.lua")
AddCSLuaFile("vgui/mainmenu.lua")
AddCSLuaFile("vgui/pmainmenu.lua")
AddCSLuaFile("vgui/poptions.lua")
AddCSLuaFile("vgui/phelp.lua")
AddCSLuaFile("vgui/pclassselect.lua")
AddCSLuaFile("vgui/pweapons.lua")
AddCSLuaFile("vgui/pendboard.lua")
AddCSLuaFile("vgui/pworth.lua")
AddCSLuaFile("vgui/parsenal.lua")
AddCSLuaFile("vgui/premantle.lua")
AddCSLuaFile("vgui/zshealtharea.lua")
AddCSLuaFile("vgui/zsstatusarea.lua")
AddCSLuaFile("vgui/zsgamestate.lua")
include("sh_globals.lua")
include("obj_entity_extend_sv.lua")
include("obj_player_extend_sv.lua")
include("obj_weapon_extend_sv.lua")
AddCSLuaFile("loader.lua")
include("loader.lua")
include("shared.lua")
include("sv_options.lua")
include("mapeditor.lua")
include("sv_playerspawnentities.lua")
include("sv_profiling.lua")
include("sv_sigils.lua")
include("sv_concommands.lua")
include("itemstocks/sv_stock.lua")
include("vault/server.lua")
include("skillweb/sv_registry.lua")
include("skillweb/sv_skillweb.lua")
include("sv_zombieescape.lua")
include("zsbots/init.lua")
include_library("statistics")
local pairs = pairs
local ipairs = ipairs
local IN_WALK = IN_WALK
local IN_USE = IN_USE
local IN_RELOAD = IN_RELOAD
local IN_MOVERIGHT = IN_MOVERIGHT
local IN_MOVELEFT = IN_MOVELEFT
local IN_ATTACK = IN_ATTACK
local IN_ATTACK2 = IN_ATTACK2
local IN_ZOOM = IN_ZOOM
local IN_SPEED = IN_SPEED
local CurTime = CurTime
local Vector = Vector
local Angle = Angle
local vector_origin = vector_origin
local HITGROUP_HEAD = HITGROUP_HEAD
local HITGROUP_LEFTLEG = HITGROUP_LEFTLEG
local HITGROUP_RIGHTLEG = HITGROUP_RIGHTLEG
local math_max = math.max
local M_Player = FindMetaTable("Player")
local P_GetBarricadeGhosting = M_Player.GetBarricadeGhosting
local P_BarricadeGhostingThink = M_Player.BarricadeGhostingThink
local P_Team = M_Player.Team
local P_Alive = M_Player.Alive
local player_GetAll = player.GetAll
local P_GetPhantomHealth = M_Player.GetPhantomHealth
function GM:WorldHint(hint, pos, ent, lifetime, filter)
net.Start("zs_worldhint")
net.WriteString(hint)
net.WriteVector(pos or ent and ent:IsValid() and ent:GetPos() or vector_origin)
net.WriteEntity(ent or NULL)
net.WriteFloat(lifetime or 8)
if filter then
net.Send(filter)
else
net.Broadcast()
end
end
function GM:CreateGibs(pos, headoffset)
headoffset = headoffset or 0
local headpos = Vector(pos.x, pos.y, pos.z + headoffset)
for i = 1, 2 do
local ent = ents.CreateLimited("prop_playergib")
if ent:IsValid() then
ent:SetPos(headpos + VectorRand():GetNormalized() * math.Rand(1, 5))
ent:SetAngles(VectorRand():Angle())
ent:SetGibType(i)
ent:Spawn()
end
end
for i = 1, 4 do
local ent = ents.CreateLimited("prop_playergib")
if ent:IsValid() then
ent:SetPos(pos + VectorRand():GetNormalized() * math.Rand(1, 12))
ent:SetAngles(VectorRand():Angle())
ent:SetGibType(math.random(3, #GAMEMODE.HumanGibs))
ent:Spawn()
end
end
end
function GM:DisallowHumanPickup(pl, entity)
end
function GM:TryHumanPickup(pl, entity)
if self.ZombieEscape or pl.NoObjectPickup or not pl:Alive() or pl:Team() ~= TEAM_HUMAN or (entity.NoPickupsTime and CurTime() < entity.NoPickupsTime and entity.NoPickupsOwner ~= pl) then return end
if gamemode.Call("DisallowHumanPickup", pl, entity) or pl:GetInfo("zs_nopickupprops") == "1" then return end
if entity:IsValid() and not entity.m_NoPickup then
local phys = entity:GetPhysicsObject()
if phys:IsValid() and phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then return end
local entclass = string.sub(entity:GetClass(), 1, 12)
local carrymaxmass = CARRY_MAXIMUM_MASS * (pl.PropCarryCapacityMul or 1)
local carrymaxvol = CARRY_MAXIMUM_VOLUME * (pl.PropCarryCapacityMul or 1)
if (entclass == "prop_physics" or entclass == "func_physbox" or entity.HumanHoldable and entity:HumanHoldable(pl)) and not entity:IsNailed() and entity:GetMoveType() == MOVETYPE_VPHYSICS and entity:GetPhysicsObject():IsValid() and entity:GetPhysicsObject():GetMass() <= carrymaxmass and entity:GetPhysicsObject():IsMoveable() and entity:OBBMins():Length() + entity:OBBMaxs():Length() <= carrymaxvol then
local holder = entity:GetHolder()
if not holder and not pl:IsHolding() and CurTime() >= (pl.NextHold or 0)
and pl:GetShootPos():DistToSqr(entity:NearestPoint(pl:GetShootPos())) <= 4096 and pl:GetGroundEntity() ~= entity then --64^2
local newstatus = ents.Create("status_human_holding")
if newstatus:IsValid() then
pl.NextHold = CurTime() + 0.25
pl.NextUnHold = CurTime() + 0.05
newstatus:SetPos(pl:GetShootPos())
newstatus:SetOwner(pl)
newstatus:SetParent(pl)
newstatus:SetObject(entity)
newstatus:Spawn()
end
end
end
end
end
function GM:AddResources()
resource.AddFile("resource/fonts/typenoksidi.ttf")
resource.AddFile("resource/fonts/hidden.ttf")
resource.AddFile("resource/fonts/ghoulfriaoe.ttf")
resource.AddFile("resource/fonts/remingtonnoiseless.ttf")
resource.AddFile("particles/vman_explosion.pcf")
for _, filename in pairs(file.Find("materials/zombiesurvival/*.vmt", "GAME")) do
resource.AddFile("materials/zombiesurvival/"..filename)
end
for _, filename in pairs(file.Find("materials/zombiesurvival/*.png", "GAME")) do
resource.AddFile("materials/zombiesurvival/"..filename)
end
for _, filename in pairs(file.Find("materials/zombiesurvival/killicons/*.vmt", "GAME")) do
resource.AddFile("materials/zombiesurvival/killicons/"..filename)
end
for _, filename in pairs(file.Find("materials/zombiesurvival/killicons/*.png", "GAME")) do
resource.AddFile("materials/zombiesurvival/killicons/"..filename)
end
resource.AddFile("materials/zombiesurvival/filmgrain/filmgrain.vmt")
resource.AddFile("materials/zombiesurvival/filmgrain/filmgrain.vtf")
for _, filename in pairs(file.Find("sound/zombiesurvival/*.ogg", "GAME")) do
resource.AddFile("sound/zombiesurvival/"..filename)
end
for _, filename in pairs(file.Find("sound/zombiesurvival/ui/*.ogg", "GAME")) do
resource.AddFile("sound/zombiesurvival/ui/"..filename)
end
for _, filename in pairs(file.Find("sound/zombiesurvival/*.wav", "GAME")) do
resource.AddFile("sound/zombiesurvival/"..filename)
end
for _, filename in pairs(file.Find("sound/zombiesurvival/*.mp3", "GAME")) do
resource.AddFile("sound/zombiesurvival/"..filename)
end
local _____, dirs = file.Find("sound/zombiesurvival/beats/*", "GAME")
for _, dirname in pairs(dirs) do
for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.ogg", "GAME")) do
resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
end
for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.wav", "GAME")) do
resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
end
for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.mp3", "GAME")) do
resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
end
end
resource.AddFile("materials/refract_ring.vmt")
resource.AddFile("materials/killicon/redeem_v2.vtf")
resource.AddFile("materials/killicon/redeem_v2.vmt")
resource.AddFile("models/weapons/v_zombiearms.mdl")
resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet.vmt")
resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet.vtf")
resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet_normal.vtf")
resource.AddFile("materials/models/weapons/v_zombiearms/ghoulsheet.vmt")
resource.AddFile("materials/models/weapons/v_zombiearms/ghoulsheet.vtf")
resource.AddFile("models/weapons/v_fza.mdl")
resource.AddFile("models/weapons/v_pza.mdl")
resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet.vmt")
resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet.vtf")
resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet_normal.vtf")
resource.AddFile("models/weapons/c_annabelle.mdl")
resource.AddFile("materials/models/weapons/w_annabelle/gun.vtf")
resource.AddFile("materials/models/weapons/sledge.vtf")
resource.AddFile("materials/models/weapons/sledge.vmt")
resource.AddFile("materials/models/weapons/temptexture/handsmesh1.vtf")
resource.AddFile("materials/models/weapons/temptexture/handsmesh1.vmt")
resource.AddFile("materials/models/weapons/hammer2.vtf")
resource.AddFile("materials/models/weapons/hammer2.vmt")
resource.AddFile("materials/models/weapons/hammer.vtf")
resource.AddFile("materials/models/weapons/hammer.vmt")
resource.AddFile("models/weapons/w_sledgehammer.mdl")
resource.AddFile("models/weapons/v_sledgehammer/c_sledgehammer.mdl")
resource.AddFile("models/weapons/w_hammer.mdl")
resource.AddFile("models/weapons/v_hammer/c_hammer.mdl")
resource.AddFile("models/weapons/c_aegiskit.mdl")
resource.AddFile("materials/models/weapons/v_hand/armtexture.vmt")
resource.AddFile("models/weapons/v_supershorty/v_supershorty.mdl")
resource.AddFile("models/weapons/w_supershorty.mdl")
for _, filename in pairs(file.Find("materials/weapons/v_supershorty/*.vmt", "GAME")) do
resource.AddFile("materials/weapons/v_supershorty/"..filename)
end
for _, filename in pairs(file.Find("materials/weapons/v_supershorty/*.vtf", "GAME")) do
resource.AddFile("materials/weapons/v_supershorty/"..filename)
end
for _, filename in pairs(file.Find("materials/weapons/w_supershorty/*.vmt", "GAME")) do
resource.AddFile("materials/weapons/w_supershorty/"..filename)
end
for _, filename in pairs(file.Find("materials/weapons/w_supershorty/*.vtf", "GAME")) do
resource.AddFile("materials/weapons/w_supershorty/"..filename)
end
for _, filename in pairs(file.Find("materials/weapons/survivor01_hands/*.vmt", "GAME")) do
resource.AddFile("materials/weapons/survivor01_hands/"..filename)
end
for _, filename in pairs(file.Find("materials/weapons/survivor01_hands/*.vtf", "GAME")) do
resource.AddFile("materials/weapons/survivor01_hands/"..filename)
end
for _, filename in pairs(file.Find("materials/models/weapons/v_pza/*.*", "GAME")) do
resource.AddFile("materials/models/weapons/v_pza/"..string.lower(filename))
end
resource.AddFile("models/player/fatty/fatty.mdl")
resource.AddFile("materials/models/player/elis/fty/001.vmt")
resource.AddFile("materials/models/player/elis/fty/001.vtf")
resource.AddFile("materials/models/player/elis/fty/001_normal.vtf")
resource.AddFile("models/player/zelpa/stalker.mdl")
resource.AddFile("models/vinrax/player/doll_player.mdl")
resource.AddFile("models/player/zombie_classic_hbfix.mdl")
resource.AddFile("models/player/zombie_lacerator2.mdl")
resource.AddFile("sound/weapons/melee/golf club/golf_hit-01.ogg")
resource.AddFile("sound/weapons/melee/golf club/golf_hit-02.ogg")
resource.AddFile("sound/weapons/melee/golf club/golf_hit-03.ogg")
resource.AddFile("sound/weapons/melee/golf club/golf_hit-04.ogg")
resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-1.ogg")
resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-2.ogg")
resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-3.ogg")
resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-4.ogg")
resource.AddFile("sound/weapons/melee/shovel/shovel_hit-01.ogg")
resource.AddFile("sound/weapons/melee/shovel/shovel_hit-02.ogg")
resource.AddFile("sound/weapons/melee/shovel/shovel_hit-03.ogg")
resource.AddFile("sound/weapons/melee/shovel/shovel_hit-04.ogg")
resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-01.ogg")
resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-02.ogg")
resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-03.ogg")
resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-04.ogg")
resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-01.ogg")
resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-02.ogg")
resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-03.ogg")
resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-04.ogg")
resource.AddFile("sound/weapons/zs_sawnoff/sawnoff_fire1.ogg")
resource.AddFile("sound/weapons/zs_sawnoff/barrelup.ogg")
resource.AddFile("sound/weapons/zs_sawnoff/barreldown.ogg")
resource.AddFile("sound/weapons/zs_longarm/longarm_fire.ogg")
resource.AddFile("sound/weapons/zs_scar/scar_fire1.ogg")
resource.AddFile("sound/nox/sword_hit.ogg")
resource.AddFile("sound/nox/sword_miss.ogg")
resource.AddFile("sound/nox/frotchet_test1.ogg")
resource.AddFile("sound/nox/scatterfrost.ogg")
resource.AddFile("sound/weapons/zs_gluon/egon_off1.wav")
resource.AddFile("sound/weapons/zs_heph/electro4.wav")
resource.AddFile("sound/weapons/zs_heph/electro5.wav")
resource.AddFile("sound/weapons/zs_heph/electro6.wav")
resource.AddFile("sound/weapons/zs_inner/innershot.ogg")
resource.AddFile("sound/weapons/zs_glad/gladshot4.wav")
resource.AddFile("sound/weapons/zs_flak/load1.wav")
resource.AddFile("sound/weapons/zs_flak/shot1.wav")
resource.AddFile("sound/weapons/zs_rail/rail.wav")
resource.AddFile("sound/weapons/zs_asmd/secondary2.wav")
resource.AddFile("sound/weapons/zs_asmd/main3.wav")
resource.AddFile("sound/weapons/zs_power/power1.ogg")
resource.AddFile("sound/weapons/zs_power/power4.wav")
resource.AddFile("materials/zombiesurvival/arsenalcrate.png")
resource.AddFile("sound/"..tostring(self.LastHumanSound))
resource.AddFile("sound/"..tostring(self.AllLoseSound))
resource.AddFile("sound/"..tostring(self.HumanWinSound))
resource.AddFile("sound/"..tostring(self.DeathSound))
end
function GM:Initialize()
self:FixSkillConnections()
self:RegisterPlayerSpawnEntities()
self:AddResources()
self:PrecacheResources()
self:AddCustomAmmo()
self:CreateWeaponQualities()
self:AddNetworkStrings()
self:RegisterFood()
self:LoadProfiler()
self:SetPantsMode(self.PantsMode, true)
self:SetClassicMode(self:IsClassicMode(), true)
self:SetBabyMode(self:IsBabyMode(), true)
self:SetRedeemBrains(self.DefaultRedeem)
self:RefreshMapIsObjective()
game.ConsoleCommand("fire_dmgscale 1\n")
game.ConsoleCommand("mp_flashlight 1\n")
game.ConsoleCommand("sv_gravity 600\n")
end
function GM:AddNetworkStrings()
util.AddNetworkString("zs_gamestate")
util.AddNetworkString("zs_wavestart")
util.AddNetworkString("zs_waveend")
util.AddNetworkString("zs_lasthuman")
util.AddNetworkString("zs_gamemodecall")
util.AddNetworkString("zs_lasthumanpos")
util.AddNetworkString("zs_endround")
util.AddNetworkString("zs_centernotify")
util.AddNetworkString("zs_topnotify")
util.AddNetworkString("zs_zvols")
util.AddNetworkString("zs_nextboss")
util.AddNetworkString("zs_classunlock")
util.AddNetworkString("zs_sigilcorrupted")
util.AddNetworkString("zs_sigiluncorrupted")
util.AddNetworkString("zs_survivor")
util.AddNetworkString("zs_itemstock")
util.AddNetworkString("zs_playerredeemed")
util.AddNetworkString("zs_dohulls")
util.AddNetworkString("zs_penalty")
util.AddNetworkString("zs_nextresupplyuse")
util.AddNetworkString("zs_stowagecaches")
util.AddNetworkString("zs_lifestats")
util.AddNetworkString("zs_lifestatsbd")
util.AddNetworkString("zs_lifestatshd")
util.AddNetworkString("zs_lifestatsbe")
util.AddNetworkString("zs_boss_spawned")
util.AddNetworkString("zs_boss_slain")
util.AddNetworkString("zs_commission")
util.AddNetworkString("zs_healother")
util.AddNetworkString("zs_healby")
util.AddNetworkString("zs_buffby")
util.AddNetworkString("zs_buffwith")
util.AddNetworkString("zs_repairobject")
util.AddNetworkString("zs_worldhint")
util.AddNetworkString("zs_honmention")
util.AddNetworkString("zs_floatscore")
util.AddNetworkString("zs_floatscore_vec")
util.AddNetworkString("zs_zclass")
util.AddNetworkString("zs_dmg")
util.AddNetworkString("zs_dmg_prop")
util.AddNetworkString("zs_legdamage")
util.AddNetworkString("zs_armdamage")
util.AddNetworkString("zs_extrastartingworth")
util.AddNetworkString("zs_ammopickup")
util.AddNetworkString("zs_ammogive")
util.AddNetworkString("zs_ammogiven")
util.AddNetworkString("zs_deployablelost")
util.AddNetworkString("zs_deployableclaim")
util.AddNetworkString("zs_deployableout")
util.AddNetworkString("zs_trinketconsumed")
util.AddNetworkString("zs_nailremoved")
util.AddNetworkString("zs_remantlercontent")
util.AddNetworkString("zs_classunlockstate")
util.AddNetworkString("zs_changeclass")
util.AddNetworkString("zs_currentround")
util.AddNetworkString("zs_zsfriend")
util.AddNetworkString("zs_zsfriendadded")
util.AddNetworkString("zs_remantleconf")
util.AddNetworkString("zs_nestbuilt")
util.AddNetworkString("zs_nestspec")
util.AddNetworkString("zs_tvcamera")
util.AddNetworkString("zs_inventoryitem")
util.AddNetworkString("zs_trycraft")
util.AddNetworkString("zs_updatealtselwep")
util.AddNetworkString("zs_invitem")
util.AddNetworkString("zs_invgiven")
util.AddNetworkString("zs_wipeinventory")
util.AddNetworkString("zs_skills_active")
util.AddNetworkString("zs_skills_unlocked")
util.AddNetworkString("zs_skills_desired")
util.AddNetworkString("zs_skill_is_desired")
util.AddNetworkString("zs_skill_is_unlocked")
util.AddNetworkString("zs_skills_all_desired")
util.AddNetworkString("zs_skill_set_desired")
util.AddNetworkString("zs_skills_init")
util.AddNetworkString("zs_skills_reset")
util.AddNetworkString("zs_skills_remort")
util.AddNetworkString("zs_skills_nextreset")
util.AddNetworkString("zs_skills_notify")
util.AddNetworkString("zs_skills_refunded")
util.AddNetworkString("zs_crow_kill_crow")
util.AddNetworkString("zs_pl_kill_pl")
util.AddNetworkString("zs_pls_kill_pl")
util.AddNetworkString("zs_pl_kill_self")
util.AddNetworkString("zs_death")
util.AddNetworkString("voice_eyepain")
util.AddNetworkString("voice_giveammo")
util.AddNetworkString("voice_death")
util.AddNetworkString("voice_zombiedeath")
util.AddNetworkString("voice_pain")
util.AddNetworkString("voice_zombiepain")
end
function GM:IsClassicMode()
return self.ClassicMode
end
function GM:IsBabyMode()
return self.BabyMode
end
function GM:CenterNotifyAll(...)
net.Start("zs_centernotify")
net.WriteTable({...})
net.Broadcast()
end
GM.CenterNotify = GM.CenterNotifyAll
function GM:TopNotifyAll(...)
net.Start("zs_topnotify")
net.WriteTable({...})
net.Broadcast()
end
GM.TopNotify = GM.TopNotifyAll
function GM:ShowHelp(pl)
pl:SendLua("GAMEMODE:ShowHelp()")
end
function GM:ShowTeam(pl)
if pl:Team() == TEAM_HUMAN and not self.ZombieEscape then
pl:SendLua(self:GetWave() > 0 and "GAMEMODE:OpenArsenalMenu()" or "MakepWorth()")
end
end
function GM:ShowSpare1(pl)
if pl:Team() == TEAM_UNDEAD then
if self:ShouldUseAlternateDynamicSpawn() then
pl:CenterNotify(COLOR_RED, translate.ClientGet(pl, "no_class_switch_in_this_mode"))
else
pl:SendLua("GAMEMODE:OpenClassSelect()")
end
elseif pl:Team() == TEAM_HUMAN then
pl:SendLua("GAMEMODE:ToggleSkillWeb()")
end
end
function GM:ShowSpare2(pl)
pl:SendLua("MakepOptions()")
end
function GM:SetupSpawnPoints()
local ztab = ents.FindByClass("info_player_undead")
ztab = table.Add(ztab, ents.FindByClass("info_player_zombie"))
local htab = ents.FindByClass("info_player_human")
-- Terrorist spawns are usually in some kind of house or a main base in CS_ in order to guard the hosties. Put the humans there.
-- Otherwise, this is probably a DE_, ZM_, or ZH_ map. In DE_ maps, the T's spawn away from the main part of the map and are zombies in zombie plugins so let's do the same.
local mapname = string.lower(game.GetMap())
local stockspawnbehavior = string.sub(mapname, 1, 3) == "cs_" or string.sub(mapname, 1, 3) == "zs_"
-- Only add CS:S and HL2:DM spawn points if no ZS specific spawn points exist.
if #ztab == 0 then
if stockspawnbehavior then
ztab = table.Add(ztab, ents.FindByClass("info_player_counterterrorist"))
else
ztab = table.Add(ztab, ents.FindByClass("info_player_terrorist"))
end
ztab = table.Add(ztab, ents.FindByClass("info_player_rebel"))
end
if #htab == 0 then
if stockspawnbehavior then
htab = table.Add(htab, ents.FindByClass("info_player_terrorist"))
else
htab = table.Add(htab, ents.FindByClass("info_player_counterterrorist"))
end
htab = table.Add(htab, ents.FindByClass("info_player_combine"))
end
-- Add all the old ZS spawns from GMod9.
for _, oldspawn in pairs(ents.FindByClass("gmod_player_start")) do
if oldspawn.BlueTeam then
table.insert(htab, oldspawn)
else
table.insert(ztab, oldspawn)
end
end
-- You shouldn't play a DM map since spawns are shared but whatever. Let's make sure that there aren't team spawns first.
if #htab == 0 then
htab = ents.FindByClass("info_player_start")
htab = table.Add(htab, ents.FindByClass("info_player_deathmatch")) -- Zombie Master
end
if #ztab == 0 then
ztab = ents.FindByClass("info_player_start")
ztab = table.Add(ztab, ents.FindByClass("info_zombiespawn")) -- Zombie Master
end
team.SetSpawnPoint(TEAM_UNDEAD, ztab)
team.SetSpawnPoint(TEAM_HUMAN, htab)
team.SetSpawnPoint(TEAM_SPECTATOR, htab)
self.RedeemSpawnPoints = ents.FindByClass("info_player_redeemed")
self.BossSpawnPoints = table.Add(ents.FindByClass("info_player_zombie_boss"), ents.FindByClass("info_player_undead_boss"))
end
function GM:PlayerPointsAdded(pl, amount)
end
local weaponmodelstoweapon = {}
weaponmodelstoweapon["models/props/cs_office/computer_keyboard.mdl"] = "weapon_zs_keyboard"
weaponmodelstoweapon["models/props_c17/computer01_keyboard.mdl"] = "weapon_zs_keyboard"
weaponmodelstoweapon["models/props_c17/metalpot001a.mdl"] = "weapon_zs_pot"
weaponmodelstoweapon["models/props_interiors/pot02a.mdl"] = "weapon_zs_fryingpan"
weaponmodelstoweapon["models/props_c17/metalpot002a.mdl"] = "weapon_zs_fryingpan"
weaponmodelstoweapon["models/props_junk/shovel01a.mdl"] = "weapon_zs_shovel"
weaponmodelstoweapon["models/props/cs_militia/axe.mdl"] = "weapon_zs_axe"
weaponmodelstoweapon["models/props_c17/tools_wrench01a.mdl"] = "weapon_zs_hammer"
weaponmodelstoweapon["models/weapons/w_knife_t.mdl"] = "weapon_zs_swissarmyknife"
weaponmodelstoweapon["models/weapons/w_knife_ct.mdl"] = "weapon_zs_swissarmyknife"
weaponmodelstoweapon["models/weapons/w_crowbar.mdl"] = "weapon_zs_crowbar"
weaponmodelstoweapon["models/weapons/w_stunbaton.mdl"] = "weapon_zs_stunbaton"
weaponmodelstoweapon["models/props_interiors/furniture_lamp01a.mdl"] = "weapon_zs_lamp"
weaponmodelstoweapon["models/props_junk/rock001a.mdl"] = "weapon_zs_stone"
--weaponmodelstoweapon["models/props_c17/canister01a.mdl"] = "weapon_zs_oxygentank"
weaponmodelstoweapon["models/props_canal/mattpipe.mdl"] = "weapon_zs_pipe"
weaponmodelstoweapon["models/props_junk/meathook001a.mdl"] = "weapon_zs_hook"
weaponmodelstoweapon["models/props_junk/glassbottle01a.mdl"] = "weapon_zs_glassbottle"
weaponmodelstoweapon["models/props_lab/ladel.mdl"] = "weapon_zs_ladel"
weaponmodelstoweapon["models/props_junk/watermelon01.mdl"] = "weapon_zs_f_watermelon"
weaponmodelstoweapon["models/props/cs_italy/bananna_bunch.mdl"] = "weapon_zs_f_banana"
weaponmodelstoweapon["models/props/cs_italy/bananna.mdl"] = "weapon_zs_f_banana"
weaponmodelstoweapon["models/props/cs_italy/orange.mdl"] = "weapon_zs_f_orange"
weaponmodelstoweapon["models/props_junk/popcan01a.mdl"] = "weapon_zs_f_soda"
weaponmodelstoweapon["models/props_junk/garbage_milkcarton002a.mdl"] = "weapon_zs_f_milk"
weaponmodelstoweapon["models/props/cs_office/water_bottle.mdl"] = "weapon_zs_f_water"
weaponmodelstoweapon["models/props_junk/garbage_takeoutcarton001a.mdl"] = "weapon_zs_f_takeout"
weaponmodelstoweapon["models/props_c17/pushbroom.mdl"] = "weapon_zs_pushbroom"
function GM:InitPostEntity()
self.DidInitPostEntity = true
self:AssignItemProperties()
self:FixWeaponBase()
gamemode.Call("InitPostEntityMap")
RunConsoleCommand("mapcyclefile", "mapcycle_zombiesurvival.txt")
if string.find(string.lower(GetConVar("hostname"):GetString()), "hellsgamers", 1, true) then
self.Think = function() end
self.DoPlayerDeath = self.Think
self.SetWave = self.Think
timer.Simple(20, function() RunConsoleCommand("quit") end)
ErrorNoHalt("You are literally not allowed to host this version. See license.txt")
end
end
function GM:SetupProps()
for _, ent in pairs(ents.FindByClass("prop_physics*")) do
local mdl = ent:GetModel()
if mdl then
mdl = string.lower(mdl)
if mdl == "models/props_c17/furniturestove001a.mdl" then
local phys = ent:GetPhysicsObject()
if phys:IsValid() then
phys:SetMass(500)
end
end
if table.HasValue(self.BannedProps, mdl) then
ent:Remove()
elseif weaponmodelstoweapon[mdl] then
local wep = ents.Create("prop_weapon")
if wep:IsValid() then
wep:SetPos(ent:GetPos())
wep:SetAngles(ent:GetAngles())
wep:SetWeaponType(weaponmodelstoweapon[mdl])
wep:SetShouldRemoveAmmo(false)
wep:Spawn()
ent:Remove()
end
elseif ent:GetName() == "" and self.WorldConversions[mdl] then
local wep = ents.Create("prop_invitem")
if wep:IsValid() then
wep:SetPos(ent:GetPos())
wep:SetAngles(ent:GetAngles())
wep:SetInventoryItemType(self.WorldConversions[mdl].Result)
wep:Spawn()
ent:Remove()
end
elseif ent:GetMaxHealth() == 1 and ent:Health() == 0 and ent:GetKeyValues().damagefilter ~= "invul" and ent:GetName() == "" then
local health = math.min(2500, math.ceil((ent:OBBMins():Length() + ent:OBBMaxs():Length()) * 10))
local hmul = self.PropHealthMultipliers[mdl]
if hmul then
health = health * hmul
end
ent.PropHealth = health
ent.TotalHealth = health
else
ent:SetHealth(math.ceil(ent:Health() * 3))
ent:SetMaxHealth(ent:Health())
end
end
end
end
function GM:RemoveUnusedEntities()
-- Causes a lot of needless lag.
util.RemoveAll("prop_ragdoll")
-- Remove NPCs because first of all this game is PvP and NPCs can cause crashes.
util.RemoveAll("npc_maker")
util.RemoveAll("npc_template_maker")
util.RemoveAll("npc_zombie")
util.RemoveAll("npc_zombie_torso")
util.RemoveAll("npc_fastzombie")
util.RemoveAll("npc_headcrab")
util.RemoveAll("npc_headcrab_fast")
util.RemoveAll("npc_headcrab_black")
util.RemoveAll("npc_poisonzombie")
-- Such a headache. Just remove them all.
util.RemoveAll("item_ammo_crate")
-- Shouldn't exist.
util.RemoveAll("item_suit*")
util.RemoveAll("func_recharge")
end
function GM:ReplaceMapWeapons()
local prefix = game.GetMap():lower():sub(1, 3)
if prefix == "dm_" or prefix == "pb_" then
util.RemoveAll("weapon_*")
return
end
for _, ent in pairs(ents.FindByClass("weapon_*")) do
local wepclass = ent:GetClass()
if wepclass ~= "weapon_map_base" then
if string.sub(wepclass, 1, 10) == "weapon_zs_" then
local wep = ents.Create("prop_weapon")
if wep:IsValid() then
wep:SetPos(ent:GetPos())
wep:SetAngles(ent:GetAngles())
wep:SetWeaponType(ent:GetClass())
wep:SetShouldRemoveAmmo(false)
wep:Spawn()
wep.IsPreplaced = true
end
end
ent:Remove()
end
end
end
local ammoreplacements = {
["item_ammo_357"] = "357",
["item_ammo_357_large"] = "357",
["item_ammo_pistol"] = "pistol",
["item_ammo_pistol_large"] = "pistol",
["item_ammo_buckshot"] = "buckshot",
["item_ammo_ar2"] = "ar2",
["item_ammo_ar2_large"] = "ar2",
["item_ammo_ar2_altfire"] = "pulse",
["item_ammo_crossbow"] = "xbowbolt",
["item_ammo_smg1"] = "smg1",
["item_ammo_smg1_large"] = "smg1",
["item_box_buckshot"] = "buckshot"
}
function GM:ReplaceMapAmmo()
local prefix = game.GetMap():lower():sub(1, 3)
if prefix == "dm_" or prefix == "pb_" then
util.RemoveAll("item_ammo_*")
util.RemoveAll("item_health*")
util.RemoveAll("item_rpg_round")
util.RemoveAll("item_box_buckshot")
for _, e in pairs(ents.FindByModel("models/props_c17/oildrum001_explosive.mdl")) do
if e:IsValid() and e:GetClass():sub(1, 12) == "prop_physics" then
e:Remove()
end
end
return
end
for classname, ammotype in pairs(ammoreplacements) do
for _, ent in pairs(ents.FindByClass(classname)) do
local newent = ents.Create("prop_ammo")
if newent:IsValid() then
newent:SetAmmoType(ammotype)
newent.PlacedInMap = true
newent:SetPos(ent:GetPos())
newent:SetAngles(ent:GetAngles())
newent:Spawn()
newent:SetAmmo(self.AmmoCache[ammotype] or 1)
end
ent:Remove()
end
end
util.RemoveAll("item_item_crate")
end
function GM:ReplaceMapBatteries()
util.RemoveAll("item_battery")
end
function GM:CreateZombieGas()
if NOZOMBIEGASSES then return end
local humanspawns = team.GetValidSpawnPoint(TEAM_HUMAN)
local zombiespawns = team.GetValidSpawnPoint(TEAM_UNDEAD)
for _, zombie_spawn in pairs(zombiespawns) do
local gasses = ents.FindByClass("zombiegasses")
if 4 < #gasses then
return
end
if #gasses > 0 and math.random(5) ~= 1 then
continue
end
local spawnpos = zombie_spawn:GetPos() + Vector(0, 0, 24)
local near = false
if not self.ZombieEscape then
for __, human_spawn in pairs(humanspawns) do
if human_spawn:IsValid() and human_spawn:GetPos():DistToSqr(spawnpos) < 90000 then
near = true
break
end
end
end
if not near then
for __, gas in pairs(gasses) do
if gas:GetPos():DistToSqr(spawnpos) < 122500 then --350^2
near = true
break
end
end
end
if not near then
local ent = ents.Create("zombiegasses")
if ent:IsValid() then
ent:SetPos(spawnpos)
ent:Spawn()
end
end
end
end
function GM:PlayerShouldTakeNailRemovalPenalty(pl, nail, nailowner, prop)
if gamemode.Call("PlayerIsAdmin", pl) then return false end
if nailowner.ZSFriends[pl] then return false end
if not gamemode.Call("CanPlaceNail", nailowner) then return false end
if pl:BarricadeExpertPrecedence(nailowner) == 1 then -- If I'm better than they are.
return false
end
local firstnail = prop:GetFirstNail()
if firstnail and pl == firstnail:GetOwner() then
return false
end
-- just gonna consider this obsolete
--if nailowner:Frags() >= 75 or owner:Frags() < 75 then return true end
return true
end
local playermins = Vector(-17, -17, 0)
local playermaxs = Vector(17, 17, 4)
local LastSpawnPoints = {}
function GM:PlayerSelectSpawn(pl)
local spawninplayer = false
local teamid = pl:Team()
local tab
local epicenter
if pl.m_PreRedeem and teamid == TEAM_HUMAN and #self.RedeemSpawnPoints >= 1 then
tab = self.RedeemSpawnPoints
elseif teamid == TEAM_UNDEAD then
if pl:GetZombieClassTable().Boss and (not pl.DeathClass or self.ZombieClasses[pl.DeathClass].Boss) and #self.BossSpawnPoints >= 1 then
tab = self.BossSpawnPoints
elseif self.DynamicSpawning --[[and CurTime() >= self:GetWaveStart() + 1]] then -- If we're a bit in the wave then we can spawn on top of heavily dense groups with no humans looking at us.
if self:ShouldUseAlternateDynamicSpawn() then -- This system is used for zombie escape, classic mode, baby mode, etc.
-- If they're near a human, use position where they died.
for _, h in pairs(team.GetPlayers(TEAM_HUMAN)) do
if h:GetPos():DistToSqr(epicenter or pl:GetPos()) < 1048576 then --1024^2
epicenter = pl.KilledPos
break
end
end
-- Not near a human when they died, so use best dynamic spawn based on human epicenter.
if not epicenter then
local best = self:GetBestDynamicSpawn(pl)
if IsValid(best) then return best end
end
tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
local dynamicspawns = self:GetDynamicSpawns(pl)
if #dynamicspawns > 0 then
spawninplayer = true
table.Add(tab, dynamicspawns)
end
else
local dyn = pl.ForceDynamicSpawn
if dyn then -- We were spectating an entity.
pl.ForceDynamicSpawn = nil
if self:DynamicSpawnIsValid(dyn) then
if dyn:GetClass() == "prop_creepernest" then -- For honorable mentions
local owner = dyn:GetOwner()
if owner and owner:IsValid() and owner:Team() == TEAM_UNDEAD then
owner.NestSpawns = owner.NestSpawns + 1
end
end
return dyn
end
end
-- Otherwise we just use whatever we can (creeper nests too)
tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
local dynamicspawns = self:GetDynamicSpawns(pl)
if #dynamicspawns > 0 then
spawninplayer = true
table.Add(tab, dynamicspawns)
end
end
end
end
if not tab or #tab == 0 then tab = team.GetValidSpawnPoint(teamid) or {} end
-- Now we have a table of our potential spawn points, including dynamic spawns (other players).
-- We validate if the spawn is blocked, disabled, or otherwise not suitable below.
if #tab > 0 then
local potential = {}
-- Filter out spawns that are disabled or blocked.
for _, spawn in pairs(tab) do
if spawn:IsValid() and not spawn.Disabled and (spawn:IsPlayer() or spawn ~= LastSpawnPoints[teamid] or #tab == 1) and spawn:IsInWorld() then
local blocked
if not self.ObjectiveMap or teamid == TEAM_UNDEAD then
local spawnpos = spawn:GetPos()
for _, ent in pairs(ents.FindInBox(spawnpos + playermins, spawnpos + playermaxs)) do
if not spawninplayer and IsValid(ent) and ent:IsPlayer() or string.sub(ent:GetClass(), 1, 5) == "prop_" then
blocked = true
break
end
end
end
if not blocked then
potential[#potential + 1] = spawn
end
end
end
-- Now our final spawn list is ready.
if #potential > 0 then
local spawn
if teamid == TEAM_UNDEAD then
if pl:KeyDown(IN_ATTACK2) then
spawn = self:GetClosestSpawnPoint(potential, epicenter or self:GetTeamEpicentre(TEAM_HUMAN))
elseif pl:KeyDown(IN_RELOAD) then
spawn = self:GetFurthestSpawnPoint(potential, epicenter or self:GetTeamEpicentre(TEAM_HUMAN))
elseif math.random(2) == 2 then
-- Let every other left click masher spawn randomly instead of closest so we have wandering zombies.
spawn = table.Random(potential)
else
spawn = self:GetClosestSpawnPoint(potential, epicenter or self:GetTeamEpicentre(TEAM_HUMAN))
end
else
spawn = table.Random(potential)
end
if spawn then
LastSpawnPoints[teamid] = spawn
pl.SpawnedOnSpawnPoint = spawn:GetClass():sub(1, 11) == "info_player"
pl.DidntSpawnOnSpawnPoint = pl.DidntSpawnOnSpawnPoint or not pl.SpawnedOnSpawnPoint
return spawn
end
end
end
pl.SpawnedOnSpawnPoint = false
pl.DidntSpawnOnSpawnPoint = true
-- Fallback.
return LastSpawnPoints[teamid] or #tab > 0 and table.Random(tab) or pl
end
local function BossZombieSort(za, zb)
local ascore = za.WaveBarricadeDamage * 0.05 + za.WaveHumanDamage
local bscore = zb.WaveBarricadeDamage * 0.05 + zb.WaveHumanDamage
if ascore == bscore then
return za:Deaths() < zb:Deaths()
end
return ascore > bscore
end
function GM:SpawnBossZombie(bossplayer, silent, bossindex, triggerboss)
if not bossplayer then
bossplayer = self:CalculateNextBoss()
end
if not bossplayer then return end
if not bossindex then
bossindex = bossplayer:GetBossZombieIndex()
end
if bossindex == -1 then return end
if not triggerboss then
bossplayer.BossDeathNotification = true
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_ZOMBIECLASS, GAMEMODE.ZombieClasses[bossindex].Name, "BossSpawn", 1)
end
self.LastBossZombieSpawned = self:GetWave()
local curclass = bossplayer.DeathClass or bossplayer:GetZombieClass()
bossplayer:KillSilent()
bossplayer:SetZombieClass(bossindex)
bossplayer:DoHulls(bossindex, TEAM_UNDEAD)
bossplayer.DeathClass = nil
bossplayer:UnSpectateAndSpawn()
bossplayer.DeathClass = curclass
bossplayer.BossHealRemaining = 750
if not silent then
net.Start("zs_boss_spawned")
net.WriteEntity(bossplayer)
net.WriteUInt(bossindex, 8)
net.Broadcast()
end
end
function GM:SendZombieVolunteers(pl, nonemptyonly)
if nonemptyonly and #self.ZombieVolunteers == 0 then return end
net.Start("zs_zvols")
net.WriteUInt(#self.ZombieVolunteers, 8)
for _, p in ipairs(self.ZombieVolunteers) do
net.WriteEntity(p)
end
if pl then
net.Send(pl)
else
net.Broadcast()
end
end
function GM:ZombieSpawnDistanceSort(other)
return self._ZombieSpawnDistance < other._ZombieSpawnDistance
end
function GM:ZombieSpawnDistanceSortSigils(other)
return self._ZombieSpawnDistance > other._ZombieSpawnDistance
end
function GM:SortZombieSpawnDistances(allplayers)
local plpos, dist
-- If using sigils then we sort by inverse distance from sigils instead of this.
local zspawns = self:GetSigils()
local sortbysigils = #zspawns > 0
if not sortbysigils then
zspawns = ents.FindByClass("zombiegasses")
if #zspawns == 0 then
zspawns = team.GetValidSpawnPoint(TEAM_UNDEAD)
end
end
local maxdist = 1280000000--12800000
for _, pl in pairs(allplayers) do
if pl:Team() == TEAM_UNDEAD then
pl._ZombieSpawnDistance = sortbysigils and maxdist + 2 or -2
elseif pl:GetInfo("zs_alwaysvolunteer") == "1" then
pl._ZombieSpawnDistance = sortbysigils and maxdist + 1 or -1
elseif CLIENT or pl.LastNotAFK and CurTime() <= pl.LastNotAFK + 60 then
plpos = pl:GetPos()
dist = maxdist
for __, ent in pairs(zspawns) do
dist = math.min(dist, ent:NearestPoint(plpos):DistToSqr(plpos))
end
pl._ZombieSpawnDistance = dist
else
pl._ZombieSpawnDistance = sortbysigils and 128 or maxdist -- AFK people should NOT be considered volunteers but also people ACTIVELY next to sigils should be picked AFTER AFK people.
end
end
table.sort(allplayers, sortbysigils and self.ZombieSpawnDistanceSortSigils or self.ZombieSpawnDistanceSort)
end
function GM:ShouldRestartRound()
if self.TimeLimit == -1 or self.RoundLimit == -1 then return true end
local roundlimit = self.RoundLimit
--[[if self.ZombieEscape and roundlimit > 0 then
roundlimit = math.ceil(roundlimit * 1.5)
end]]
local timelimit = self.TimeLimit
if self.ZombieEscape and timelimit > 0 then
timelimit = timelimit * 1.5
end
if timelimit > 0 and CurTime() >= timelimit
or roundlimit > 0 and self.CurrentRound >= roundlimit
or not self.ZombieEscape and ROUNDWINNER == TEAM_HUMAN then
return false
end
return true
end
local NextTick = 0
function GM:Think()
local time = CurTime()
local wave = self:GetWave()
if not self.RoundEnded then
if self:GetWaveActive() then
if self:GetWaveEnd() <= time and self:GetWaveEnd() ~= -1 then
gamemode.Call("SetWaveActive", false)
end
elseif self:GetWaveStart() ~= -1 then
if self:GetWaveStart() <= time then
gamemode.Call("SetWaveActive", true)
elseif self.BossZombies and not self.PantsMode and not self:IsClassicMode() and not self.ZombieEscape
and self.LastBossZombieSpawned ~= wave and wave > 0 and not self.RoundEnded
and (self.BossZombiePlayersRequired <= 0 or #player.GetAll() >= self.BossZombiePlayersRequired) then
if self:GetWaveStart() - 5 <= time then
self:SpawnBossZombie()
else
self:CalculateNextBoss()
end
end
end
end
local allplayers = player_GetAll()
for _, pl in pairs(allplayers) do
if pl.ShouldFlinch then
pl.ShouldFlinch = nil
--pl:Flinch()
end
if P_Team(pl) == TEAM_HUMAN then
if P_GetBarricadeGhosting(pl) then
P_BarricadeGhostingThink(pl)
end
if pl.PointQueue >= 1 and time >= pl.LastDamageDealtTime + 2 then
pl:PointCashOut(pl.LastDamageDealtPos or pl:GetPos(), FM_NONE)
end
if P_GetPhantomHealth(pl) > 0 and P_Alive(pl) and pl:IsSkillActive(SKILL_BLOODLUST) then
pl:SetPhantomHealth(math_max(0, P_GetPhantomHealth(pl) - 5 * FrameTime()))
end
end
end
if wave == 0 then
self:CalculateZombieVolunteers()
end
if NextTick <= time then
NextTick = time + 1
local plpos
if wave == 0 and not self:GetWaveActive() then
for _, pl in pairs(allplayers) do
if P_Team(pl) == TEAM_HUMAN then
plpos = pl:GetPos()
if pl.LastAFKPosition and (pl.LastAFKPosition.x ~= plpos.x or pl.LastAFKPosition.y ~= plpos.y) then
pl.LastNotAFK = time
end
pl.LastAFKPosition = plpos
end
end
end
for _, pl in pairs(allplayers) do
if P_Team(pl) == TEAM_HUMAN and P_Alive(pl) then
plpos = pl:GetPos()
if doafk then
if pl.LastAFKPosition and (pl.LastAFKPosition.x ~= plpos.x or pl.LastAFKPosition.y ~= plpos.y) then
pl.LastNotAFK = time
end
pl.LastAFKPosition = plpos
end
if pl:WaterLevel() >= 3 and not (pl.status_drown and pl.status_drown:IsValid()) then
pl:GiveStatus("drown")
end
local healmax = pl:IsSkillActive(SKILL_D_FRAIL) and math.floor(pl:GetMaxHealth() * 0.25) or pl:GetMaxHealth()
if pl:IsSkillActive(SKILL_REGENERATOR) and time >= pl.NextRegenerate and pl:Health() < math.min(healmax, pl:GetMaxHealth() * 0.6) then
pl.NextRegenerate = time + 6
pl:SetHealth(math.min(healmax, pl:Health() + 1))
end
if pl:HasTrinket("regenimplant") and time >= pl.NextRegenTrinket and pl:Health() < healmax then
pl.NextRegenTrinket = time + 12
pl:SetHealth(math.min(healmax, pl:Health() + 1))
end
if pl:IsSkillActive(SKILL_BLOODARMOR) and pl.MaxBloodArmor > 0 and time >= pl.NextBloodArmorRegen and pl:GetBloodArmor() < pl.MaxBloodArmor then
pl.NextBloodArmorRegen = time + 8
pl:SetBloodArmor(math.min(pl.MaxBloodArmor, pl:GetBloodArmor() + (1 * pl.BloodarmorGainMul)))
end
if pl:KeyDown(IN_SPEED) and pl:GetVelocity() ~= vector_origin and pl:IsSkillActive(SKILL_CARDIOTONIC) then
if pl:GetBloodArmor() > 0 then
pl:SetBloodArmor(pl:GetBloodArmor() - 1)
if pl:GetBloodArmor() == 0 and pl:IsSkillActive(SKILL_BLOODLETTER) then
local bleed = pl:GiveStatus("bleed")
if bleed and bleed:IsValid() then
bleed:AddDamage(5)
bleed.Damager = pl
end
end
else
pl:ResetSpeed()
end
end
if pl:IsSkillActive(SKILL_D_LATEBUYER) and not pl.LateBuyerMessage then
local midwave = self:GetWave() < self:GetNumberOfWaves() / 2 or self:GetWave() == self:GetNumberOfWaves() / 2 and self:GetWaveActive() and time < self:GetWaveEnd() - (self:GetWaveEnd() - self:GetWaveStart()) / 2
if not midwave then
pl:CenterNotify(COLOR_CYAN, translate.ClientGet(pl, "late_buyer_finished"))
pl:SendLua("surface.PlaySound(\"buttons/button5.wav\")")
pl.LateBuyerMessage = true
end
end
pl:CheckTrinketRecharges()
if pl:HasTrinket("autoreload") and pl.OldWeaponToReload and time > (pl.NextAutomatedReload or 0) then
local mywep = pl.OldWeaponToReload
if mywep and mywep:IsValid() and mywep.FinishReload then
local max1 = mywep:GetPrimaryClipSize()
if max1 > 0 then
local ammotype = mywep:GetPrimaryAmmoType()
local spare = pl:GetAmmoCount(ammotype)
local current = mywep:Clip1()
local needed = max1 - current
needed = math.min(spare, needed)
mywep:SetClip1(current + needed)
pl:RemoveAmmo(needed, ammotype)
end
end
pl.NextAutomatedReload = math.huge
pl.OldWeaponToReload = nil
end
if pl:IsSkillActive(SKILL_STOWAGE) and self:GetWave() > 0 and time > (pl.NextResupplyUse or 0) then
local stockpiling = pl:IsSkillActive(SKILL_STOCKPILE)
pl.NextResupplyUse = time + self.ResupplyBoxCooldown * (pl.ResupplyDelayMul or 1) * (stockpiling and 2.12 or 1)
pl.StowageCaches = (pl.StowageCaches or 0) + (stockpiling and 2 or 1)
net.Start("zs_nextresupplyuse")
net.WriteFloat(pl.NextResupplyUse)
net.Send(pl)
net.Start("zs_stowagecaches")
net.WriteInt(pl.StowageCaches, 8)
net.Send(pl)
end
end
end
if self:GetEscapeStage() == ESCAPESTAGE_DEATH then
for _, pl in pairs(allplayers) do
if P_Team(pl) == TEAM_HUMAN then
pl:TakeSpecialDamage(15, DMG_ACID)
end
end
end
end
end
function GM:PlayerSwitchWeapon(pl, old, new)
if pl:HasTrinket("autoreload") then
pl.NextAutomatedReload = CurTime() + 3.95
pl.OldWeaponToReload = old
end
end
-- We calculate the volunteers. If the list changed then broadcast the new list.
function GM:CalculateZombieVolunteers()
local volunteers = {}
local allplayers = player_GetAll()
self:SortZombieSpawnDistances(allplayers)
for i = 1, self:GetDesiredStartingZombies() do
volunteers[i] = allplayers[i]
end
local mismatch = false
if #volunteers ~= #self.ZombieVolunteers then
mismatch = true
else
for i=1, #volunteers do
if volunteers[i] ~= self.ZombieVolunteers[i] then
mismatch = true
break
end
end
end
if mismatch then
self.ZombieVolunteers = volunteers
self:SendZombieVolunteers()
end
end
GM.LastCalculatedBossTime = 0
function GM:CalculateNextBoss()
local zombies = {}
for _, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
if ent:GetInfo("zs_nobosspick") == "0" and not ent:GetZombieClassTable().Boss then
table.insert(zombies, ent)
end
end
table.sort(zombies, BossZombieSort)
local newboss = zombies[1]
if newboss ~= self.LastCalculatedBoss or CurTime() >= self.LastCalculatedBossTime + 2 then
self.LastCalculatedBoss = newboss
self.LastCalculatedBossTime = CurTime()
net.Start("zs_nextboss")
if newboss and newboss:IsValid() then
net.WriteEntity(newboss)
net.WriteUInt(newboss:GetBossZombieIndex(), 8)
else
net.WriteEntity(NULL)
net.WriteUInt(1, 8)
end
net.Broadcast()
end
return newboss
end
function GM:LastBite(victim, attacker)
LAST_BITE = attacker
end
function GM:CalculateInfliction(victim, attacker)
if self.RoundEnded or self:GetWave() == 0 then return self.CappedInfliction end
local players = 0
local zombies = 0
local humans = 0
local wonhumans = 0
local hum
for _, pl in pairs(player.GetAllActive()) do
if not pl.Disconnecting then
if pl:Team() == TEAM_UNDEAD then
zombies = zombies + 1
elseif pl:HasWon() then
wonhumans = wonhumans + 1
else
humans = humans + 1
hum = pl
end
end
end
players = humans + zombies
if players == 0 and wonhumans == 0 then return self.CappedInfliction end
local infliction = math.max(zombies / players, self.CappedInfliction)
self.CappedInfliction = infliction
if humans == 1 and 2 < zombies then
gamemode.Call("LastHuman", hum)
elseif 1 <= infliction then
infliction = 1
if wonhumans >= 1 then
gamemode.Call("EndRound", TEAM_HUMAN)
else
gamemode.Call("EndRound", TEAM_UNDEAD)
if attacker and attacker:IsValid() and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD and attacker ~= victim then
gamemode.Call("LastBite", victim, attacker)
end
end
end
if not self:IsClassicMode() and not self.ZombieEscape and not self:IsBabyMode() and not self.PantsMode then
for k, v in ipairs(self.ZombieClasses) do
if v.Infliction and infliction >= v.Infliction and not self:IsClassUnlocked(v.Name) then
v.Unlocked = true
for _, ent in pairs(ents.FindByClass("logic_classunlock")) do
local classname = v.Name
if ent.Class == string.lower(classname) then
ent:Input("onclassunlocked", ent, ent, classname)
end
end
if not self.PantsMode and not self:IsClassicMode() and not self:IsBabyMode() and not self.ZombieEscape and not v.Locked then
net.Start("zs_classunlockstate")
net.WriteInt(k, 8)
net.WriteBool(v.Unlocked)
net.Broadcast()
for _, pl in pairs(player.GetAll()) do
pl:CenterNotify(COLOR_RED, translate.ClientFormat(pl, "infliction_reached", v.Infliction * 100))
pl:CenterNotify(translate.ClientFormat(pl, "x_unlocked", translate.ClientGet(pl, v.TranslationName)))
end
end
end
end
end
for _, ent in pairs(ents.FindByClass("logic_infliction")) do
if ent.Infliction <= infliction then
ent:Input("oninflictionreached", NULL, NULL, infliction)
end
end
return infliction
end
timer.Create("CalculateInfliction", 2, 0, function() gamemode.Call("CalculateInfliction") end)
function GM:OnNPCKilled(ent, attacker, inflictor)
end
function GM:LastHuman(pl)
if not LASTHUMAN then
net.Start("zs_lasthuman")
net.WriteEntity(pl or NULL)
net.Broadcast()
for _, ent in pairs(ents.FindByClass("logic_infliction")) do
ent:Input("onlasthuman", pl, pl, pl and pl:IsValid() and pl:EntIndex() or -1)
end
LASTHUMAN = true
end
self.TheLastHuman = pl
end
function GM:PlayerHealedTeamMember(pl, other, health, wep, pointmul, nobymsg, floater)
health = health - other:RemoveUselessDamage(health)
if self:GetWave() == 0 or health <= 0 or pl == other then return end
pl.HealedThisRound = pl.HealedThisRound + health
if pointmul ~= 0 then
local hpperpoint = self.MedkitPointsPerHealth
if hpperpoint <= 0 then return end
local points = health / hpperpoint * pointmul
pl:AddPoints(points)
end
net.Start("zs_healother")
net.WriteBool(not floater)
net.WriteEntity(other)
net.WriteFloat(health)
net.Send(pl)
if not nobymsg then
net.Start("zs_healby")
net.WriteFloat(health)
net.WriteEntity(pl)
net.Send(other)
end
end
function GM:ObjectPackedUp(pack, packer, owner)
end
function GM:PlayerRepairedObject(pl, other, health, wep)
health = health - other:RemoveUselessDamage(health)
if self:GetWave() == 0 or health <= 0 then return end
pl.RepairedThisRound = pl.RepairedThisRound + health
local hpperpoint = self.RepairPointsPerHealth
if hpperpoint <= 0 then return end
local points = health / hpperpoint
pl:AddPoints(points)
net.Start("zs_repairobject")
net.WriteEntity(other)
net.WriteFloat(health)
net.Send(pl)
end
function GM:CacheHonorableMentions()
if self.CachedHMs then return end
self.CachedHMs = {}
for i, hm in ipairs(self.HonorableMentions) do
if hm.GetPlayer then
local pl, magnitude = hm.GetPlayer(self)
if pl then
self.CachedHMs[i] = {pl, i, magnitude or 0}
end
end
end
gamemode.Call("PostDoHonorableMentions")
end
function GM:DoHonorableMentions(filter)
self:CacheHonorableMentions()
for i, tab in pairs(self.CachedHMs) do
net.Start("zs_honmention")
net.WriteEntity(tab[1])
net.WriteUInt(tab[2], 8)
net.WriteInt(tab[3], 32)
if filter then
net.Send(filter)
else
net.Broadcast()
end
end
end
function GM:PostDoHonorableMentions()
end
function GM:PostEndRound(winner)
self:SaveAllVaults()
end
-- You can override or hook and return false in case you have your own map change system.
local function RealMap(map)
return string.match(map, "(.+)%.bsp")
end
function GM:LoadNextMap()
-- Just in case.
timer.Simple(10, game.LoadNextMap)
timer.Simple(15, function() RunConsoleCommand("changelevel", game.GetMap()) end)
if file.Exists(GetConVar("mapcyclefile"):GetString(), "GAME") then
game.LoadNextMap()
else
local maps = file.Find("maps/zs_*.bsp", "GAME")
maps = table.Add(maps, file.Find("maps/ze_*.bsp", "GAME"))
maps = table.Add(maps, file.Find("maps/zm_*.bsp", "GAME"))
table.sort(maps)
if #maps > 0 then
local currentmap = game.GetMap()
for i, map in ipairs(maps) do
local lowermap = string.lower(map)
local realmap = RealMap(lowermap)
if realmap == currentmap then
if maps[i + 1] then
local nextmap = RealMap(maps[i + 1])
if nextmap then
RunConsoleCommand("changelevel", nextmap)
end
else
local nextmap = RealMap(maps[1])
if nextmap then
RunConsoleCommand("changelevel", nextmap)
end
end
break
end
end
end
end
end
function GM:PreRestartRound()
for _, pl in pairs(player.GetAll()) do
pl:StripWeapons()
pl:Spectate(OBS_MODE_ROAMING)
pl:GodDisable()
end
end
GM.CurrentRound = 1
function GM:RestartRound()
self.CurrentRound = self.CurrentRound + 1
net.Start("zs_currentround")
net.WriteUInt(self.CurrentRound, 6)
net.Broadcast()
self:RestartLua()
self:RestartGame()
net.Start("zs_gamemodecall")
net.WriteString("RestartRound")
net.Broadcast()
end
GM.DynamicSpawning = true
GM.CappedInfliction = 0
GM.PeakPopulation = 0
GM.StartingZombie = {}
GM.CheckedOut = {}
GM.PreviouslyDied = {}
GM.StoredUndeadFrags = {}
function GM:RestartLua()
self.CachedHMs = nil
self.TheLastHuman = nil
self.LastBossZombieSpawned = nil
self.UseSigils = nil
--self:SetAllSigilsDestroyed(false)
-- logic_pickups
self.MaxWeaponPickups = nil
self.MaxAmmoPickups = nil
self.MaxFlashlightPickups = nil
self.WeaponRequiredForAmmo = nil
for _, pl in pairs(player.GetAll()) do
pl.AmmoPickups = nil
pl.WeaponPickups = nil
end
self.OverrideEndSlomo = nil
if type(GetGlobalBool("endcamera", 1)) ~= "number" then
SetGlobalBool("endcamera", nil)
end
if GetGlobalString("winmusic", "-") ~= "-" then
SetGlobalString("winmusic", nil)
end
if GetGlobalString("losemusic", "-") ~= "-" then
SetGlobalString("losemusic", nil)
end
if type(GetGlobalVector("endcamerapos", 1)) ~= "number" then
SetGlobalVector("endcamerapos", nil)
end
self.CappedInfliction = 0
self.PeakPopulation = 0
self.StartingZombie = {}
self.CheckedOut = {}
self.PreviouslyDied = {}
self.StoredUndeadFrags = {}
ROUNDWINNER = nil
LAST_BITE = nil
LASTHUMAN = nil
hook.Remove("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage")
hook.Remove("PlayerCanHearPlayersVoice", "EndRoundCanHearPlayersVoice")
self:RevertZombieClasses()
self:ClearItemStocks(true)
end
-- I don't know.
local function CheckBroken()
for _, pl in pairs(player.GetAll()) do
if pl:Alive() and (pl:Health() <= 0 or pl:GetObserverMode() ~= OBS_MODE_NONE or pl:OBBMaxs().x ~= 16) then
pl:SetObserverMode(OBS_MODE_NONE)
pl:UnSpectateAndSpawn()
end
end
end
function GM:DoRestartGame()
self.RoundEnded = nil
for _, ent in pairs(ents.FindByClass("prop_weapon")) do
ent:Remove()
end
for _, ent in pairs(ents.FindByClass("prop_ammo")) do
ent:Remove()
end
for _, ent in pairs(ents.FindByClass("prop_invitem")) do
ent:Remove()
end
self:SetUseSigils(false)
self:SetEscapeStage(ESCAPESTAGE_NONE)
self:SetWave(0)
self:SetWaveActive(false)
if self.ZombieEscape then
self:SetWaveStart(CurTime() + 30)
else
self:SetWaveStart(CurTime() + self.WaveZeroLength)
end
self:SetWaveEnd(self:GetWaveStart() + self:GetWaveOneLength())
SetGlobalInt("numwaves", -2)
timer.Create("CheckBroken", 10, 1, CheckBroken)
game.CleanUpMap(false, self.CleanupFilter)
gamemode.Call("InitPostEntityMap")
for _, pl in pairs(player.GetAll()) do
pl:UnSpectateAndSpawn()
pl:GodDisable()
gamemode.Call("PlayerInitialSpawnRound", pl)
gamemode.Call("PlayerReadyRound", pl)
if pl:Team() == TEAM_UNDEAD then -- bots?
pl:KillSilent()
end
end
end
function GM:RestartGame()
for _, pl in pairs(player.GetAll()) do
pl:StripWeapons()
pl:StripAmmo()
pl:SetFrags(0)
pl:SetDeaths(0)
pl:SetPoints(0)
if not pl.IsZSBot then
pl:ChangeTeam(TEAM_HUMAN)
end
pl:DoHulls()
pl:SetZombieClass(self.DefaultZombieClass)
pl.DeathClass = nil
end
for _, ent in pairs(ents.FindByClass("prop_obj_sigil")) do
ent:Remove()
end
self:SetWave(0)
if self.ZombieEscape then
self:SetWaveStart(CurTime() + 30)
else
self:SetWaveStart(CurTime() + self.WaveZeroLength)
end
self:SetWaveEnd(self:GetWaveStart() + self:GetWaveOneLength())
self:SetWaveActive(false)
SetGlobalInt("numwaves", -2)
if GetGlobalString("hudoverride"..TEAM_UNDEAD, "") ~= "" then
SetGlobalString("hudoverride"..TEAM_UNDEAD, "")
end
if GetGlobalString("hudoverride"..TEAM_HUMAN, "") ~= "" then
SetGlobalString("hudoverride"..TEAM_HUMAN, "")
end
timer.Simple(0.25, function() GAMEMODE:DoRestartGame() end)
end
function GM:InitPostEntityMap(fromze)
pcall(gamemode.Call, "LoadMapEditorFile")
gamemode.Call("SetupSpawnPoints")
gamemode.Call("RemoveUnusedEntities")
if not fromze then
gamemode.Call("ReplaceMapWeapons")
gamemode.Call("ReplaceMapAmmo")
gamemode.Call("ReplaceMapBatteries")
end
gamemode.Call("CreateZombieGas")
gamemode.Call("SetupProps")
for _, ent in pairs(ents.FindByClass("prop_ammo")) do ent.PlacedInMap = true end
for _, ent in pairs(ents.FindByClass("prop_weapon")) do ent.PlacedInMap = true end
for _, ent in pairs(ents.FindByClass("func_door_rotating")) do ent.NoTraceAttack = true end
for _, ent in pairs(ents.FindByClass("func_physbox")) do ent.IsPhysbox = true end
for _, ent in pairs(ents.FindByClass("func_physbox_multiplayer")) do
ent.IsPhysbox = true
ent.IgnoreZEProtect = true
end
for _, ent in pairs(ents.FindByClass("item_*")) do ent.NoNails = true end
if self.ObjectiveMap then
self:SetDynamicSpawning(false)
self.BossZombies = false
end
if game.MaxPlayers() > 16 then
local e = ents.FindByClass("shadow_control")[1]
if not e then
e = ents.Create("shadow_control")
e:Spawn()
end
if e:IsValid() then
e:SetKeyValue("disableallshadows", "1")
end
util.RemoveAll("func_precipitation")
end
gamemode.Call("CreateSigils")
end
function GM:SetDynamicSpawning(onoff)
SetGlobalBool("DynamicSpawningDisabled", not onoff)
self.DynamicSpawning = onoff
end
local function EndRoundPlayerShouldTakeDamage(pl, attacker) return pl:Team() == TEAM_UNDEAD or not attacker:IsPlayer() end
local function EndRoundPlayerCanSuicide(pl) return pl:Team() == TEAM_UNDEAD end
local function EndRoundSetupPlayerVisibility(pl)
if GAMEMODE.LastHumanPosition and GAMEMODE.RoundEnded then
AddOriginToPVS(GAMEMODE.LastHumanPosition)
else
hook.Remove("SetupPlayerVisibility", "EndRoundSetupPlayerVisibility")
end
end
function GM:OnPlayerWin(pl)
local xp = math.Clamp(#player.GetAll() * 6, 20, 200) * (GAMEMODE.WinXPMulti or 1)
if self.ZombieEscape then
xp = xp / 4
end
pl:AddZSXP(xp)
end
function GM:OnPlayerLose(pl)
end
function GM:EndRound(winner)
if self.RoundEnded then return end
self.RoundEnded = true
self.RoundEndedTime = CurTime()
ROUNDWINNER = winner
if self.OverrideEndSlomo == nil or self.OverrideEndSlomo then
game.SetTimeScale(0.25)
timer.Simple(2, function() game.SetTimeScale(1) end)
end
hook.Add("PlayerCanHearPlayersVoice", "EndRoundCanHearPlayersVoice", function() return true, false end)
if self.OverrideEndCamera == nil or self.OverrideEndCamera then
hook.Add("SetupPlayerVisibility", "EndRoundSetupPlayerVisibility", EndRoundSetupPlayerVisibility)
end
if self:ShouldRestartRound() then
timer.Simple(self.EndGameTime - 3, function() gamemode.Call("PreRestartRound") end)
timer.Simple(self.EndGameTime, function() gamemode.Call("RestartRound") end)
else
timer.Simple(self.EndGameTime, function() gamemode.Call("LoadNextMap") end)
end
-- Get rid of some lag.
util.RemoveAll("prop_ammo")
util.RemoveAll("prop_weapon")
util.RemoveAll("prop_invitem")
timer.Simple(5, function() gamemode.Call("DoHonorableMentions") end)
if winner == TEAM_HUMAN then
self.LastHumanPosition = nil
for _, pl in pairs(player.GetAll()) do
if pl:Team() == TEAM_HUMAN then
if not self:GetUseSigils() then
gamemode.Call("OnPlayerWin", pl)
end
elseif pl:Team() == TEAM_UNDEAD then
gamemode.Call("OnPlayerLose", pl)
end
end
hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerShouldTakeDamage)
elseif winner == TEAM_UNDEAD then
hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerCanSuicide)
for _, pl in pairs(team.GetPlayers(TEAM_UNDEAD)) do
gamemode.Call("OnPlayerLose", pl)
end
end
net.Start("zs_endround")
net.WriteUInt(winner or -1, 8)
net.WriteString(game.GetMapNext())
net.Broadcast()
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_ROUND,
winner == TEAM_HUMAN and "Wins" or ("LossWave"..self:GetWave()), game.GetMap(), 1)
if winner == TEAM_HUMAN then
for _, ent in pairs(ents.FindByClass("logic_winlose")) do
ent:Input("onwin")
end
else
for _, ent in pairs(ents.FindByClass("logic_winlose")) do
ent:Input("onlose")
end
end
gamemode.Call("PostEndRound", winner)
self:SetWaveStart(CurTime() + 9999)
end
function GM:ScalePlayerDamage(pl, hitgroup, dmginfo)
local attacker = dmginfo:GetAttacker()
local inflictor = dmginfo:GetInflictor()
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, inflictor:GetClass(), "Hits", 1)
if hitgroup == HITGROUP_HEAD then
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, inflictor:GetClass(), "Headshots", 1)
end
if not dmginfo:IsBulletDamage() then return end
if hitgroup == HITGROUP_HEAD and dmginfo:IsBulletDamage() then
pl.m_LastHeadShot = CurTime()
end
--local crouchpunish = pl:ShouldCrouchJumpPunish()
if not pl:CallZombieFunction2("ScalePlayerDamage", hitgroup, dmginfo) then
if hitgroup == HITGROUP_HEAD then
dmginfo:SetDamage(dmginfo:GetDamage() * (inflictor.HeadshotMulti or 2) * (attacker:IsPlayer() and attacker:GetStatus("renegade") and 1.1 or 1))
elseif hitgroup == HITGROUP_LEFTLEG or hitgroup == HITGROUP_RIGHTLEG then
--if not crouchpunish then
if not pl:ShouldCrouchJumpPunish() then
dmginfo:SetDamage(dmginfo:GetDamage() / 4)
end
end
end
if (hitgroup == HITGROUP_LEFTLEG or hitgroup == HITGROUP_RIGHTLEG) and self:PlayerShouldTakeDamage(pl, dmginfo:GetAttacker()) and not pl:CallZombieFunction1("IgnoreLegDamage", dmginfo) then
pl:AddLegDamage(
pl:ShouldCrouchJumpPunish() and not (pl.LastBarricadeHit and pl.LastBarricadeHit + 2 > CurTime()) and dmginfo:GetDamage()/4
or dmginfo:GetDamage()
)
end
end
function GM:PlayerReady(pl)
gamemode.Call("PlayerReadyRound", pl)
self:PlayerReadyVault(pl)
pl.PlayerReady = true
end
function GM:PlayerReadyRound(pl)
if not pl:IsValid() then return end
self:FullGameUpdate(pl)
pl:UpdateAllZombieClasses()
local classid = pl:GetZombieClass()
pl:SetZombieClass(classid, true, pl)
if self.OverrideStartingWorth then
pl:SendLua("GAMEMODE.StartingWorth="..tostring(self.StartingWorth))
end
if pl:Team() == TEAM_UNDEAD then
-- This is just so they get updated on what class they are and have their hulls set up right.
pl:DoHulls(classid, TEAM_UNDEAD)
elseif pl:Team() == TEAM_HUMAN then
if self:GetWave() <= 0 and self.StartingWorth > 0 and not self.StartingLoadout and not self.ZombieEscape then
pl:SendLua("InitialWorthMenu()")
else
gamemode.Call("GiveDefaultOrRandomEquipment", pl)
end
end
net.Start("zs_currentround")
net.WriteUInt(self.CurrentRound, 6)
net.Send(pl)
if self.RoundEnded then
pl:SendLua("gamemode.Call(\"EndRound\", "..tostring(ROUNDWINNER)..", \""..game.GetMapNext().."\")")
gamemode.Call("DoHonorableMentions", pl)
end
if pl:GetInfo("zs_noredeem") == "1" then
pl.NoRedeeming = true
end
if self:GetWave() == 0 then
self:SendZombieVolunteers(pl, true)
end
if self:IsClassicMode() then
pl:SendLua("SetGlobalBool(\"classicmode\", true)")
elseif self:IsBabyMode() then
pl:SendLua("SetGlobalBool(\"babymode\", true)")
end
self:RefreshItemStocks(pl)
self:ClassUnlocksUpdate(pl)
end
function GM:FullGameUpdate(pl)
net.Start("zs_gamestate")
net.WriteInt(self:GetWave(), 16)
net.WriteFloat(self:GetWaveStart())
net.WriteFloat(self:GetWaveEnd())
if pl then
net.Send(pl)
else
net.Broadcast()
end
end
concommand.Add("initpostentity", function(sender, command, arguments)
if not sender.DidInitPostEntity then
sender.DidInitPostEntity = true
gamemode.Call("PlayerReady", sender)
end
end)
local playerheight = Vector(0, 0, 72)
local function groupsort(ga, gb)
return #ga > #gb
end
function GM:AttemptHumanDynamicSpawn(pl)
if not self.DynamicSpawning or not pl:IsValidLivingHuman() then return false end
local sigils = self:GetSigils()
local randsigil = sigils[math.random(#sigils)]
if randsigil and randsigil:IsValid() and not randsigil:GetSigilCorrupted() then
pl:SetBarricadeGhosting(true)
pl:SetPos(randsigil:GetPos())
return true
end
local group, pos, nearzombie
local allplayers = team.GetPlayers(TEAM_HUMAN)
local groups = self:GetTeamRallyGroups(TEAM_HUMAN)
table.sort(groups, groupsort)
for i=1, #groups do
group = groups[i]
for _, otherpl in pairs(group) do
if otherpl ~= pl then
pos = otherpl:GetPos() + Vector(0, 0, 1)
if otherpl:Alive() and otherpl:GetMoveType() == MOVETYPE_WALK and not util.TraceHull({start = pos, endpos = pos + playerheight, mins = playermins, maxs = playermaxs, mask = MASK_SOLID, filter = allplayers}).Hit then
nearzombie = false
for __, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
if ent:Alive() and ent:GetPos():DistToSqr(pos) <= 65536 then --256^2
nearzombie = true
end
end
if not nearzombie then
pl:SetPos(otherpl:GetPos())
return true
end
end
end
end
end
return false
end
function GM:PlayerInitialSpawn(pl)
pl.MaxBloodArmor = GAMEMODE.ZombieEscape and 0 or 10
pl.NextFlashlightSwitch = 0
pl.NextPainSound = 0
pl.NextFlinch = 0
pl.LastSentESW = 0
pl.m_LastWaveStartSpawn = 0
pl.m_LastGasHeal = 0
self:InitializeVault(pl)
gamemode.Call("PlayerInitialSpawnRound", pl)
self.PeakPopulation = math.max(self.PeakPopulation, #player.GetAll())
end
function GM:PlayerInitialSpawnRound(pl)
pl:SprintDisable()
if pl:KeyDown(IN_WALK) then
pl:ConCommand("-walk")
end
pl:SetCanWalk(false)
pl:SetCanZoom(false)
-- This is the culprit for shitty player to player collisions when standing on an enemy's head. No idea why.
pl:SetNoCollideWithTeammates(false) --pl:SetNoCollideWithTeammates(true)
pl:SetCustomCollisionCheck(true)
pl.ZombiesKilled = 0
pl.ZombiesKilledAssists = 0
pl.Headshots = 0
pl.BrainsEaten = 0
pl.ResupplyBoxUsedByOthers = 0
pl.WaveJoined = self:GetWave()
pl.CrowKills = 0
pl.DefenceDamage = 0
pl.StrengthBoostDamage = 0
pl.BarricadeDamage = 0
pl.PointsRemainder = 0
pl.XPRemainder = 0
pl.LegDamage = 0
pl.ArmDamage = 0
pl.DamageDealt = {}
pl.DamageDealt[TEAM_UNDEAD] = 0
pl.DamageDealt[TEAM_HUMAN] = 0
pl.ZSFriends = {}
pl.LifeBarricadeDamage = 0
pl.LifeHumanDamage = 0
pl.LifeBrainsEaten = 0
pl.WaveBarricadeDamage = 0
pl.WaveHumanDamage = 0
pl.PointQueue = 0
pl.LastDamageDealtTime = 0
pl.HealedThisRound = 0
pl.RepairedThisRound = 0
pl.NextRegenerate = 0
pl.NextBloodArmorRegen = 0
pl.NextRegenTrinket = 0
pl.LateBuyerMessage = nil
pl.NestsDestroyed = 0
pl.NestSpawns = 0
pl.LastRevive = 0
pl.ZSInventory = {}
--local nosend = not pl.DidInitPostEntity
pl.DamageVulnerability = nil
self:LoadVault(pl)
local uniqueid = pl:UniqueID()
if self.PreviouslyDied[uniqueid] or ZSBOT then
-- They already died and reconnected.
pl:ChangeTeam(TEAM_UNDEAD)
elseif LASTHUMAN then ----
pl.SpawnedTime = CurTime()
pl:ChangeTeam(TEAM_UNDEAD)
elseif self:GetWave() <= 0 then
pl.SpawnedTime = CurTime()
pl:ChangeTeam(TEAM_HUMAN)
if self.DynamicSpawning then
timer.Simple(1, function()
GAMEMODE:AttemptHumanDynamicSpawn(pl)
pl:SetBarricadeGhosting(true, true)
end)
end
elseif self:GetNumberOfWaves() == -1 or self.NoNewHumansWave <= self:GetWave() or team.NumPlayers(TEAM_UNDEAD) == 0 and 1 <= team.NumPlayers(TEAM_HUMAN) then -- Joined during game, no zombies, some humans or joined past the deadline.
pl:ChangeTeam(TEAM_UNDEAD)
self.PreviouslyDied[uniqueid] = CurTime()
else
pl.SpawnedTime = CurTime()
pl:ChangeTeam(TEAM_HUMAN)
if self.DynamicSpawning then
timer.Simple(0, function()
GAMEMODE:AttemptHumanDynamicSpawn(pl)
pl:SetBarricadeGhosting(true, true)
end)
end
end
if pl:Team() == TEAM_UNDEAD and not self:GetWaveActive() then
pl:SetZombieClassName("Crow")
pl.DeathClass = self.DefaultZombieClass
else
pl:SetZombieClass(self.DefaultZombieClass)
end
if pl:Team() == TEAM_UNDEAD and self.StoredUndeadFrags[uniqueid] then
pl:SetFrags(self.StoredUndeadFrags[uniqueid])
self.StoredUndeadFrags[uniqueid] = nil
end
end
function GM:GetDynamicSpawning()
return self.DynamicSpawning
end
function GM:PrePlayerRedeemed(pl, silent, noequip)
end
function GM:PostPlayerRedeemed(pl, silent, noequip)
end
function GM:PlayerDisconnected(pl)
pl.Disconnecting = true
local uid = pl:UniqueID()
self.PreviouslyDied[uid] = CurTime()
if pl:Team() == TEAM_HUMAN then
pl:DropAll()
elseif pl:Team() == TEAM_UNDEAD then
self.StoredUndeadFrags[uid] = pl:Frags()
end
if pl:Health() > 0 and not pl:IsSpectator() then
local lastattacker = pl:GetLastAttacker()
if IsValid(lastattacker) then
pl:TakeDamage(1000, lastattacker, lastattacker)
PrintTranslatedMessage(HUD_PRINTCONSOLE, "disconnect_killed", pl:Name(), lastattacker:Name())
end
end
self:SaveVault(pl)
gamemode.Call("CalculateInfliction")
end
function GM:CanDamageNail(ent, attacker, inflictor, damage, dmginfo)
return not attacker:IsPlayer() or attacker:Team() == TEAM_UNDEAD
end
function GM:CanPlaceNail(pl, tr)
if tr and not pl:HasBarricadeExpert() and tr.Entity.ExpertProtection and tr.Entity.ExpertProtection > CurTime() then
return false
end
return true
end
function GM:CanRemoveNail(pl, nail)
return not nail.m_NailUnremovable
end
function GM:CanRemoveOthersNail(pl, nailowner, ent)
-- obsolete
--[[local plpoints = pl:Frags()
local ownerpoints = nailowner:Frags()
if plpoints >= 75 or ownerpoints < 75 then return true end]]
if gamemode.Call("PlayerIsAdmin", pl) then return true end
if nailowner.ZSFriends[pl] then return true end
if pl:BarricadeExpertPrecedence(nailowner) == -1 then
pl:PrintTranslatedMessage(HUD_PRINTCENTER, "cant_remove_nails_of_superior_player")
return false
end
return true
end
function GM:SetRedeemBrains(amount)
SetGlobalInt("redeembrains", amount)
end
-- Reevaluates a prop and its constraint system (or all props if no arguments) to determine if they should be frozen or not from nails.
function GM:EvaluatePropFreeze(ent, neighbors)
if not ent then
for _, e in pairs(ents.GetAll()) do
if e and e:IsValid() then
self:EvaluatePropFreeze(e)
end
end
return
end
if ent:IsNailedToWorldHierarchy() then
ent:SetNailFrozen(true)
elseif ent:GetNailFrozen() then
ent:SetNailFrozen(false)
end
neighbors = neighbors or {}
table.insert(neighbors, ent)
local baseent, attachent
for _, nail in pairs(ent:GetNails()) do
if nail:IsValid() then
baseent = nail:GetBaseEntity()
attachent = nail:GetAttachEntity()
if baseent:IsValid() and not baseent:IsWorld() and not table.HasValue(neighbors, baseent) then
self:EvaluatePropFreeze(baseent, neighbors)
end
if attachent:IsValid() and not attachent:IsWorld() and not table.HasValue(neighbors, attachent) then
self:EvaluatePropFreeze(attachent, neighbors)
end
end
end
end
-- A nail takes some damage. isdead is true if the damage is enough to remove the nail. The nail is invalid after this function call if it dies.
function GM:OnNailDamaged(ent, attacker, inflictor, damage, dmginfo)
end
-- A nail is removed between two entities. The nail is no longer considered valid right after this function and is not in the entities' Nails tables. remover may not be nil if it was removed with the hammer's unnail ability.
local function evalfreeze(ent)
if ent and ent:IsValid() then
gamemode.Call("EvaluatePropFreeze", ent)
end
end
function GM:OnNailRemoved(nail, ent1, ent2, remover)
if ent1 and ent1:IsValid() and not ent1:IsWorld() then
timer.Simple(0, function() evalfreeze(ent1) end)
timer.Simple(0.2, function() evalfreeze(ent1) end)
end
if ent2 and ent2:IsValid() and not ent2:IsWorld() then
timer.Simple(0, function() evalfreeze(ent2) end)
timer.Simple(0.2, function() evalfreeze(ent2) end)
end
if remover and remover:IsValid() and remover:IsPlayer() then
local deployer = nail:GetDeployer()
local deployername = "[unconnected]"
if deployer:IsValid() and deployer:Team() == TEAM_HUMAN then
deployername = deployer:Name()
if deployer ~= remover then
net.Start("zs_nailremoved")
net.WriteEntity(remover)
net.Send(deployer)
end
end
PrintTranslatedMessage(HUD_PRINTCONSOLE, "nail_removed_by", remover:Name(), deployername)
if remover:HasBarricadeExpert() then
if ent1 and ent1:IsValid() and not ent1:IsWorld() then ent1.ExpertProtection = CurTime() + 5 end
if ent2 and ent2:IsValid() and not ent2:IsWorld() then ent2.ExpertProtection = CurTime() + 5 end
end
end
end
-- A nail is created between two entities.
function GM:OnNailCreated(ent1, ent2, nail)
if ent1 and ent1:IsValid() and not ent1:IsWorld() then
timer.Simple(0, function() evalfreeze(ent1) end)
end
if ent2 and ent2:IsValid() and not ent2:IsWorld() then
timer.Simple(0, function() evalfreeze(ent2) end)
end
end
function GM:RemoveDuplicateAmmo(pl)
local AmmoCounts = {}
local WepAmmos = {}
for _, wep in pairs(pl:GetWeapons()) do
if wep.Primary then
local ammotype = wep:ValidPrimaryAmmo()
if ammotype and wep.Primary.DefaultClip > 0 then
AmmoCounts[ammotype] = (AmmoCounts[ammotype] or 0) + 1
WepAmmos[wep] = wep.Primary.DefaultClip - wep.Primary.ClipSize
end
local ammotype2 = wep:ValidSecondaryAmmo()
if ammotype2 and wep.Secondary.DefaultClip > 0 then
AmmoCounts[ammotype2] = (AmmoCounts[ammotype2] or 0) + 1
WepAmmos[wep] = wep.Secondary.DefaultClip - wep.Secondary.ClipSize
end
end
end
for ammotype, count in pairs(AmmoCounts) do
if count > 1 then
local highest = 0
local highestwep
for wep, extraammo in pairs(WepAmmos) do
if wep.Primary.Ammo == ammotype then
highest = math.max(highest, extraammo)
highestwep = wep
end
end
if highestwep then
for wep, extraammo in pairs(WepAmmos) do
if wep ~= highestwep and wep.Primary.Ammo == ammotype then
pl:RemoveAmmo(extraammo, ammotype)
end
end
end
end
end
end
local function TimedOut(pl)
if pl:IsValid() and pl:Team() == TEAM_HUMAN and pl:Alive() and not GAMEMODE.CheckedOut[pl:UniqueID()] then
gamemode.Call("GiveRandomEquipment", pl)
end
end
function GM:GiveDefaultOrRandomEquipment(pl)
if not self.CheckedOut[pl:UniqueID()] and not self.ZombieEscape then
if self.StartingLoadout then
self:GiveStartingLoadout(pl)
else
pl:SendLua("GAMEMODE:RequestedDefaultCart()")
if self.StartingWorth > 0 then
timer.Simple(4, function() TimedOut(pl) end)
end
end
end
end
function GM:GiveStartingLoadout(pl)
if self.CheckedOut[pl:UniqueID()] then return end
self.CheckedOut[pl:UniqueID()] = true
for item, amount in pairs(self.StartingLoadout) do
for i=1, amount do
pl:Give(item)
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, item, "StartingLoadout", 1)
end
end
end
function GM:GiveRandomEquipment(pl)
if self.CheckedOut[pl:UniqueID()] or self.ZombieEscape then return end
self.CheckedOut[pl:UniqueID()] = true
if self.StartingLoadout then
self:GiveStartingLoadout(pl)
elseif GAMEMODE.OverrideStartingWorth then
pl:Give("weapon_zs_swissarmyknife")
elseif #self.StartLoadouts >= 1 then
for _, id in pairs(self.StartLoadouts[math.random(#self.StartLoadouts)]) do
local tab = FindStartingItem(id)
if tab then
if tab.Callback then
tab.Callback(pl)
elseif tab.SWEP then
if not pl:AddInventoryItem(tab.SWEP) then
pl:StripWeapon(tab.SWEP)
pl:Give(tab.SWEP)
end
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, tab.SWEP, "RandomCheckouts", 1)
end
end
end
end
end
function GM:PlayerCanCheckout(pl)
return pl:IsValid() and pl:Team() == TEAM_HUMAN and pl:Alive() and not self.CheckedOut[pl:UniqueID()] and not self.StartingLoadout and not self.ZombieEscape and self.StartingWorth > 0 and self:GetWave() < 2
end
function GM:PlayerDeathThink(pl)
if self.RoundEnded or pl.Revive or self:GetWave() == 0 then return end
if pl:GetObserverMode() == OBS_MODE_CHASE then
local target = pl:GetObserverTarget()
if not target or not target:IsValid() or target:IsPlayer() and (not target:Alive() or target:Team() ~= pl:Team()) then
pl:StripWeapons()
pl:Spectate(OBS_MODE_ROAMING)
pl:SpectateEntity(NULL)
end
end
if pl:Team() ~= TEAM_UNDEAD then
pl.StartCrowing = nil
pl.StartSpectating = nil
return
end
if pl.NextSpawnTime and pl.NextSpawnTime <= CurTime() then -- Force spawn.
pl.NextSpawnTime = nil
pl:RefreshDynamicSpawnPoint()
pl:UnSpectateAndSpawn()
elseif pl:GetObserverMode() == OBS_MODE_NONE then -- Not in spectator yet.
if self:GetWaveActive() then -- During wave.
if not pl.StartSpectating or CurTime() >= pl.StartSpectating then
pl.StartSpectating = nil
pl:StripWeapons()
local best = self:GetBestDynamicSpawn(pl)
if best then
pl:Spectate(OBS_MODE_CHASE)
pl:SpectateEntity(best)
else
pl:Spectate(OBS_MODE_ROAMING)
pl:SpectateEntity(NULL)
end
end
elseif not pl.StartCrowing or CurTime() >= pl.StartCrowing then -- Not during wave. Turn in to a crow. If we die as a crow then we get turned to spectator anyway.
pl:ChangeToCrow()
end
else -- In spectator.
if pl:KeyDown(IN_ATTACK) or pl:KeyDown(IN_ATTACK2) or pl:IsBot() then
pl:RefreshDynamicSpawnPoint()
local forcespawn = pl.ForceDynamicSpawn
if forcespawn and forcespawn.MinionSpawn then
pl:TrySpawnAsGoreChild(forcespawn)
else
if self:GetWaveActive() then
pl:UnSpectateAndSpawn()
else
pl:ChangeToCrow()
end
end
elseif pl:KeyDown(IN_RELOAD) then
if self:GetWaveActive() then
pl.ForceDynamicSpawn = nil
local prev = self.DynamicSpawning
self.DynamicSpawning = false
pl:UnSpectateAndSpawn()
self.DynamicSpawning = prev
else
pl:ChangeToCrow()
end
elseif pl:KeyPressed(IN_JUMP) then
if pl:GetObserverMode() ~= OBS_MODE_ROAMING then
pl:Spectate(OBS_MODE_ROAMING)
pl:SpectateEntity(NULL)
pl.SpectatedPlayerKey = nil
end
end
end
end
function GM:ShouldAntiGrief(ent, attacker, dmginfo, health)
return ent.m_AntiGrief and self.GriefMinimumHealth <= health and attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN and not dmginfo:IsExplosionDamage()
end
function GM:PropBreak(attacker, ent)
gamemode.Call("PropBroken", ent, attacker)
end
function GM:PropBroken(ent, attacker)
if IsValid(ent) and IsValid(attacker) and not ent._PROPBROKEN and attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN then
ent._PROPBROKEN = true
if attacker.LogID then --failsafe for local dev
PrintMessage(HUD_PRINTCONSOLE, attacker:LogID().." broke "..ent:GetModel())
end
end
end
function GM:NestDestroyed(ent, attacker)
if IsValid(ent) and IsValid(attacker) and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
PrintMessage(HUD_PRINTCONSOLE, attacker:LogID().." team killed a nest at "..tostring(ent:GetPos()).." (builder: "..(ent:GetOwner() and ent:GetOwner():IsValid() and ent:GetOwner():IsPlayer() and ent:GetOwner():LogID() or "unknown")..")")
end
end
function GM:EntityTakeDamage(ent, dmginfo)
local attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor()
if attacker == inflictor and attacker:IsProjectile() and dmginfo:GetDamageType() == DMG_CRUSH then -- Fixes projectiles doing physics-based damage.
dmginfo:SetDamage(0)
dmginfo:ScaleDamage(0)
return
end
if ent.LastHeld and CurTime() < ent.LastHeld + 0.1 and attacker:IsPlayer() and P_Team(attacker) == TEAM_HUMAN then
dmginfo:SetDamage(0)
dmginfo:SetDamageType(0)
dmginfo:ScaleDamage(0)
dmginfo:SetDamageForce(vector_origin)
return
end
-- Props about to be broken props take 3x damage from anything except zombies
if ent._BARRICADEBROKEN and not (attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD) then
dmginfo:SetDamage(dmginfo:GetDamage() * 3)
end
if ent.GetObjectHealth and not (attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN) then
ent.m_LastDamaged = CurTime()
end
if ent.ProcessDamage and ent:ProcessDamage(dmginfo) then return end
attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor()
-- Don't allow blowing up props during wave 0.
if self:GetWave() <= 0 and string.sub(ent:GetClass(), 1, 12) == "prop_physics" and inflictor.NoPropDamageDuringWave0 then
dmginfo:SetDamage(0)
dmginfo:SetDamageType(DMG_ALWAYSGIB)
return
end
-- We need to stop explosive chains team killing.
if inflictor:IsValid() then
local dmgtype = dmginfo:GetDamageType()
if ent:IsPlayer() and (dmgtype == DMG_ALWAYSGIB or dmgtype == DMG_BURN or dmgtype == DMG_SLOWBURN) and string.sub(inflictor:GetClass(), 1, 12) == "prop_physics" then -- We'll assume a barrel did this damage to a player
if inflictor.LastDamagedByTeam == ent:Team() and inflictor.LastDamagedBy ~= ent then -- A team member is trying to screw with us
dmginfo:SetDamage(0)
dmginfo:ScaleDamage(0)
return
end
elseif string.sub(ent:GetClass(), 1, 12) == "prop_physics" then -- Physics object damaged by...
if inflictor:IsPlayer() then
ent.LastDamagedByTeam = inflictor:Team()
ent.LastDamagedBy = inflictor
elseif (dmgtype == DMG_ALWAYSGIB or dmgtype == DMG_BURN or dmgtype == DMG_SLOWBURN) and string.sub(inflictor:GetClass(), 1, 12) == "prop_physics" then -- A barrel damaging a barrel. Probably.
if inflictor.LastDamagedByTeam then
ent.LastDamagedByTeam = inflictor.LastDamagedByTeam
ent.LastDamagedBy = inflictor.LastDamagedBy
end
end
end
end
-- Prop is nailed. Forward damage to the nails.
if ent:DamageNails(attacker, inflictor, dmginfo:GetDamage(), dmginfo) then return end
local dispatchdamagedisplay = false
local entclass = ent:GetClass()
if ent:IsPlayer() then
dispatchdamagedisplay = true
if attacker.PBAttacker and attacker.PBAttacker:IsValid() then
attacker = attacker.PBAttacker
end
if attacker:IsValid() then
if attacker:IsPlayer() then
ent:SetLastAttacker(attacker)
local myteam = attacker:Team()
local otherteam = ent:Team()
if myteam ~= otherteam then
local damage = math.min(dmginfo:GetDamage(), ent:Health())
if damage > 0 then
local time = CurTime()
attacker.DamageDealt[myteam] = attacker.DamageDealt[myteam] + damage
if myteam == TEAM_UNDEAD then
if otherteam == TEAM_HUMAN then
attacker:AddLifeHumanDamage(damage)
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_ZOMBIECLASS, attacker:GetZombieClassTable().Name, "HumanDamage", damage)
end
elseif myteam == TEAM_HUMAN and otherteam == TEAM_UNDEAD then
ent.DamagedBy[attacker] = (ent.DamagedBy[attacker] or 0) + damage
if time >= ent.m_LastWaveStartSpawn + 3 and time >= ent.m_LastGasHeal + 2 then
local points = damage / ent:GetMaxHealth() * ent:GetZombieClassTable().Points
if POINTSMULTIPLIER then
points = points * POINTSMULTIPLIER
end
if ent.PointsMultiplier then
points = points * ent.PointsMultiplier
end
attacker.PointQueue = attacker.PointQueue + points
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, inflictor:GetClass(), "PointsEarned", points)
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, inflictor:GetClass(), "Damage", damage)
end
local pos = ent:GetPos()
pos.z = pos.z + 32
attacker.LastDamageDealtPos = pos
attacker.LastDamageDealtTime = time
end
end
end
elseif attacker:GetClass() == "trigger_hurt" then
ent.LastHitWithTriggerHurt = CurTime()
end
end
elseif ent.PropHealth then -- A prop that was invulnerable and converted to vulnerable.
if ent._PROPBROKEN or self.NoPropDamageFromHumanMelee and attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN and inflictor.IsMelee then
dmginfo:SetDamage(0)
return
end
if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.PropHealth) then
attacker:AntiGrief(dmginfo)
if dmginfo:GetDamage() <= 0 then return end
end
ent.PropHealth = ent.PropHealth - dmginfo:GetDamage()
dispatchdamagedisplay = true
if ent.PropHealth <= 0 then
local effectdata = EffectData()
effectdata:SetOrigin(ent:GetPos())
util.Effect("Explosion", effectdata, true, true)
ent:Fire("break")
gamemode.Call("PropBroken", ent, attacker)
else
local brit = math.Clamp(ent.PropHealth / ent.TotalHealth, 0, 1)
local col = ent:GetColor()
col.r = 255
col.g = 255 * brit
col.b = 255 * brit
ent:SetColor(col)
end
elseif entclass == "func_door_rotating" then
if ent:GetKeyValues().damagefilter == "invul" or ent.Broken then return end
if self.ZombieEscape then
return
end
if not ent.Heal then
local br = ent:BoundingRadius()
if br > 80 then return end -- Don't break these kinds of doors that are bigger than this.
local health = br * 35
ent.Heal = health
ent.TotalHeal = health
end
if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
attacker:AntiGrief(dmginfo)
if dmginfo:GetDamage() <= 0 then return end
end
if dmginfo:GetDamage() >= 20 and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
ent:EmitSound(math.random(2) == 1 and "npc/zombie/zombie_pound_door.wav" or "ambient/materials/door_hit1.wav")
end
ent.Heal = ent.Heal - dmginfo:GetDamage()
local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
local col = ent:GetColor()
col.r = 255
col.g = 255 * brit
col.b = 255 * brit
ent:SetColor(col)
dispatchdamagedisplay = true
if ent.Heal <= 0 then
ent.Broken = true
ent:EmitSound("Breakable.Metal")
ent:Fire("unlock", "", 0)
ent:Fire("open", "", 0.01) -- Trigger any area portals.
ent:Fire("break", "", 0.1)
ent:Fire("kill", "", 0.15)
end
elseif entclass == "prop_door_rotating" then
if ent:GetKeyValues().damagefilter == "invul" or ent:HasSpawnFlags(2048) and ent:IsDoorLocked() or ent.Broken then return end
if not ent.Heal then
ent.Heal = ent:BoundingRadius() * 35
ent.TotalHeal = ent.Heal
end
if dmginfo:GetDamage() >= 20 and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
ent:EmitSound(math.random(2) == 1 and "npc/zombie/zombie_pound_door.wav" or "ambient/materials/door_hit1.wav")
end
if self.ZombieEscape then
return
end
if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
attacker:AntiGrief(dmginfo)
if dmginfo:GetDamage() <= 0 then return end
end
ent.Heal = ent.Heal - dmginfo:GetDamage()
local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
local col = ent:GetColor()
col.r = 255
col.g = 255 * brit
col.b = 255 * brit
ent:SetColor(col)
dispatchdamagedisplay = true
if ent.Heal <= 0 then
ent.Broken = true
ent:EmitSound("Breakable.Metal")
ent:Fire("unlock", "", 0)
ent:Fire("open", "", 0.01) -- Trigger any area portals.
ent:Fire("break", "", 0.1)
ent:Fire("kill", "", 0.15)
local physprop = ents.Create("prop_physics")
if physprop:IsValid() then
physprop:SetPos(ent:GetPos())
physprop:SetAngles(ent:GetAngles())
physprop:SetSkin(ent:GetSkin() or 0)
physprop:SetMaterial(ent:GetMaterial())
physprop:SetModel(ent:GetModel())
physprop:Spawn()
physprop:SetPhysicsAttacker(attacker)
if attacker:IsValid() then
local phys = physprop:GetPhysicsObject()
if phys:IsValid() then
phys:SetVelocityInstantaneous((physprop:NearestPoint(attacker:EyePos()) - attacker:EyePos()):GetNormalized() * math.Clamp(dmginfo:GetDamage() * 3, 40, 300))
end
end
if physprop:GetMaxHealth() == 1 and physprop:Health() == 0 then
local health = math.ceil((physprop:OBBMins():Length() + physprop:OBBMaxs():Length()) * 2)
if health < 2000 then
physprop.PropHealth = health
physprop.TotalHealth = health
end
end
end
end
elseif entclass == "func_breakable" then
if ent:GetKeyValues().damagefilter == "invul" then return end
if self.ZombieEscape then
dispatchdamagedisplay = true
return
end
if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent:GetMaxHealth()) then
attacker:AntiGrief(dmginfo, true)
if dmginfo:GetDamage() <= 0 then return end
end
if ent:Health() == 0 and ent:GetMaxHealth() == 1 then return end
local brit = math.Clamp(ent:Health() / ent:GetMaxHealth(), 0, 1)
local col = ent:GetColor()
col.r = 255
col.g = 255 * brit
col.b = 255 * brit
ent:SetColor(col)
dispatchdamagedisplay = true
elseif string.sub(entclass, 1, 12) == "func_physbox" then
local holder, status = ent:GetHolder()
if holder then status:Remove() end
if ent:GetKeyValues().damagefilter == "invul" then return end
if not ent.IgnoreZEProtect and self.ZombieEscape then
dispatchdamagedisplay = true
return
end
ent.Heal = ent.Heal or ent:BoundingRadius() * 35
ent.TotalHeal = ent.TotalHeal or ent.Heal
if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
attacker:AntiGrief(dmginfo)
if dmginfo:GetDamage() <= 0 then return end
end
ent.Heal = ent.Heal - dmginfo:GetDamage()
local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
local col = ent:GetColor()
col.r = 255
col.g = 255 * brit
col.b = 255 * brit
ent:SetColor(col)
dispatchdamagedisplay = true
if ent.Heal <= 0 then
local foundaxis = false
local entname = ent:GetName()
local allaxis = ents.FindByClass("phys_hinge")
for _, axis in pairs(allaxis) do
local keyvalues = axis:GetKeyValues()
if keyvalues.attach1 == entname or keyvalues.attach2 == entname then
foundaxis = true
axis:Remove()
ent.Heal = ent.Heal + 120
end
end
if not foundaxis then
ent:Fire("break", "", 0)
end
end
elseif ent:IsBarricadeProp() and attacker:IsValidLivingZombie() or ent.ZombieConstruction and attacker:IsValidLivingHuman() then
dispatchdamagedisplay = true
end
local dmg = dmginfo:GetDamage()
if dmg > 0 then
local holder, status = ent:GetHolder()
if holder and not holder.BuffTaut then status:Remove() end
local dmgpos = dmginfo:GetDamagePosition()
local hasdmgsess = attacker:IsPlayer() and attacker:HasDamageNumberSession()
if attacker:IsPlayer() and dispatchdamagedisplay and not hasdmgsess then
self:DamageFloater(attacker, ent, dmgpos, dmg)
elseif hasdmgsess and dispatchdamagedisplay then
attacker:CollectDamageNumberSession(dmg, dmgpos, ent:IsPlayer())
end
end
end
function GM:DamageFloater(attacker, victim, dmgpos, dmg, definiteply)
if attacker == victim then return end
if dmgpos == vector_origin then dmgpos = victim:NearestPoint(attacker:EyePos()) end
net.Start((definiteply or victim:IsPlayer()) and "zs_dmg" or "zs_dmg_prop")
if INFDAMAGEFLOATER then
INFDAMAGEFLOATER = nil
net.WriteUInt(9999, 16)
else
net.WriteUInt(math.ceil(dmg), 16)
end
net.WriteVector(dmgpos)
net.Send(attacker)
end
function GM:SetRandomToZombie()
local plays = team.GetPlayers(TEAM_HUMAN)
local pl = plays[math.random(#plays)]
if not pl then return end
pl:ChangeTeam(TEAM_UNDEAD)
pl:SetFrags(0)
pl:SetDeaths(0)
self.StartingZombie[pl:UniqueID()] = true
self.PreviouslyDied[pl:UniqueID()] = CurTime()
pl:UnSpectateAndSpawn()
return pl
end
function GM:PreOnPlayerChangedTeam(pl, oldteam, newteam)
--[[if oldteam == TEAM_HUMAN then
self:SaveVault(pl)
end]]
end
function GM:OnPlayerChangedTeam(pl, oldteam, newteam)
if newteam == TEAM_UNDEAD then
pl:SetPoints(0)
--pl.WaveBarricadeDamage = 0
--pl.WaveHumanDamage = 0
pl.DamagedBy = {}
pl:SetBarricadeGhosting(false)
self.CheckedOut[pl:UniqueID()] = true
elseif newteam == TEAM_HUMAN then
self.PreviouslyDied[pl:UniqueID()] = nil
if self.PointSaving > 0 and pl.PointsVault ~= nil and not self.ZombieEscape and not self:IsClassicMode() then
pl:SetPoints(math.floor(pl.PointsVault))
else
pl:SetPoints(0)
end
self:RefreshItemStocks(pl)
end
if newteam ~= TEAM_HUMAN then
pl:RemoveSkills()
end
pl:SetLastAttacker(nil)
for _, p in pairs(player.GetAll()) do
if p.LastAttacker == pl then
p.LastAttacker = nil
end
end
pl.PointQueue = 0
timer.Simple(0, function() gamemode.Call("CalculateInfliction") end)
end
function GM:SetToDefaultZombieClass(pl)
if pl:Team() == TEAM_UNDEAD then
pl:KillSilent()
pl:SetZombieClass(self.DefaultZombieClass or 1)
pl:UnSpectateAndSpawn()
else
pl:SetZombieClass(self.DefaultZombieClass or 1)
end
end
function GM:SetPantsMode(mode)
if self.ZombieEscape then return end
self.PantsMode = mode and self.ZombieClasses["Zombie Legs"] ~= nil and not self:IsClassicMode() and not self:IsBabyMode()
if self.PantsMode then
local index = self.ZombieClasses["Zombie Legs"].Index
self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
self.DefaultZombieClass = index
for _, pl in pairs(player.GetAll()) do
local classname = pl:GetZombieClassTable().Name
if classname ~= "Zombie Legs" and classname ~= "Crow" then
if pl:Team() == TEAM_UNDEAD then
pl:KillSilent()
pl:SetZombieClass(index)
pl:UnSpectateAndSpawn()
else
pl:SetZombieClass(index)
end
end
pl.DeathClass = index
end
else
self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
for _, pl in pairs(player.GetAll()) do
if pl:GetZombieClassTable().Name == "Zombie Legs" then
self:SetToDefaultZombieClass(pl)
end
end
end
end
function GM:SetClassicMode(mode)
if self.ZombieEscape then return end
self.ClassicMode = mode and self.ZombieClasses["Classic Zombie"] ~= nil and not self.PantsMode and not self:IsBabyMode()
SetGlobalBool("classicmode", self.ClassicMode)
if self:IsClassicMode() then
util.RemoveAll("prop_nail")
local index = self.ZombieClasses["Classic Zombie"].Index
self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
self.DefaultZombieClass = index
for _, pl in pairs(player.GetAll()) do
local classname = pl:GetZombieClassTable().Name
if classname ~= "Classic Zombie" and classname ~= "Crow" then
if pl:Team() == TEAM_UNDEAD then
pl:KillSilent()
pl:SetZombieClass(index)
pl:UnSpectateAndSpawn()
else
pl:SetZombieClass(index)
end
end
pl.DeathClass = index
end
else
self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
for _, pl in pairs(player.GetAll()) do
if pl:GetZombieClassTable().Name == "Classic Zombie" then
self:SetToDefaultZombieClass(pl)
end
end
end
end
function GM:SetBabyMode(mode)
if self.ZombieEscape then return end
self.BabyMode = mode and self.ZombieClasses["Gore Child"] ~= nil and not self.PantsMode and not self:IsClassicMode()
SetGlobalBool("babymode", self.BabyMode)
if self:IsBabyMode() then
local index = self.ZombieClasses["Gore Child"].Index
self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
self.DefaultZombieClass = index
for _, pl in pairs(player.GetAll()) do
local classname = pl:GetZombieClassTable().Name
if classname ~= "Gore Child" and classname ~= "Giga Gore Child" and classname ~= "Crow" then
if pl:Team() == TEAM_UNDEAD then
pl:KillSilent()
pl:SetZombieClass(index)
pl:UnSpectateAndSpawn()
else
pl:SetZombieClass(index)
end
end
pl.DeathClass = index
end
else
self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
for _, pl in pairs(player.GetAll()) do
if pl:GetZombieClassTable().Name == "Gore Child" then
self:SetToDefaultZombieClass(pl)
end
end
end
end
GM.InitialVolunteers = {}
function GM:SetClosestsToZombie()
local allplayers = player.GetAllActive()
local numplayers = #allplayers
if numplayers <= 1 then return end
local desiredzombies = self:GetDesiredStartingZombies()
self:SortZombieSpawnDistances(allplayers)
local zombies = {}
for _, pl in pairs(allplayers) do
if pl:Team() ~= TEAM_HUMAN or not pl:Alive() then
table.insert(zombies, pl)
end
end
-- Need to place some people back on the human team.
if #zombies > desiredzombies then
local toswap = #zombies - desiredzombies
for _, pl in pairs(zombies) do
if pl.DiedDuringWave0 and pl:GetInfo("zs_alwaysvolunteer") ~= "1" and not pl.IsZSBot then
pl:ChangeTeam(TEAM_HUMAN)
pl:UnSpectateAndSpawn()
toswap = toswap - 1
if toswap <= 0 then
break
end
end
end
end
for i = 1, desiredzombies do
local pl = allplayers[i]
if pl:Team() ~= TEAM_UNDEAD then
pl:ChangeTeam(TEAM_UNDEAD)
self.PreviouslyDied[pl:UniqueID()] = CurTime()
self.InitialVolunteers[pl:UniqueID()] = true
end
pl:SetFrags(0)
pl:SetDeaths(0)
local unlocked = {}
for _, v in ipairs(self.ZombieClasses) do
if v.Unlocked and not v.Hidden and v.NotRandomStart then
unlocked[#unlocked + 1] = v.Index
end
end
pl:SetZombieClass(unlocked[math.random(#unlocked)])
self.StartingZombie[pl:UniqueID()] = true
pl:UnSpectateAndSpawn()
end
end
function GM:AllowPlayerPickup(pl, ent)
return false
end
function GM:PlayerShouldTakeDamage(pl, attacker)
if attacker.PBAttacker and attacker.PBAttacker:IsValid() and CurTime() < attacker.NPBAttacker then -- Protection against prop_physbox team killing. physboxes don't respond to SetPhysicsAttacker()
attacker = attacker.PBAttacker
end
if attacker:IsPlayer() and attacker ~= pl and not attacker.AllowTeamDamage and not pl.AllowTeamDamage and attacker:Team() == pl:Team() then return false end
return true
end
function GM:PlayerHurt(victim, attacker, healthremaining, damage)
if healthremaining < 1 then return end
if victim:Team() == TEAM_HUMAN then
victim:PlayPainSound()
if healthremaining < 75 then
victim:ResetSpeed(nil, healthremaining)
end
if healthremaining < victim:GetMaxHealth() * 0.5 and victim:GetBloodArmor() < victim.MaxBloodArmor + 10 and victim:HasTrinket("bloodpack") then
victim:SetBloodArmor(math.min(victim:GetBloodArmor() + (20 * victim.BloodarmorGainMul), victim.MaxBloodArmor + (20 * victim.MaxBloodArmorMul)))
victim:TakeInventoryItem("trinket_bloodpack")
net.Start("zs_trinketconsumed")
net.WriteString("Blood Transfusion Pack")
net.Send(victim)
end
else
victim:PlayZombiePainSound()
end
end
function GM:WeaponDeployed(pl, wep)
self:DoChangeDeploySpeed(wep)
-- Don't change speed instantly to stop people from shooting and then running away with a faster weapon.
local timername = tostring(pl).."speedchange"
timer.Remove(timername)
local speed = pl:ResetSpeed(true) -- Determine what speed we SHOULD get without actually setting it.
if speed < pl:GetMaxSpeed() then
pl:SetSpeed(speed)
elseif pl:GetMaxSpeed() < speed then
local unbound = pl:IsSkillActive(SKILL_UNBOUND) and 0.4 or 1
timer.Create(timername, (0.333 / (pl.DeploySpeedMultiplier or 1)) * unbound, 1, function() if pl:IsValid() then pl:SetHumanSpeed(speed) end end)
end
end
function GM:KeyPress(pl, key)
if key == IN_USE then
if pl:Team() == TEAM_HUMAN and pl:Alive() then
if pl:IsCarrying() then
pl.status_human_holding:OnRemove() -- No idea...
pl.status_human_holding:RemoveNextFrame()
else
self:TryHumanPickup(pl, pl:TraceLine(64).Entity)
end
end
elseif key == IN_SPEED then
if pl:Alive() then
if pl:Team() == TEAM_HUMAN then
pl:DispatchAltUse()
if not pl:IsCarrying() and pl:KeyPressed(IN_SPEED) and pl:IsSkillActive(SKILL_CARDIOTONIC) and pl:GetBloodArmor() > 0 then
pl:SetBloodArmor(pl:GetBloodArmor() - 1)
pl:EmitSound("player/suit_sprint.wav", 50)
if pl:GetBloodArmor() == 0 and pl:IsSkillActive(SKILL_BLOODLETTER) then
local bleed = pl:GiveStatus("bleed")
if bleed and bleed:IsValid() then
bleed:AddDamage(5)
bleed.Damager = pl
end
end
pl:ResetSpeed()
end
elseif pl:Team() == TEAM_UNDEAD then
pl:CallZombieFunction0("AltUse")
end
end
elseif key == IN_ZOOM then
if pl:Team() == TEAM_HUMAN and pl:Alive() and not self.ZombieEscape then
if pl:IsOnGround() then
pl.LastGhostFailureVelocity = nil
pl:SetBarricadeGhosting(true)
else
local plvel = pl:GetVelocity()
if pl:GetPhysicsObject():IsPenetrating() then
if (plvel.x == 0 and plvel.y == 0 and (plvel.z == -4.5 or plvel.z == 0)) then
pl.LastGhostFailureVelocity = nil
pl:SetBarricadeGhosting(true)
else
pl:SetLocalVelocity(vector_origin)
end
elseif pl.LastGhostFailureVelocity == plvel then
pl.LastGhostFailureVelocity = nil
pl:SetBarricadeGhosting(true)
else
pl.LastGhostFailureVelocity = plvel
end
end
end
end
end
function GM:GetNearestSpawn(pos, teamid)
local nearest = NULL
local nearestdist = math.huge
for _, ent in pairs(team.GetValidSpawnPoint(teamid)) do
if ent.Disabled then continue end
local dist = ent:GetPos():DistToSqr(pos)
if dist < nearestdist then
nearestdist = dist
nearest = ent
end
end
return nearest
end
function GM:EntityWouldBlockSpawn(ent)
local spawnpoint = self:GetNearestSpawn(ent:GetPos(), TEAM_UNDEAD)
if spawnpoint:IsValid() then
local spawnpos = spawnpoint:GetPos()
if spawnpos:DistToSqr(ent:NearestPoint(spawnpos)) <= 1600 then return true end
end
return false
end
function GM:GetNearestSpawnDistance(pos, teamid)
local nearest = self:GetNearestSpawn(pos, teamid)
if nearest:IsValid() then
return nearest:GetPos():Distance(pos)
end
return -1
end
function GM:ShutDown()
self:SaveAllVaults()
end
function GM:PlayerUse(pl, ent)
if not pl:Alive() or pl:Team() == TEAM_UNDEAD and pl:GetZombieClassTable().NoUse or pl:GetBarricadeGhosting() then return false end
if pl:IsHolding() and pl:GetHolding() ~= ent then return false end
local entclass = ent:GetClass()
if entclass == "prop_door_rotating" then
if CurTime() < (ent.m_AntiDoorSpam or 0) then -- Prop doors can be glitched shut by mashing the use button.
return false
end
ent.m_AntiDoorSpam = CurTime() + 0.85
elseif entclass == "item_healthcharger" then
if pl:Team() == TEAM_UNDEAD then
return false
elseif pl:IsSkillActive(SKILL_D_FRAIL) and pl:Health() >= math.floor(pl:GetMaxHealth() * 0.25) then
return false
end
elseif pl:Team() == TEAM_HUMAN and not pl:IsCarrying() and pl:KeyPressed(IN_USE) then
self:TryHumanPickup(pl, ent)
end
return true
end
function GM:PlayerDeath(pl, inflictor, attacker)
end
function GM:PlayerDeathSound()
return true
end
local function SortDist(pa, pb)
return pa._temp < pb._temp
end
function GM:CanPlayerSuicide(pl)
if self.RoundEnded or pl:HasWon() then return false end
if pl:Team() == TEAM_HUMAN then
if self:GetWave() <= self.NoSuicideWave then
pl:PrintTranslatedMessage(HUD_PRINTCENTER, "give_time_before_suicide")
return false
end
-- If a person is going to suicide with no last attacker, give the kill to the closest zombie.
if not IsValid(pl:GetLastAttacker()) then
local plpos = pl:EyePos()
local tosort = {}
for _, zom in pairs(team.GetPlayers(TEAM_UNDEAD)) do
if zom:Alive() then
local dist = zom:GetPos():DistToSqr(plpos)
if dist <= 262144 then --512^2
zom._temp = dist
table.insert(tosort, zom)
end
end
end
table.sort(tosort, SortDist)
if tosort[1] then
pl:SetLastAttacker(tosort[1])
end
end
elseif pl:Team() == TEAM_UNDEAD then
local ret = pl:CallZombieFunction0("CanPlayerSuicide")
if ret == false then return false end
end
return pl:GetObserverMode() == OBS_MODE_NONE and pl:Alive() and (not pl.SpawnNoSuicide or pl.SpawnNoSuicide < CurTime())
end
function GM:DefaultRevive(pl)
local status = pl:GiveStatus("revive")
if status and status:IsValid() then
status:SetReviveTime(CurTime() + 2)
end
end
function GM:HumanKilledZombie(pl, attacker, inflictor, dmginfo, headshot, suicide)
if (pl:GetZombieClassTable().Points or 0) == 0 or self.RoundEnded then return end
-- Simply distributes based on damage but also do some stuff for assists.
local totaldamage = 0
for otherpl, dmg in pairs(pl.DamagedBy) do
if otherpl:IsValid() and otherpl:Team() == TEAM_HUMAN then
totaldamage = totaldamage + dmg
end
end
local mostassistdamage = 0
local halftotaldamage = totaldamage / 2
local mostdamager
for otherpl, dmg in pairs(pl.DamagedBy) do
if otherpl ~= attacker and otherpl:IsValid() and otherpl:Team() == TEAM_HUMAN and dmg > mostassistdamage and dmg >= halftotaldamage then
mostassistdamage = dmg
mostdamager = otherpl
end
end
attacker.ZombiesKilled = attacker.ZombiesKilled + 1
if mostdamager then
attacker:PointCashOut(pl, FM_LOCALKILLOTHERASSIST)
mostdamager:PointCashOut(pl, FM_LOCALASSISTOTHERKILL)
mostdamager.ZombiesKilledAssists = mostdamager.ZombiesKilledAssists + 1
else
attacker:PointCashOut(pl, FM_NONE)
end
if inflictor:IsValid() and inflictor == attacker:GetActiveWeapon() then
local wep = attacker:GetActiveWeapon()
if attacker.MeleeMovementSpeedOnKill and attacker.MeleeMovementSpeedOnKill ~= 0 and wep.IsMelee then
local boost = attacker:GiveStatus("adrenalineamp", 10)
if boost and boost:IsValid() then
boost:SetSpeed(attacker.MeleeMovementSpeedOnKill)
end
end
if #self.Food > 0 and pl.ChefMarkTime and pl.ChefMarkTime > CurTime() and pl.ChefMarkOwner == attacker then
local rfood = self.Food[math.random(#self.Food)]
if not attacker:HasWeapon(rfood) then
attacker:Give(rfood)
end
end
if pl:WasHitInHead() then
attacker.Headshots = (attacker.Headshots or 0) + 1
end
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_WEAPON, wep:GetClass(), "Kills", 1)
if wep.OnZombieKilled then
wep:OnZombieKilled(pl, totaldamage, dmginfo)
end
end
gamemode.Call("PostHumanKilledZombie", pl, attacker, inflictor, dmginfo, mostdamager, mostassistdamage, headshot)
return mostdamager
end
function GM:PostHumanKilledZombie(pl, attacker, inflictor, dmginfo, assistpl, assistamount, headshot)
end
function GM:ZombieKilledHuman(pl, attacker, inflictor, dmginfo, headshot, suicide)
if self.RoundEnded then return end
local plpos = pl:GetPos()
local dist = 999999999
local xp = 18 * (GAMEMODE.ZombieXPMulti or 1)
for _, ent in pairs(team.GetValidSpawnPoint(TEAM_UNDEAD)) do
dist = math.min(ent:GetPos():DistToSqr(plpos), dist)
end
pl.ZombieSpawnDeathDistance = math.ceil(math.sqrt(dist))
attacker:AddBrains(1)
attacker:AddLifeBrainsEaten(1)
attacker:AddZSXP(self.InitialVolunteers[attacker:UniqueID()] and xp or math.floor(xp/4))
local classtab = attacker:GetZombieClassTable()
if classtab and classtab.Name then
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_ZOMBIECLASS, classtab.Name, "BrainsEaten", 1)
end
if not pl.Gibbed and not suicide then
local status = pl:GiveStatus("revive_slump_human")
if status then
status:SetReviveTime(CurTime() + 4)
status:SetZombieInitializeTime(CurTime() + 2)
end
pl:SetZombieClassName(self.ZombieEscape and "Super Zombie" or self:IsClassicMode() and "Classic Zombie" or self:IsBabyMode() and "Gore Child" or "Fresh Dead")
end
gamemode.Call("PostZombieKilledHuman", pl, attacker, inflictor, dmginfo, headshot, suicide)
return attacker:Frags()
end
function GM:PostZombieKilledHuman(pl, attacker, inflictor, dmginfo, headshot, suicide)
end
local function DelayedChangeToZombie(pl)
if pl:IsValid() then
if pl.ChangeTeamFrags then
pl:SetFrags(pl.ChangeTeamFrags)
pl.ChangeTeamFrags = 0
end
pl:ChangeTeam(TEAM_UNDEAD)
end
end
function GM:DoPlayerDeath(pl, attacker, dmginfo)
pl:RemoveEphemeralStatuses()
pl:Extinguish()
pl:SetPhantomHealth(0)
local inflictor = dmginfo:GetInflictor()
local plteam = pl:Team()
local ct = CurTime()
local suicide = attacker == pl or attacker:IsWorld()
if attacker.PBAttacker and attacker.PBAttacker:IsValid() then
attacker = attacker.PBAttacker
end
pl:Freeze(false)
local headshot = pl:WasHitInHead()
if suicide then attacker = pl:GetLastAttacker() or attacker end
pl:SetLastAttacker()
if inflictor == NULL then inflictor = attacker end
if inflictor == attacker and attacker:IsPlayer() then
local wep = attacker:GetActiveWeapon()
if wep:IsValid() then
inflictor = wep
end
end
if headshot then
local effectdata = EffectData()
effectdata:SetOrigin(dmginfo:GetDamagePosition())
local force = dmginfo:GetDamageForce()
effectdata:SetMagnitude(force:Length() * 3)
effectdata:SetNormal(force:GetNormalized())
effectdata:SetEntity(pl)
util.Effect("headshot", effectdata, true, true)
end
if not pl:CallZombieFunction5("OnKilled", attacker, inflictor, suicide, headshot, dmginfo) then
if pl:Health() <= -70 and not pl.NoGibs and not self.ZombieEscape then
pl:Gib(dmginfo)
elseif not pl.KnockedDown then
pl:CreateRagdoll()
end
end
pl:RemoveStatus("overridemodel", false, true)
local revive = false
local assistpl
if plteam == TEAM_UNDEAD then
if GAMEMODE.ZombieEscape then
local zewep = pl:GetWeapon("weapon_knife")
if zewep and zewep:IsValid() then
pl:DropWeapon(zewep)
end
end
local classtable = pl:GetZombieClassTable()
pl:PlayZombieDeathSound()
if classtable.Boss and not self.ObjectiveMap and pl.BossDeathNotification then
net.Start("zs_boss_slain")
net.WriteEntity(pl)
net.WriteUInt(classtable.Index, 8)
net.Broadcast()
timer.Simple(0, function()
pl:MakeBossDrop()
end)
pl.BossDeathNotification = nil
end
if not classtable.NoDeaths then
pl:AddDeaths(1)
end
if self:GetWaveActive() then
pl.StartSpectating = ct + 2
else
pl.StartCrowing = ct + 3
end
if attacker:IsValid() and attacker:IsPlayer() and attacker ~= pl then
if classtable.Revives and not pl.Gibbed and not headshot and CurTime() > pl.LastRevive + 4 then
if classtable.ReviveCallback then
revive = classtable:ReviveCallback(pl, attacker, dmginfo)
elseif math.random(4) ~= 1 then
self:DefaultRevive(pl)
revive = true
end
end
if revive then
pl.LastRevive = CurTime()
elseif attacker:Team() == TEAM_HUMAN then
assistpl = gamemode.Call("HumanKilledZombie", pl, attacker, inflictor, dmginfo, headshot, suicide)
end
end
if not revive and (pl.LifeBarricadeDamage ~= 0 or pl.LifeHumanDamage ~= 0 or pl.LifeBrainsEaten ~= 0) then
timer.Simple(0, function() if pl:IsValid() then pl:SendLifeStats() end end)
end
pl:CallZombieFunction5("PostOnKilled", attacker, inflictor, suicide, headshot, dmginfo)
elseif plteam == TEAM_HUMAN then
pl.NextSpawnTime = ct + 4
pl:PlayDeathSound()
if attacker:IsPlayer() and attacker ~= pl then
gamemode.Call("ZombieKilledHuman", pl, attacker, inflictor, dmginfo, headshot, suicide)
end
pl:DropAll()
timer.Simple(0, function() DelayedChangeToZombie(pl) end) -- We don't want people shooting barrels near teammates.
self.PreviouslyDied[pl:UniqueID()] = CurTime()
if self:GetWave() == 0 then
pl.DiedDuringWave0 = true
end
local frags = pl:Frags()
if frags < 0 then
pl.ChangeTeamFrags = math.ceil(frags / 5)
else
pl.ChangeTeamFrags = 0
end
if pl.SpawnedTime then
pl.SurvivalTime = math.max(ct - pl.SpawnedTime, pl.SurvivalTime or 0)
pl.SpawnedTime = nil
end
if team.NumPlayers(TEAM_HUMAN) <= 1 then
self.LastHumanPosition = pl:WorldSpaceCenter()
net.Start("zs_lasthumanpos")
net.WriteVector(self.LastHumanPosition)
net.Broadcast()
end
local hands = pl:GetHands()
if IsValid(hands) then
hands:Remove()
end
end
if revive or pl:CallZombieFunction2("NoDeathMessage", attacker, dmginfo) or pl:IsSpectator() then return end
if attacker == pl then
net.Start("zs_pl_kill_self")
net.WriteEntity(pl)
net.WriteUInt(plteam, 8)
net.Broadcast()
elseif attacker:IsPlayer() then
if assistpl then
net.Start("zs_pls_kill_pl")
net.WriteEntity(pl)
net.WriteEntity(attacker)
net.WriteEntity(assistpl)
net.WriteString(inflictor:GetClass())
net.WriteUInt(plteam, 8)
net.WriteUInt(attacker:Team(), 8) -- Assuming assistants are always on the same team.
net.WriteBit(headshot)
net.Broadcast()
gamemode.Call("PlayerKilledByPlayer", pl, assistpl, inflictor, headshot, dmginfo, true)
else
net.Start("zs_pl_kill_pl")
net.WriteEntity(pl)
net.WriteEntity(attacker)
net.WriteString(inflictor:GetClass())
net.WriteUInt(plteam, 8)
net.WriteUInt(attacker:Team(), 8)
net.WriteBit(headshot)
net.Broadcast()
end
gamemode.Call("PlayerKilledByPlayer", pl, attacker, inflictor, headshot, dmginfo)
else
net.Start("zs_death")
net.WriteEntity(pl)
net.WriteString(inflictor:GetClass())
net.WriteString(attacker:GetClass())
net.WriteUInt(plteam, 8)
net.Broadcast()
end
end
function GM:WeaponEquip(wep)
if wep.m_WeaponDeploySpeed then
timer.Simple(0, function() GAMEMODE:DoChangeDeploySpeed(wep) end)
end
end
function GM:PlayerKilledByPlayer(pl, attacker, inflictor, headshot, dmginfo, is_assistant)
end
function GM:PlayerCanPickupWeapon(pl, ent)
if pl:IsSpectator() then return false end
if pl:Team() == TEAM_UNDEAD then return ent:GetClass() == pl:GetZombieClassTable().SWEP end
return not ent.ZombieOnly
end
function GM:PlayerCanPickupItem(pl, ent)
if pl:IsSkillActive(SKILL_D_FRAIL) then
local class = ent:GetClass()
if class == "item_healthkit" or class == "item_healthvial" then
local healamount = #class == 14 and 25 or 10
if pl:Health() + healamount > math.floor(pl:GetMaxHealth() * 0.25) then
return false
end
end
end
return true
end
-- This function is only for footsteps for players not in the local player's pvs or something.
-- The cl_init.lua version usually overrides this number so I just set it to a static number to save cycles.
function GM:PlayerStepSoundTime(pl, iType, bWalking)
return 350
end
-- Again, don't bother overriding anything due to above.
function GM:PlayerFootstep(pl, vPos, iFoot, strSoundName, fVolume, pFilter)
end
local VoiceSetTranslate = {}
VoiceSetTranslate["models/player/alyx.mdl"] = VOICESET_ALYX
VoiceSetTranslate["models/player/barney.mdl"] = VOICESET_BARNEY
VoiceSetTranslate["models/player/combine_soldier.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/player/combine_soldier_prisonguard.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/player/combine_super_soldier.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/player/police.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/grim.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/player/police_fem.mdl"] = VOICESET_COMBINE
VoiceSetTranslate["models/player/monk.mdl"] = VOICESET_MONK
VoiceSetTranslate["models/jason278-players/gabe_3.mdl"] = VOICESET_MONK
VoiceSetTranslate["models/player/mossman.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/brsp.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/moe_glados_p.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/mossman_arctic.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/p2_chell.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/dawson/obese_male_deluxe/obese_male_deluxe.mdl"] = VOICESET_MONK
VoiceSetTranslate["models/player/cirno/cirno_player.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/dewobedil/eromanga_sensei/sagiri/pajama_p.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/loyalists/mmd/remilia/remilia_mp_pm.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/loyalists/mmd/flandre/flandre_mp_pm.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/jazzmcfly/kantai/yuudachi/yuudachi.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/dewobedil/vocaloid/haku/bikini_p.mdl"] = VOICESET_FEMALE
VoiceSetTranslate["models/player/dewobedil/touhou/junko/default_p.mdl"] = VOICESET_FEMALE
function GM:PlayerSpawn(pl)
pl:StripWeapons()
pl:WipePlayerInventory()
pl:GiveAmmo(1, "dummy", true) -- Fixes empty weapon deploy bug.
pl:RemoveStatus("confusion", false, true)
pl:RemoveFlags(FL_ONGROUND) -- fixes :OnGround() returning true on spawn even if they're not on the ground.
if pl:GetMaterial() ~= "" then
pl:SetMaterial("")
end
pl:UnSpectate()
pl.StartCrowing = nil
pl.StartSpectating = nil
pl.NextSpawnTime = nil
pl.Gibbed = nil
pl.SpawnNoSuicide = CurTime() + 1
pl.SpawnedTime = CurTime()
pl:ShouldDropWeapon(false)
pl:SetLegDamage(0)
pl:SetLastAttacker()
local pcol = Vector(pl:GetInfo("cl_playercolor"))
pcol.x = math.Clamp(pcol.x, 0, 2.5)
pcol.y = math.Clamp(pcol.y, 0, 2.5)
pcol.z = math.Clamp(pcol.z, 0, 2.5)
pl:SetPlayerColor(pcol)
if pl:Team() == TEAM_UNDEAD then
if pl.ActivatedHumanSkills then
pl.ActivatedHumanSkills = false
pl:ApplySkills({})
end
if not pl.Revived then
pl.DamagedBy = {}
end
pl.LifeBarricadeDamage = 0
pl.LifeHumanDamage = 0
pl.LifeBrainsEaten = 0
pl.BossHealRemaining = nil
if self:GetUseSigils() and self:GetEscapeSequence() and self:GetEscapeStage() >= ESCAPESTAGE_BOSS and not pl.Revived then
pl:SetZombieClassName("Super Zombie")
elseif pl.DeathClass and self:GetWaveActive() then
pl:SetZombieClass(self:GetBestAvailableZombieClass(pl.DeathClass))
pl.DeathClass = nil
end
local cur = pl:GetZombieClassTable().Name
local best = self:GetBestAvailableZombieClass(cur)
if cur ~= best then
pl:SetZombieClass(best)
end
local classtab = pl:GetZombieClassTable()
pl:DoHulls(pl:GetZombieClass(), TEAM_UNDEAD)
--pl:SetCustomCollisionCheck(pl.NoCollideAll == true)
pl:CollisionRulesChanged()
if classtab.Model then
pl:SetModel(classtab.Model)
elseif classtab.UsePlayerModel then
local mdl = player_manager.TranslatePlayerModel(pl:GetInfo("cl_playermodel"))
if table.HasValue(self.RestrictedModels, mdl) then
pl:SelectRandomPlayerModel()
else
pl:SetModel(mdl)
end
elseif classtab.UsePreviousModel then
local curmodel = string.lower(pl:GetModel())
if table.HasValue(self.RestrictedModels, curmodel) or (not VoiceSetTranslate[curmodel] and string.sub(curmodel, 1, 14) ~= "models/player") then
pl:SelectRandomPlayerModel()
end
elseif classtab.UseRandomModel then
pl:SelectRandomPlayerModel()
else
pl:SetModel("models/player/zombie_classic_hbfix.mdl")
end
if classtab.NoPlayerColor then
pl:SetColor(COLOR_WHITE)
pl:SetPlayerColor(Vector(255, 255, 255))
end
if classtab.Boss then
pl:SetHealth(classtab.Health)
else
local lowundead = team.NumPlayers(TEAM_UNDEAD) < 4
local healthmulti = (self.ObjectiveMap or self.ZombieEscape) and 1 or lowundead and 1.5 or 1
pl:SetHealth(classtab.Health * healthmulti)
end
if classtab.SWEP then
pl:Give(classtab.SWEP)
end
pl:SetNoTarget(true)
pl:SetMaxHealth(1)
pl:ResetSpeed()
pl:SetCrouchedWalkSpeed(classtab.CrouchedWalkSpeed or 0.45)
if not pl.Revived or not self:GetWaveActive() or CurTime() > self:GetWaveEnd() then
pl.StartCrowing = 0
end
if pl.ForceSpawnAngles then
pl:SetEyeAngles(pl.ForceSpawnAngles)
pl.ForceSpawnAngles = nil
end
if not pl.Revived and not pl:GetZombieClassTable().NeverAlive and pl.SpawnedOnSpawnPoint and not pl.DidntSpawnOnSpawnPoint then
pl:GiveStatus("zombiespawnbuff", self.ObjectiveMap and 1.5 or 3)
end
pl.DidntSpawnOnSpawnPoint = nil
pl.SpawnedOnSpawnPoint = nil
local overridemodel = pl:GetZombieClassTable().OverrideModel
if overridemodel then
local current = pl:GiveStatus("overridemodel")
if current and current:IsValid() then
current:SetModel(overridemodel)
current:ResetBones()
pl:CallZombieFunction1("ManipulateOverrideModel", current)
end
else
pl:RemoveStatus("overridemodel", false, true)
end
local oldhands = pl:GetHands()
if IsValid(oldhands) then
oldhands:Remove()
end
GAMEMODE.StatTracking:IncreaseElementKV(STATTRACK_TYPE_ZOMBIECLASS, classtab.Name, "ClassSpawn", 1)
pl:CallZombieFunction0("OnSpawned")
elseif pl:Team() == TEAM_HUMAN then
pl.PointQueue = 0
pl.PackedItems = {}
pl:ClearUselessDamage()
local desiredname = pl:GetInfo("cl_playermodel")
local modelname = player_manager.TranslatePlayerModel(#desiredname == 0 and self.RandomPlayerModels[math.random(#self.RandomPlayerModels)] or desiredname)
local lowermodelname = string.lower(modelname)
if table.HasValue(self.RestrictedModels, lowermodelname) then
modelname = "models/player/alyx.mdl"
lowermodelname = modelname
end
pl:SetModel(modelname)
-- Cache the voice set.
if VoiceSetTranslate[lowermodelname] then
pl:SetDTInt(DT_PLAYER_INT_VOICESET, VoiceSetTranslate[lowermodelname])
elseif string.find(lowermodelname, "female", 1, true) then
pl:SetDTInt(DT_PLAYER_INT_VOICESET, VOICESET_FEMALE)
else
pl:SetDTInt(DT_PLAYER_INT_VOICESET, VOICESET_MALE)
end
--pl.HumanSpeedAdder = nil
pl:SetNoTarget(false)
pl:SetMaxHealth(100)
--pl:SetCustomCollisionCheck(false)
pl:CollisionRulesChanged()
if not self.NoSkills then
pl.ActivatedHumanSkills = true
pl.AdjustedStartPointsSkill = nil
pl.AdjustedStartScrapSkill = nil
pl:ApplySkills()
end
pl.StowageCaches = 0
net.Start("zs_stowagecaches")
net.WriteInt(pl.StowageCaches, 8)
net.Send(pl)
pl:ResetSpeed()
pl:ResetJumpPower()
pl:SetCrouchedWalkSpeed(0.45)
pl:SetViewOffset(DEFAULT_VIEW_OFFSET)
pl:SetViewOffsetDucked(DEFAULT_VIEW_OFFSET_DUCKED)
if self.ZombieEscape then
pl:Give("weapon_zs_zeknife")
pl:Give("weapon_zs_zegrenade")
pl:Give(table.Random(self.ZombieEscapeWeaponsPrimary))
pl:Give(table.Random(self.ZombieEscapeWeaponsSecondary))
else
local start = pl:GetRandomStartingItem()
if start then
local func = self:GetInventoryItemType(start) == INVCAT_TRINKETS and pl.AddInventoryItem or pl.Give
func(pl, start)
end
pl:Give("weapon_zs_fists")
if self.StartingLoadout then
self:GiveStartingLoadout(pl)
elseif pl.m_PreRedeem then
if self.RedeemLoadout then
for _, class in pairs(self.RedeemLoadout) do
pl:Give(class)
end
else
pl:Give("weapon_zs_redeemers")
pl:Give("weapon_zs_swissarmyknife")
end
end
end
local oldhands = pl:GetHands()
if IsValid(oldhands) then
oldhands:Remove()
end
local hands = ents.Create("zs_hands")
if hands:IsValid() then
hands:DoSetup(pl)
hands:Spawn()
end
end
local wcol = Vector(pl:GetInfo("cl_weaponcolor"))
wcol.x = math.Clamp(wcol.x, 0, 2.5)
wcol.y = math.Clamp(wcol.y, 0, 2.5)
wcol.z = math.Clamp(wcol.z, 0, 2.5)
pl:SetWeaponColor(wcol)
end
function GM:SetWave(wave)
local previouslylocked = {}
local UnlockedClasses = {}
for classid, classtab in ipairs(GAMEMODE.ZombieClasses) do
if not gamemode.Call("IsClassUnlocked", classid) then
previouslylocked[classid] = true
end
end
SetGlobalInt("wave", wave)
for classid in pairs(previouslylocked) do
if gamemode.Call("IsClassUnlocked", classid) then
local classtab = self.ZombieClasses[classid]
classtab.Locked = false
classtab.Unlocked = true
if not classtab.UnlockedNotify then
classtab.UnlockedNotify = true
table.insert(UnlockedClasses, classid)
end
for _, ent in pairs(ents.FindByClass("logic_classunlock")) do
local classname = GAMEMODE.ZombieClasses[classid].Name
if ent.Class == string.lower(classname) then
ent:Input("onclassunlocked", ent, ent, classname)
end
end
net.Start("zs_classunlockstate")
net.WriteInt(classid, 8)
net.WriteBool(classtab.Unlocked)
net.Broadcast()
end
end
if #UnlockedClasses > 0 then
for _, pl in pairs(player.GetAll()) do
local classnames = {}
for __, classid in pairs(UnlockedClasses) do
local classtbl = self.ZombieClasses[classid]
table.insert(classnames, translate.ClientGet(pl, classtbl.TranslationName))
end
net.Start("zs_classunlock")
net.WriteString(string.AndSeparate(classnames))
net.Send(pl)
end
end
end
GM.NextEscapeDamage = 0
function GM:WaveStateChanged(newstate)
if newstate then
if self:GetWave() == 0 then
gamemode.Call("CreateSigils", true) -- Try creating sigils again. Only really matters if nobody seeded the map yet.
self:SetClosestsToZombie()
local humans = {}
for _, pl in pairs(player.GetAll()) do
if pl:Team() == TEAM_HUMAN and pl:Alive() then
table.insert(humans, pl)
end
end
for _, pl in pairs(humans) do
if pl.PlayerReady then -- There's a chance they might not be ready to send their desired cart yet.
gamemode.Call("GiveDefaultOrRandomEquipment", pl)
end
end
-- We should spawn a crate in a random spawn point if no one has any.
if not self.ZombieEscape and #ents.FindByClass("prop_arsenalcrate") == 0 then
local have = false
for _, pl in pairs(humans) do
if pl:HasWeapon("weapon_zs_arsenalcrate") then
have = true
break
end
end
if not have and #humans >= 1 then
local spawn = self:PlayerSelectSpawn(humans[math.random(#humans)])
if spawn and spawn:IsValid() then
local ent = ents.Create("prop_arsenalcrate")
if ent:IsValid() then
ent:SetPos(spawn:GetPos() + Vector(0, 0, 8))
ent:Spawn()
ent:DropToFloor()
ent:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER) -- Just so no one gets stuck in it.
ent.NoTakeOwnership = true
end
end
end
end
end
local prevwave = self:GetWave()
if self:GetUseSigils() and prevwave >= self:GetNumberOfWaves() then return end
gamemode.Call("SetWave", prevwave + 1)
gamemode.Call("SetWaveStart", CurTime())
if self.ZombieEscape then
gamemode.Call("SetWaveEnd", -1)
SetGlobalInt("numwaves", -1)
else
gamemode.Call("SetWaveEnd", self:GetWaveStart() + self:GetWaveOneLength() + (self:GetWave() - 1) * (GetGlobalBool("classicmode") and self.TimeAddedPerWaveClassic or self.TimeAddedPerWave))
end
net.Start("zs_wavestart")
net.WriteInt(self:GetWave(), 16)
net.WriteFloat(self:GetWaveEnd())
net.Broadcast()
for _, pl in pairs(team.GetPlayers(TEAM_UNDEAD)) do
pl.m_LastWaveStartSpawn = CurTime()
if pl:GetZombieClassTable().Name == "Crow" then
pl:SetZombieClass(pl.DeathClass or 1)
pl:UnSpectateAndSpawn()
elseif not pl:Alive() and not pl.Revive then
pl:UnSpectateAndSpawn()
end
end
for _, pl in pairs(player.GetAll()) do
pl.WaveBarricadeDamage = 0
pl.WaveHumanDamage = 0
end
local curwave = self:GetWave()
for _, ent in pairs(ents.FindByClass("logic_waves")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwavestart", ent, ent, curwave)
end
end
for _, ent in pairs(ents.FindByClass("logic_wavestart")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwavestart", ent, ent, curwave)
end
end
elseif self:GetWave() >= self:GetNumberOfWaves() then -- Last wave is over
if self:GetUseSigils() then
if self:GetEscapeStage() == ESCAPESTAGE_BOSS then
-- 2 minutes is enough to decide people left are stuck or griefing.
self:SetEscapeStage(ESCAPESTAGE_DEATH)
gamemode.Call("SetWaveEnd", -1)
elseif self:GetEscapeStage() == ESCAPESTAGE_ESCAPE then
self:SetEscapeStage(ESCAPESTAGE_BOSS)
-- Some time to get out with everyone spawning as bosses.
gamemode.Call("SetWaveEnd", CurTime() + 45)
-- Start spawning boss zombies.
elseif self:GetEscapeStage() == ESCAPESTAGE_NONE then
-- If we're using sigils, remove them all and spawn the doors.
for _, sigil in pairs(ents.FindByClass("prop_obj_sigil")) do
local ent = ents.Create("prop_obj_exit")
if ent:IsValid() then
ent:SetPos(sigil.NodePos or sigil:GetPos())
ent:SetAngles(sigil:GetAngles())
ent:Spawn()
end
sigil:Remove()
end
-- Some time to escape.
gamemode.Call("SetWaveActive", true)
gamemode.Call("SetWaveEnd", CurTime() + 45)
self:SetEscapeStage(ESCAPESTAGE_ESCAPE)
local curwave = self:GetWave()
for _, ent in pairs(ents.FindByClass("logic_waves")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
for _, ent in pairs(ents.FindByClass("logic_waveend")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
end
else
-- If not using sigils then humans all win.
gamemode.Call("EndRound", TEAM_HUMAN)
local curwave = self:GetWave()
for _, ent in pairs(ents.FindByClass("logic_waves")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
for _, ent in pairs(ents.FindByClass("logic_waveend")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
end
else
gamemode.Call("SetWaveStart", CurTime() + (GetGlobalBool("classicmode") and self.WaveIntermissionLengthClassic or self.WaveIntermissionLength))
net.Start("zs_waveend")
net.WriteInt(self:GetWave(), 16)
net.WriteFloat(self:GetWaveStart())
net.Broadcast()
local pointsbonus
if self.EndWavePointsBonus > 0 then
pointsbonus = self.EndWavePointsBonus + (self:GetWave() - 1) * self.EndWavePointsBonusPerWave
end
for _, pl in pairs(player.GetAll()) do
if pl:Team() == TEAM_HUMAN and pl:Alive() then
if self.EndWaveHealthBonus > 0 then
pl:SetHealth(math.min(pl:GetMaxHealth(), pl:Health() + self.EndWaveHealthBonus))
end
if pointsbonus then
local pointsreward = pointsbonus + (pl.EndWavePointsExtra or 0)
if pl:IsSkillActive(SKILL_SCOURER) then
pl:GiveAmmo(math.ceil(pointsreward), "scrap")
else
pl:AddPoints(pointsreward, nil, nil, true)
end
end
elseif pl:Team() == TEAM_UNDEAD and not pl:Alive() and not pl.Revive then
local curclass = pl.DeathClass or pl:GetZombieClass()
local crowindex = GAMEMODE.ZombieClasses["Crow"].Index
pl:SetZombieClass(crowindex)
pl:DoHulls(crowindex, TEAM_UNDEAD)
pl.DeathClass = nil
pl:UnSpectateAndSpawn()
pl.DeathClass = curclass
end
pl.SkipCrow = nil
end
local curwave = self:GetWave()
for _, ent in pairs(ents.FindByClass("logic_waves")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
for _, ent in pairs(ents.FindByClass("logic_waveend")) do
if ent.Wave == curwave or ent.Wave == -1 then
ent:Input("onwaveend", ent, ent, curwave)
end
end
end
gamemode.Call("OnWaveStateChanged")
end
function GM:PlayerSwitchFlashlight(pl, newstate)
if pl:Team() == TEAM_UNDEAD then
return false
end
if pl:Team() == TEAM_HUMAN and CurTime() >= pl.NextFlashlightSwitch then
pl.NextFlashlightSwitch = CurTime() + 0.75
return true
end
return false
end
function GM:PlayerStepSoundTime(pl, iType, bWalking)
return 350
end
function GM:OnZEWeaponPickup(pl, wep)
end
net.Receive("zs_changeclass", function(len, sender)
if sender:Team() ~= TEAM_UNDEAD or sender.Revive or GAMEMODE.PantsMode or GAMEMODE:IsClassicMode() or GAMEMODE:IsBabyMode() or GAMEMODE.ZombieEscape then return end
local classname = GAMEMODE:GetBestAvailableZombieClass(net.ReadString())
local suicide = net.ReadBool()
local classtab = GAMEMODE.ZombieClasses[classname]
if not classtab or classtab.Disabled or classtab.Hidden and not (classtab.CanUse and classtab:CanUse(sender)) then return end
if not gamemode.Call("IsClassUnlocked", classname) then
sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "class_not_unlocked_will_be_unlocked_x", classtab.Wave))
elseif sender:GetZombieClassTable().Name == classname and not sender.DeathClass then
sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "you_are_already_a_x", translate.ClientGet(sender, classtab.TranslationName)))
else
sender.DeathClass = classtab.Index
sender:CenterNotify(translate.ClientFormat(sender, "you_will_spawn_as_a_x", translate.ClientGet(sender, classtab.TranslationName)))
if suicide and sender:Alive() and GAMEMODE:GetWaveActive() and (CurTime() < GAMEMODE:GetWaveEnd() - 4 or GAMEMODE:GetWaveEnd() < 0) and not sender:GetZombieClassTable().Boss and gamemode.Call("CanPlayerSuicide", sender) then
sender:Kill()
end
end
end)
net.Receive("zs_zsfriend", function(len, sender)
local zsfriendid = net:ReadString()
local zsfriendent = player.GetBySteamID(zsfriendid)
if not zsfriendent then return end
local isfriend = net:ReadBool()
sender.ZSFriends[zsfriendent] = isfriend
net.Start("zs_zsfriendadded")
net.WriteEntity(sender)
net.WriteBool(isfriend)
net.Send(zsfriendent)
end)
net.Receive("zs_nestspec", function(len, sender)
if not sender:IsValidZombie() then return end
if sender:GetObserverMode() == OBS_MODE_NONE then return end
local nest = net:ReadEntity()
local neveralive = sender:GetZombieClassTable().NeverAlive
if neveralive and nest.MinionSpawn then
sender:TrySpawnAsGoreChild(nest)
end
if sender:Alive() or neveralive then return end
if nest:IsValid() then
sender:Spectate(OBS_MODE_CHASE)
sender:SpectateEntity(nest)
end
end)