a61575eb3d
Changed order of player's variable assignments so that StartingWorth is set before being displayed for the first time.
3778 lines
112 KiB
Lua
3778 lines
112 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.
|
|
|
|
]]
|
|
|
|
-- CRAFTING AND ITEM IDEAS
|
|
--[[
|
|
ITEMS
|
|
nighkeez: you run a bit faster while wearing them. Also attaches white boot props to your feet.
|
|
AWTH barrel: if it so much as bangs in to something then it blows up with a huge explosion (like fire bomb size).
|
|
stabber: stubber with a knife in the barrel. A melee weapon with very low size but high reach.
|
|
hot milk: puts you to sleep for a stupid amount of time and you regenerate health a little bit.
|
|
gelbanana: green gel banana. using it gives you 8 health.
|
|
body armor: nullifies one hit that does 20 or more damage and then immediately breaks.
|
|
|
|
RECIPEES
|
|
boot prop + boot prop = nighkeez
|
|
nighkeez + bananas prop = clown shoes
|
|
explosive barrel + explosive barrel = big explosive barrel
|
|
oxygen canister + big explosive barrel = AWTH barrel
|
|
stubber + knife = stabber
|
|
milk + heat source = hot milk
|
|
ammonia + bleach = mustard gas on the spot. spams yellow fumes everywhere and lethally poisons the user.
|
|
bananas + microwave = gelbanana
|
|
metal barrel + something = body armor
|
|
--]]
|
|
|
|
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_crafts.lua")
|
|
AddCSLuaFile("sh_util.lua")
|
|
AddCSLuaFile("sh_options.lua")
|
|
AddCSLuaFile("sh_zombieclasses.lua")
|
|
AddCSLuaFile("sh_animations.lua")
|
|
AddCSLuaFile("sh_sigils.lua")
|
|
|
|
AddCSLuaFile("cl_draw.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("obj_vector_extend.lua")
|
|
AddCSLuaFile("obj_player_extend.lua")
|
|
AddCSLuaFile("obj_player_extend_cl.lua")
|
|
AddCSLuaFile("obj_weapon_extend.lua")
|
|
AddCSLuaFile("obj_entity_extend.lua")
|
|
|
|
AddCSLuaFile("vgui/dgamestate.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/dexroundedpanel.lua")
|
|
AddCSLuaFile("vgui/dexroundedframe.lua")
|
|
AddCSLuaFile("vgui/dexrotatedimage.lua")
|
|
AddCSLuaFile("vgui/dexnotificationslist.lua")
|
|
AddCSLuaFile("vgui/dexchanginglabel.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/ppointshop.lua")
|
|
AddCSLuaFile("vgui/zshealtharea.lua")
|
|
|
|
include("shared.lua")
|
|
include("sv_options.lua")
|
|
include("sv_crafts.lua")
|
|
include("obj_entity_extend_sv.lua")
|
|
include("obj_player_extend_sv.lua")
|
|
include("mapeditor.lua")
|
|
include("sv_playerspawnentities.lua")
|
|
include("sv_profiling.lua")
|
|
include("sv_sigils.lua")
|
|
|
|
include("sv_zombieescape.lua")
|
|
|
|
if file.Exists(GM.FolderName.."/gamemode/maps/"..game.GetMap()..".lua", "LUA") then
|
|
include("maps/"..game.GetMap()..".lua")
|
|
end
|
|
|
|
function BroadcastLua(code)
|
|
for _, pl in pairs(player.GetAll()) do
|
|
pl:SendLua(code)
|
|
end
|
|
end
|
|
|
|
player.GetByUniqueID = player.GetByUniqueID or function(uid)
|
|
for _, pl in pairs(player.GetAll()) do
|
|
if pl:UniqueID() == uid then return pl end
|
|
end
|
|
end
|
|
|
|
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() * 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() * 12)
|
|
ent:SetAngles(VectorRand():Angle())
|
|
ent:SetGibType(math.random(3, #GAMEMODE.HumanGibs))
|
|
ent:Spawn()
|
|
end
|
|
end
|
|
end
|
|
|
|
function GM:TryHumanPickup(pl, entity)
|
|
if self.ZombieEscape or pl.NoObjectPickup then return end
|
|
|
|
if entity:IsValid() and not entity.m_NoPickup then
|
|
local entclass = entity:GetClass()
|
|
if (string.sub(entclass, 1, 12) == "prop_physics" or entclass == "func_physbox" or entity.HumanHoldable and entity:HumanHoldable(pl)) and pl:Team() == TEAM_HUMAN and not entity:IsNailed() and pl:Alive() and entity:GetMoveType() == MOVETYPE_VPHYSICS and entity:GetPhysicsObject():IsValid() and entity:GetPhysicsObject():GetMass() <= CARRY_MAXIMUM_MASS and entity:GetPhysicsObject():IsMoveable() and entity:OBBMins():Length() + entity:OBBMaxs():Length() <= CARRY_MAXIMUM_VOLUME then
|
|
local holder, status = entity:GetHolder()
|
|
if not holder and not pl:IsHolding() and CurTime() >= (pl.NextHold or 0)
|
|
and pl:GetShootPos():Distance(entity:NearestPoint(pl:GetShootPos())) <= 64 and pl:GetGroundEntity() ~= entity then
|
|
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")
|
|
|
|
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/killicons/*.vmt", "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/*.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.vtf")
|
|
resource.AddFile("materials/killicon/redeem.vmt")
|
|
resource.AddFile("materials/killicon/zs_axe.vtf")
|
|
resource.AddFile("materials/killicon/zs_keyboard.vtf")
|
|
resource.AddFile("materials/killicon/zs_sledgehammer.vtf")
|
|
resource.AddFile("materials/killicon/zs_fryingpan.vtf")
|
|
resource.AddFile("materials/killicon/zs_pot.vtf")
|
|
resource.AddFile("materials/killicon/zs_plank.vtf")
|
|
resource.AddFile("materials/killicon/zs_hammer.vtf")
|
|
resource.AddFile("materials/killicon/zs_shovel.vtf")
|
|
resource.AddFile("materials/killicon/zs_axe.vmt")
|
|
resource.AddFile("materials/killicon/zs_keyboard.vmt")
|
|
resource.AddFile("materials/killicon/zs_sledgehammer.vmt")
|
|
resource.AddFile("materials/killicon/zs_fryingpan.vmt")
|
|
resource.AddFile("materials/killicon/zs_pot.vmt")
|
|
resource.AddFile("materials/killicon/zs_plank.vmt")
|
|
resource.AddFile("materials/killicon/zs_hammer.vmt")
|
|
resource.AddFile("materials/killicon/zs_shovel.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/v_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/v_sledgehammer.mdl")
|
|
resource.AddFile("models/weapons/w_hammer.mdl")
|
|
resource.AddFile("models/weapons/v_hammer/v_hammer.mdl")
|
|
|
|
resource.AddFile("models/weapons/v_aegiskit.mdl")
|
|
|
|
resource.AddFile("materials/models/weapons/v_hand/armtexture.vmt")
|
|
|
|
resource.AddFile("models/wraith_zsv1.mdl")
|
|
for _, filename in pairs(file.Find("materials/models/wraith1/*.vmt", "GAME")) do
|
|
resource.AddFile("materials/models/wraith1/"..filename)
|
|
end
|
|
for _, filename in pairs(file.Find("materials/models/wraith1/*.vtf", "GAME")) do
|
|
resource.AddFile("materials/models/wraith1/"..filename)
|
|
end
|
|
|
|
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/vinrax/player/doll_player.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("materials/noxctf/sprite_bloodspray1.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray2.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray3.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray4.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray5.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray6.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray7.vmt")
|
|
resource.AddFile("materials/noxctf/sprite_bloodspray8.vmt")
|
|
|
|
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:RegisterPlayerSpawnEntities()
|
|
self:AddResources()
|
|
self:PrecacheResources()
|
|
self:AddCustomAmmo()
|
|
self:AddNetworkStrings()
|
|
self:LoadProfiler()
|
|
|
|
self:SetPantsMode(self.PantsMode, true)
|
|
self:SetClassicMode(self:IsClassicMode(), true)
|
|
self:SetBabyMode(self:IsBabyMode(), true)
|
|
|
|
local mapname = string.lower(game.GetMap())
|
|
if string.find(mapname, "_obj_", 1, true) or string.find(mapname, "objective", 1, true) then
|
|
self.ObjectiveMap = true
|
|
end
|
|
|
|
if string.sub(mapname, 1, 3) == "zm_" then
|
|
NOZOMBIEGASSES = true
|
|
end
|
|
|
|
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_playerredeemed")
|
|
util.AddNetworkString("zs_dohulls")
|
|
util.AddNetworkString("zs_penalty")
|
|
util.AddNetworkString("zs_nextresupplyuse")
|
|
util.AddNetworkString("zs_lifestats")
|
|
util.AddNetworkString("zs_lifestatsbd")
|
|
util.AddNetworkString("zs_lifestatshd")
|
|
util.AddNetworkString("zs_lifestatsbe")
|
|
util.AddNetworkString("zs_boss_spawned")
|
|
util.AddNetworkString("zs_commission")
|
|
util.AddNetworkString("zs_healother")
|
|
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_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")
|
|
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:OpenPointsShop()" 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
|
|
else
|
|
pl:SendLua("MakepWeapons()")
|
|
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"))
|
|
ztab = table.Add(ztab, ents.FindByClass("info_player_rebel"))
|
|
|
|
local htab = ents.FindByClass("info_player_human")
|
|
htab = table.Add(htab, ents.FindByClass("info_player_combine"))
|
|
|
|
local mapname = string.lower(game.GetMap())
|
|
-- 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.
|
|
if string.sub(mapname, 1, 3) == "cs_" or string.sub(mapname, 1, 3) == "zs_" then
|
|
ztab = table.Add(ztab, ents.FindByClass("info_player_counterterrorist"))
|
|
htab = table.Add(htab, ents.FindByClass("info_player_terrorist"))
|
|
else -- 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.
|
|
ztab = table.Add(ztab, ents.FindByClass("info_player_terrorist"))
|
|
htab = table.Add(htab, ents.FindByClass("info_player_counterterrorist"))
|
|
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)
|
|
|
|
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"
|
|
function GM:InitPostEntity()
|
|
gamemode.Call("InitPostEntityMap")
|
|
|
|
RunConsoleCommand("mapcyclefile", "mapcycle_zombiesurvival.txt")
|
|
|
|
if string.find(string.lower(GetConVarString("hostname")), "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 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: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_suitcharger")
|
|
end
|
|
|
|
function GM:ReplaceMapWeapons()
|
|
for _, ent in pairs(ents.FindByClass("weapon_*")) do
|
|
if string.sub(ent:GetClass(), 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
|
|
|
|
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()
|
|
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)
|
|
|
|
for _, spawn in pairs(team.GetValidSpawnPoint(TEAM_UNDEAD)) do
|
|
local gasses = ents.FindByClass("zombiegasses")
|
|
local numgasses = #gasses
|
|
if 4 < numgasses then
|
|
break
|
|
elseif numgasses == 0 or math.random(4) == 1 then
|
|
local spawnpos = spawn:GetPos() + Vector(0, 0, 24)
|
|
local near = false
|
|
|
|
if not self.ZombieEscape then
|
|
for _, humspawn in pairs(humanspawns) do
|
|
if humspawn:IsValid() and humspawn:GetPos():Distance(spawnpos) < 400 then
|
|
near = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not near then
|
|
for _, gas in pairs(gasses) do
|
|
if gas:GetPos():Distance(spawnpos) < 300 then
|
|
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
|
|
end
|
|
|
|
function GM:CheckDynamicSpawnHR(ent)
|
|
if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_UNDEAD then
|
|
ent.DynamicSpawnedOn = ent.DynamicSpawnedOn + 1
|
|
end
|
|
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 #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
|
|
-- If they're near a human, use position where they died.
|
|
for _, h in pairs(team.GetPlayers(TEAM_HUMAN)) do
|
|
if h:GetPos():Distance(epicenter or pl:GetPos()) < 1024 then
|
|
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
|
|
pl.ForceDynamicSpawn = nil
|
|
if self:DynamicSpawnIsValid(dyn) then
|
|
self:CheckDynamicSpawnHR(dyn)
|
|
|
|
if dyn:GetClass() == "prop_creepernest" then
|
|
local owner = dyn.Owner
|
|
if owner and owner:IsValid() and owner:Team() == TEAM_UNDEAD then
|
|
owner.NestSpawns = owner.NestSpawns + 1
|
|
end
|
|
end
|
|
|
|
return dyn
|
|
end
|
|
|
|
epicenter = dyn:GetPos() -- Ok, at least skew our epicenter to what they tried to spawn at.
|
|
tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
|
|
local dynamicspawns = self:GetDynamicSpawns(pl)
|
|
if #dynamicspawns > 0 then
|
|
spawninplayer = true
|
|
table.Add(tab, dynamicspawns)
|
|
end
|
|
else
|
|
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
|
|
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.
|
|
|
|
local count = #tab
|
|
if count > 0 then
|
|
local potential = {}
|
|
|
|
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
|
|
local spawnpos = spawn:GetPos()
|
|
for _, ent in pairs(ents.FindInBox(spawnpos + playermins, spawnpos + playermaxs)) do
|
|
if ent:IsPlayer() and not spawninplayer or string.sub(ent:GetClass(), 1, 5) == "prop_" then
|
|
blocked = true
|
|
break
|
|
end
|
|
end
|
|
if not blocked then
|
|
potential[#potential + 1] = spawn
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Now our final spawn list. Pick the one that's closest to the humans if we're a zombie. Otherwise use a random spawn.
|
|
if #potential > 0 then
|
|
local spawn = teamid == TEAM_UNDEAD and self:GetClosestSpawnPoint(potential, epicenter or self:GetTeamEpicentre(TEAM_HUMAN)) or table.Random(potential)
|
|
if spawn then
|
|
LastSpawnPoints[teamid] = spawn
|
|
self:CheckDynamicSpawnHR(spawn)
|
|
return spawn
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fallback.
|
|
return LastSpawnPoints[teamid] or #tab > 0 and table.Random(tab) or pl
|
|
end
|
|
|
|
local function BossZombieSort(a, b)
|
|
local ascore = a.BarricadeDamage * 0.2 + a.DamageDealt[TEAM_UNDEAD]
|
|
local bscore = b.BarricadeDamage * 0.2 + b.DamageDealt[TEAM_UNDEAD]
|
|
if ascore == bscore then
|
|
return a:Deaths() < b:Deaths()
|
|
end
|
|
|
|
return ascore > bscore
|
|
end
|
|
function GM:SpawnBossZombie(bossplayer, silent)
|
|
if not bossplayer then
|
|
local livingbosses = 0
|
|
local zombies = {}
|
|
for _, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
|
|
if ent:GetZombieClassTable().Boss and ent:Alive() then
|
|
livingbosses = livingbosses + 1
|
|
if livingbosses >= 3 then return end
|
|
else
|
|
table.insert(zombies, ent)
|
|
end
|
|
end
|
|
table.sort(zombies, BossZombieSort)
|
|
|
|
bossplayer = zombies[1]
|
|
end
|
|
|
|
if not bossplayer then return end
|
|
|
|
local bossindex = bossplayer:GetBossZombieIndex()
|
|
if bossindex == -1 then return 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
|
|
|
|
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
|
|
|
|
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 self:GetWaveStart() - 10 <= time and not self.RoundEnded
|
|
and (self.BossZombiePlayersRequired <= 0 or #player.GetAll() >= self.BossZombiePlayersRequired) then
|
|
self:SpawnBossZombie()
|
|
end
|
|
end
|
|
end
|
|
|
|
local humans = team.GetPlayers(TEAM_HUMAN)
|
|
for _, pl in pairs(humans) do
|
|
if pl:Team() == TEAM_HUMAN then
|
|
if pl:GetBarricadeGhosting() then
|
|
pl:BarricadeGhostingThink()
|
|
end
|
|
|
|
if pl.m_PointQueue >= 1 and time >= pl.m_LastDamageDealt + 3 then
|
|
pl:PointCashOut((pl.m_LastDamageDealtPosition or pl:GetPos()) + Vector(0, 0, 32), FM_NONE)
|
|
end
|
|
end
|
|
end
|
|
|
|
if wave == 0 then
|
|
self:CalculateZombieVolunteers()
|
|
end
|
|
|
|
if NextTick <= time then
|
|
NextTick = time + 1
|
|
|
|
local doafk = not self:GetWaveActive() and wave == 0
|
|
local dopoison = self:GetEscapeStage() == ESCAPESTAGE_DEATH
|
|
|
|
for _, pl in pairs(humans) do
|
|
if pl:Alive() then
|
|
if doafk then
|
|
local plpos = pl:GetPos()
|
|
if pl.LastAFKPosition and (pl.LastAFKPosition.x ~= plpos.x or pl.LastAFKPosition.y ~= plpos.y) then
|
|
pl.LastNotAFK = CurTime()
|
|
end
|
|
pl.LastAFKPosition = plpos
|
|
end
|
|
|
|
if pl:WaterLevel() >= 3 and not (pl.status_drown and pl.status_drown:IsValid()) then
|
|
pl:GiveStatus("drown")
|
|
else
|
|
pl:PreventSkyCade()
|
|
end
|
|
|
|
if self:GetWave() >= 1 and time >= pl.BonusDamageCheck + 60 then
|
|
pl.BonusDamageCheck = time
|
|
pl:AddPoints(2)
|
|
pl:PrintTranslatedMessage(HUD_PRINTCONSOLE, "minute_points_added", 2)
|
|
end
|
|
|
|
if pl.BuffRegenerative and time >= pl.NextRegenerate and pl:Health() < pl:GetMaxHealth() / 2 then
|
|
pl.NextRegenerate = time + 5
|
|
pl:SetHealth(pl:Health() + 1)
|
|
end
|
|
|
|
if dopoison then
|
|
pl:TakeSpecialDamage(5, DMG_POISON)
|
|
end
|
|
end
|
|
end
|
|
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
|
|
|
|
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.GetAll()) 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 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
|
|
|
|
if not self.PantsMode and not self:IsClassicMode() and not self:IsBabyMode() and not self.ZombieEscape then
|
|
if not v.Locked then
|
|
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
|
|
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)
|
|
if self:GetWave() == 0 then return end
|
|
|
|
pl.HealedThisRound = pl.HealedThisRound + health
|
|
pl.CarryOverHealth = (pl.CarryOverHealth or 0) + health
|
|
|
|
local hpperpoint = self.MedkitPointsPerHealth
|
|
if hpperpoint <= 0 then return end
|
|
|
|
local points = math.floor(pl.CarryOverHealth / hpperpoint)
|
|
|
|
if 1 <= points then
|
|
pl:AddPoints(points)
|
|
|
|
pl.CarryOverHealth = pl.CarryOverHealth - points * hpperpoint
|
|
|
|
net.Start("zs_healother")
|
|
net.WriteEntity(other)
|
|
net.WriteUInt(points, 16)
|
|
net.Send(pl)
|
|
end
|
|
end
|
|
|
|
function GM:ObjectPackedUp(pack, packer, owner)
|
|
end
|
|
|
|
function GM:PlayerRepairedObject(pl, other, health, wep)
|
|
if self:GetWave() == 0 then return end
|
|
|
|
pl.RepairedThisRound = pl.RepairedThisRound + health
|
|
pl.CarryOverRepair = (pl.CarryOverRepair or 0) + health
|
|
|
|
local hpperpoint = self.RepairPointsPerHealth
|
|
if hpperpoint <= 0 then return end
|
|
|
|
local points = math.floor(pl.CarryOverRepair / hpperpoint)
|
|
|
|
if 1 <= points then
|
|
pl:AddPoints(points)
|
|
|
|
pl.CarryOverRepair = pl.CarryOverRepair - points * hpperpoint
|
|
|
|
net.Start("zs_repairobject")
|
|
net.WriteEntity(other)
|
|
net.WriteUInt(points, 16)
|
|
net.Send(pl)
|
|
end
|
|
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)
|
|
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(GetConVarString("mapcyclefile"), "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
|
|
|
|
self:RestartLua()
|
|
self:RestartGame()
|
|
|
|
net.Start("zs_gamemodecall")
|
|
net.WriteString("RestartRound")
|
|
net.Broadcast()
|
|
end
|
|
|
|
GM.DynamicSpawning = true
|
|
GM.CappedInfliction = 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.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.StartingZombie = {}
|
|
self.CheckedOut = {}
|
|
self.PreviouslyDied = {}
|
|
self.StoredUndeadFrags = {}
|
|
|
|
ROUNDWINNER = nil
|
|
LAST_BITE = nil
|
|
LASTHUMAN = nil
|
|
|
|
hook.Remove("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage")
|
|
hook.Remove("PlayerCanHearPlayersVoice", "EndRoundCanHearPlayersVoice")
|
|
|
|
self:RevertZombieClasses()
|
|
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
|
|
|
|
self:SetUseSigils(false)
|
|
self:SetEscapeStage(ESCAPESTAGE_NONE)
|
|
|
|
self:SetWave(0)
|
|
if GAMEMODE.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)
|
|
|
|
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)
|
|
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)
|
|
pl:ChangeTeam(TEAM_HUMAN)
|
|
pl:DoHulls()
|
|
pl:SetZombieClass(self.DefaultZombieClass)
|
|
pl.DeathClass = nil
|
|
end
|
|
|
|
self:SetWave(0)
|
|
if GAMEMODE.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("prop_flashlightbattery")) do ent.PlacedInMap = true end
|
|
|
|
if self.ObjectiveMap then
|
|
self:SetDynamicSpawning(false)
|
|
self.BossZombies = false
|
|
end
|
|
|
|
--[[if not game.IsDedicated() then
|
|
gamemode.Call("CreateSigils")
|
|
end]]
|
|
end
|
|
|
|
local function EndRoundPlayerShouldTakeDamage(pl, attacker) return pl:Team() ~= TEAM_HUMAN or not attacker:IsPlayer() end
|
|
local function EndRoundPlayerCanSuicide(pl) return pl:Team() ~= TEAM_HUMAN end
|
|
|
|
local function EndRoundSetupPlayerVisibility(pl)
|
|
if GAMEMODE.LastHumanPosition and GAMEMODE.RoundEnded then
|
|
AddOriginToPVS(GAMEMODE.LastHumanPosition)
|
|
else
|
|
hook.Remove("SetupPlayerVisibility", "EndRoundSetupPlayerVisibility")
|
|
end
|
|
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 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")
|
|
|
|
timer.Simple(5, function() gamemode.Call("DoHonorableMentions") end)
|
|
|
|
if winner == TEAM_HUMAN then
|
|
self.LastHumanPosition = nil
|
|
|
|
hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerShouldTakeDamage)
|
|
elseif winner == TEAM_UNDEAD then
|
|
hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerCanSuicide)
|
|
end
|
|
|
|
net.Start("zs_endround")
|
|
net.WriteUInt(winner or -1, 8)
|
|
net.WriteString(game.GetMapNext())
|
|
net.Broadcast()
|
|
|
|
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:PlayerReady(pl)
|
|
gamemode.Call("PlayerReadyRound", pl)
|
|
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 self:GetWave() <= 0 and self.StartingWorth > 0 and not self.StartingLoadout and not self.ZombieEscape then
|
|
pl:SendLua("MakepWorth()")
|
|
else
|
|
gamemode.Call("GiveDefaultOrRandomEquipment", pl)
|
|
end
|
|
|
|
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
|
|
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 playermins = Vector(-17, -17, 0)
|
|
local playermaxs = Vector(17, 17, 4)
|
|
local function groupsort(a, b)
|
|
return #a > #b
|
|
end
|
|
function GM:AttemptHumanDynamicSpawn(pl)
|
|
if pl:IsValid() and pl:IsPlayer() and pl:Alive() and pl:Team() == TEAM_HUMAN and self.DynamicSpawning then
|
|
local groups = self:GetTeamRallyGroups(TEAM_HUMAN)
|
|
table.sort(groups, groupsort)
|
|
for i=1, #groups do
|
|
local group = groups[i]
|
|
|
|
local allplayers = team.GetPlayers(TEAM_HUMAN)
|
|
for _, otherpl in pairs(group) do
|
|
if otherpl ~= pl then
|
|
local 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
|
|
local nearzombie = false
|
|
for __, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
|
|
if ent:Alive() and ent:GetPos():Distance(pos) <= 256 then
|
|
nearzombie = true
|
|
end
|
|
end
|
|
|
|
if not nearzombie then
|
|
pl:SetPos(otherpl:GetPos())
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function GM:PlayerInitialSpawn(pl)
|
|
gamemode.Call("PlayerInitialSpawnRound", pl)
|
|
end
|
|
|
|
function GM:PlayerInitialSpawnRound(pl)
|
|
pl:SprintDisable()
|
|
if pl:KeyDown(IN_WALK) then
|
|
pl:ConCommand("-walk")
|
|
end
|
|
|
|
pl:SetCanWalk(false)
|
|
pl:SetCanZoom(false)
|
|
pl:SetNoCollideWithTeammates(true)
|
|
pl:SetCustomCollisionCheck(true)
|
|
|
|
pl.ZombiesKilled = 0
|
|
pl.ZombiesKilledAssists = 0
|
|
pl.BrainsEaten = 0
|
|
|
|
pl.ResupplyBoxUsedByOthers = 0
|
|
|
|
pl.WaveJoined = self:GetWave()
|
|
|
|
pl.CrowKills = 0
|
|
pl.CrowVsCrowKills = 0
|
|
pl.CrowBarricadeDamage = 0
|
|
|
|
pl.BarricadeDamage = 0
|
|
pl.DynamicSpawnedOn = 0
|
|
|
|
pl.NextPainSound = 0
|
|
|
|
pl.BonusDamageCheck = 0
|
|
|
|
pl.DamageDealt = {}
|
|
pl.DamageDealt[TEAM_UNDEAD] = 0
|
|
pl.DamageDealt[TEAM_HUMAN] = 0
|
|
|
|
pl.HealedThisRound = 0
|
|
pl.CarryOverHealth = 0
|
|
pl.RepairedThisRound = 0
|
|
pl.CarryOverRepair = 0
|
|
pl.PointsCommission = 0
|
|
pl.CarryOverCommision = 0
|
|
pl.NextRegenerate = 0
|
|
pl.NestsDestroyed = 0
|
|
pl.NestSpawns = 0
|
|
|
|
local nosend = not pl.DidInitPostEntity
|
|
pl.HumanSpeedAdder = nil
|
|
pl.HumanSpeedAdder = nil
|
|
pl.HumanRepairMultiplier = nil
|
|
pl.HumanHealMultiplier = nil
|
|
pl.BuffResistant = nil
|
|
pl.BuffRegenerative = nil
|
|
pl.BuffMuscular = nil
|
|
pl.IsWeak = nil
|
|
pl.HumanSpeedAdder = nil
|
|
pl:SetPalsy(false, nosend)
|
|
pl:SetHemophilia(false, nosend)
|
|
pl:SetUnlucky(false)
|
|
pl.Clumsy = nil
|
|
pl.NoGhosting = nil
|
|
pl.NoObjectPickup = nil
|
|
pl.DamageVulnerability = nil
|
|
|
|
local uniqueid = pl:UniqueID()
|
|
|
|
if table.HasValue(self.FanList, uniqueid) then
|
|
pl.DamageVulnerability = (pl.DamageVulnerability or 1) + 10
|
|
pl:PrintTranslatedMessage(HUD_PRINTTALK, "thanks_for_being_a_fan_of_zs")
|
|
end
|
|
|
|
if self.PreviouslyDied[uniqueid] then
|
|
-- They already died and reconnected.
|
|
pl:ChangeTeam(TEAM_UNDEAD)
|
|
elseif LASTHUMAN then
|
|
-- Joined during last human.
|
|
pl.SpawnedTime = CurTime()
|
|
pl:ChangeTeam(TEAM_UNDEAD)
|
|
elseif self:GetWave() <= 0 then
|
|
-- Joined during ready phase.
|
|
pl.SpawnedTime = CurTime()
|
|
pl:ChangeTeam(TEAM_HUMAN)
|
|
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
|
|
-- Joined past the ready phase but before the deadline.
|
|
pl.SpawnedTime = CurTime()
|
|
pl:ChangeTeam(TEAM_HUMAN)
|
|
if self.DynamicSpawning then
|
|
timer.Simple(0, function() GAMEMODE:AttemptHumanDynamicSpawn(pl) end)
|
|
end
|
|
end
|
|
|
|
if pl:Team() == TEAM_UNDEAD and not self:GetWaveActive() and self.ZombieClasses["Crow"] then
|
|
pl:SetZombieClass(self.ZombieClasses["Crow"].Index)
|
|
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:PlayerRedeemed(pl, silent, noequip)
|
|
if not silent then
|
|
net.Start("zs_playerredeemed")
|
|
net.WriteEntity(pl)
|
|
net.WriteString(pl:Name())
|
|
net.Broadcast()
|
|
end
|
|
|
|
pl:RemoveStatus("overridemodel", false, true)
|
|
|
|
pl:ChangeTeam(TEAM_HUMAN)
|
|
pl:DoHulls()
|
|
if not noequip then pl.m_PreRedeem = true end
|
|
pl:UnSpectateAndSpawn()
|
|
pl.m_PreRedeem = nil
|
|
|
|
local frags = pl:Frags()
|
|
if frags < 0 then
|
|
pl:SetFrags(frags * 5)
|
|
else
|
|
pl:SetFrags(0)
|
|
end
|
|
pl:SetDeaths(0)
|
|
|
|
pl.DeathClass = nil
|
|
pl:SetZombieClass(self.DefaultZombieClass)
|
|
|
|
pl.SpawnedTime = CurTime()
|
|
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 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
|
|
|
|
gamemode.Call("CalculateInfliction")
|
|
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
|
|
self:EvaluatePropFreeze(e)
|
|
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)
|
|
|
|
for _, nail in pairs(ent:GetNails()) do
|
|
if nail:IsValid() then
|
|
local baseent = nail:GetBaseEntity()
|
|
local 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()
|
|
if deployer:IsValid() and deployer ~= remover and deployer:Team() == TEAM_HUMAN then
|
|
PrintTranslatedMessage(HUD_PRINTCONSOLE, "nail_removed_by", remover:Name(), deployer:Name())
|
|
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)
|
|
for item, amount in pairs(self.StartingLoadout) do
|
|
for i=1, amount do
|
|
pl:Give(item)
|
|
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
|
|
pl:StripWeapon(tab.SWEP)
|
|
pl:Give(tab.SWEP)
|
|
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
|
|
|
|
concommand.Add("zs_pointsshopbuy", function(sender, command, arguments)
|
|
if not (sender:IsValid() and sender:IsConnected()) or #arguments == 0 then return end
|
|
|
|
if sender:GetUnlucky() then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "banned_for_life_warning"))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
if not sender:NearArsenalCrate() then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "need_to_be_near_arsenal_crate"))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
if not gamemode.Call("PlayerCanPurchase", sender) then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "cant_purchase_right_now"))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
local itemtab
|
|
local id = arguments[1]
|
|
local num = tonumber(id)
|
|
if num then
|
|
itemtab = GAMEMODE.Items[num]
|
|
else
|
|
for i, tab in pairs(GAMEMODE.Items) do
|
|
if tab.Signature == id then
|
|
itemtab = tab
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if not itemtab or not itemtab.PointShop then return end
|
|
|
|
local points = sender:GetPoints()
|
|
local cost = itemtab.Worth
|
|
if not GAMEMODE:GetWaveActive() then
|
|
cost = cost * GAMEMODE.ArsenalCrateMultiplier
|
|
end
|
|
|
|
if GAMEMODE:IsClassicMode() and itemtab.NoClassicMode then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "cant_use_x_in_classic", itemtab.Name))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
if GAMEMODE.ZombieEscape and itemtab.NoZombieEscape then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "cant_use_x_in_zombie_escape", itemtab.Name))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
cost = math.ceil(cost)
|
|
|
|
if points < cost then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "dont_have_enough_points"))
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
return
|
|
end
|
|
|
|
if itemtab.Callback then
|
|
itemtab.Callback(sender)
|
|
elseif itemtab.SWEP then
|
|
if sender:HasWeapon(itemtab.SWEP) then
|
|
local stored = weapons.GetStored(itemtab.SWEP)
|
|
if stored and stored.AmmoIfHas then
|
|
sender:GiveAmmo(stored.Primary.DefaultClip, stored.Primary.Ammo)
|
|
else
|
|
local wep = ents.Create("prop_weapon")
|
|
if wep:IsValid() then
|
|
wep:SetPos(sender:GetShootPos())
|
|
wep:SetAngles(sender:GetAngles())
|
|
wep:SetWeaponType(itemtab.SWEP)
|
|
wep:SetShouldRemoveAmmo(true)
|
|
wep:Spawn()
|
|
end
|
|
end
|
|
else
|
|
local wep = sender:Give(itemtab.SWEP)
|
|
if wep and wep:IsValid() and wep.EmptyWhenPurchased and wep:GetOwner():IsValid() then
|
|
if wep.Primary then
|
|
local primary = wep:ValidPrimaryAmmo()
|
|
if primary then
|
|
sender:RemoveAmmo(math.max(0, wep.Primary.DefaultClip - wep.Primary.ClipSize), primary)
|
|
end
|
|
end
|
|
if wep.Secondary then
|
|
local secondary = wep:ValidSecondaryAmmo()
|
|
if secondary then
|
|
sender:RemoveAmmo(math.max(0, wep.Secondary.DefaultClip - wep.Secondary.ClipSize), secondary)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
return
|
|
end
|
|
|
|
sender:TakePoints(cost)
|
|
sender:PrintTranslatedMessage(HUD_PRINTTALK, "purchased_x_for_y_points", itemtab.Name, cost)
|
|
sender:SendLua("surface.PlaySound(\"ambient/levels/labs/coinslot1.wav\")")
|
|
|
|
local nearest = sender:NearestArsenalCrateOwnedByOther()
|
|
if nearest then
|
|
local owner = nearest:GetObjectOwner()
|
|
if owner:IsValid() then
|
|
local nonfloorcommission = cost * 0.07
|
|
local commission = math.floor(nonfloorcommission)
|
|
if commission > 0 then
|
|
owner.PointsCommission = owner.PointsCommission + cost
|
|
|
|
owner:AddPoints(commission)
|
|
|
|
net.Start("zs_commission")
|
|
net.WriteEntity(nearest)
|
|
net.WriteEntity(sender)
|
|
net.WriteUInt(commission, 16)
|
|
net.Send(owner)
|
|
end
|
|
|
|
local leftover = nonfloorcommission - commission
|
|
if leftover > 0 then
|
|
owner.CarryOverCommision = owner.CarryOverCommision + leftover
|
|
if owner.CarryOverCommision >= 1 then
|
|
local carried = math.floor(owner.CarryOverCommision)
|
|
owner.CarryOverCommision = owner.CarryOverCommision - carried
|
|
owner:AddPoints(carried)
|
|
|
|
net.Start("zs_commission")
|
|
net.WriteEntity(nearest)
|
|
net.WriteEntity(sender)
|
|
net.WriteUInt(carried, 16)
|
|
net.Send(owner)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("worthrandom", function(sender, command, arguments)
|
|
if sender:IsValid() and sender:IsConnected() and gamemode.Call("PlayerCanCheckout", sender) then
|
|
gamemode.Call("GiveRandomEquipment", sender)
|
|
end
|
|
end)
|
|
|
|
concommand.Add("worthcheckout", function(sender, command, arguments)
|
|
if not (sender:IsValid() and sender:IsConnected()) or #arguments == 0 then return end
|
|
|
|
if not gamemode.Call("PlayerCanCheckout", sender) then
|
|
sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "cant_use_worth_anymore"))
|
|
return
|
|
end
|
|
|
|
local cost = 0
|
|
local hasalready = {}
|
|
|
|
for _, id in pairs(arguments) do
|
|
local tab = FindStartingItem(id)
|
|
if tab and not hasalready[id] then
|
|
cost = cost + tab.Worth
|
|
hasalready[id] = true
|
|
end
|
|
end
|
|
|
|
if cost > GAMEMODE.StartingWorth then return end
|
|
|
|
local hasalready = {}
|
|
|
|
for _, id in pairs(arguments) do
|
|
local tab = FindStartingItem(id)
|
|
if tab and not hasalready[id] then
|
|
if tab.NoClassicMode and GAMEMODE:IsClassicMode() then
|
|
sender:PrintMessage(HUD_PRINTTALK, translate.ClientFormat(sender, "cant_use_x_in_classic_mode", tab.Name))
|
|
elseif tab.Callback then
|
|
tab.Callback(sender)
|
|
hasalready[id] = true
|
|
elseif tab.SWEP then
|
|
sender:StripWeapon(tab.SWEP) -- "Fixes" players giving each other empty weapons to make it so they get no ammo from the Worth menu purchase.
|
|
sender:Give(tab.SWEP)
|
|
hasalready[id] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if table.Count(hasalready) > 0 then
|
|
GAMEMODE.CheckedOut[sender:UniqueID()] = true
|
|
end
|
|
|
|
gamemode.Call("RemoveDuplicateAmmo", sender)
|
|
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_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:KeyDown(IN_WALK) then
|
|
pl:TrySpawnAsGoreChild()
|
|
elseif pl:KeyDown(IN_ATTACK) then
|
|
if self:GetWaveActive() then
|
|
pl:RefreshDynamicSpawnPoint()
|
|
pl:UnSpectateAndSpawn()
|
|
else
|
|
pl:ChangeToCrow()
|
|
end
|
|
elseif pl:KeyPressed(IN_ATTACK2) then
|
|
pl.SpectatedPlayerKey = (pl.SpectatedPlayerKey or 0) + 1
|
|
|
|
local livingzombies = {}
|
|
for _, v in pairs(ents.FindByClass("prop_creepernest")) do
|
|
if v:GetNestBuilt() then table.insert(livingzombies, v) end
|
|
end
|
|
for _, v in pairs(team.GetPlayers(TEAM_ZOMBIE)) do
|
|
if v:Alive() then table.insert(livingzombies, v) end
|
|
end
|
|
--[[for _, v in pairs(team.GetSpawnPointGrouped(TEAM_UNDEAD)) do
|
|
table.insert(livingzombies, v)
|
|
end]]
|
|
|
|
pl:StripWeapons()
|
|
|
|
if pl.SpectatedPlayerKey > #livingzombies then
|
|
pl.SpectatedPlayerKey = 1
|
|
end
|
|
|
|
local specplayer = livingzombies[pl.SpectatedPlayerKey]
|
|
if specplayer then
|
|
pl:Spectate(OBS_MODE_CHASE)
|
|
pl:SpectateEntity(specplayer)
|
|
end
|
|
elseif pl:KeyPressed(IN_JUMP) then
|
|
pl:Spectate(OBS_MODE_ROAMING)
|
|
pl:SpectateEntity(NULL)
|
|
pl.SpectatedPlayerKey = nil
|
|
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)
|
|
end
|
|
|
|
function GM:NestDestroyed(ent, attacker)
|
|
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._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_BULLET)
|
|
return
|
|
end
|
|
|
|
-- We need to stop explosive chains team killing.
|
|
if inflictor:IsValid() then
|
|
local dmgtype = dmginfo:GetDamageType()
|
|
if dmgtype == DMG_BLAST or dmgtype == DMG_BURN or dmgtype == DMG_SLOWBURN then
|
|
if ent:IsPlayer() then
|
|
if inflictor.LastExplosionTeam == ent:Team() and inflictor.LastExplosionAttacker ~= ent and inflictor.LastExplosionTime and CurTime() < inflictor.LastExplosionTime + 10 then -- Player damaged by physics object explosion.
|
|
dmginfo:SetDamage(0)
|
|
dmginfo:ScaleDamage(0)
|
|
return
|
|
end
|
|
elseif inflictor ~= ent and string.sub(ent:GetClass(), 1, 12) == "prop_physics" and string.sub(inflictor:GetClass(), 1, 12) == "prop_physics" then -- Physics object damaged by physics object explosion.
|
|
ent.LastExplosionAttacker = inflictor.LastExplosionAttacker
|
|
ent.LastExplosionTeam = inflictor.LastExplosionTeam
|
|
ent.LastExplosionTime = CurTime()
|
|
end
|
|
elseif inflictor:IsPlayer() and string.sub(ent:GetClass(), 1, 12) == "prop_physics" then -- Physics object damaged by player.
|
|
ent.LastExplosionAttacker = inflictor
|
|
ent.LastExplosionTeam = inflictor:Team()
|
|
ent.LastExplosionTime = CurTime()
|
|
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
|
|
elseif ent.PropHealth then -- A prop that was invulnerable and converted to vulnerable.
|
|
if 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 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) or ent.Broken then 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
|
|
|
|
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)
|
|
|
|
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_physbox" then
|
|
local holder, status = ent:GetHolder()
|
|
if holder then status:Remove() end
|
|
|
|
if ent:GetKeyValues().damagefilter == "invul" then 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 entclass == "func_breakable" then
|
|
if ent:GetKeyValues().damagefilter == "invul" then 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 ent:IsBarricadeProp() and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
|
|
dispatchdamagedisplay = true
|
|
end
|
|
|
|
if dmginfo:GetDamage() > 0 or ent:IsPlayer() and ent:GetZombieClassTable().Name == "Shade" then
|
|
local holder, status = ent:GetHolder()
|
|
if holder then status:Remove() end
|
|
|
|
if attacker:IsPlayer() and dispatchdamagedisplay then
|
|
self:DamageFloater(attacker, ent, dmginfo)
|
|
end
|
|
end
|
|
end
|
|
|
|
function GM:DamageFloater(attacker, victim, dmginfo)
|
|
local dmgpos = dmginfo:GetDamagePosition()
|
|
if dmgpos == vector_origin then dmgpos = victim:NearestPoint(attacker:EyePos()) end
|
|
|
|
net.Start(victim:IsPlayer() and "zs_dmg" or "zs_dmg_prop")
|
|
net.WriteUInt(math.ceil(dmginfo:GetDamage()), 16)
|
|
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:OnPlayerChangedTeam(pl, oldteam, newteam)
|
|
if newteam == TEAM_UNDEAD then
|
|
pl:SetPoints(0)
|
|
pl.DamagedBy = {}
|
|
pl:SetBarricadeGhosting(false)
|
|
self.CheckedOut[pl:UniqueID()] = true
|
|
elseif newteam == TEAM_HUMAN then
|
|
self.PreviouslyDied[pl:UniqueID()] = nil
|
|
end
|
|
|
|
pl.m_PointQueue = 0
|
|
|
|
timer.Simple(0, function() gamemode.Call("CalculateInfliction") 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
end
|
|
end
|
|
end
|
|
|
|
function GM:SetClosestsToZombie()
|
|
local allplayers = player.GetAll()
|
|
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" then
|
|
pl:SetTeam(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()
|
|
end
|
|
pl:SetFrags(0)
|
|
pl:SetDeaths(0)
|
|
self.StartingZombie[pl:UniqueID()] = true
|
|
pl:UnSpectateAndSpawn()
|
|
end
|
|
|
|
for _, pl in pairs(allplayers) do
|
|
if pl:Team() == TEAM_HUMAN and pl._ZombieSpawnDistance <= 128 then
|
|
pl:SetPos(self:PlayerSelectSpawn(pl):GetPos())
|
|
end
|
|
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 0 < healthremaining then
|
|
victim:PlayPainSound()
|
|
end
|
|
|
|
if victim:Team() == TEAM_HUMAN then
|
|
victim.BonusDamageCheck = CurTime()
|
|
|
|
if healthremaining < 75 and 1 <= healthremaining then
|
|
victim:ResetSpeed(nil, healthremaining)
|
|
end
|
|
end
|
|
|
|
if attacker:IsValid() then
|
|
if attacker:IsPlayer() then
|
|
victim:SetLastAttacker(attacker)
|
|
|
|
local myteam = attacker:Team()
|
|
local otherteam = victim:Team()
|
|
if myteam ~= otherteam then
|
|
damage = math.min(damage, victim.m_PreHurtHealth)
|
|
victim.m_PreHurtHealth = healthremaining
|
|
|
|
attacker.DamageDealt[myteam] = attacker.DamageDealt[myteam] + damage
|
|
|
|
if myteam == TEAM_UNDEAD then
|
|
attacker:AddLifeHumanDamage(damage)
|
|
elseif otherteam == TEAM_UNDEAD then
|
|
victim.DamagedBy[attacker] = (victim.DamagedBy[attacker] or 0) + damage
|
|
if (not victim.m_LastWaveStartSpawn or CurTime() >= victim.m_LastWaveStartSpawn + 3)
|
|
and (healthremaining <= 0 or not victim.m_LastGasHeal or CurTime() >= victim.m_LastGasHeal + 2) then
|
|
attacker.m_PointQueue = attacker.m_PointQueue + damage / victim:GetMaxHealth() * (victim:GetZombieClassTable().Points or 0)
|
|
end
|
|
attacker.m_LastDamageDealtPosition = victim:GetPos()
|
|
attacker.m_LastDamageDealt = CurTime()
|
|
end
|
|
end
|
|
elseif attacker:GetClass() == "trigger_hurt" then
|
|
victim.LastHitWithTriggerHurt = CurTime()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Don't change speed instantly to stop people from shooting and then running away with a faster weapon.
|
|
function GM:WeaponDeployed(pl, wep)
|
|
local timername = tostring(pl).."speedchange"
|
|
timer.Destroy(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
|
|
timer.CreateEx(timername, 0.333, 1, ValidFunction, pl, "SetHumanSpeed", speed)
|
|
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: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()
|
|
elseif pl:Team() == TEAM_UNDEAD then
|
|
pl:CallZombieFunction("AltUse")
|
|
end
|
|
end
|
|
elseif key == IN_ZOOM then
|
|
if pl:Team() == TEAM_HUMAN and pl:Alive() and pl:IsOnGround() and not self.ZombieEscape then --and pl:GetGroundEntity():IsWorld() then
|
|
pl:SetBarricadeGhosting(true)
|
|
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():Distance(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:Distance(ent:NearestPoint(spawnpos)) <= 40 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: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 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(a, b)
|
|
return a._temp < b._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():Distance(plpos)
|
|
if dist <= 512 then
|
|
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:CallZombieFunction("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
|
|
|
|
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 = 99999
|
|
for _, ent in pairs(team.GetValidSpawnPoint(TEAM_UNDEAD)) do
|
|
dist = math.min(math.ceil(ent:GetPos():Distance(plpos)), dist)
|
|
end
|
|
pl.ZombieSpawnDeathDistance = dist
|
|
|
|
attacker:AddBrains(1)
|
|
attacker:AddLifeBrainsEaten(1)
|
|
|
|
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
|
|
|
|
local classtab = self.ZombieEscape and self.ZombieClasses["Super Zombie"] or self:IsClassicMode() and self.ZombieClasses["Classic Zombie"] or self:IsBabyMode() and GAMEMODE.ZombieClasses["Gore Child"] or GAMEMODE.ZombieClasses["Fresh Dead"]
|
|
if classtab then
|
|
pl:SetZombieClass(classtab.Index)
|
|
end
|
|
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:RemoveStatus("confusion", false, true)
|
|
|
|
local inflictor = dmginfo:GetInflictor()
|
|
local plteam = pl:Team()
|
|
local ct = CurTime()
|
|
local suicide = attacker == pl or attacker:IsWorld()
|
|
|
|
pl:Freeze(false)
|
|
|
|
local headshot = pl:LastHitGroup() == HITGROUP_HEAD and pl.m_LastHeadShot and CurTime() <= pl.m_LastHeadShot + 0.1
|
|
|
|
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:CallZombieFunction("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
|
|
local assistpl
|
|
if plteam == TEAM_UNDEAD then
|
|
local classtable = pl:GetZombieClassTable()
|
|
|
|
pl:PlayZombieDeathSound()
|
|
|
|
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 then
|
|
if classtable.ReviveCallback then
|
|
revive = classtable:ReviveCallback(pl, attacker, dmginfo)
|
|
elseif math.random(1, 4) ~= 1 then
|
|
self:DefaultRevive(pl)
|
|
revive = true
|
|
end
|
|
end
|
|
|
|
if not revive and attacker:Team() ~= TEAM_UNDEAD 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
|
|
net.Start("zs_lifestats")
|
|
net.WriteUInt(math.ceil(pl.LifeBarricadeDamage or 0), 24)
|
|
net.WriteUInt(math.ceil(pl.LifeHumanDamage or 0), 24)
|
|
net.WriteUInt(pl.LifeBrainsEaten or 0, 16)
|
|
net.Send(pl)
|
|
end
|
|
|
|
pl:CallZombieFunction("PostOnKilled", attacker, inflictor, suicide, headshot, dmginfo)
|
|
else
|
|
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:CallZombieFunction("NoDeathMessage", attacker, dmginfo) then return end
|
|
|
|
if attacker == pl then
|
|
net.Start("zs_pl_kill_self")
|
|
net.WriteEntity(pl)
|
|
net.WriteUInt(plteam, 16)
|
|
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, 16)
|
|
net.WriteUInt(attacker:Team(), 16) -- 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, 16)
|
|
net.WriteUInt(attacker:Team(), 16)
|
|
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, 16)
|
|
net.Broadcast()
|
|
end
|
|
end
|
|
|
|
function GM:PlayerKilledByPlayer(pl, attacker, inflictor, headshot, dmginfo)
|
|
end
|
|
|
|
function GM:PlayerCanPickupWeapon(pl, ent)
|
|
if pl:Team() == TEAM_UNDEAD then return ent:GetClass() == pl:GetZombieClassTable().SWEP end
|
|
|
|
return not ent.ZombieOnly and ent:GetClass() ~= "weapon_stunstick"
|
|
end
|
|
|
|
function GM:PlayerFootstep(pl, vPos, iFoot, strSoundName, fVolume, pFilter)
|
|
end
|
|
|
|
function GM:PlayerStepSoundTime(pl, iType, bWalking)
|
|
local fStepTime = 350
|
|
|
|
if iType == STEPSOUNDTIME_NORMAL or iType == STEPSOUNDTIME_WATER_FOOT then
|
|
local fMaxSpeed = pl:GetMaxSpeed()
|
|
if fMaxSpeed <= 100 then
|
|
fStepTime = 400
|
|
elseif fMaxSpeed <= 300 then
|
|
fStepTime = 350
|
|
else
|
|
fStepTime = 250
|
|
end
|
|
elseif iType == STEPSOUNDTIME_ON_LADDER then
|
|
fStepTime = 450
|
|
elseif iType == STEPSOUNDTIME_WATER_KNEE then
|
|
fStepTime = 600
|
|
end
|
|
|
|
if pl:Crouching() then
|
|
fStepTime = fStepTime + 50
|
|
end
|
|
|
|
return fStepTime
|
|
end
|
|
|
|
concommand.Add("zsdropweapon", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) or CurTime() < (sender.NextWeaponDrop or 0) or GAMEMODE.ZombieEscape then return end
|
|
sender.NextWeaponDrop = CurTime() + 0.15
|
|
|
|
local currentwep = sender:GetActiveWeapon()
|
|
if currentwep and currentwep:IsValid() then
|
|
local ent = sender:DropWeaponByType(currentwep:GetClass())
|
|
if ent and ent:IsValid() then
|
|
local shootpos = sender:GetShootPos()
|
|
local aimvec = sender:GetAimVector()
|
|
ent:SetPos(util.TraceHull({start = shootpos, endpos = shootpos + aimvec * 32, mask = MASK_SOLID, filter = sender, mins = Vector(-2, -2, -2), maxs = Vector(2, 2, 2)}).HitPos)
|
|
ent:SetAngles(sender:GetAngles())
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("zsemptyclip", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) then return end
|
|
|
|
sender.NextEmptyClip = sender.NextEmptyClip or 0
|
|
if sender.NextEmptyClip <= CurTime() then
|
|
sender.NextEmptyClip = CurTime() + 0.1
|
|
|
|
local wep = sender:GetActiveWeapon()
|
|
if wep:IsValid() and not wep.NoMagazine then
|
|
local primary = wep:ValidPrimaryAmmo()
|
|
if primary and 0 < wep:Clip1() then
|
|
sender:GiveAmmo(wep:Clip1(), primary, true)
|
|
wep:SetClip1(0)
|
|
end
|
|
local secondary = wep:ValidSecondaryAmmo()
|
|
if secondary and 0 < wep:Clip2() then
|
|
sender:GiveAmmo(wep:Clip2(), secondary, true)
|
|
wep:SetClip2(0)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("zsgiveammo", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not sender:IsValid() or not sender:Alive() or sender:Team() ~= TEAM_HUMAN then return end
|
|
|
|
local ammotype = arguments[1]
|
|
if not ammotype or #ammotype == 0 or not GAMEMODE.AmmoCache[ammotype] then return end
|
|
|
|
local count = sender:GetAmmoCount(ammotype)
|
|
if count <= 0 then
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_spare_ammo_to_give")
|
|
return
|
|
end
|
|
|
|
local ent
|
|
local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
|
|
if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
|
|
ent = dent
|
|
end
|
|
|
|
if not ent then
|
|
ent = sender:MeleeTrace(48, 2).Entity
|
|
end
|
|
|
|
if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
|
|
local desiredgive = math.min(count, GAMEMODE.AmmoCache[ammotype])
|
|
if desiredgive >= 1 then
|
|
sender:RemoveAmmo(desiredgive, ammotype)
|
|
ent:GiveAmmo(desiredgive, ammotype)
|
|
|
|
if CurTime() >= (sender.NextGiveAmmoSound or 0) then
|
|
sender.NextGiveAmmoSound = CurTime() + 1
|
|
sender:PlayGiveAmmoSound()
|
|
end
|
|
|
|
sender:RestartGesture(ACT_GMOD_GESTURE_ITEM_GIVE)
|
|
|
|
return
|
|
end
|
|
else
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
|
|
end
|
|
end)
|
|
|
|
concommand.Add("zsgiveweapon", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) or GAMEMODE.ZombieEscape then return end
|
|
|
|
local currentwep = sender:GetActiveWeapon()
|
|
if currentwep and currentwep:IsValid() then
|
|
local ent
|
|
local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
|
|
if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
|
|
ent = dent
|
|
end
|
|
|
|
if not ent then
|
|
ent = sender:MeleeTrace(48, 2).Entity
|
|
end
|
|
|
|
if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
|
|
if not ent:HasWeapon(currentwep:GetClass()) then
|
|
sender:GiveWeaponByType(currentwep, ent, false)
|
|
else
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "person_has_weapon")
|
|
end
|
|
else
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("zsgiveweaponclip", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) then return end
|
|
|
|
local currentwep = sender:GetActiveWeapon()
|
|
if currentwep and currentwep:IsValid() then
|
|
local ent
|
|
local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
|
|
if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
|
|
ent = dent
|
|
end
|
|
|
|
if not ent then
|
|
ent = sender:MeleeTrace(48, 2).Entity
|
|
end
|
|
|
|
if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
|
|
if not ent:HasWeapon(currentwep:GetClass()) then
|
|
sender:GiveWeaponByType(currentwep, ent, true)
|
|
else
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "person_has_weapon")
|
|
end
|
|
else
|
|
sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
|
|
sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("zsdropammo", function(sender, command, arguments)
|
|
if GAMEMODE.ZombieEscape then return end
|
|
|
|
if not sender:IsValid() or not sender:Alive() or sender:Team() ~= TEAM_HUMAN or CurTime() < (sender.NextDropClip or 0) then return end
|
|
|
|
sender.NextDropClip = CurTime() + 0.2
|
|
|
|
local wep = sender:GetActiveWeapon()
|
|
if not wep:IsValid() then return end
|
|
|
|
local ammotype = arguments[1] or wep:GetPrimaryAmmoTypeString()
|
|
if GAMEMODE.AmmoNames[ammotype] and GAMEMODE.AmmoCache[ammotype] then
|
|
local ent = sender:DropAmmoByType(ammotype, GAMEMODE.AmmoCache[ammotype] * 2)
|
|
if ent and ent:IsValid() then
|
|
ent:SetPos(sender:EyePos() + sender:GetAimVector() * 8)
|
|
ent:SetAngles(sender:GetAngles())
|
|
local phys = ent:GetPhysicsObject()
|
|
if phys:IsValid() then
|
|
phys:Wake()
|
|
phys:SetVelocityInstantaneous(sender:GetVelocity() * 0.85)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
local VoiceSetTranslate = {}
|
|
VoiceSetTranslate["models/player/alyx.mdl"] = "alyx"
|
|
VoiceSetTranslate["models/player/barney.mdl"] = "barney"
|
|
VoiceSetTranslate["models/player/breen.mdl"] = "male"
|
|
VoiceSetTranslate["models/player/combine_soldier.mdl"] = "combine"
|
|
VoiceSetTranslate["models/player/combine_soldier_prisonguard.mdl"] = "combine"
|
|
VoiceSetTranslate["models/player/combine_super_soldier.mdl"] = "combine"
|
|
VoiceSetTranslate["models/player/eli.mdl"] = "male"
|
|
VoiceSetTranslate["models/player/gman_high.mdl"] = "male"
|
|
VoiceSetTranslate["models/player/kleiner.mdl"] = "male"
|
|
VoiceSetTranslate["models/player/monk.mdl"] = "monk"
|
|
VoiceSetTranslate["models/player/mossman.mdl"] = "female"
|
|
VoiceSetTranslate["models/player/odessa.mdl"] = "male"
|
|
VoiceSetTranslate["models/player/police.mdl"] = "combine"
|
|
VoiceSetTranslate["models/player/brsp.mdl"] = "female"
|
|
VoiceSetTranslate["models/player/moe_glados_p.mdl"] = "female"
|
|
VoiceSetTranslate["models/grim.mdl"] = "combine"
|
|
VoiceSetTranslate["models/jason278-players/gabe_3.mdl"] = "monk"
|
|
function GM:PlayerSpawn(pl)
|
|
pl:StripWeapons()
|
|
pl:RemoveStatus("confusion", false, true)
|
|
|
|
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()
|
|
|
|
if pl:Team() == TEAM_UNDEAD then
|
|
pl:RemoveStatus("overridemodel", false, true)
|
|
|
|
if not pl.Revived then
|
|
pl.DamagedBy = {}
|
|
end
|
|
|
|
pl.LifeBarricadeDamage = 0
|
|
pl.LifeHumanDamage = 0
|
|
pl.LifeBrainsEaten = 0
|
|
|
|
if self:GetEscapeSequence() and self:GetEscapeStage() >= ESCAPESTAGE_BOSS then
|
|
local bossindex = pl:GetBossZombieIndex()
|
|
if bossindex ~= -1 then
|
|
pl:SetZombieClass(bossindex)
|
|
end
|
|
elseif pl.DeathClass and self:GetWaveActive() then
|
|
pl:SetZombieClass(pl.DeathClass)
|
|
pl.DeathClass = nil
|
|
end
|
|
|
|
local classtab = pl:GetZombieClassTable()
|
|
pl:DoHulls(pl:GetZombieClass(), TEAM_UNDEAD)
|
|
|
|
if classtab.Model then
|
|
pl:SetModel(classtab.Model)
|
|
elseif classtab.UsePlayerModel then
|
|
local desiredname = pl:GetInfo("cl_playermodel")
|
|
if #desiredname == 0 then
|
|
pl:SelectRandomPlayerModel()
|
|
else
|
|
pl:SetModel(player_manager.TranslatePlayerModel(desiredname))
|
|
end
|
|
elseif classtab.UsePreviousModel then
|
|
local curmodel = string.lower(pl:GetModel())
|
|
if table.HasValue(self.RestrictedModels, curmodel) or string.sub(curmodel, 1, 14) ~= "models/player/" then
|
|
pl:SelectRandomPlayerModel()
|
|
end
|
|
elseif classtab.UseRandomModel then
|
|
pl:SelectRandomPlayerModel()
|
|
else
|
|
pl:SetModel("models/player/zombie_classic.mdl")
|
|
end
|
|
|
|
local numundead = team.NumPlayers(TEAM_UNDEAD)
|
|
if self.OutnumberedHealthBonus <= numundead or classtab.Boss then
|
|
pl:SetHealth(classtab.Health)
|
|
else
|
|
pl:SetHealth(classtab.Health * 1.5)
|
|
end
|
|
|
|
if classtab.SWEP then
|
|
pl:Give(classtab.SWEP)
|
|
end
|
|
|
|
pl:SetNoTarget(true)
|
|
pl:SetMaxHealth(1)
|
|
|
|
pl:ResetSpeed()
|
|
pl:SetCrouchedWalkSpeed(classtab.CrouchedWalkSpeed or 0.70)
|
|
|
|
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
|
|
|
|
pl:CallZombieFunction("OnSpawned")
|
|
else
|
|
pl.m_PointQueue = 0
|
|
pl.PackedItems = {}
|
|
|
|
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.VoiceSet = VoiceSetTranslate[lowermodelname]
|
|
elseif string.find(lowermodelname, "female", 1, true) then
|
|
pl.VoiceSet = "female"
|
|
else
|
|
pl.VoiceSet = "male"
|
|
end
|
|
|
|
pl.HumanSpeedAdder = nil
|
|
|
|
pl.BonusDamageCheck = CurTime()
|
|
|
|
pl:ResetSpeed()
|
|
pl:SetJumpPower(DEFAULT_JUMP_POWER)
|
|
pl:SetCrouchedWalkSpeed(0.65)
|
|
|
|
pl:SetNoTarget(false)
|
|
pl:SetMaxHealth(100)
|
|
|
|
if self.ZombieEscape then
|
|
pl:Give("weapon_zs_zeknife")
|
|
pl:Give("weapon_zs_zegrenade")
|
|
pl:Give(table.Random(self.ZombieEscapeWeapons))
|
|
elseif 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
|
|
|
|
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
|
|
|
|
pl:DoMuscularBones()
|
|
pl:DoNoodleArmBones()
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
pl.m_PreHurtHealth = pl:Health()
|
|
end
|
|
|
|
function GM:SetWave(wave)
|
|
local previouslylocked = {}
|
|
for i, classtab in ipairs(GAMEMODE.ZombieClasses) do
|
|
if not gamemode.Call("IsClassUnlocked", classid) then
|
|
previouslylocked[i] = true
|
|
end
|
|
end
|
|
|
|
SetGlobalInt("wave", wave)
|
|
|
|
for classid in pairs(previouslylocked) do
|
|
if gamemode.Call("IsClassUnlocked", classid) then
|
|
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
|
|
end
|
|
end
|
|
end
|
|
|
|
GM.NextEscapeDamage = 0
|
|
function GM:WaveStateChanged(newstate)
|
|
if newstate then
|
|
if self:GetWave() == 0 then
|
|
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
|
|
|
|
if #humans >= 1 then
|
|
for _, pl in pairs(humans) do
|
|
gamemode.Call("GiveDefaultOrRandomEquipment", pl)
|
|
pl.BonusDamageCheck = CurTime()
|
|
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())
|
|
ent:Spawn()
|
|
ent:DropToFloor()
|
|
ent:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER) -- Just so no one gets stuck in it.
|
|
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(player.GetAll()) do
|
|
if pl:Team() == TEAM_UNDEAD then
|
|
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
|
|
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
|
|
self:SetEscapeStage(ESCAPESTAGE_DEATH)
|
|
|
|
PrintMessage(3, "Escape sequence death fog stage")
|
|
|
|
gamemode.Call("SetWaveEnd", -1)
|
|
elseif self:GetEscapeStage() == ESCAPESTAGE_ESCAPE then
|
|
self:SetEscapeStage(ESCAPESTAGE_BOSS)
|
|
|
|
-- 2 minutes to get out with everyone spawning as bosses.
|
|
gamemode.Call("SetWaveEnd", CurTime() + 120)
|
|
|
|
PrintMessage(3, "Escape sequence boss stage")
|
|
|
|
-- 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:Destroy()
|
|
end
|
|
|
|
--[[net.Start("zs_waveend")
|
|
net.WriteInt(self:GetWave(), 16)
|
|
net.WriteFloat(CurTime())
|
|
net.Broadcast()]]
|
|
PrintMessage(3, "Escape sequence started")
|
|
|
|
-- 2 minutes to escape.
|
|
gamemode.Call("SetWaveActive", true)
|
|
gamemode.Call("SetWaveEnd", CurTime() + 120)
|
|
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()
|
|
|
|
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
|
|
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
|
|
if pl:Alive() then
|
|
pl:SendLua("gamemode.Call(\"ToggleZombieVision\")")
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function GM:PlayerStepSoundTime(pl, iType, bWalking)
|
|
return 350
|
|
end
|
|
|
|
concommand.Add("zs_class", function(sender, command, arguments)
|
|
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 = table.concat(arguments, " ")
|
|
local classtab = GAMEMODE.ZombieClasses[classname]
|
|
if not classtab 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 sender:Alive() and not sender:GetZombieClassTable().Boss and gamemode.Call("CanPlayerSuicide", sender) then
|
|
sender:Kill()
|
|
end
|
|
end
|
|
end)
|