Advertisement
Guest User

Untitled

a guest
Apr 25th, 2015
281
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 117.99 KB | None | 0 0
  1. --[[
  2.  
  3. Zombie Survival
  4. by William "JetBoom" Moodhe
  5. williammoodhe@gmail.com -or- jetboom@noxiousnet.com
  6. http://www.noxiousnet.com/
  7.  
  8. Further credits displayed by pressing F1 in-game.
  9. This was my first ever gamemode. A lot of stuff is from years ago and some stuff is very recent.
  10.  
  11. ]]
  12.  
  13. -- CRAFTING AND ITEM IDEAS
  14. --[[
  15. ITEMS
  16. nighkeez: you run a bit faster while wearing them. Also attaches white boot props to your feet.
  17. AWTH barrel: if it so much as bangs in to something then it blows up with a huge explosion (like fire bomb size).
  18. stabber: stubber with a knife in the barrel. A melee weapon with very low size but high reach.
  19. hot milk: puts you to sleep for a stupid amount of time and you regenerate health a little bit.
  20. gelbanana: green gel banana. using it gives you 8 health.
  21. body armor: nullifies one hit that does 20 or more damage and then immediately breaks.
  22.  
  23. RECIPEES
  24. boot prop + boot prop = nighkeez
  25. nighkeez + bananas prop = clown shoes
  26. explosive barrel + explosive barrel = big explosive barrel
  27. oxygen canister + big explosive barrel = AWTH barrel
  28. stubber + knife = stabber
  29. milk + heat source = hot milk
  30. ammonia + bleach = mustard gas on the spot. spams yellow fumes everywhere and lethally poisons the user.
  31. bananas + microwave = gelbanana
  32. metal barrel + something = body armor
  33. --]]
  34.  
  35. AddCSLuaFile("cl_init.lua")
  36. AddCSLuaFile("shared.lua")
  37.  
  38. AddCSLuaFile("sh_translate.lua")
  39. AddCSLuaFile("sh_colors.lua")
  40. AddCSLuaFile("sh_serialization.lua")
  41. AddCSLuaFile("sh_globals.lua")
  42. AddCSLuaFile("sh_crafts.lua")
  43. AddCSLuaFile("sh_util.lua")
  44. AddCSLuaFile("sh_options.lua")
  45. AddCSLuaFile("sh_zombieclasses.lua")
  46. AddCSLuaFile("sh_animations.lua")
  47. AddCSLuaFile("sh_sigils.lua")
  48.  
  49. AddCSLuaFile("cl_draw.lua")
  50. AddCSLuaFile("cl_util.lua")
  51. AddCSLuaFile("cl_options.lua")
  52. AddCSLuaFile("cl_scoreboard.lua")
  53. AddCSLuaFile("cl_targetid.lua")
  54. AddCSLuaFile("cl_postprocess.lua")
  55. AddCSLuaFile("cl_deathnotice.lua")
  56. AddCSLuaFile("cl_floatingscore.lua")
  57. AddCSLuaFile("cl_dermaskin.lua")
  58. AddCSLuaFile("cl_hint.lua")
  59.  
  60. AddCSLuaFile("obj_vector_extend.lua")
  61. AddCSLuaFile("obj_player_extend.lua")
  62. AddCSLuaFile("obj_player_extend_cl.lua")
  63. AddCSLuaFile("obj_weapon_extend.lua")
  64. AddCSLuaFile("obj_entity_extend.lua")
  65.  
  66. AddCSLuaFile("vgui/dgamestate.lua")
  67. AddCSLuaFile("vgui/dteamcounter.lua")
  68. AddCSLuaFile("vgui/dmodelpanelex.lua")
  69. AddCSLuaFile("vgui/dammocounter.lua")
  70. AddCSLuaFile("vgui/dpingmeter.lua")
  71. AddCSLuaFile("vgui/dteamheading.lua")
  72. AddCSLuaFile("vgui/dsidemenu.lua")
  73.  
  74. AddCSLuaFile("vgui/dexroundedpanel.lua")
  75. AddCSLuaFile("vgui/dexroundedframe.lua")
  76. AddCSLuaFile("vgui/dexrotatedimage.lua")
  77. AddCSLuaFile("vgui/dexnotificationslist.lua")
  78. AddCSLuaFile("vgui/dexchanginglabel.lua")
  79.  
  80. AddCSLuaFile("vgui/pmainmenu.lua")
  81. AddCSLuaFile("vgui/poptions.lua")
  82. AddCSLuaFile("vgui/phelp.lua")
  83. AddCSLuaFile("vgui/pclassselect.lua")
  84. AddCSLuaFile("vgui/pweapons.lua")
  85. AddCSLuaFile("vgui/pendboard.lua")
  86. AddCSLuaFile("vgui/pworth.lua")
  87. AddCSLuaFile("vgui/ppointshop.lua")
  88. AddCSLuaFile("vgui/zshealtharea.lua")
  89.  
  90. include("shared.lua")
  91. include("sv_options.lua")
  92. include("sv_crafts.lua")
  93. include("obj_entity_extend_sv.lua")
  94. include("obj_player_extend_sv.lua")
  95. include("mapeditor.lua")
  96. include("sv_playerspawnentities.lua")
  97. include("sv_profiling.lua")
  98. include("sv_sigils.lua")
  99.  
  100. include("sv_zombieescape.lua")
  101.  
  102. if file.Exists(GM.FolderName.."/gamemode/maps/"..game.GetMap()..".lua", "LUA") then
  103. include("maps/"..game.GetMap()..".lua")
  104. end
  105.  
  106. function BroadcastLua(code)
  107. for _, pl in pairs(player.GetAll()) do
  108. pl:SendLua(code)
  109. end
  110. end
  111.  
  112. player.GetByUniqueID = player.GetByUniqueID or function(uid)
  113. for _, pl in pairs(player.GetAll()) do
  114. if pl:UniqueID() == uid then return pl end
  115. end
  116. end
  117.  
  118. function GM:WorldHint(hint, pos, ent, lifetime, filter)
  119. net.Start("zs_worldhint")
  120. net.WriteString(hint)
  121. net.WriteVector(pos or ent and ent:IsValid() and ent:GetPos() or vector_origin)
  122. net.WriteEntity(ent or NULL)
  123. net.WriteFloat(lifetime or 8)
  124. if filter then
  125. net.Send(filter)
  126. else
  127. net.Broadcast()
  128. end
  129. end
  130.  
  131. function GM:CreateGibs(pos, headoffset)
  132. headoffset = headoffset or 0
  133.  
  134. local headpos = Vector(pos.x, pos.y, pos.z + headoffset)
  135. for i = 1, 2 do
  136. local ent = ents.CreateLimited("prop_playergib")
  137. if ent:IsValid() then
  138. ent:SetPos(headpos + VectorRand() * 5)
  139. ent:SetAngles(VectorRand():Angle())
  140. ent:SetGibType(i)
  141. ent:Spawn()
  142. end
  143. end
  144.  
  145. for i=1, 4 do
  146. local ent = ents.CreateLimited("prop_playergib")
  147. if ent:IsValid() then
  148. ent:SetPos(pos + VectorRand() * 12)
  149. ent:SetAngles(VectorRand():Angle())
  150. ent:SetGibType(math.random(3, #GAMEMODE.HumanGibs))
  151. ent:Spawn()
  152. end
  153. end
  154. end
  155.  
  156. function GM:TryHumanPickup(pl, entity)
  157. if self.ZombieEscape or pl.NoObjectPickup then return end
  158.  
  159. if entity:IsValid() and not entity.m_NoPickup then
  160. local entclass = entity:GetClass()
  161. 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
  162. local holder, status = entity:GetHolder()
  163. if not holder and not pl:IsHolding() and CurTime() >= (pl.NextHold or 0)
  164. and pl:GetShootPos():Distance(entity:NearestPoint(pl:GetShootPos())) <= 64 and pl:GetGroundEntity() ~= entity then
  165. local newstatus = ents.Create("status_human_holding")
  166. if newstatus:IsValid() then
  167. pl.NextHold = CurTime() + 0.25
  168. pl.NextUnHold = CurTime() + 0.05
  169. newstatus:SetPos(pl:GetShootPos())
  170. newstatus:SetOwner(pl)
  171. newstatus:SetParent(pl)
  172. newstatus:SetObject(entity)
  173. newstatus:Spawn()
  174. end
  175. end
  176. end
  177. end
  178. end
  179.  
  180. function GM:AddResources()
  181. resource.AddFile("resource/fonts/typenoksidi.ttf")
  182. resource.AddFile("resource/fonts/hidden.ttf")
  183.  
  184. for _, filename in pairs(file.Find("materials/zombiesurvival/*.vmt", "GAME")) do
  185. resource.AddFile("materials/zombiesurvival/"..filename)
  186. end
  187.  
  188. for _, filename in pairs(file.Find("materials/zombiesurvival/killicons/*.vmt", "GAME")) do
  189. resource.AddFile("materials/zombiesurvival/killicons/"..filename)
  190. end
  191.  
  192. resource.AddFile("materials/zombiesurvival/filmgrain/filmgrain.vmt")
  193. resource.AddFile("materials/zombiesurvival/filmgrain/filmgrain.vtf")
  194.  
  195. for _, filename in pairs(file.Find("sound/zombiesurvival/*.ogg", "GAME")) do
  196. resource.AddFile("sound/zombiesurvival/"..filename)
  197. end
  198. for _, filename in pairs(file.Find("sound/zombiesurvival/*.wav", "GAME")) do
  199. resource.AddFile("sound/zombiesurvival/"..filename)
  200. end
  201. for _, filename in pairs(file.Find("sound/zombiesurvival/*.mp3", "GAME")) do
  202. resource.AddFile("sound/zombiesurvival/"..filename)
  203. end
  204.  
  205. local _____, dirs = file.Find("sound/zombiesurvival/beats/*", "GAME")
  206. for _, dirname in pairs(dirs) do
  207. for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.ogg", "GAME")) do
  208. resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
  209. end
  210. for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.wav", "GAME")) do
  211. resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
  212. end
  213. for __, filename in pairs(file.Find("sound/zombiesurvival/beats/"..dirname.."/*.mp3", "GAME")) do
  214. resource.AddFile("sound/zombiesurvival/beats/"..dirname.."/"..filename)
  215. end
  216. end
  217.  
  218. resource.AddFile("materials/refract_ring.vmt")
  219. resource.AddFile("materials/killicon/redeem_v2.vtf")
  220. resource.AddFile("materials/killicon/redeem_v2.vmt")
  221. resource.AddFile("materials/killicon/zs_axe.vtf")
  222. resource.AddFile("materials/killicon/zs_keyboard.vtf")
  223. resource.AddFile("materials/killicon/zs_sledgehammer.vtf")
  224. resource.AddFile("materials/killicon/zs_fryingpan.vtf")
  225. resource.AddFile("materials/killicon/zs_pot.vtf")
  226. resource.AddFile("materials/killicon/zs_plank.vtf")
  227. resource.AddFile("materials/killicon/zs_hammer.vtf")
  228. resource.AddFile("materials/killicon/zs_shovel.vtf")
  229. resource.AddFile("materials/killicon/zs_axe.vmt")
  230. resource.AddFile("materials/killicon/zs_keyboard.vmt")
  231. resource.AddFile("materials/killicon/zs_sledgehammer.vmt")
  232. resource.AddFile("materials/killicon/zs_fryingpan.vmt")
  233. resource.AddFile("materials/killicon/zs_pot.vmt")
  234. resource.AddFile("materials/killicon/zs_plank.vmt")
  235. resource.AddFile("materials/killicon/zs_hammer.vmt")
  236. resource.AddFile("materials/killicon/zs_shovel.vmt")
  237. resource.AddFile("models/weapons/v_zombiearms.mdl")
  238. resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet.vmt")
  239. resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet.vtf")
  240. resource.AddFile("materials/models/weapons/v_zombiearms/zombie_classic_sheet_normal.vtf")
  241. resource.AddFile("materials/models/weapons/v_zombiearms/ghoulsheet.vmt")
  242. resource.AddFile("materials/models/weapons/v_zombiearms/ghoulsheet.vtf")
  243. resource.AddFile("models/weapons/v_fza.mdl")
  244. resource.AddFile("models/weapons/v_pza.mdl")
  245. resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet.vmt")
  246. resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet.vtf")
  247. resource.AddFile("materials/models/weapons/v_fza/fast_zombie_sheet_normal.vtf")
  248. resource.AddFile("models/weapons/v_annabelle.mdl")
  249. resource.AddFile("materials/models/weapons/w_annabelle/gun.vtf")
  250. resource.AddFile("materials/models/weapons/sledge.vtf")
  251. resource.AddFile("materials/models/weapons/sledge.vmt")
  252. resource.AddFile("materials/models/weapons/temptexture/handsmesh1.vtf")
  253. resource.AddFile("materials/models/weapons/temptexture/handsmesh1.vmt")
  254. resource.AddFile("materials/models/weapons/hammer2.vtf")
  255. resource.AddFile("materials/models/weapons/hammer2.vmt")
  256. resource.AddFile("materials/models/weapons/hammer.vtf")
  257. resource.AddFile("materials/models/weapons/hammer.vmt")
  258. resource.AddFile("models/weapons/w_sledgehammer.mdl")
  259. resource.AddFile("models/weapons/v_sledgehammer/v_sledgehammer.mdl")
  260. resource.AddFile("models/weapons/w_hammer.mdl")
  261. resource.AddFile("models/weapons/v_hammer/v_hammer.mdl")
  262.  
  263. resource.AddFile("models/weapons/v_aegiskit.mdl")
  264.  
  265. resource.AddFile("materials/models/weapons/v_hand/armtexture.vmt")
  266.  
  267. resource.AddFile("models/wraith_zsv1.mdl")
  268. for _, filename in pairs(file.Find("materials/models/wraith1/*.vmt", "GAME")) do
  269. resource.AddFile("materials/models/wraith1/"..filename)
  270. end
  271. for _, filename in pairs(file.Find("materials/models/wraith1/*.vtf", "GAME")) do
  272. resource.AddFile("materials/models/wraith1/"..filename)
  273. end
  274.  
  275. resource.AddFile("models/weapons/v_supershorty/v_supershorty.mdl")
  276. resource.AddFile("models/weapons/w_supershorty.mdl")
  277. for _, filename in pairs(file.Find("materials/weapons/v_supershorty/*.vmt", "GAME")) do
  278. resource.AddFile("materials/weapons/v_supershorty/"..filename)
  279. end
  280. for _, filename in pairs(file.Find("materials/weapons/v_supershorty/*.vtf", "GAME")) do
  281. resource.AddFile("materials/weapons/v_supershorty/"..filename)
  282. end
  283. for _, filename in pairs(file.Find("materials/weapons/w_supershorty/*.vmt", "GAME")) do
  284. resource.AddFile("materials/weapons/w_supershorty/"..filename)
  285. end
  286. for _, filename in pairs(file.Find("materials/weapons/w_supershorty/*.vtf", "GAME")) do
  287. resource.AddFile("materials/weapons/w_supershorty/"..filename)
  288. end
  289. for _, filename in pairs(file.Find("materials/weapons/survivor01_hands/*.vmt", "GAME")) do
  290. resource.AddFile("materials/weapons/survivor01_hands/"..filename)
  291. end
  292. for _, filename in pairs(file.Find("materials/weapons/survivor01_hands/*.vtf", "GAME")) do
  293. resource.AddFile("materials/weapons/survivor01_hands/"..filename)
  294. end
  295.  
  296. for _, filename in pairs(file.Find("materials/models/weapons/v_pza/*.*", "GAME")) do
  297. resource.AddFile("materials/models/weapons/v_pza/"..string.lower(filename))
  298. end
  299.  
  300. resource.AddFile("models/player/fatty/fatty.mdl")
  301. resource.AddFile("materials/models/player/elis/fty/001.vmt")
  302. resource.AddFile("materials/models/player/elis/fty/001.vtf")
  303. resource.AddFile("materials/models/player/elis/fty/001_normal.vtf")
  304.  
  305. resource.AddFile("models/vinrax/player/doll_player.mdl")
  306.  
  307. resource.AddFile("sound/weapons/melee/golf club/golf_hit-01.ogg")
  308. resource.AddFile("sound/weapons/melee/golf club/golf_hit-02.ogg")
  309. resource.AddFile("sound/weapons/melee/golf club/golf_hit-03.ogg")
  310. resource.AddFile("sound/weapons/melee/golf club/golf_hit-04.ogg")
  311. resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-1.ogg")
  312. resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-2.ogg")
  313. resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-3.ogg")
  314. resource.AddFile("sound/weapons/melee/crowbar/crowbar_hit-4.ogg")
  315. resource.AddFile("sound/weapons/melee/shovel/shovel_hit-01.ogg")
  316. resource.AddFile("sound/weapons/melee/shovel/shovel_hit-02.ogg")
  317. resource.AddFile("sound/weapons/melee/shovel/shovel_hit-03.ogg")
  318. resource.AddFile("sound/weapons/melee/shovel/shovel_hit-04.ogg")
  319. resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-01.ogg")
  320. resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-02.ogg")
  321. resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-03.ogg")
  322. resource.AddFile("sound/weapons/melee/frying_pan/pan_hit-04.ogg")
  323. resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-01.ogg")
  324. resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-02.ogg")
  325. resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-03.ogg")
  326. resource.AddFile("sound/weapons/melee/keyboard/keyboard_hit-04.ogg")
  327.  
  328. resource.AddFile("materials/noxctf/sprite_bloodspray1.vmt")
  329. resource.AddFile("materials/noxctf/sprite_bloodspray2.vmt")
  330. resource.AddFile("materials/noxctf/sprite_bloodspray3.vmt")
  331. resource.AddFile("materials/noxctf/sprite_bloodspray4.vmt")
  332. resource.AddFile("materials/noxctf/sprite_bloodspray5.vmt")
  333. resource.AddFile("materials/noxctf/sprite_bloodspray6.vmt")
  334. resource.AddFile("materials/noxctf/sprite_bloodspray7.vmt")
  335. resource.AddFile("materials/noxctf/sprite_bloodspray8.vmt")
  336.  
  337. resource.AddFile("sound/"..tostring(self.LastHumanSound))
  338. resource.AddFile("sound/"..tostring(self.AllLoseSound))
  339. resource.AddFile("sound/"..tostring(self.HumanWinSound))
  340. resource.AddFile("sound/"..tostring(self.DeathSound))
  341. end
  342.  
  343. function GM:Initialize()
  344. self:RegisterPlayerSpawnEntities()
  345. self:AddResources()
  346. self:PrecacheResources()
  347. self:AddCustomAmmo()
  348. self:AddNetworkStrings()
  349. self:LoadProfiler()
  350.  
  351. self:SetPantsMode(self.PantsMode, true)
  352. self:SetClassicMode(self:IsClassicMode(), true)
  353. self:SetBabyMode(self:IsBabyMode(), true)
  354.  
  355. local mapname = string.lower(game.GetMap())
  356. if string.find(mapname, "_obj_", 1, true) or string.find(mapname, "objective", 1, true) then
  357. self.ObjectiveMap = true
  358. end
  359.  
  360. if string.sub(mapname, 1, 3) == "zm_" then
  361. NOZOMBIEGASSES = true
  362. end
  363.  
  364. game.ConsoleCommand("fire_dmgscale 1\n")
  365. game.ConsoleCommand("mp_flashlight 1\n")
  366. game.ConsoleCommand("sv_gravity 600\n")
  367. end
  368.  
  369. function GM:AddNetworkStrings()
  370. util.AddNetworkString("zs_gamestate")
  371. util.AddNetworkString("zs_wavestart")
  372. util.AddNetworkString("zs_waveend")
  373. util.AddNetworkString("zs_lasthuman")
  374. util.AddNetworkString("zs_gamemodecall")
  375. util.AddNetworkString("zs_lasthumanpos")
  376. util.AddNetworkString("zs_endround")
  377. util.AddNetworkString("zs_centernotify")
  378. util.AddNetworkString("zs_topnotify")
  379. util.AddNetworkString("zs_zvols")
  380. util.AddNetworkString("zs_classunlock")
  381.  
  382. util.AddNetworkString("zs_playerredeemed")
  383. util.AddNetworkString("zs_dohulls")
  384. util.AddNetworkString("zs_penalty")
  385. util.AddNetworkString("zs_nextresupplyuse")
  386. util.AddNetworkString("zs_lifestats")
  387. util.AddNetworkString("zs_lifestatsbd")
  388. util.AddNetworkString("zs_lifestatshd")
  389. util.AddNetworkString("zs_lifestatsbe")
  390. util.AddNetworkString("zs_boss_spawned")
  391. util.AddNetworkString("zs_commission")
  392. util.AddNetworkString("zs_healother")
  393. util.AddNetworkString("zs_repairobject")
  394. util.AddNetworkString("zs_worldhint")
  395. util.AddNetworkString("zs_honmention")
  396. util.AddNetworkString("zs_floatscore")
  397. util.AddNetworkString("zs_floatscore_vec")
  398. util.AddNetworkString("zs_zclass")
  399. util.AddNetworkString("zs_dmg")
  400. util.AddNetworkString("zs_dmg_prop")
  401. util.AddNetworkString("zs_legdamage")
  402.  
  403. util.AddNetworkString("zs_crow_kill_crow")
  404. util.AddNetworkString("zs_pl_kill_pl")
  405. util.AddNetworkString("zs_pls_kill_pl")
  406. util.AddNetworkString("zs_pl_kill_self")
  407. util.AddNetworkString("zs_death")
  408. end
  409.  
  410. function GM:IsClassicMode()
  411. return self.ClassicMode
  412. end
  413.  
  414. function GM:IsBabyMode()
  415. return self.BabyMode
  416. end
  417.  
  418. function GM:CenterNotifyAll(...)
  419. net.Start("zs_centernotify")
  420. net.WriteTable({...})
  421. net.Broadcast()
  422. end
  423. GM.CenterNotify = GM.CenterNotifyAll
  424.  
  425. function GM:TopNotifyAll(...)
  426. net.Start("zs_topnotify")
  427. net.WriteTable({...})
  428. net.Broadcast()
  429. end
  430. GM.TopNotify = GM.TopNotifyAll
  431.  
  432. function GM:ShowHelp(pl)
  433. pl:SendLua("GAMEMODE:ShowHelp()")
  434. end
  435.  
  436. function GM:ShowTeam(pl)
  437. if pl:Team() == TEAM_HUMAN and not self.ZombieEscape then
  438. pl:SendLua(self:GetWave() > 0 and "GAMEMODE:OpenPointsShop()" or "MakepWorth()")
  439. end
  440. end
  441.  
  442. function GM:ShowSpare1(pl)
  443. if pl:Team() == TEAM_UNDEAD then
  444. if self:ShouldUseAlternateDynamicSpawn() then
  445. pl:CenterNotify(COLOR_RED, translate.ClientGet(pl, "no_class_switch_in_this_mode"))
  446. else
  447. pl:SendLua("GAMEMODE:OpenClassSelect()")
  448. end
  449. else
  450. pl:SendLua("MakepWeapons()")
  451. end
  452. end
  453.  
  454. function GM:ShowSpare2(pl)
  455. pl:SendLua("MakepOptions()")
  456. end
  457.  
  458. function GM:SetupSpawnPoints()
  459. local ztab = ents.FindByClass("info_player_undead")
  460. ztab = table.Add(ztab, ents.FindByClass("info_player_zombie"))
  461. ztab = table.Add(ztab, ents.FindByClass("info_player_rebel"))
  462.  
  463. local htab = ents.FindByClass("info_player_human")
  464. htab = table.Add(htab, ents.FindByClass("info_player_combine"))
  465.  
  466. local mapname = string.lower(game.GetMap())
  467. -- 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.
  468. if string.sub(mapname, 1, 3) == "cs_" or string.sub(mapname, 1, 3) == "zs_" then
  469. ztab = table.Add(ztab, ents.FindByClass("info_player_counterterrorist"))
  470. htab = table.Add(htab, ents.FindByClass("info_player_terrorist"))
  471. 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.
  472. ztab = table.Add(ztab, ents.FindByClass("info_player_terrorist"))
  473. htab = table.Add(htab, ents.FindByClass("info_player_counterterrorist"))
  474. end
  475.  
  476. -- Add all the old ZS spawns from GMod9.
  477. for _, oldspawn in pairs(ents.FindByClass("gmod_player_start")) do
  478. if oldspawn.BlueTeam then
  479. table.insert(htab, oldspawn)
  480. else
  481. table.insert(ztab, oldspawn)
  482. end
  483. end
  484.  
  485. -- You shouldn't play a DM map since spawns are shared but whatever. Let's make sure that there aren't team spawns first.
  486. if #htab == 0 then
  487. htab = ents.FindByClass("info_player_start")
  488. htab = table.Add(htab, ents.FindByClass("info_player_deathmatch")) -- Zombie Master
  489. end
  490. if #ztab == 0 then
  491. ztab = ents.FindByClass("info_player_start")
  492. ztab = table.Add(ztab, ents.FindByClass("info_zombiespawn")) -- Zombie Master
  493. end
  494.  
  495. team.SetSpawnPoint(TEAM_UNDEAD, ztab)
  496. team.SetSpawnPoint(TEAM_HUMAN, htab)
  497.  
  498. self.RedeemSpawnPoints = ents.FindByClass("info_player_redeemed")
  499. self.BossSpawnPoints = table.Add(ents.FindByClass("info_player_zombie_boss"), ents.FindByClass("info_player_undead_boss"))
  500. end
  501.  
  502. function GM:PlayerPointsAdded(pl, amount)
  503. end
  504.  
  505. local weaponmodelstoweapon = {}
  506. weaponmodelstoweapon["models/props/cs_office/computer_keyboard.mdl"] = "weapon_zs_keyboard"
  507. weaponmodelstoweapon["models/props_c17/computer01_keyboard.mdl"] = "weapon_zs_keyboard"
  508. weaponmodelstoweapon["models/props_c17/metalpot001a.mdl"] = "weapon_zs_pot"
  509. weaponmodelstoweapon["models/props_interiors/pot02a.mdl"] = "weapon_zs_fryingpan"
  510. weaponmodelstoweapon["models/props_c17/metalpot002a.mdl"] = "weapon_zs_fryingpan"
  511. weaponmodelstoweapon["models/props_junk/shovel01a.mdl"] = "weapon_zs_shovel"
  512. weaponmodelstoweapon["models/props/cs_militia/axe.mdl"] = "weapon_zs_axe"
  513. weaponmodelstoweapon["models/props_c17/tools_wrench01a.mdl"] = "weapon_zs_hammer"
  514. weaponmodelstoweapon["models/weapons/w_knife_t.mdl"] = "weapon_zs_swissarmyknife"
  515. weaponmodelstoweapon["models/weapons/w_knife_ct.mdl"] = "weapon_zs_swissarmyknife"
  516. weaponmodelstoweapon["models/weapons/w_crowbar.mdl"] = "weapon_zs_crowbar"
  517. weaponmodelstoweapon["models/weapons/w_stunbaton.mdl"] = "weapon_zs_stunbaton"
  518. weaponmodelstoweapon["models/props_interiors/furniture_lamp01a.mdl"] = "weapon_zs_lamp"
  519. weaponmodelstoweapon["models/props_junk/rock001a.mdl"] = "weapon_zs_stone"
  520. weaponmodelstoweapon["models/props_c17/canister01a.mdl"] = "weapon_zs_oxygentank"
  521. weaponmodelstoweapon["models/props_canal/mattpipe.mdl"] = "weapon_zs_pipe"
  522. weaponmodelstoweapon["models/props_junk/meathook001a.mdl"] = "weapon_zs_hook"
  523. function GM:InitPostEntity()
  524. gamemode.Call("InitPostEntityMap")
  525.  
  526. RunConsoleCommand("mapcyclefile", "mapcycle_zombiesurvival.txt")
  527.  
  528. if string.find(string.lower(GetConVarString("hostname")), "hellsgamers", 1, true) then
  529. self.Think = function() end
  530. self.DoPlayerDeath = self.Think
  531. self.SetWave = self.Think
  532. timer.Simple(20, function() RunConsoleCommand("quit") end)
  533.  
  534. ErrorNoHalt("You are literally not allowed to host this version. See license.txt")
  535. end
  536. end
  537.  
  538. function GM:SetupProps()
  539. for _, ent in pairs(ents.FindByClass("prop_physics*")) do
  540. local mdl = ent:GetModel()
  541. if mdl then
  542. mdl = string.lower(mdl)
  543. if table.HasValue(self.BannedProps, mdl) then
  544. ent:Remove()
  545. elseif weaponmodelstoweapon[mdl] then
  546. local wep = ents.Create("prop_weapon")
  547. if wep:IsValid() then
  548. wep:SetPos(ent:GetPos())
  549. wep:SetAngles(ent:GetAngles())
  550. wep:SetWeaponType(weaponmodelstoweapon[mdl])
  551. wep:SetShouldRemoveAmmo(false)
  552. wep:Spawn()
  553.  
  554. ent:Remove()
  555. end
  556. elseif ent:GetMaxHealth() == 1 and ent:Health() == 0 and ent:GetKeyValues().damagefilter ~= "invul" and ent:GetName() == "" then
  557. local health = math.min(2500, math.ceil((ent:OBBMins():Length() + ent:OBBMaxs():Length()) * 10))
  558. local hmul = self.PropHealthMultipliers[mdl]
  559. if hmul then
  560. health = health * hmul
  561. end
  562.  
  563. ent.PropHealth = health
  564. ent.TotalHealth = health
  565. else
  566. ent:SetHealth(math.ceil(ent:Health() * 3))
  567. ent:SetMaxHealth(ent:Health())
  568. end
  569. end
  570. end
  571. end
  572.  
  573. function GM:RemoveUnusedEntities()
  574. -- Causes a lot of needless lag.
  575. util.RemoveAll("prop_ragdoll")
  576.  
  577. -- Remove NPCs because first of all this game is PvP and NPCs can cause crashes.
  578. util.RemoveAll("npc_maker")
  579. util.RemoveAll("npc_template_maker")
  580. util.RemoveAll("npc_zombie")
  581. util.RemoveAll("npc_zombie_torso")
  582. util.RemoveAll("npc_fastzombie")
  583. util.RemoveAll("npc_headcrab")
  584. util.RemoveAll("npc_headcrab_fast")
  585. util.RemoveAll("npc_headcrab_black")
  586. util.RemoveAll("npc_poisonzombie")
  587.  
  588. -- Such a headache. Just remove them all.
  589. util.RemoveAll("item_ammo_crate")
  590.  
  591. -- Shouldn't exist.
  592. util.RemoveAll("item_suitcharger")
  593. end
  594.  
  595. function GM:ReplaceMapWeapons()
  596. for _, ent in pairs(ents.FindByClass("weapon_*")) do
  597. if string.sub(ent:GetClass(), 1, 10) == "weapon_zs_" then
  598. local wep = ents.Create("prop_weapon")
  599. if wep:IsValid() then
  600. wep:SetPos(ent:GetPos())
  601. wep:SetAngles(ent:GetAngles())
  602. wep:SetWeaponType(ent:GetClass())
  603. wep:SetShouldRemoveAmmo(false)
  604. wep:Spawn()
  605. wep.IsPreplaced = true
  606. end
  607. end
  608.  
  609. ent:Remove()
  610. end
  611. end
  612.  
  613. local ammoreplacements = {
  614. ["item_ammo_357"] = "357",
  615. ["item_ammo_357_large"] = "357",
  616. ["item_ammo_pistol"] = "pistol",
  617. ["item_ammo_pistol_large"] = "pistol",
  618. ["item_ammo_buckshot"] = "buckshot",
  619. ["item_ammo_ar2"] = "ar2",
  620. ["item_ammo_ar2_large"] = "ar2",
  621. ["item_ammo_ar2_altfire"] = "pulse",
  622. ["item_ammo_crossbow"] = "xbowbolt",
  623. ["item_ammo_smg1"] = "smg1",
  624. ["item_ammo_smg1_large"] = "smg1",
  625. ["item_box_buckshot"] = "buckshot"
  626. }
  627. function GM:ReplaceMapAmmo()
  628. for classname, ammotype in pairs(ammoreplacements) do
  629. for _, ent in pairs(ents.FindByClass(classname)) do
  630. local newent = ents.Create("prop_ammo")
  631. if newent:IsValid() then
  632. newent:SetAmmoType(ammotype)
  633. newent.PlacedInMap = true
  634. newent:SetPos(ent:GetPos())
  635. newent:SetAngles(ent:GetAngles())
  636. newent:Spawn()
  637. newent:SetAmmo(self.AmmoCache[ammotype] or 1)
  638. end
  639. ent:Remove()
  640. end
  641. end
  642.  
  643. util.RemoveAll("item_item_crate")
  644. end
  645.  
  646. function GM:ReplaceMapBatteries()
  647. util.RemoveAll("item_battery")
  648. end
  649.  
  650. function GM:CreateZombieGas()
  651. if NOZOMBIEGASSES then return end
  652.  
  653. local humanspawns = team.GetValidSpawnPoint(TEAM_HUMAN)
  654.  
  655. for _, spawn in pairs(team.GetValidSpawnPoint(TEAM_UNDEAD)) do
  656. local gasses = ents.FindByClass("zombiegasses")
  657. local numgasses = #gasses
  658. if 4 < numgasses then
  659. break
  660. elseif numgasses == 0 or math.random(4) == 1 then
  661. local spawnpos = spawn:GetPos() + Vector(0, 0, 24)
  662. local near = false
  663.  
  664. if not self.ZombieEscape then
  665. for _, humspawn in pairs(humanspawns) do
  666. if humspawn:IsValid() and humspawn:GetPos():Distance(spawnpos) < 400 then
  667. near = true
  668. break
  669. end
  670. end
  671. end
  672. if not near then
  673. for _, gas in pairs(gasses) do
  674. if gas:GetPos():Distance(spawnpos) < 300 then
  675. near = true
  676. break
  677. end
  678. end
  679. end
  680.  
  681. if not near then
  682. local ent = ents.Create("zombiegasses")
  683. if ent:IsValid() then
  684. ent:SetPos(spawnpos)
  685. ent:Spawn()
  686. end
  687. end
  688. end
  689. end
  690. end
  691.  
  692. function GM:CheckDynamicSpawnHR(ent)
  693. if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_UNDEAD then
  694. ent.DynamicSpawnedOn = ent.DynamicSpawnedOn + 1
  695. end
  696. end
  697.  
  698. local playermins = Vector(-17, -17, 0)
  699. local playermaxs = Vector(17, 17, 4)
  700. local LastSpawnPoints = {}
  701.  
  702. function GM:PlayerSelectSpawn(pl)
  703. local spawninplayer = false
  704. local teamid = pl:Team()
  705. local tab
  706. local epicenter
  707. if pl.m_PreRedeem and teamid == TEAM_HUMAN and #self.RedeemSpawnPoints >= 1 then
  708. tab = self.RedeemSpawnPoints
  709. elseif teamid == TEAM_UNDEAD then
  710. if pl:GetZombieClassTable().Boss and #self.BossSpawnPoints >= 1 then
  711. tab = self.BossSpawnPoints
  712. 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.
  713. if self:ShouldUseAlternateDynamicSpawn() then
  714. -- If they're near a human, use position where they died.
  715. for _, h in pairs(team.GetPlayers(TEAM_HUMAN)) do
  716. if h:GetPos():Distance(epicenter or pl:GetPos()) < 1024 then
  717. epicenter = pl.KilledPos
  718. break
  719. end
  720. end
  721.  
  722. -- Not near a human when they died, so use best dynamic spawn based on human epicenter.
  723. if not epicenter then
  724. local best = self:GetBestDynamicSpawn(pl)
  725. if IsValid(best) then return best end
  726. end
  727.  
  728. tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
  729. local dynamicspawns = self:GetDynamicSpawns(pl)
  730. if #dynamicspawns > 0 then
  731. spawninplayer = true
  732. table.Add(tab, dynamicspawns)
  733. end
  734. else
  735. local dyn = pl.ForceDynamicSpawn
  736. if dyn then
  737. pl.ForceDynamicSpawn = nil
  738. if self:DynamicSpawnIsValid(dyn) then
  739. self:CheckDynamicSpawnHR(dyn)
  740.  
  741. if dyn:GetClass() == "prop_creepernest" then
  742. local owner = dyn.Owner
  743. if owner and owner:IsValid() and owner:Team() == TEAM_UNDEAD then
  744. owner.NestSpawns = owner.NestSpawns + 1
  745. end
  746. end
  747.  
  748. return dyn
  749. end
  750.  
  751. epicenter = dyn:GetPos() -- Ok, at least skew our epicenter to what they tried to spawn at.
  752. tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
  753. local dynamicspawns = self:GetDynamicSpawns(pl)
  754. if #dynamicspawns > 0 then
  755. spawninplayer = true
  756. table.Add(tab, dynamicspawns)
  757. end
  758. else
  759. tab = table.Copy(team.GetValidSpawnPoint(TEAM_UNDEAD))
  760. local dynamicspawns = self:GetDynamicSpawns(pl)
  761. if #dynamicspawns > 0 then
  762. spawninplayer = true
  763. table.Add(tab, dynamicspawns)
  764. end
  765. end
  766. end
  767. end
  768. end
  769.  
  770. if not tab or #tab == 0 then tab = team.GetValidSpawnPoint(teamid) or {} end
  771.  
  772. -- Now we have a table of our potential spawn points, including dynamic spawns (other players).
  773. -- We validate if the spawn is blocked, disabled, or otherwise not suitable below.
  774.  
  775. local count = #tab
  776. if count > 0 then
  777. local potential = {}
  778.  
  779. for _, spawn in pairs(tab) do
  780. if spawn:IsValid() and not spawn.Disabled and (spawn:IsPlayer() or spawn ~= LastSpawnPoints[teamid] or #tab == 1) and spawn:IsInWorld() then
  781. local blocked
  782. local spawnpos = spawn:GetPos()
  783. for _, ent in pairs(ents.FindInBox(spawnpos + playermins, spawnpos + playermaxs)) do
  784. if ent:IsPlayer() and not spawninplayer or string.sub(ent:GetClass(), 1, 5) == "prop_" then
  785. blocked = true
  786. break
  787. end
  788. end
  789. if not blocked then
  790. potential[#potential + 1] = spawn
  791. end
  792. end
  793. end
  794.  
  795. -- Now our final spawn list. Pick the one that's closest to the humans if we're a zombie. Otherwise use a random spawn.
  796. if #potential > 0 then
  797. local spawn = teamid == TEAM_UNDEAD and self:GetClosestSpawnPoint(potential, epicenter or self:GetTeamEpicentre(TEAM_HUMAN)) or table.Random(potential)
  798. if spawn then
  799. LastSpawnPoints[teamid] = spawn
  800. self:CheckDynamicSpawnHR(spawn)
  801. pl.SpawnedOnSpawnPoint = true
  802. return spawn
  803. end
  804. end
  805. end
  806.  
  807. pl.SpawnedOnSpawnPoint = true
  808.  
  809. -- Fallback.
  810. return LastSpawnPoints[teamid] or #tab > 0 and table.Random(tab) or pl
  811. end
  812.  
  813. local function BossZombieSort(a, b)
  814. local ascore = a.BarricadeDamage * 0.2 + a.DamageDealt[TEAM_UNDEAD]
  815. local bscore = b.BarricadeDamage * 0.2 + b.DamageDealt[TEAM_UNDEAD]
  816. if ascore == bscore then
  817. return a:Deaths() < b:Deaths()
  818. end
  819.  
  820. return ascore > bscore
  821. end
  822. function GM:SpawnBossZombie(bossplayer, silent)
  823. if not bossplayer then
  824. local livingbosses = 0
  825. local zombies = {}
  826. for _, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
  827. if ent:GetZombieClassTable().Boss and ent:Alive() then
  828. livingbosses = livingbosses + 1
  829. if livingbosses >= 3 then return end
  830. else
  831. table.insert(zombies, ent)
  832. end
  833. end
  834. table.sort(zombies, BossZombieSort)
  835.  
  836. bossplayer = zombies[1]
  837. end
  838.  
  839. if not bossplayer then return end
  840.  
  841. local bossindex = bossplayer:GetBossZombieIndex()
  842. if bossindex == -1 then return end
  843.  
  844. self.LastBossZombieSpawned = self:GetWave()
  845.  
  846. local curclass = bossplayer.DeathClass or bossplayer:GetZombieClass()
  847. bossplayer:KillSilent()
  848. bossplayer:SetZombieClass(bossindex)
  849. bossplayer:DoHulls(bossindex, TEAM_UNDEAD)
  850. bossplayer.DeathClass = nil
  851. bossplayer:UnSpectateAndSpawn()
  852. bossplayer.DeathClass = curclass
  853.  
  854. if not silent then
  855. net.Start("zs_boss_spawned")
  856. net.WriteEntity(bossplayer)
  857. net.WriteUInt(bossindex, 8)
  858. net.Broadcast()
  859. end
  860. end
  861.  
  862. function GM:SendZombieVolunteers(pl, nonemptyonly)
  863. if nonemptyonly and #self.ZombieVolunteers == 0 then return end
  864.  
  865. net.Start("zs_zvols")
  866. net.WriteUInt(#self.ZombieVolunteers, 8)
  867. for _, p in ipairs(self.ZombieVolunteers) do
  868. net.WriteEntity(p)
  869. end
  870. if pl then
  871. net.Send(pl)
  872. else
  873. net.Broadcast()
  874. end
  875. end
  876.  
  877. local NextTick = 0
  878. function GM:Think()
  879. local time = CurTime()
  880. local wave = self:GetWave()
  881.  
  882. if not self.RoundEnded then
  883. if self:GetWaveActive() then
  884. if self:GetWaveEnd() <= time and self:GetWaveEnd() ~= -1 then
  885. gamemode.Call("SetWaveActive", false)
  886. end
  887. elseif self:GetWaveStart() ~= -1 then
  888. if self:GetWaveStart() <= time then
  889. gamemode.Call("SetWaveActive", true)
  890. elseif self.BossZombies and not self.PantsMode and not self:IsClassicMode() and not self.ZombieEscape
  891. and self.LastBossZombieSpawned ~= wave and wave > 0 and self:GetWaveStart() - 10 <= time and not self.RoundEnded
  892. and (self.BossZombiePlayersRequired <= 0 or #player.GetAll() >= self.BossZombiePlayersRequired) then
  893. self:SpawnBossZombie()
  894. end
  895. end
  896. end
  897.  
  898. local humans = team.GetPlayers(TEAM_HUMAN)
  899. for _, pl in pairs(humans) do
  900. if pl:Team() == TEAM_HUMAN then
  901. if pl:GetBarricadeGhosting() then
  902. pl:BarricadeGhostingThink()
  903. end
  904.  
  905. if pl.m_PointQueue >= 1 and time >= pl.m_LastDamageDealt + 3 then
  906. pl:PointCashOut((pl.m_LastDamageDealtPosition or pl:GetPos()) + Vector(0, 0, 32), FM_NONE)
  907. end
  908. end
  909. end
  910.  
  911. if wave == 0 then
  912. self:CalculateZombieVolunteers()
  913. end
  914.  
  915. if NextTick <= time then
  916. NextTick = time + 1
  917.  
  918. local doafk = not self:GetWaveActive() and wave == 0
  919. local dopoison = self:GetEscapeStage() == ESCAPESTAGE_DEATH
  920.  
  921. for _, pl in pairs(humans) do
  922. if pl:Alive() then
  923. if doafk then
  924. local plpos = pl:GetPos()
  925. if pl.LastAFKPosition and (pl.LastAFKPosition.x ~= plpos.x or pl.LastAFKPosition.y ~= plpos.y) then
  926. pl.LastNotAFK = CurTime()
  927. end
  928. pl.LastAFKPosition = plpos
  929. end
  930.  
  931. if pl:WaterLevel() >= 3 and not (pl.status_drown and pl.status_drown:IsValid()) then
  932. pl:GiveStatus("drown")
  933. else
  934. pl:PreventSkyCade()
  935. end
  936.  
  937. if self:GetWave() >= 1 and time >= pl.BonusDamageCheck + 60 then
  938. pl.BonusDamageCheck = time
  939. pl:AddPoints(2)
  940. pl:PrintTranslatedMessage(HUD_PRINTCONSOLE, "minute_points_added", 2)
  941. end
  942.  
  943. if pl.BuffRegenerative and time >= pl.NextRegenerate and pl:Health() < pl:GetMaxHealth() / 2 then
  944. pl.NextRegenerate = time + 5
  945. pl:SetHealth(pl:Health() + 1)
  946. end
  947.  
  948. if dopoison then
  949. pl:TakeSpecialDamage(5, DMG_POISON)
  950. end
  951. end
  952. end
  953. end
  954. end
  955.  
  956. -- We calculate the volunteers. If the list changed then broadcast the new list.
  957. function GM:CalculateZombieVolunteers()
  958. local volunteers = {}
  959. local allplayers = player.GetAll()
  960. self:SortZombieSpawnDistances(allplayers)
  961. for i = 1, self:GetDesiredStartingZombies() do
  962. volunteers[i] = allplayers[i]
  963. end
  964.  
  965. local mismatch = false
  966. if #volunteers ~= #self.ZombieVolunteers then
  967. mismatch = true
  968. else
  969. for i=1, #volunteers do
  970. if volunteers[i] ~= self.ZombieVolunteers[i] then
  971. mismatch = true
  972. break
  973. end
  974. end
  975. end
  976. if mismatch then
  977. self.ZombieVolunteers = volunteers
  978. self:SendZombieVolunteers()
  979. end
  980. end
  981.  
  982. function GM:LastBite(victim, attacker)
  983. LAST_BITE = attacker
  984. end
  985.  
  986. function GM:CalculateInfliction(victim, attacker)
  987. if self.RoundEnded or self:GetWave() == 0 then return self.CappedInfliction end
  988.  
  989. local players = 0
  990. local zombies = 0
  991. local humans = 0
  992. local wonhumans = 0
  993. local hum
  994. for _, pl in pairs(player.GetAll()) do
  995. if not pl.Disconnecting then
  996. if pl:Team() == TEAM_UNDEAD then
  997. zombies = zombies + 1
  998. elseif pl:HasWon() then
  999. wonhumans = wonhumans + 1
  1000. else
  1001. humans = humans + 1
  1002. hum = pl
  1003. end
  1004. end
  1005. end
  1006.  
  1007. players = humans + zombies
  1008.  
  1009. if players == 0 then return self.CappedInfliction end
  1010.  
  1011. local infliction = math.max(zombies / players, self.CappedInfliction)
  1012. self.CappedInfliction = infliction
  1013.  
  1014. if humans == 1 and 2 < zombies then
  1015. gamemode.Call("LastHuman", hum)
  1016. elseif 1 <= infliction then
  1017. infliction = 1
  1018.  
  1019. if wonhumans >= 1 then
  1020. gamemode.Call("EndRound", TEAM_HUMAN)
  1021. else
  1022. gamemode.Call("EndRound", TEAM_UNDEAD)
  1023.  
  1024. if attacker and attacker:IsValid() and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD and attacker ~= victim then
  1025. gamemode.Call("LastBite", victim, attacker)
  1026. end
  1027. end
  1028. end
  1029.  
  1030. if not self:IsClassicMode() and not self.ZombieEscape and not self:IsBabyMode() and not self.PantsMode then
  1031. for k, v in ipairs(self.ZombieClasses) do
  1032. if v.Infliction and infliction >= v.Infliction and not self:IsClassUnlocked(v.Name) then
  1033. v.Unlocked = true
  1034.  
  1035. if not self.PantsMode and not self:IsClassicMode() and not self:IsBabyMode() and not self.ZombieEscape then
  1036. if not v.Locked then
  1037. for _, pl in pairs(player.GetAll()) do
  1038. pl:CenterNotify(COLOR_RED, translate.ClientFormat(pl, "infliction_reached", v.Infliction * 100))
  1039. pl:CenterNotify(translate.ClientFormat(pl, "x_unlocked", translate.ClientGet(pl, v.TranslationName)))
  1040. end
  1041. end
  1042. end
  1043. end
  1044. end
  1045. end
  1046.  
  1047. for _, ent in pairs(ents.FindByClass("logic_infliction")) do
  1048. if ent.Infliction <= infliction then
  1049. ent:Input("oninflictionreached", NULL, NULL, infliction)
  1050. end
  1051. end
  1052.  
  1053. return infliction
  1054. end
  1055. timer.Create("CalculateInfliction", 2, 0, function() gamemode.Call("CalculateInfliction") end)
  1056.  
  1057. function GM:OnNPCKilled(ent, attacker, inflictor)
  1058. end
  1059.  
  1060. function GM:LastHuman(pl)
  1061. if not LASTHUMAN then
  1062. net.Start("zs_lasthuman")
  1063. net.WriteEntity(pl or NULL)
  1064. net.Broadcast()
  1065.  
  1066. for _, ent in pairs(ents.FindByClass("logic_infliction")) do
  1067. ent:Input("onlasthuman", pl, pl, pl and pl:IsValid() and pl:EntIndex() or -1)
  1068. end
  1069.  
  1070. LASTHUMAN = true
  1071. end
  1072.  
  1073. self.TheLastHuman = pl
  1074. end
  1075.  
  1076. function GM:PlayerHealedTeamMember(pl, other, health, wep)
  1077. if self:GetWave() == 0 then return end
  1078.  
  1079. pl.HealedThisRound = pl.HealedThisRound + health
  1080. pl.CarryOverHealth = (pl.CarryOverHealth or 0) + health
  1081.  
  1082. local hpperpoint = self.MedkitPointsPerHealth
  1083. if hpperpoint <= 0 then return end
  1084.  
  1085. local points = math.floor(pl.CarryOverHealth / hpperpoint)
  1086.  
  1087. if 1 <= points then
  1088. pl:AddPoints(points)
  1089.  
  1090. pl.CarryOverHealth = pl.CarryOverHealth - points * hpperpoint
  1091.  
  1092. net.Start("zs_healother")
  1093. net.WriteEntity(other)
  1094. net.WriteUInt(points, 16)
  1095. net.Send(pl)
  1096. end
  1097. end
  1098.  
  1099. function GM:ObjectPackedUp(pack, packer, owner)
  1100. end
  1101.  
  1102. function GM:PlayerRepairedObject(pl, other, health, wep)
  1103. if self:GetWave() == 0 then return end
  1104.  
  1105. pl.RepairedThisRound = pl.RepairedThisRound + health
  1106. pl.CarryOverRepair = (pl.CarryOverRepair or 0) + health
  1107.  
  1108. local hpperpoint = self.RepairPointsPerHealth
  1109. if hpperpoint <= 0 then return end
  1110.  
  1111. local points = math.floor(pl.CarryOverRepair / hpperpoint)
  1112.  
  1113. if 1 <= points then
  1114. pl:AddPoints(points)
  1115.  
  1116. pl.CarryOverRepair = pl.CarryOverRepair - points * hpperpoint
  1117.  
  1118. net.Start("zs_repairobject")
  1119. net.WriteEntity(other)
  1120. net.WriteUInt(points, 16)
  1121. net.Send(pl)
  1122. end
  1123. end
  1124.  
  1125. function GM:CacheHonorableMentions()
  1126. if self.CachedHMs then return end
  1127.  
  1128. self.CachedHMs = {}
  1129.  
  1130. for i, hm in ipairs(self.HonorableMentions) do
  1131. if hm.GetPlayer then
  1132. local pl, magnitude = hm.GetPlayer(self)
  1133. if pl then
  1134. self.CachedHMs[i] = {pl, i, magnitude or 0}
  1135. end
  1136. end
  1137. end
  1138.  
  1139. gamemode.Call("PostDoHonorableMentions")
  1140. end
  1141.  
  1142. function GM:DoHonorableMentions(filter)
  1143. self:CacheHonorableMentions()
  1144.  
  1145. for i, tab in pairs(self.CachedHMs) do
  1146. net.Start("zs_honmention")
  1147. net.WriteEntity(tab[1])
  1148. net.WriteUInt(tab[2], 8)
  1149. net.WriteInt(tab[3], 32)
  1150. if filter then
  1151. net.Send(filter)
  1152. else
  1153. net.Broadcast()
  1154. end
  1155. end
  1156. end
  1157.  
  1158. function GM:PostDoHonorableMentions()
  1159. end
  1160.  
  1161. function GM:PostEndRound(winner)
  1162. end
  1163.  
  1164. -- You can override or hook and return false in case you have your own map change system.
  1165. local function RealMap(map)
  1166. return string.match(map, "(.+)%.bsp")
  1167. end
  1168. function GM:LoadNextMap()
  1169. -- Just in case.
  1170. timer.Simple(10, game.LoadNextMap)
  1171. timer.Simple(15, function() RunConsoleCommand("changelevel", game.GetMap()) end)
  1172.  
  1173. if file.Exists(GetConVarString("mapcyclefile"), "GAME") then
  1174. game.LoadNextMap()
  1175. else
  1176. local maps = file.Find("maps/zs_*.bsp", "GAME")
  1177. maps = table.Add(maps, file.Find("maps/ze_*.bsp", "GAME"))
  1178. maps = table.Add(maps, file.Find("maps/zm_*.bsp", "GAME"))
  1179. table.sort(maps)
  1180. if #maps > 0 then
  1181. local currentmap = game.GetMap()
  1182. for i, map in ipairs(maps) do
  1183. local lowermap = string.lower(map)
  1184. local realmap = RealMap(lowermap)
  1185. if realmap == currentmap then
  1186. if maps[i + 1] then
  1187. local nextmap = RealMap(maps[i + 1])
  1188. if nextmap then
  1189. RunConsoleCommand("changelevel", nextmap)
  1190. end
  1191. else
  1192. local nextmap = RealMap(maps[1])
  1193. if nextmap then
  1194. RunConsoleCommand("changelevel", nextmap)
  1195. end
  1196. end
  1197.  
  1198. break
  1199. end
  1200. end
  1201. end
  1202. end
  1203. end
  1204.  
  1205. function GM:PreRestartRound()
  1206. for _, pl in pairs(player.GetAll()) do
  1207. pl:StripWeapons()
  1208. pl:Spectate(OBS_MODE_ROAMING)
  1209. pl:GodDisable()
  1210. end
  1211. end
  1212.  
  1213. GM.CurrentRound = 1
  1214. function GM:RestartRound()
  1215. self.CurrentRound = self.CurrentRound + 1
  1216.  
  1217. self:RestartLua()
  1218. self:RestartGame()
  1219.  
  1220. net.Start("zs_gamemodecall")
  1221. net.WriteString("RestartRound")
  1222. net.Broadcast()
  1223. end
  1224.  
  1225. GM.DynamicSpawning = true
  1226. GM.CappedInfliction = 0
  1227. GM.StartingZombie = {}
  1228. GM.CheckedOut = {}
  1229. GM.PreviouslyDied = {}
  1230. GM.StoredUndeadFrags = {}
  1231.  
  1232. function GM:RestartLua()
  1233. self.CachedHMs = nil
  1234. self.TheLastHuman = nil
  1235. self.LastBossZombieSpawned = nil
  1236. self.UseSigils = nil
  1237.  
  1238. -- logic_pickups
  1239. self.MaxWeaponPickups = nil
  1240. self.MaxAmmoPickups = nil
  1241. self.MaxFlashlightPickups = nil
  1242. self.WeaponRequiredForAmmo = nil
  1243. for _, pl in pairs(player.GetAll()) do
  1244. pl.AmmoPickups = nil
  1245. pl.WeaponPickups = nil
  1246. end
  1247.  
  1248. self.OverrideEndSlomo = nil
  1249. if type(GetGlobalBool("endcamera", 1)) ~= "number" then
  1250. SetGlobalBool("endcamera", nil)
  1251. end
  1252. if GetGlobalString("winmusic", "-") ~= "-" then
  1253. SetGlobalString("winmusic", nil)
  1254. end
  1255. if GetGlobalString("losemusic", "-") ~= "-" then
  1256. SetGlobalString("losemusic", nil)
  1257. end
  1258. if type(GetGlobalVector("endcamerapos", 1)) ~= "number" then
  1259. SetGlobalVector("endcamerapos", nil)
  1260. end
  1261.  
  1262. self.CappedInfliction = 0
  1263.  
  1264. self.StartingZombie = {}
  1265. self.CheckedOut = {}
  1266. self.PreviouslyDied = {}
  1267. self.StoredUndeadFrags = {}
  1268.  
  1269. ROUNDWINNER = nil
  1270. LAST_BITE = nil
  1271. LASTHUMAN = nil
  1272.  
  1273. hook.Remove("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage")
  1274. hook.Remove("PlayerCanHearPlayersVoice", "EndRoundCanHearPlayersVoice")
  1275.  
  1276. self:RevertZombieClasses()
  1277. end
  1278.  
  1279. -- I don't know.
  1280. local function CheckBroken()
  1281. for _, pl in pairs(player.GetAll()) do
  1282. if pl:Alive() and (pl:Health() <= 0 or pl:GetObserverMode() ~= OBS_MODE_NONE or pl:OBBMaxs().x ~= 16) then
  1283. pl:SetObserverMode(OBS_MODE_NONE)
  1284. pl:UnSpectateAndSpawn()
  1285. end
  1286. end
  1287. end
  1288.  
  1289. function GM:DoRestartGame()
  1290. self.RoundEnded = nil
  1291.  
  1292. for _, ent in pairs(ents.FindByClass("prop_weapon")) do
  1293. ent:Remove()
  1294. end
  1295.  
  1296. for _, ent in pairs(ents.FindByClass("prop_ammo")) do
  1297. ent:Remove()
  1298. end
  1299.  
  1300. self:SetUseSigils(false)
  1301. self:SetEscapeStage(ESCAPESTAGE_NONE)
  1302.  
  1303. self:SetWave(0)
  1304. if GAMEMODE.ZombieEscape then
  1305. self:SetWaveStart(CurTime() + 30)
  1306. else
  1307. self:SetWaveStart(CurTime() + self.WaveZeroLength)
  1308. end
  1309. self:SetWaveEnd(self:GetWaveStart() + self:GetWaveOneLength())
  1310. self:SetWaveActive(false)
  1311.  
  1312. SetGlobalInt("numwaves", -2)
  1313.  
  1314. timer.Create("CheckBroken", 10, 1, CheckBroken)
  1315.  
  1316. game.CleanUpMap(false, self.CleanupFilter)
  1317. gamemode.Call("InitPostEntityMap")
  1318.  
  1319. for _, pl in pairs(player.GetAll()) do
  1320. pl:UnSpectateAndSpawn()
  1321. pl:GodDisable()
  1322. gamemode.Call("PlayerInitialSpawnRound", pl)
  1323. gamemode.Call("PlayerReadyRound", pl)
  1324. end
  1325. end
  1326.  
  1327. function GM:RestartGame()
  1328. for _, pl in pairs(player.GetAll()) do
  1329. pl:StripWeapons()
  1330. pl:StripAmmo()
  1331. pl:SetFrags(0)
  1332. pl:SetDeaths(0)
  1333. pl:SetPoints(0)
  1334. pl:ChangeTeam(TEAM_HUMAN)
  1335. pl:DoHulls()
  1336. pl:SetZombieClass(self.DefaultZombieClass)
  1337. pl.DeathClass = nil
  1338. end
  1339.  
  1340. self:SetWave(0)
  1341. if GAMEMODE.ZombieEscape then
  1342. self:SetWaveStart(CurTime() + 30)
  1343. else
  1344. self:SetWaveStart(CurTime() + self.WaveZeroLength)
  1345. end
  1346. self:SetWaveEnd(self:GetWaveStart() + self:GetWaveOneLength())
  1347. self:SetWaveActive(false)
  1348.  
  1349. SetGlobalInt("numwaves", -2)
  1350. if GetGlobalString("hudoverride"..TEAM_UNDEAD, "") ~= "" then
  1351. SetGlobalString("hudoverride"..TEAM_UNDEAD, "")
  1352. end
  1353. if GetGlobalString("hudoverride"..TEAM_HUMAN, "") ~= "" then
  1354. SetGlobalString("hudoverride"..TEAM_HUMAN, "")
  1355. end
  1356.  
  1357. timer.Simple(0.25, function() GAMEMODE:DoRestartGame() end)
  1358. end
  1359.  
  1360. function GM:InitPostEntityMap(fromze)
  1361. pcall(gamemode.Call, "LoadMapEditorFile")
  1362.  
  1363. gamemode.Call("SetupSpawnPoints")
  1364. gamemode.Call("RemoveUnusedEntities")
  1365. if not fromze then
  1366. gamemode.Call("ReplaceMapWeapons")
  1367. gamemode.Call("ReplaceMapAmmo")
  1368. gamemode.Call("ReplaceMapBatteries")
  1369. end
  1370. gamemode.Call("CreateZombieGas")
  1371. gamemode.Call("SetupProps")
  1372.  
  1373. for _, ent in pairs(ents.FindByClass("prop_ammo")) do ent.PlacedInMap = true end
  1374. for _, ent in pairs(ents.FindByClass("prop_weapon")) do ent.PlacedInMap = true end
  1375.  
  1376. if self.ObjectiveMap then
  1377. self:SetDynamicSpawning(false)
  1378. self.BossZombies = false
  1379. end
  1380.  
  1381. --[[if not game.IsDedicated() then
  1382. gamemode.Call("CreateSigils")
  1383. end]]
  1384. end
  1385.  
  1386. local function EndRoundPlayerShouldTakeDamage(pl, attacker) return pl:Team() ~= TEAM_HUMAN or not attacker:IsPlayer() end
  1387. local function EndRoundPlayerCanSuicide(pl) return pl:Team() ~= TEAM_HUMAN end
  1388.  
  1389. local function EndRoundSetupPlayerVisibility(pl)
  1390. if GAMEMODE.LastHumanPosition and GAMEMODE.RoundEnded then
  1391. AddOriginToPVS(GAMEMODE.LastHumanPosition)
  1392. else
  1393. hook.Remove("SetupPlayerVisibility", "EndRoundSetupPlayerVisibility")
  1394. end
  1395. end
  1396.  
  1397. function GM:EndRound(winner)
  1398. if self.RoundEnded then return end
  1399. self.RoundEnded = true
  1400. self.RoundEndedTime = CurTime()
  1401. ROUNDWINNER = winner
  1402.  
  1403. if self.OverrideEndSlomo == nil or self.OverrideEndSlomo then
  1404. game.SetTimeScale(0.25)
  1405. timer.Simple(2, function() game.SetTimeScale(1) end)
  1406. end
  1407.  
  1408. hook.Add("PlayerCanHearPlayersVoice", "EndRoundCanHearPlayersVoice", function() return true end)
  1409.  
  1410. if self.OverrideEndCamera == nil or self.OverrideEndCamera then
  1411. hook.Add("SetupPlayerVisibility", "EndRoundSetupPlayerVisibility", EndRoundSetupPlayerVisibility)
  1412. end
  1413.  
  1414. if self:ShouldRestartRound() then
  1415. timer.Simple(self.EndGameTime - 3, function() gamemode.Call("PreRestartRound") end)
  1416. timer.Simple(self.EndGameTime, function() gamemode.Call("RestartRound") end)
  1417. else
  1418. timer.Simple(self.EndGameTime, function() gamemode.Call("LoadNextMap") end)
  1419. end
  1420.  
  1421. -- Get rid of some lag.
  1422. util.RemoveAll("prop_ammo")
  1423. util.RemoveAll("prop_weapon")
  1424.  
  1425. timer.Simple(5, function() gamemode.Call("DoHonorableMentions") end)
  1426.  
  1427. if winner == TEAM_HUMAN then
  1428. self.LastHumanPosition = nil
  1429.  
  1430. hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerShouldTakeDamage)
  1431. elseif winner == TEAM_UNDEAD then
  1432. hook.Add("PlayerShouldTakeDamage", "EndRoundShouldTakeDamage", EndRoundPlayerCanSuicide)
  1433. end
  1434.  
  1435. net.Start("zs_endround")
  1436. net.WriteUInt(winner or -1, 8)
  1437. net.WriteString(game.GetMapNext())
  1438. net.Broadcast()
  1439.  
  1440. if winner == TEAM_HUMAN then
  1441. for _, ent in pairs(ents.FindByClass("logic_winlose")) do
  1442. ent:Input("onwin")
  1443. end
  1444. else
  1445. for _, ent in pairs(ents.FindByClass("logic_winlose")) do
  1446. ent:Input("onlose")
  1447. end
  1448. end
  1449.  
  1450. gamemode.Call("PostEndRound", winner)
  1451.  
  1452. self:SetWaveStart(CurTime() + 9999)
  1453. end
  1454.  
  1455. function GM:PlayerReady(pl)
  1456. gamemode.Call("PlayerReadyRound", pl)
  1457. end
  1458.  
  1459. function GM:PlayerReadyRound(pl)
  1460. if not pl:IsValid() then return end
  1461.  
  1462. self:FullGameUpdate(pl)
  1463. pl:UpdateAllZombieClasses()
  1464.  
  1465. local classid = pl:GetZombieClass()
  1466. pl:SetZombieClass(classid, true, pl)
  1467.  
  1468. if self.OverrideStartingWorth then
  1469. pl:SendLua("GAMEMODE.StartingWorth="..tostring(self.StartingWorth))
  1470. end
  1471.  
  1472. if pl:Team() == TEAM_UNDEAD then
  1473. -- This is just so they get updated on what class they are and have their hulls set up right.
  1474. pl:DoHulls(classid, TEAM_UNDEAD)
  1475. elseif self:GetWave() <= 0 and self.StartingWorth > 0 and not self.StartingLoadout and not self.ZombieEscape then
  1476. pl:SendLua("MakepWorth()")
  1477. else
  1478. gamemode.Call("GiveDefaultOrRandomEquipment", pl)
  1479. end
  1480.  
  1481. if self.RoundEnded then
  1482. pl:SendLua("gamemode.Call(\"EndRound\", "..tostring(ROUNDWINNER)..", \""..game.GetMapNext().."\")")
  1483. gamemode.Call("DoHonorableMentions", pl)
  1484. end
  1485.  
  1486. if pl:GetInfo("zs_noredeem") == "1" then
  1487. pl.NoRedeeming = true
  1488. end
  1489.  
  1490. if self:GetWave() == 0 then
  1491. self:SendZombieVolunteers(pl, true)
  1492. end
  1493.  
  1494. if self:IsClassicMode() then
  1495. pl:SendLua("SetGlobalBool(\"classicmode\", true)")
  1496. elseif self:IsBabyMode() then
  1497. pl:SendLua("SetGlobalBool(\"babymode\", true)")
  1498. end
  1499. end
  1500.  
  1501. function GM:FullGameUpdate(pl)
  1502. net.Start("zs_gamestate")
  1503. net.WriteInt(self:GetWave(), 16)
  1504. net.WriteFloat(self:GetWaveStart())
  1505. net.WriteFloat(self:GetWaveEnd())
  1506. if pl then
  1507. net.Send(pl)
  1508. else
  1509. net.Broadcast()
  1510. end
  1511. end
  1512.  
  1513. concommand.Add("initpostentity", function(sender, command, arguments)
  1514. if not sender.DidInitPostEntity then
  1515. sender.DidInitPostEntity = true
  1516.  
  1517. gamemode.Call("PlayerReady", sender)
  1518. end
  1519. end)
  1520.  
  1521. local playerheight = Vector(0, 0, 72)
  1522. local playermins = Vector(-17, -17, 0)
  1523. local playermaxs = Vector(17, 17, 4)
  1524. local function groupsort(a, b)
  1525. return #a > #b
  1526. end
  1527. function GM:AttemptHumanDynamicSpawn(pl)
  1528. if pl:IsValid() and pl:IsPlayer() and pl:Alive() and pl:Team() == TEAM_HUMAN and self.DynamicSpawning then
  1529. local groups = self:GetTeamRallyGroups(TEAM_HUMAN)
  1530. table.sort(groups, groupsort)
  1531. for i=1, #groups do
  1532. local group = groups[i]
  1533.  
  1534. local allplayers = team.GetPlayers(TEAM_HUMAN)
  1535. for _, otherpl in pairs(group) do
  1536. if otherpl ~= pl then
  1537. local pos = otherpl:GetPos() + Vector(0, 0, 1)
  1538. 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
  1539. local nearzombie = false
  1540. for __, ent in pairs(team.GetPlayers(TEAM_UNDEAD)) do
  1541. if ent:Alive() and ent:GetPos():Distance(pos) <= 256 then
  1542. nearzombie = true
  1543. end
  1544. end
  1545.  
  1546. if not nearzombie then
  1547. pl:SetPos(otherpl:GetPos())
  1548. return true
  1549. end
  1550. end
  1551. end
  1552. end
  1553. end
  1554. end
  1555.  
  1556. return false
  1557. end
  1558.  
  1559. function GM:PlayerInitialSpawn(pl)
  1560. gamemode.Call("PlayerInitialSpawnRound", pl)
  1561. end
  1562.  
  1563. function GM:PlayerInitialSpawnRound(pl)
  1564. pl:SprintDisable()
  1565. if pl:KeyDown(IN_WALK) then
  1566. pl:ConCommand("-walk")
  1567. end
  1568.  
  1569. pl:SetCanWalk(false)
  1570. pl:SetCanZoom(false)
  1571. pl:SetNoCollideWithTeammates(true)
  1572. pl:SetCustomCollisionCheck(true)
  1573.  
  1574. pl.ZombiesKilled = 0
  1575. pl.ZombiesKilledAssists = 0
  1576. pl.BrainsEaten = 0
  1577.  
  1578. pl.ResupplyBoxUsedByOthers = 0
  1579.  
  1580. pl.WaveJoined = self:GetWave()
  1581.  
  1582. pl.CrowKills = 0
  1583. pl.CrowVsCrowKills = 0
  1584. pl.CrowBarricadeDamage = 0
  1585.  
  1586. pl.BarricadeDamage = 0
  1587. pl.DynamicSpawnedOn = 0
  1588.  
  1589. pl.NextPainSound = 0
  1590.  
  1591. pl.BonusDamageCheck = 0
  1592.  
  1593. pl.DamageDealt = {}
  1594. pl.DamageDealt[TEAM_UNDEAD] = 0
  1595. pl.DamageDealt[TEAM_HUMAN] = 0
  1596.  
  1597. pl.HealedThisRound = 0
  1598. pl.CarryOverHealth = 0
  1599. pl.RepairedThisRound = 0
  1600. pl.CarryOverRepair = 0
  1601. pl.PointsCommission = 0
  1602. pl.CarryOverCommision = 0
  1603. pl.NextRegenerate = 0
  1604. pl.NestsDestroyed = 0
  1605. pl.NestSpawns = 0
  1606.  
  1607. local nosend = not pl.DidInitPostEntity
  1608. pl.HumanSpeedAdder = nil
  1609. pl.HumanSpeedAdder = nil
  1610. pl.HumanRepairMultiplier = nil
  1611. pl.HumanHealMultiplier = nil
  1612. pl.BuffResistant = nil
  1613. pl.BuffRegenerative = nil
  1614. pl.BuffMuscular = nil
  1615. pl.IsWeak = nil
  1616. pl.HumanSpeedAdder = nil
  1617. pl:SetPalsy(false, nosend)
  1618. pl:SetHemophilia(false, nosend)
  1619. pl:SetUnlucky(false)
  1620. pl.Clumsy = nil
  1621. pl.NoGhosting = nil
  1622. pl.NoObjectPickup = nil
  1623. pl.DamageVulnerability = nil
  1624.  
  1625. local uniqueid = pl:UniqueID()
  1626.  
  1627. if table.HasValue(self.FanList, uniqueid) then
  1628. pl.DamageVulnerability = (pl.DamageVulnerability or 1) + 10
  1629. pl:PrintTranslatedMessage(HUD_PRINTTALK, "thanks_for_being_a_fan_of_zs")
  1630. end
  1631.  
  1632. if self.PreviouslyDied[uniqueid] then
  1633. -- They already died and reconnected.
  1634. pl:ChangeTeam(TEAM_UNDEAD)
  1635. elseif LASTHUMAN then
  1636. -- Joined during last human.
  1637. pl.SpawnedTime = CurTime()
  1638. pl:ChangeTeam(TEAM_UNDEAD)
  1639. elseif self:GetWave() <= 0 then
  1640. -- Joined during ready phase.
  1641. pl.SpawnedTime = CurTime()
  1642. pl:ChangeTeam(TEAM_HUMAN)
  1643. 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.
  1644. pl:ChangeTeam(TEAM_UNDEAD)
  1645. self.PreviouslyDied[uniqueid] = CurTime()
  1646. else
  1647. -- Joined past the ready phase but before the deadline.
  1648. pl.SpawnedTime = CurTime()
  1649. pl:ChangeTeam(TEAM_HUMAN)
  1650. if self.DynamicSpawning then
  1651. timer.Simple(0, function() GAMEMODE:AttemptHumanDynamicSpawn(pl) end)
  1652. end
  1653. end
  1654.  
  1655. if pl:Team() == TEAM_UNDEAD and not self:GetWaveActive() and self.ZombieClasses["Crow"] then
  1656. pl:SetZombieClass(self.ZombieClasses["Crow"].Index)
  1657. pl.DeathClass = self.DefaultZombieClass
  1658. else
  1659. pl:SetZombieClass(self.DefaultZombieClass)
  1660. end
  1661.  
  1662. if pl:Team() == TEAM_UNDEAD and self.StoredUndeadFrags[uniqueid] then
  1663. pl:SetFrags(self.StoredUndeadFrags[uniqueid])
  1664. self.StoredUndeadFrags[uniqueid] = nil
  1665. end
  1666. end
  1667.  
  1668. function GM:GetDynamicSpawning()
  1669. return self.DynamicSpawning
  1670. end
  1671.  
  1672. function GM:PlayerRedeemed(pl, silent, noequip)
  1673. pl:RemoveStatus("overridemodel", false, true)
  1674.  
  1675. pl:ChangeTeam(TEAM_HUMAN)
  1676. if not noequip then pl.m_PreRedeem = true end
  1677. pl:UnSpectateAndSpawn()
  1678. pl.m_PreRedeem = nil
  1679. pl:DoHulls()
  1680.  
  1681. local frags = pl:Frags()
  1682. if frags < 0 then
  1683. pl:SetFrags(frags * 5)
  1684. else
  1685. pl:SetFrags(0)
  1686. end
  1687. pl:SetDeaths(0)
  1688.  
  1689. pl.DeathClass = nil
  1690. pl:SetZombieClass(self.DefaultZombieClass)
  1691.  
  1692. pl.SpawnedTime = CurTime()
  1693.  
  1694. if not silent then
  1695. net.Start("zs_playerredeemed")
  1696. net.WriteEntity(pl)
  1697. net.WriteString(pl:Name())
  1698. net.Broadcast()
  1699. end
  1700. end
  1701.  
  1702. function GM:PlayerDisconnected(pl)
  1703. pl.Disconnecting = true
  1704.  
  1705. local uid = pl:UniqueID()
  1706.  
  1707. self.PreviouslyDied[uid] = CurTime()
  1708.  
  1709. if pl:Team() == TEAM_HUMAN then
  1710. pl:DropAll()
  1711. elseif pl:Team() == TEAM_UNDEAD then
  1712. self.StoredUndeadFrags[uid] = pl:Frags()
  1713. end
  1714.  
  1715. if pl:Health() > 0 then
  1716. local lastattacker = pl:GetLastAttacker()
  1717. if IsValid(lastattacker) then
  1718. pl:TakeDamage(1000, lastattacker, lastattacker)
  1719.  
  1720. PrintTranslatedMessage(HUD_PRINTCONSOLE, "disconnect_killed", pl:Name(), lastattacker:Name())
  1721. end
  1722. end
  1723.  
  1724. gamemode.Call("CalculateInfliction")
  1725. end
  1726.  
  1727. -- Reevaluates a prop and its constraint system (or all props if no arguments) to determine if they should be frozen or not from nails.
  1728. function GM:EvaluatePropFreeze(ent, neighbors)
  1729. if not ent then
  1730. for _, e in pairs(ents.GetAll()) do
  1731. self:EvaluatePropFreeze(e)
  1732. end
  1733.  
  1734. return
  1735. end
  1736.  
  1737. if ent:IsNailedToWorldHierarchy() then
  1738. ent:SetNailFrozen(true)
  1739. elseif ent:GetNailFrozen() then
  1740. ent:SetNailFrozen(false)
  1741. end
  1742.  
  1743. neighbors = neighbors or {}
  1744. table.insert(neighbors, ent)
  1745.  
  1746. for _, nail in pairs(ent:GetNails()) do
  1747. if nail:IsValid() then
  1748. local baseent = nail:GetBaseEntity()
  1749. local attachent = nail:GetAttachEntity()
  1750. if baseent:IsValid() and not baseent:IsWorld() and not table.HasValue(neighbors, baseent) then
  1751. self:EvaluatePropFreeze(baseent, neighbors)
  1752. end
  1753. if attachent:IsValid() and not attachent:IsWorld() and not table.HasValue(neighbors, attachent) then
  1754. self:EvaluatePropFreeze(attachent, neighbors)
  1755. end
  1756. end
  1757. end
  1758. end
  1759.  
  1760. -- 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.
  1761. function GM:OnNailDamaged(ent, attacker, inflictor, damage, dmginfo)
  1762. end
  1763.  
  1764. -- 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.
  1765. local function evalfreeze(ent)
  1766. if ent and ent:IsValid() then
  1767. gamemode.Call("EvaluatePropFreeze", ent)
  1768. end
  1769. end
  1770. function GM:OnNailRemoved(nail, ent1, ent2, remover)
  1771. if ent1 and ent1:IsValid() and not ent1:IsWorld() then
  1772. timer.Simple(0, function() evalfreeze(ent1) end)
  1773. timer.Simple(0.2, function() evalfreeze(ent1) end)
  1774. end
  1775. if ent2 and ent2:IsValid() and not ent2:IsWorld() then
  1776. timer.Simple(0, function() evalfreeze(ent2) end)
  1777. timer.Simple(0.2, function() evalfreeze(ent2) end)
  1778. end
  1779.  
  1780. if remover and remover:IsValid() and remover:IsPlayer() then
  1781. local deployer = nail:GetDeployer()
  1782. if deployer:IsValid() and deployer ~= remover and deployer:Team() == TEAM_HUMAN then
  1783. PrintTranslatedMessage(HUD_PRINTCONSOLE, "nail_removed_by", remover:Name(), deployer:Name())
  1784. end
  1785. end
  1786. end
  1787.  
  1788. -- A nail is created between two entities.
  1789. function GM:OnNailCreated(ent1, ent2, nail)
  1790. if ent1 and ent1:IsValid() and not ent1:IsWorld() then
  1791. timer.Simple(0, function() evalfreeze(ent1) end)
  1792. end
  1793. if ent2 and ent2:IsValid() and not ent2:IsWorld() then
  1794. timer.Simple(0, function() evalfreeze(ent2) end)
  1795. end
  1796. end
  1797.  
  1798. function GM:RemoveDuplicateAmmo(pl)
  1799. local AmmoCounts = {}
  1800. local WepAmmos = {}
  1801. for _, wep in pairs(pl:GetWeapons()) do
  1802. if wep.Primary then
  1803. local ammotype = wep:ValidPrimaryAmmo()
  1804. if ammotype and wep.Primary.DefaultClip > 0 then
  1805. AmmoCounts[ammotype] = (AmmoCounts[ammotype] or 0) + 1
  1806. WepAmmos[wep] = wep.Primary.DefaultClip - wep.Primary.ClipSize
  1807. end
  1808. local ammotype2 = wep:ValidSecondaryAmmo()
  1809. if ammotype2 and wep.Secondary.DefaultClip > 0 then
  1810. AmmoCounts[ammotype2] = (AmmoCounts[ammotype2] or 0) + 1
  1811. WepAmmos[wep] = wep.Secondary.DefaultClip - wep.Secondary.ClipSize
  1812. end
  1813. end
  1814. end
  1815. for ammotype, count in pairs(AmmoCounts) do
  1816. if count > 1 then
  1817. local highest = 0
  1818. local highestwep
  1819. for wep, extraammo in pairs(WepAmmos) do
  1820. if wep.Primary.Ammo == ammotype then
  1821. highest = math.max(highest, extraammo)
  1822. highestwep = wep
  1823. end
  1824. end
  1825. if highestwep then
  1826. for wep, extraammo in pairs(WepAmmos) do
  1827. if wep ~= highestwep and wep.Primary.Ammo == ammotype then
  1828. pl:RemoveAmmo(extraammo, ammotype)
  1829. end
  1830. end
  1831. end
  1832. end
  1833. end
  1834. end
  1835.  
  1836. local function TimedOut(pl)
  1837. if pl:IsValid() and pl:Team() == TEAM_HUMAN and pl:Alive() and not GAMEMODE.CheckedOut[pl:UniqueID()] then
  1838. gamemode.Call("GiveRandomEquipment", pl)
  1839. end
  1840. end
  1841.  
  1842. function GM:GiveDefaultOrRandomEquipment(pl)
  1843. if not self.CheckedOut[pl:UniqueID()] and not self.ZombieEscape then
  1844. if self.StartingLoadout then
  1845. self:GiveStartingLoadout(pl)
  1846. else
  1847. pl:SendLua("GAMEMODE:RequestedDefaultCart()")
  1848. if self.StartingWorth > 0 then
  1849. timer.Simple(4, function() TimedOut(pl) end)
  1850. end
  1851. end
  1852. end
  1853. end
  1854.  
  1855. function GM:GiveStartingLoadout(pl)
  1856. for item, amount in pairs(self.StartingLoadout) do
  1857. for i=1, amount do
  1858. pl:Give(item)
  1859. end
  1860. end
  1861. end
  1862.  
  1863. function GM:GiveRandomEquipment(pl)
  1864. if self.CheckedOut[pl:UniqueID()] or self.ZombieEscape then return end
  1865. self.CheckedOut[pl:UniqueID()] = true
  1866.  
  1867. if self.StartingLoadout then
  1868. self:GiveStartingLoadout(pl)
  1869. elseif GAMEMODE.OverrideStartingWorth then
  1870. pl:Give("weapon_zs_swissarmyknife")
  1871. elseif #self.StartLoadouts >= 1 then
  1872. for _, id in pairs(self.StartLoadouts[math.random(#self.StartLoadouts)]) do
  1873. local tab = FindStartingItem(id)
  1874. if tab then
  1875. if tab.Callback then
  1876. tab.Callback(pl)
  1877. elseif tab.SWEP then
  1878. pl:StripWeapon(tab.SWEP)
  1879. pl:Give(tab.SWEP)
  1880. end
  1881. end
  1882. end
  1883. end
  1884. end
  1885.  
  1886. function GM:PlayerCanCheckout(pl)
  1887. 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
  1888. end
  1889.  
  1890. concommand.Add("zs_pointsshopbuy", function(sender, command, arguments)
  1891. if not (sender:IsValid() and sender:IsConnected()) or #arguments == 0 then return end
  1892.  
  1893. if sender:GetUnlucky() then
  1894. sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "banned_for_life_warning"))
  1895. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1896. return
  1897. end
  1898.  
  1899. if not sender:NearArsenalCrate() then
  1900. sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "need_to_be_near_arsenal_crate"))
  1901. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1902. return
  1903. end
  1904.  
  1905. if not gamemode.Call("PlayerCanPurchase", sender) then
  1906. sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "cant_purchase_right_now"))
  1907. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1908. return
  1909. end
  1910.  
  1911. local itemtab
  1912. local id = arguments[1]
  1913. local num = tonumber(id)
  1914. if num then
  1915. itemtab = GAMEMODE.Items[num]
  1916. else
  1917. for i, tab in pairs(GAMEMODE.Items) do
  1918. if tab.Signature == id then
  1919. itemtab = tab
  1920. break
  1921. end
  1922. end
  1923. end
  1924.  
  1925. if not itemtab or not itemtab.PointShop then return end
  1926.  
  1927. local points = sender:GetPoints()
  1928. local cost = itemtab.Worth
  1929. if not GAMEMODE:GetWaveActive() then
  1930. cost = cost * GAMEMODE.ArsenalCrateMultiplier
  1931. end
  1932.  
  1933. if GAMEMODE:IsClassicMode() and itemtab.NoClassicMode then
  1934. sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "cant_use_x_in_classic", itemtab.Name))
  1935. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1936. return
  1937. end
  1938.  
  1939. if GAMEMODE.ZombieEscape and itemtab.NoZombieEscape then
  1940. sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "cant_use_x_in_zombie_escape", itemtab.Name))
  1941. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1942. return
  1943. end
  1944.  
  1945. cost = math.ceil(cost)
  1946.  
  1947. if points < cost then
  1948. sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "dont_have_enough_points"))
  1949. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  1950. return
  1951. end
  1952.  
  1953. if itemtab.Callback then
  1954. itemtab.Callback(sender)
  1955. elseif itemtab.SWEP then
  1956. if sender:HasWeapon(itemtab.SWEP) then
  1957. local stored = weapons.GetStored(itemtab.SWEP)
  1958. if stored and stored.AmmoIfHas then
  1959. sender:GiveAmmo(stored.Primary.DefaultClip, stored.Primary.Ammo)
  1960. else
  1961. local wep = ents.Create("prop_weapon")
  1962. if wep:IsValid() then
  1963. wep:SetPos(sender:GetShootPos())
  1964. wep:SetAngles(sender:GetAngles())
  1965. wep:SetWeaponType(itemtab.SWEP)
  1966. wep:SetShouldRemoveAmmo(true)
  1967. wep:Spawn()
  1968. end
  1969. end
  1970. else
  1971. local wep = sender:Give(itemtab.SWEP)
  1972. if wep and wep:IsValid() and wep.EmptyWhenPurchased and wep:GetOwner():IsValid() then
  1973. if wep.Primary then
  1974. local primary = wep:ValidPrimaryAmmo()
  1975. if primary then
  1976. sender:RemoveAmmo(math.max(0, wep.Primary.DefaultClip - wep.Primary.ClipSize), primary)
  1977. end
  1978. end
  1979. if wep.Secondary then
  1980. local secondary = wep:ValidSecondaryAmmo()
  1981. if secondary then
  1982. sender:RemoveAmmo(math.max(0, wep.Secondary.DefaultClip - wep.Secondary.ClipSize), secondary)
  1983. end
  1984. end
  1985. end
  1986. end
  1987. else
  1988. return
  1989. end
  1990.  
  1991. sender:TakePoints(cost)
  1992. sender:PrintTranslatedMessage(HUD_PRINTTALK, "purchased_x_for_y_points", itemtab.Name, cost)
  1993. sender:SendLua("surface.PlaySound(\"ambient/levels/labs/coinslot1.wav\")")
  1994.  
  1995. local nearest = sender:NearestArsenalCrateOwnedByOther()
  1996. if nearest then
  1997. local owner = nearest:GetObjectOwner()
  1998. if owner:IsValid() then
  1999. local nonfloorcommission = cost * 0.07
  2000. local commission = math.floor(nonfloorcommission)
  2001. if commission > 0 then
  2002. owner.PointsCommission = owner.PointsCommission + cost
  2003.  
  2004. owner:AddPoints(commission)
  2005.  
  2006. net.Start("zs_commission")
  2007. net.WriteEntity(nearest)
  2008. net.WriteEntity(sender)
  2009. net.WriteUInt(commission, 16)
  2010. net.Send(owner)
  2011. end
  2012.  
  2013. local leftover = nonfloorcommission - commission
  2014. if leftover > 0 then
  2015. owner.CarryOverCommision = owner.CarryOverCommision + leftover
  2016. if owner.CarryOverCommision >= 1 then
  2017. local carried = math.floor(owner.CarryOverCommision)
  2018. owner.CarryOverCommision = owner.CarryOverCommision - carried
  2019. owner:AddPoints(carried)
  2020.  
  2021. net.Start("zs_commission")
  2022. net.WriteEntity(nearest)
  2023. net.WriteEntity(sender)
  2024. net.WriteUInt(carried, 16)
  2025. net.Send(owner)
  2026. end
  2027. end
  2028. end
  2029. end
  2030. end)
  2031.  
  2032. concommand.Add("worthrandom", function(sender, command, arguments)
  2033. if sender:IsValid() and sender:IsConnected() and gamemode.Call("PlayerCanCheckout", sender) then
  2034. gamemode.Call("GiveRandomEquipment", sender)
  2035. end
  2036. end)
  2037.  
  2038. concommand.Add("worthcheckout", function(sender, command, arguments)
  2039. if not (sender:IsValid() and sender:IsConnected()) or #arguments == 0 then return end
  2040.  
  2041. if not gamemode.Call("PlayerCanCheckout", sender) then
  2042. sender:CenterNotify(COLOR_RED, translate.ClientGet(sender, "cant_use_worth_anymore"))
  2043. return
  2044. end
  2045.  
  2046. local cost = 0
  2047. local hasalready = {}
  2048.  
  2049. for _, id in pairs(arguments) do
  2050. local tab = FindStartingItem(id)
  2051. if tab and not hasalready[id] then
  2052. cost = cost + tab.Worth
  2053. hasalready[id] = true
  2054. end
  2055. end
  2056.  
  2057. if cost > GAMEMODE.StartingWorth then return end
  2058.  
  2059. local hasalready = {}
  2060.  
  2061. for _, id in pairs(arguments) do
  2062. local tab = FindStartingItem(id)
  2063. if tab and not hasalready[id] then
  2064. if tab.NoClassicMode and GAMEMODE:IsClassicMode() then
  2065. sender:PrintMessage(HUD_PRINTTALK, translate.ClientFormat(sender, "cant_use_x_in_classic_mode", tab.Name))
  2066. elseif tab.Callback then
  2067. tab.Callback(sender)
  2068. hasalready[id] = true
  2069. elseif tab.SWEP then
  2070. sender:StripWeapon(tab.SWEP) -- "Fixes" players giving each other empty weapons to make it so they get no ammo from the Worth menu purchase.
  2071. sender:Give(tab.SWEP)
  2072. hasalready[id] = true
  2073. end
  2074. end
  2075. end
  2076.  
  2077. if table.Count(hasalready) > 0 then
  2078. GAMEMODE.CheckedOut[sender:UniqueID()] = true
  2079. end
  2080.  
  2081. gamemode.Call("RemoveDuplicateAmmo", sender)
  2082. end)
  2083.  
  2084. function GM:PlayerDeathThink(pl)
  2085. if self.RoundEnded or pl.Revive or self:GetWave() == 0 then return end
  2086.  
  2087. if pl:GetObserverMode() == OBS_MODE_CHASE then
  2088. local target = pl:GetObserverTarget()
  2089. if not target or not target:IsValid() or target:IsPlayer() and (not target:Alive() or target:Team() ~= pl:Team()) then
  2090. pl:StripWeapons()
  2091. pl:Spectate(OBS_MODE_ROAMING)
  2092. pl:SpectateEntity(NULL)
  2093. end
  2094. end
  2095.  
  2096. if pl:Team() ~= TEAM_UNDEAD then
  2097. pl.StartCrowing = nil
  2098. pl.StartSpectating = nil
  2099. return
  2100. end
  2101.  
  2102. if pl.NextSpawnTime and pl.NextSpawnTime <= CurTime() then -- Force spawn.
  2103. pl.NextSpawnTime = nil
  2104.  
  2105. pl:RefreshDynamicSpawnPoint()
  2106. pl:UnSpectateAndSpawn()
  2107. elseif pl:GetObserverMode() == OBS_MODE_NONE then -- Not in spectator yet.
  2108. if self:GetWaveActive() then -- During wave.
  2109. if not pl.StartSpectating or CurTime() >= pl.StartSpectating then
  2110. pl.StartSpectating = nil
  2111.  
  2112. pl:StripWeapons()
  2113. local best = self:GetBestDynamicSpawn(pl)
  2114. if best then
  2115. pl:Spectate(OBS_MODE_CHASE)
  2116. pl:SpectateEntity(best)
  2117. else
  2118. pl:Spectate(OBS_MODE_ROAMING)
  2119. pl:SpectateEntity(NULL)
  2120. end
  2121. end
  2122. 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.
  2123. pl:ChangeToCrow()
  2124. end
  2125. else -- In spectator.
  2126. if pl:KeyDown(IN_RELOAD) then
  2127. if self:GetWaveActive() then
  2128. pl.ForceDynamicSpawn = nil
  2129. local prev = self.DynamicSpawning
  2130. self.DynamicSpawning = false
  2131. pl:UnSpectateAndSpawn()
  2132. self.DynamicSpawning = prev
  2133. else
  2134. pl:ChangeToCrow()
  2135. end
  2136. elseif pl:KeyDown(IN_WALK) then
  2137. pl:TrySpawnAsGoreChild()
  2138. elseif pl:KeyDown(IN_ATTACK) then
  2139. if self:GetWaveActive() then
  2140. pl:RefreshDynamicSpawnPoint()
  2141. pl:UnSpectateAndSpawn()
  2142. else
  2143. pl:ChangeToCrow()
  2144. end
  2145. elseif pl:KeyPressed(IN_ATTACK2) then
  2146. pl.SpectatedPlayerKey = (pl.SpectatedPlayerKey or 0) + 1
  2147.  
  2148. local livingzombies = {}
  2149. for _, v in pairs(ents.FindByClass("prop_creepernest")) do
  2150. if v:GetNestBuilt() then table.insert(livingzombies, v) end
  2151. end
  2152. for _, v in pairs(team.GetPlayers(TEAM_ZOMBIE)) do
  2153. if v:Alive() then table.insert(livingzombies, v) end
  2154. end
  2155. --[[for _, v in pairs(team.GetSpawnPointGrouped(TEAM_UNDEAD)) do
  2156. table.insert(livingzombies, v)
  2157. end]]
  2158.  
  2159. pl:StripWeapons()
  2160.  
  2161. if pl.SpectatedPlayerKey > #livingzombies then
  2162. pl.SpectatedPlayerKey = 1
  2163. end
  2164.  
  2165. local specplayer = livingzombies[pl.SpectatedPlayerKey]
  2166. if specplayer then
  2167. pl:Spectate(OBS_MODE_CHASE)
  2168. pl:SpectateEntity(specplayer)
  2169. end
  2170. elseif pl:KeyPressed(IN_JUMP) then
  2171. pl:Spectate(OBS_MODE_ROAMING)
  2172. pl:SpectateEntity(NULL)
  2173. pl.SpectatedPlayerKey = nil
  2174. end
  2175. end
  2176. end
  2177.  
  2178. function GM:ShouldAntiGrief(ent, attacker, dmginfo, health)
  2179. return ent.m_AntiGrief and self.GriefMinimumHealth <= health and attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN and not dmginfo:IsExplosionDamage()
  2180. end
  2181.  
  2182. function GM:PropBreak(attacker, ent)
  2183. gamemode.Call("PropBroken", ent, attacker)
  2184. end
  2185.  
  2186. function GM:PropBroken(ent, attacker)
  2187. end
  2188.  
  2189. function GM:NestDestroyed(ent, attacker)
  2190. end
  2191.  
  2192. function GM:EntityTakeDamage(ent, dmginfo)
  2193. local attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor()
  2194.  
  2195. if attacker == inflictor and attacker:IsProjectile() and dmginfo:GetDamageType() == DMG_CRUSH then -- Fixes projectiles doing physics-based damage.
  2196. dmginfo:SetDamage(0)
  2197. dmginfo:ScaleDamage(0)
  2198. return
  2199. end
  2200.  
  2201. if ent._BARRICADEBROKEN and not (attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD) then
  2202. dmginfo:SetDamage(dmginfo:GetDamage() * 3)
  2203. end
  2204.  
  2205. if ent.GetObjectHealth and not (attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN) then
  2206. ent.m_LastDamaged = CurTime()
  2207. end
  2208.  
  2209. if ent.ProcessDamage and ent:ProcessDamage(dmginfo) then return end
  2210. attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor()
  2211.  
  2212. -- Don't allow blowing up props during wave 0.
  2213. if self:GetWave() <= 0 and string.sub(ent:GetClass(), 1, 12) == "prop_physics" and inflictor.NoPropDamageDuringWave0 then
  2214. dmginfo:SetDamage(0)
  2215. dmginfo:SetDamageType(DMG_BULLET)
  2216. return
  2217. end
  2218.  
  2219. -- We need to stop explosive chains team killing.
  2220. if inflictor:IsValid() then
  2221. local dmgtype = dmginfo:GetDamageType()
  2222. if dmgtype == DMG_BLAST or dmgtype == DMG_BURN or dmgtype == DMG_SLOWBURN then
  2223. if ent:IsPlayer() then
  2224. if inflictor.LastExplosionTeam == ent:Team() and inflictor.LastExplosionAttacker ~= ent and inflictor.LastExplosionTime and CurTime() < inflictor.LastExplosionTime + 10 then -- Player damaged by physics object explosion / fire.
  2225. dmginfo:SetDamage(0)
  2226. dmginfo:ScaleDamage(0)
  2227. return
  2228. end
  2229. 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 / fire.
  2230. ent.LastExplosionAttacker = inflictor.LastExplosionAttacker
  2231. ent.LastExplosionTeam = inflictor.LastExplosionTeam
  2232. ent.LastExplosionTime = CurTime()
  2233. end
  2234. elseif inflictor:IsPlayer() and string.sub(ent:GetClass(), 1, 12) == "prop_physics" then -- Physics object damaged by player.
  2235. if inflictor:Team() == TEAM_HUMAN then
  2236. local phys = ent:GetPhysicsObject()
  2237. if phys:IsValid() and phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) and inflictor:GetCarry() ~= ent or ent._LastDropped and CurTime() < ent._LastDropped + 3 and ent._LastDroppedBy ~= inflictor then -- Human player damaged a physics object while it was being carried or recently carried. They weren't the carrier.
  2238. dmginfo:SetDamage(0)
  2239. dmginfo:ScaleDamage(0)
  2240. return
  2241. end
  2242. end
  2243.  
  2244. ent.LastExplosionAttacker = inflictor
  2245. ent.LastExplosionTeam = inflictor:Team()
  2246. ent.LastExplosionTime = CurTime()
  2247. end
  2248. end
  2249.  
  2250. -- Prop is nailed. Forward damage to the nails.
  2251. if ent:DamageNails(attacker, inflictor, dmginfo:GetDamage(), dmginfo) then return end
  2252.  
  2253. local dispatchdamagedisplay = false
  2254.  
  2255. local entclass = ent:GetClass()
  2256.  
  2257. if ent:IsPlayer() then
  2258. dispatchdamagedisplay = true
  2259. elseif ent.PropHealth then -- A prop that was invulnerable and converted to vulnerable.
  2260. if self.NoPropDamageFromHumanMelee and attacker:IsPlayer() and attacker:Team() == TEAM_HUMAN and inflictor.IsMelee then
  2261. dmginfo:SetDamage(0)
  2262. return
  2263. end
  2264.  
  2265. if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.PropHealth) then
  2266. attacker:AntiGrief(dmginfo)
  2267. if dmginfo:GetDamage() <= 0 then return end
  2268. end
  2269.  
  2270. ent.PropHealth = ent.PropHealth - dmginfo:GetDamage()
  2271.  
  2272. dispatchdamagedisplay = true
  2273.  
  2274. if ent.PropHealth <= 0 then
  2275. local effectdata = EffectData()
  2276. effectdata:SetOrigin(ent:GetPos())
  2277. util.Effect("Explosion", effectdata, true, true)
  2278. ent:Fire("break")
  2279.  
  2280. gamemode.Call("PropBroken", ent, attacker)
  2281. else
  2282. local brit = math.Clamp(ent.PropHealth / ent.TotalHealth, 0, 1)
  2283. local col = ent:GetColor()
  2284. col.r = 255
  2285. col.g = 255 * brit
  2286. col.b = 255 * brit
  2287. ent:SetColor(col)
  2288. end
  2289. elseif entclass == "func_door_rotating" then
  2290. if ent:GetKeyValues().damagefilter == "invul" or ent.Broken then return end
  2291.  
  2292. if not ent.Heal then
  2293. local br = ent:BoundingRadius()
  2294. if br > 80 then return end -- Don't break these kinds of doors that are bigger than this.
  2295.  
  2296. local health = br * 35
  2297. ent.Heal = health
  2298. ent.TotalHeal = health
  2299. end
  2300.  
  2301. if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
  2302. attacker:AntiGrief(dmginfo)
  2303. if dmginfo:GetDamage() <= 0 then return end
  2304. end
  2305.  
  2306. if dmginfo:GetDamage() >= 20 and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
  2307. ent:EmitSound(math.random(2) == 1 and "npc/zombie/zombie_pound_door.wav" or "ambient/materials/door_hit1.wav")
  2308. end
  2309.  
  2310. ent.Heal = ent.Heal - dmginfo:GetDamage()
  2311. local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
  2312. local col = ent:GetColor()
  2313. col.r = 255
  2314. col.g = 255 * brit
  2315. col.b = 255 * brit
  2316. ent:SetColor(col)
  2317.  
  2318. dispatchdamagedisplay = true
  2319.  
  2320. if ent.Heal <= 0 then
  2321. ent.Broken = true
  2322.  
  2323. ent:EmitSound("Breakable.Metal")
  2324. ent:Fire("unlock", "", 0)
  2325. ent:Fire("open", "", 0.01) -- Trigger any area portals.
  2326. ent:Fire("break", "", 0.1)
  2327. ent:Fire("kill", "", 0.15)
  2328. end
  2329. elseif entclass == "prop_door_rotating" then
  2330. if ent:GetKeyValues().damagefilter == "invul" or ent:HasSpawnFlags(2048) or ent.Broken then return end
  2331.  
  2332. ent.Heal = ent.Heal or ent:BoundingRadius() * 35
  2333. ent.TotalHeal = ent.TotalHeal or ent.Heal
  2334.  
  2335. if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
  2336. attacker:AntiGrief(dmginfo)
  2337. if dmginfo:GetDamage() <= 0 then return end
  2338. end
  2339.  
  2340. if dmginfo:GetDamage() >= 20 and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
  2341. ent:EmitSound(math.random(2) == 1 and "npc/zombie/zombie_pound_door.wav" or "ambient/materials/door_hit1.wav")
  2342. end
  2343.  
  2344. ent.Heal = ent.Heal - dmginfo:GetDamage()
  2345. local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
  2346. local col = ent:GetColor()
  2347. col.r = 255
  2348. col.g = 255 * brit
  2349. col.b = 255 * brit
  2350. ent:SetColor(col)
  2351.  
  2352. dispatchdamagedisplay = true
  2353.  
  2354. if ent.Heal <= 0 then
  2355. ent.Broken = true
  2356.  
  2357. ent:EmitSound("Breakable.Metal")
  2358. ent:Fire("unlock", "", 0)
  2359. ent:Fire("open", "", 0.01) -- Trigger any area portals.
  2360. ent:Fire("break", "", 0.1)
  2361. ent:Fire("kill", "", 0.15)
  2362.  
  2363. local physprop = ents.Create("prop_physics")
  2364. if physprop:IsValid() then
  2365. physprop:SetPos(ent:GetPos())
  2366. physprop:SetAngles(ent:GetAngles())
  2367. physprop:SetSkin(ent:GetSkin() or 0)
  2368. physprop:SetMaterial(ent:GetMaterial())
  2369. physprop:SetModel(ent:GetModel())
  2370. physprop:Spawn()
  2371. physprop:SetPhysicsAttacker(attacker)
  2372. if attacker:IsValid() then
  2373. local phys = physprop:GetPhysicsObject()
  2374. if phys:IsValid() then
  2375. phys:SetVelocityInstantaneous((physprop:NearestPoint(attacker:EyePos()) - attacker:EyePos()):GetNormalized() * math.Clamp(dmginfo:GetDamage() * 3, 40, 300))
  2376. end
  2377. end
  2378. if physprop:GetMaxHealth() == 1 and physprop:Health() == 0 then
  2379. local health = math.ceil((physprop:OBBMins():Length() + physprop:OBBMaxs():Length()) * 2)
  2380. if health < 2000 then
  2381. physprop.PropHealth = health
  2382. physprop.TotalHealth = health
  2383. end
  2384. end
  2385. end
  2386. end
  2387. elseif entclass == "func_physbox" then
  2388. local holder, status = ent:GetHolder()
  2389. if holder then status:Remove() end
  2390.  
  2391. if ent:GetKeyValues().damagefilter == "invul" then return end
  2392.  
  2393. ent.Heal = ent.Heal or ent:BoundingRadius() * 35
  2394. ent.TotalHeal = ent.TotalHeal or ent.Heal
  2395.  
  2396. if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent.TotalHeal) then
  2397. attacker:AntiGrief(dmginfo)
  2398. if dmginfo:GetDamage() <= 0 then return end
  2399. end
  2400.  
  2401. ent.Heal = ent.Heal - dmginfo:GetDamage()
  2402. local brit = math.Clamp(ent.Heal / ent.TotalHeal, 0, 1)
  2403. local col = ent:GetColor()
  2404. col.r = 255
  2405. col.g = 255 * brit
  2406. col.b = 255 * brit
  2407. ent:SetColor(col)
  2408.  
  2409. dispatchdamagedisplay = true
  2410.  
  2411. if ent.Heal <= 0 then
  2412. local foundaxis = false
  2413. local entname = ent:GetName()
  2414. local allaxis = ents.FindByClass("phys_hinge")
  2415. for _, axis in pairs(allaxis) do
  2416. local keyvalues = axis:GetKeyValues()
  2417. if keyvalues.attach1 == entname or keyvalues.attach2 == entname then
  2418. foundaxis = true
  2419. axis:Remove()
  2420. ent.Heal = ent.Heal + 120
  2421. end
  2422. end
  2423.  
  2424. if not foundaxis then
  2425. ent:Fire("break", "", 0)
  2426. end
  2427. end
  2428. elseif entclass == "func_breakable" then
  2429. if ent:GetKeyValues().damagefilter == "invul" then return end
  2430.  
  2431. if gamemode.Call("ShouldAntiGrief", ent, attacker, dmginfo, ent:GetMaxHealth()) then
  2432. attacker:AntiGrief(dmginfo, true)
  2433. if dmginfo:GetDamage() <= 0 then return end
  2434. end
  2435.  
  2436. if ent:Health() == 0 and ent:GetMaxHealth() == 1 then return end
  2437.  
  2438. local brit = math.Clamp(ent:Health() / ent:GetMaxHealth(), 0, 1)
  2439. local col = ent:GetColor()
  2440. col.r = 255
  2441. col.g = 255 * brit
  2442. col.b = 255 * brit
  2443. ent:SetColor(col)
  2444.  
  2445. dispatchdamagedisplay = true
  2446. elseif ent:IsBarricadeProp() and attacker:IsPlayer() and attacker:Team() == TEAM_UNDEAD then
  2447. dispatchdamagedisplay = true
  2448. end
  2449.  
  2450. if dmginfo:GetDamage() > 0 or ent:IsPlayer() and ent:GetZombieClassTable().Name == "Shade" then
  2451. local holder, status = ent:GetHolder()
  2452. if holder then status:Remove() end
  2453.  
  2454. if attacker:IsPlayer() and dispatchdamagedisplay then
  2455. self:DamageFloater(attacker, ent, dmginfo)
  2456. end
  2457. end
  2458. end
  2459.  
  2460. function GM:DamageFloater(attacker, victim, dmginfo)
  2461. local dmgpos = dmginfo:GetDamagePosition()
  2462. if dmgpos == vector_origin then dmgpos = victim:NearestPoint(attacker:EyePos()) end
  2463.  
  2464. net.Start(victim:IsPlayer() and "zs_dmg" or "zs_dmg_prop")
  2465. if INFDAMAGEFLOATER then
  2466. INFDAMAGEFLOATER = nil
  2467. net.WriteUInt(9999, 16)
  2468. else
  2469. net.WriteUInt(math.ceil(dmginfo:GetDamage()), 16)
  2470. end
  2471. net.WriteVector(dmgpos)
  2472. net.Send(attacker)
  2473. end
  2474.  
  2475. function GM:SetRandomToZombie()
  2476. local plays = team.GetPlayers(TEAM_HUMAN)
  2477. local pl = plays[math.random(#plays)]
  2478.  
  2479. if not pl then return end
  2480.  
  2481. pl:ChangeTeam(TEAM_UNDEAD)
  2482. pl:SetFrags(0)
  2483. pl:SetDeaths(0)
  2484.  
  2485. self.StartingZombie[pl:UniqueID()] = true
  2486. self.PreviouslyDied[pl:UniqueID()] = CurTime()
  2487. pl:UnSpectateAndSpawn()
  2488.  
  2489. return pl
  2490. end
  2491.  
  2492. function GM:OnPlayerChangedTeam(pl, oldteam, newteam)
  2493. if newteam == TEAM_UNDEAD then
  2494. pl:SetPoints(0)
  2495. pl.DamagedBy = {}
  2496. pl:SetBarricadeGhosting(false)
  2497. self.CheckedOut[pl:UniqueID()] = true
  2498. elseif newteam == TEAM_HUMAN then
  2499. self.PreviouslyDied[pl:UniqueID()] = nil
  2500. end
  2501.  
  2502. pl.m_PointQueue = 0
  2503.  
  2504. timer.Simple(0, function() gamemode.Call("CalculateInfliction") end)
  2505. end
  2506.  
  2507. function GM:SetPantsMode(mode)
  2508. if self.ZombieEscape then return end
  2509.  
  2510. self.PantsMode = mode and self.ZombieClasses["Zombie Legs"] ~= nil and not self:IsClassicMode() and not self:IsBabyMode()
  2511.  
  2512. if self.PantsMode then
  2513. local index = self.ZombieClasses["Zombie Legs"].Index
  2514.  
  2515. self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2516. self.DefaultZombieClass = index
  2517.  
  2518. for _, pl in pairs(player.GetAll()) do
  2519. local classname = pl:GetZombieClassTable().Name
  2520. if classname ~= "Zombie Legs" and classname ~= "Crow" then
  2521. if pl:Team() == TEAM_UNDEAD then
  2522. pl:KillSilent()
  2523. pl:SetZombieClass(index)
  2524. pl:UnSpectateAndSpawn()
  2525. else
  2526. pl:SetZombieClass(index)
  2527. end
  2528. end
  2529. pl.DeathClass = index
  2530. end
  2531. else
  2532. self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2533.  
  2534. for _, pl in pairs(player.GetAll()) do
  2535. if pl:GetZombieClassTable().Name == "Zombie Legs" then
  2536. if pl:Team() == TEAM_UNDEAD then
  2537. pl:KillSilent()
  2538. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2539. pl:UnSpectateAndSpawn()
  2540. else
  2541. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2542. end
  2543. end
  2544. end
  2545. end
  2546. end
  2547.  
  2548. function GM:SetClassicMode(mode)
  2549. if self.ZombieEscape then return end
  2550.  
  2551. self.ClassicMode = mode and self.ZombieClasses["Classic Zombie"] ~= nil and not self.PantsMode and not self:IsBabyMode()
  2552.  
  2553. SetGlobalBool("classicmode", self.ClassicMode)
  2554.  
  2555. if self:IsClassicMode() then
  2556. util.RemoveAll("prop_nail")
  2557.  
  2558. local index = self.ZombieClasses["Classic Zombie"].Index
  2559.  
  2560. self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2561. self.DefaultZombieClass = index
  2562.  
  2563. for _, pl in pairs(player.GetAll()) do
  2564. local classname = pl:GetZombieClassTable().Name
  2565. if classname ~= "Classic Zombie" and classname ~= "Crow" then
  2566. if pl:Team() == TEAM_UNDEAD then
  2567. pl:KillSilent()
  2568. pl:SetZombieClass(index)
  2569. pl:UnSpectateAndSpawn()
  2570. else
  2571. pl:SetZombieClass(index)
  2572. end
  2573. end
  2574. pl.DeathClass = index
  2575. end
  2576. else
  2577. self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2578.  
  2579. for _, pl in pairs(player.GetAll()) do
  2580. if pl:GetZombieClassTable().Name == "Classic Zombie" then
  2581. if pl:Team() == TEAM_UNDEAD then
  2582. pl:KillSilent()
  2583. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2584. pl:UnSpectateAndSpawn()
  2585. else
  2586. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2587. end
  2588. end
  2589. end
  2590. end
  2591. end
  2592.  
  2593. function GM:SetBabyMode(mode)
  2594. if self.ZombieEscape then return end
  2595.  
  2596. self.BabyMode = mode and self.ZombieClasses["Gore Child"] ~= nil and not self.PantsMode and not self:IsClassicMode()
  2597.  
  2598. SetGlobalBool("babymode", self.BabyMode)
  2599.  
  2600. if self:IsBabyMode() then
  2601. local index = self.ZombieClasses["Gore Child"].Index
  2602.  
  2603. self.PreOverrideDefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2604. self.DefaultZombieClass = index
  2605.  
  2606. for _, pl in pairs(player.GetAll()) do
  2607. local classname = pl:GetZombieClassTable().Name
  2608. if classname ~= "Gore Child" and classname ~= "Giga Gore Child" and classname ~= "Crow" then
  2609. if pl:Team() == TEAM_UNDEAD then
  2610. pl:KillSilent()
  2611. pl:SetZombieClass(index)
  2612. pl:UnSpectateAndSpawn()
  2613. else
  2614. pl:SetZombieClass(index)
  2615. end
  2616. end
  2617. pl.DeathClass = index
  2618. end
  2619. else
  2620. self.DefaultZombieClass = self.PreOverrideDefaultZombieClass or self.DefaultZombieClass
  2621.  
  2622. for _, pl in pairs(player.GetAll()) do
  2623. if pl:GetZombieClassTable().Name == "Gore Child" then
  2624. if pl:Team() == TEAM_UNDEAD then
  2625. pl:KillSilent()
  2626. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2627. pl:UnSpectateAndSpawn()
  2628. else
  2629. pl:SetZombieClass(self.DefaultZombieClass or 1)
  2630. end
  2631. end
  2632. end
  2633. end
  2634. end
  2635.  
  2636. function GM:SetClosestsToZombie()
  2637. local allplayers = player.GetAll()
  2638. local numplayers = #allplayers
  2639. if numplayers <= 1 then return end
  2640.  
  2641. local desiredzombies = self:GetDesiredStartingZombies()
  2642.  
  2643. self:SortZombieSpawnDistances(allplayers)
  2644.  
  2645. local zombies = {}
  2646. for _, pl in pairs(allplayers) do
  2647. if pl:Team() ~= TEAM_HUMAN or not pl:Alive() then
  2648. table.insert(zombies, pl)
  2649. end
  2650. end
  2651.  
  2652. -- Need to place some people back on the human team.
  2653. if #zombies > desiredzombies then
  2654. local toswap = #zombies - desiredzombies
  2655. for _, pl in pairs(zombies) do
  2656. if pl.DiedDuringWave0 and pl:GetInfo("zs_alwaysvolunteer") ~= "1" then
  2657. pl:SetTeam(TEAM_HUMAN)
  2658. pl:UnSpectateAndSpawn()
  2659. toswap = toswap - 1
  2660. if toswap <= 0 then
  2661. break
  2662. end
  2663. end
  2664. end
  2665. end
  2666.  
  2667. for i = 1, desiredzombies do
  2668. local pl = allplayers[i]
  2669. if pl:Team() ~= TEAM_UNDEAD then
  2670. pl:ChangeTeam(TEAM_UNDEAD)
  2671. self.PreviouslyDied[pl:UniqueID()] = CurTime()
  2672. end
  2673. pl:SetFrags(0)
  2674. pl:SetDeaths(0)
  2675. self.StartingZombie[pl:UniqueID()] = true
  2676. pl:UnSpectateAndSpawn()
  2677. end
  2678.  
  2679. for _, pl in pairs(allplayers) do
  2680. if pl:Team() == TEAM_HUMAN and pl._ZombieSpawnDistance <= 128 then
  2681. pl:SetPos(self:PlayerSelectSpawn(pl):GetPos())
  2682. end
  2683. end
  2684. end
  2685.  
  2686. function GM:AllowPlayerPickup(pl, ent)
  2687. return false
  2688. end
  2689.  
  2690. function GM:PlayerShouldTakeDamage(pl, attacker)
  2691. if attacker.PBAttacker and attacker.PBAttacker:IsValid() and CurTime() < attacker.NPBAttacker then -- Protection against prop_physbox team killing. physboxes don't respond to SetPhysicsAttacker()
  2692. attacker = attacker.PBAttacker
  2693. end
  2694.  
  2695. if attacker:IsPlayer() and attacker ~= pl and not attacker.AllowTeamDamage and not pl.AllowTeamDamage and attacker:Team() == pl:Team() then return false end
  2696.  
  2697. return true
  2698. end
  2699.  
  2700. function GM:PlayerHurt(victim, attacker, healthremaining, damage)
  2701. if 0 < healthremaining then
  2702. victim:PlayPainSound()
  2703. end
  2704.  
  2705. if victim:Team() == TEAM_HUMAN then
  2706. victim.BonusDamageCheck = CurTime()
  2707.  
  2708. if healthremaining < 75 and 1 <= healthremaining then
  2709. victim:ResetSpeed(nil, healthremaining)
  2710. end
  2711. end
  2712.  
  2713. if attacker:IsValid() then
  2714. if attacker:IsPlayer() then
  2715. victim:SetLastAttacker(attacker)
  2716.  
  2717. local myteam = attacker:Team()
  2718. local otherteam = victim:Team()
  2719. if myteam ~= otherteam then
  2720. damage = math.min(damage, victim.m_PreHurtHealth)
  2721. victim.m_PreHurtHealth = healthremaining
  2722.  
  2723. attacker.DamageDealt[myteam] = attacker.DamageDealt[myteam] + damage
  2724.  
  2725. if myteam == TEAM_UNDEAD then
  2726. attacker:AddLifeHumanDamage(damage)
  2727. elseif otherteam == TEAM_UNDEAD then
  2728. victim.DamagedBy[attacker] = (victim.DamagedBy[attacker] or 0) + damage
  2729. if (not victim.m_LastWaveStartSpawn or CurTime() >= victim.m_LastWaveStartSpawn + 3)
  2730. and (healthremaining <= 0 or not victim.m_LastGasHeal or CurTime() >= victim.m_LastGasHeal + 2) then
  2731. attacker.m_PointQueue = attacker.m_PointQueue + damage / victim:GetMaxHealth() * (victim:GetZombieClassTable().Points or 0)
  2732. end
  2733. attacker.m_LastDamageDealtPosition = victim:GetPos()
  2734. attacker.m_LastDamageDealt = CurTime()
  2735. end
  2736. end
  2737. elseif attacker:GetClass() == "trigger_hurt" then
  2738. victim.LastHitWithTriggerHurt = CurTime()
  2739. end
  2740. end
  2741. end
  2742.  
  2743. -- Don't change speed instantly to stop people from shooting and then running away with a faster weapon.
  2744. function GM:WeaponDeployed(pl, wep)
  2745. local timername = tostring(pl).."speedchange"
  2746. timer.Destroy(timername)
  2747.  
  2748. local speed = pl:ResetSpeed(true) -- Determine what speed we SHOULD get without actually setting it.
  2749. if speed < pl:GetMaxSpeed() then
  2750. pl:SetSpeed(speed)
  2751. elseif pl:GetMaxSpeed() < speed then
  2752. timer.CreateEx(timername, 0.333, 1, ValidFunction, pl, "SetHumanSpeed", speed)
  2753. end
  2754. end
  2755.  
  2756. function GM:KeyPress(pl, key)
  2757. if key == IN_USE then
  2758. if pl:Team() == TEAM_HUMAN and pl:Alive() then
  2759. if pl:IsCarrying() then
  2760. pl.status_human_holding:RemoveNextFrame()
  2761. else
  2762. self:TryHumanPickup(pl, pl:TraceLine(64).Entity)
  2763. end
  2764. end
  2765. elseif key == IN_SPEED then
  2766. if pl:Alive() then
  2767. if pl:Team() == TEAM_HUMAN then
  2768. pl:DispatchAltUse()
  2769. elseif pl:Team() == TEAM_UNDEAD then
  2770. pl:CallZombieFunction("AltUse")
  2771. end
  2772. end
  2773. elseif key == IN_ZOOM then
  2774. if pl:Team() == TEAM_HUMAN and pl:Alive() and pl:IsOnGround() and not self.ZombieEscape then --and pl:GetGroundEntity():IsWorld() then
  2775. pl:SetBarricadeGhosting(true)
  2776. end
  2777. end
  2778. end
  2779.  
  2780. function GM:GetNearestSpawn(pos, teamid)
  2781. local nearest = NULL
  2782.  
  2783. local nearestdist = math.huge
  2784. for _, ent in pairs(team.GetValidSpawnPoint(teamid)) do
  2785. if ent.Disabled then continue end
  2786.  
  2787. local dist = ent:GetPos():Distance(pos)
  2788. if dist < nearestdist then
  2789. nearestdist = dist
  2790. nearest = ent
  2791. end
  2792. end
  2793.  
  2794. return nearest
  2795. end
  2796.  
  2797. function GM:EntityWouldBlockSpawn(ent)
  2798. local spawnpoint = self:GetNearestSpawn(ent:GetPos(), TEAM_UNDEAD)
  2799.  
  2800. if spawnpoint:IsValid() then
  2801. local spawnpos = spawnpoint:GetPos()
  2802. if spawnpos:Distance(ent:NearestPoint(spawnpos)) <= 40 then return true end
  2803. end
  2804.  
  2805. return false
  2806. end
  2807.  
  2808. function GM:GetNearestSpawnDistance(pos, teamid)
  2809. local nearest = self:GetNearestSpawn(pos, teamid)
  2810. if nearest:IsValid() then
  2811. return nearest:GetPos():Distance(pos)
  2812. end
  2813.  
  2814. return -1
  2815. end
  2816.  
  2817. function GM:PlayerUse(pl, ent)
  2818. if not pl:Alive() or pl:Team() == TEAM_UNDEAD and pl:GetZombieClassTable().NoUse or pl:GetBarricadeGhosting() then return false end
  2819.  
  2820. if pl:IsHolding() and pl:GetHolding() ~= ent then return false end
  2821.  
  2822. local entclass = ent:GetClass()
  2823. if entclass == "prop_door_rotating" then
  2824. if CurTime() < (ent.m_AntiDoorSpam or 0) then -- Prop doors can be glitched shut by mashing the use button.
  2825. return false
  2826. end
  2827. ent.m_AntiDoorSpam = CurTime() + 0.85
  2828. elseif entclass == "item_healthcharger" then
  2829. if pl:Team() == TEAM_UNDEAD then return false end
  2830. elseif pl:Team() == TEAM_HUMAN and not pl:IsCarrying() and pl:KeyPressed(IN_USE) then
  2831. self:TryHumanPickup(pl, ent)
  2832. end
  2833.  
  2834. return true
  2835. end
  2836.  
  2837. function GM:PlayerDeath(pl, inflictor, attacker)
  2838. end
  2839.  
  2840. function GM:PlayerDeathSound()
  2841. return true
  2842. end
  2843.  
  2844. local function SortDist(a, b)
  2845. return a._temp < b._temp
  2846. end
  2847. function GM:CanPlayerSuicide(pl)
  2848. if self.RoundEnded or pl:HasWon() then return false end
  2849.  
  2850. if pl:Team() == TEAM_HUMAN then
  2851. if self:GetWave() <= self.NoSuicideWave then
  2852. pl:PrintTranslatedMessage(HUD_PRINTCENTER, "give_time_before_suicide")
  2853. return false
  2854. end
  2855.  
  2856. -- If a person is going to suicide with no last attacker, give the kill to the closest zombie.
  2857. if not IsValid(pl:GetLastAttacker()) then
  2858. local plpos = pl:EyePos()
  2859. local tosort = {}
  2860. for _, zom in pairs(team.GetPlayers(TEAM_UNDEAD)) do
  2861. if zom:Alive() then
  2862. local dist = zom:GetPos():Distance(plpos)
  2863. if dist <= 512 then
  2864. zom._temp = dist
  2865. table.insert(tosort, zom)
  2866. end
  2867. end
  2868. end
  2869.  
  2870. table.sort(tosort, SortDist)
  2871.  
  2872. if tosort[1] then
  2873. pl:SetLastAttacker(tosort[1])
  2874. end
  2875. end
  2876. elseif pl:Team() == TEAM_UNDEAD then
  2877. local ret = pl:CallZombieFunction("CanPlayerSuicide")
  2878. if ret == false then return false end
  2879. end
  2880.  
  2881. return pl:GetObserverMode() == OBS_MODE_NONE and pl:Alive() and (not pl.SpawnNoSuicide or pl.SpawnNoSuicide < CurTime())
  2882. end
  2883.  
  2884. function GM:DefaultRevive(pl)
  2885. local status = pl:GiveStatus("revive")
  2886. if status and status:IsValid() then
  2887. status:SetReviveTime(CurTime() + 2)
  2888. end
  2889. end
  2890.  
  2891. function GM:HumanKilledZombie(pl, attacker, inflictor, dmginfo, headshot, suicide)
  2892. if (pl:GetZombieClassTable().Points or 0) == 0 or self.RoundEnded then return end
  2893.  
  2894. -- Simply distributes based on damage but also do some stuff for assists.
  2895.  
  2896. local totaldamage = 0
  2897. for otherpl, dmg in pairs(pl.DamagedBy) do
  2898. if otherpl:IsValid() and otherpl:Team() == TEAM_HUMAN then
  2899. totaldamage = totaldamage + dmg
  2900. end
  2901. end
  2902.  
  2903. local mostassistdamage = 0
  2904. local halftotaldamage = totaldamage / 2
  2905. local mostdamager
  2906. for otherpl, dmg in pairs(pl.DamagedBy) do
  2907. if otherpl ~= attacker and otherpl:IsValid() and otherpl:Team() == TEAM_HUMAN and dmg > mostassistdamage and dmg >= halftotaldamage then
  2908. mostassistdamage = dmg
  2909. mostdamager = otherpl
  2910. end
  2911. end
  2912.  
  2913. attacker.ZombiesKilled = attacker.ZombiesKilled + 1
  2914.  
  2915. if mostdamager then
  2916. attacker:PointCashOut(pl, FM_LOCALKILLOTHERASSIST)
  2917. mostdamager:PointCashOut(pl, FM_LOCALASSISTOTHERKILL)
  2918.  
  2919. mostdamager.ZombiesKilledAssists = mostdamager.ZombiesKilledAssists + 1
  2920. else
  2921. attacker:PointCashOut(pl, FM_NONE)
  2922. end
  2923.  
  2924. gamemode.Call("PostHumanKilledZombie", pl, attacker, inflictor, dmginfo, mostdamager, mostassistdamage, headshot)
  2925.  
  2926. return mostdamager
  2927. end
  2928.  
  2929. function GM:PostHumanKilledZombie(pl, attacker, inflictor, dmginfo, assistpl, assistamount, headshot)
  2930. end
  2931.  
  2932. function GM:ZombieKilledHuman(pl, attacker, inflictor, dmginfo, headshot, suicide)
  2933. if self.RoundEnded then return end
  2934.  
  2935. local plpos = pl:GetPos()
  2936. local dist = 99999
  2937. for _, ent in pairs(team.GetValidSpawnPoint(TEAM_UNDEAD)) do
  2938. dist = math.min(math.ceil(ent:GetPos():Distance(plpos)), dist)
  2939. end
  2940. pl.ZombieSpawnDeathDistance = dist
  2941.  
  2942. attacker:AddBrains(1)
  2943. attacker:AddLifeBrainsEaten(1)
  2944.  
  2945. if not pl.Gibbed and not suicide then
  2946. local status = pl:GiveStatus("revive_slump_human")
  2947. if status then
  2948. status:SetReviveTime(CurTime() + 4)
  2949. status:SetZombieInitializeTime(CurTime() + 2)
  2950. end
  2951.  
  2952. 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"]
  2953. if classtab then
  2954. pl:SetZombieClass(classtab.Index)
  2955. end
  2956. end
  2957.  
  2958. gamemode.Call("PostZombieKilledHuman", pl, attacker, inflictor, dmginfo, headshot, suicide)
  2959.  
  2960. return attacker:Frags()
  2961. end
  2962.  
  2963. function GM:PostZombieKilledHuman(pl, attacker, inflictor, dmginfo, headshot, suicide)
  2964. end
  2965.  
  2966. local function DelayedChangeToZombie(pl)
  2967. if pl:IsValid() then
  2968. if pl.ChangeTeamFrags then
  2969. pl:SetFrags(pl.ChangeTeamFrags)
  2970. pl.ChangeTeamFrags = 0
  2971. end
  2972.  
  2973. pl:ChangeTeam(TEAM_UNDEAD)
  2974. end
  2975. end
  2976.  
  2977. function GM:DoPlayerDeath(pl, attacker, dmginfo)
  2978. pl:RemoveStatus("confusion", false, true)
  2979. pl:RemoveStatus("ghoultouch", false, true)
  2980.  
  2981. local inflictor = dmginfo:GetInflictor()
  2982. local plteam = pl:Team()
  2983. local ct = CurTime()
  2984. local suicide = attacker == pl or attacker:IsWorld()
  2985.  
  2986. pl:Freeze(false)
  2987.  
  2988. local headshot = pl:LastHitGroup() == HITGROUP_HEAD and pl.m_LastHeadShot and CurTime() <= pl.m_LastHeadShot + 0.1
  2989.  
  2990. if suicide then attacker = pl:GetLastAttacker() or attacker end
  2991. pl:SetLastAttacker()
  2992.  
  2993. if inflictor == NULL then inflictor = attacker end
  2994.  
  2995. if inflictor == attacker and attacker:IsPlayer() then
  2996. local wep = attacker:GetActiveWeapon()
  2997. if wep:IsValid() then
  2998. inflictor = wep
  2999. end
  3000. end
  3001.  
  3002. if headshot then
  3003. local effectdata = EffectData()
  3004. effectdata:SetOrigin(dmginfo:GetDamagePosition())
  3005. local force = dmginfo:GetDamageForce()
  3006. effectdata:SetMagnitude(force:Length() * 3)
  3007. effectdata:SetNormal(force:GetNormalized())
  3008. effectdata:SetEntity(pl)
  3009. util.Effect("headshot", effectdata, true, true)
  3010. end
  3011.  
  3012. if not pl:CallZombieFunction("OnKilled", attacker, inflictor, suicide, headshot, dmginfo) then
  3013. if pl:Health() <= -70 and not pl.NoGibs and not self.ZombieEscape then
  3014. pl:Gib(dmginfo)
  3015. elseif not pl.KnockedDown then
  3016. pl:CreateRagdoll()
  3017. end
  3018. end
  3019.  
  3020. pl:RemoveStatus("overridemodel", false, true)
  3021.  
  3022. local revive
  3023. local assistpl
  3024. if plteam == TEAM_UNDEAD then
  3025. local classtable = pl:GetZombieClassTable()
  3026.  
  3027. pl:PlayZombieDeathSound()
  3028.  
  3029. if not classtable.NoDeaths then
  3030. pl:AddDeaths(1)
  3031. end
  3032.  
  3033. if self:GetWaveActive() then
  3034. pl.StartSpectating = ct + 2
  3035. else
  3036. pl.StartCrowing = ct + 3
  3037. end
  3038.  
  3039. if attacker:IsValid() and attacker:IsPlayer() and attacker ~= pl then
  3040. if classtable.Revives and not pl.Gibbed and not headshot then
  3041. if classtable.ReviveCallback then
  3042. revive = classtable:ReviveCallback(pl, attacker, dmginfo)
  3043. elseif math.random(1, 4) ~= 1 then
  3044. self:DefaultRevive(pl)
  3045. revive = true
  3046. end
  3047. end
  3048.  
  3049. if not revive and attacker:Team() ~= TEAM_UNDEAD then
  3050. assistpl = gamemode.Call("HumanKilledZombie", pl, attacker, inflictor, dmginfo, headshot, suicide)
  3051. end
  3052. end
  3053.  
  3054. if not revive and (pl.LifeBarricadeDamage ~= 0 or pl.LifeHumanDamage ~= 0 or pl.LifeBrainsEaten ~= 0) then
  3055. net.Start("zs_lifestats")
  3056. net.WriteUInt(math.ceil(pl.LifeBarricadeDamage or 0), 24)
  3057. net.WriteUInt(math.ceil(pl.LifeHumanDamage or 0), 24)
  3058. net.WriteUInt(pl.LifeBrainsEaten or 0, 16)
  3059. net.Send(pl)
  3060. end
  3061.  
  3062. pl:CallZombieFunction("PostOnKilled", attacker, inflictor, suicide, headshot, dmginfo)
  3063. else
  3064. pl.NextSpawnTime = ct + 4
  3065.  
  3066. pl:PlayDeathSound()
  3067.  
  3068. if attacker:IsPlayer() and attacker ~= pl then
  3069. gamemode.Call("ZombieKilledHuman", pl, attacker, inflictor, dmginfo, headshot, suicide)
  3070. end
  3071.  
  3072. pl:DropAll()
  3073. timer.Simple(0, function() DelayedChangeToZombie(pl) end) -- We don't want people shooting barrels near teammates.
  3074. self.PreviouslyDied[pl:UniqueID()] = CurTime()
  3075. if self:GetWave() == 0 then
  3076. pl.DiedDuringWave0 = true
  3077. end
  3078.  
  3079. local frags = pl:Frags()
  3080. if frags < 0 then
  3081. pl.ChangeTeamFrags = math.ceil(frags / 5)
  3082. else
  3083. pl.ChangeTeamFrags = 0
  3084. end
  3085.  
  3086. if pl.SpawnedTime then
  3087. pl.SurvivalTime = math.max(ct - pl.SpawnedTime, pl.SurvivalTime or 0)
  3088. pl.SpawnedTime = nil
  3089. end
  3090.  
  3091. if team.NumPlayers(TEAM_HUMAN) <= 1 then
  3092. self.LastHumanPosition = pl:WorldSpaceCenter()
  3093.  
  3094. net.Start("zs_lasthumanpos")
  3095. net.WriteVector(self.LastHumanPosition)
  3096. net.Broadcast()
  3097. end
  3098.  
  3099. local hands = pl:GetHands()
  3100. if IsValid(hands) then
  3101. hands:Remove()
  3102. end
  3103. end
  3104.  
  3105. if revive or pl:CallZombieFunction("NoDeathMessage", attacker, dmginfo) then return end
  3106.  
  3107. if attacker == pl then
  3108. net.Start("zs_pl_kill_self")
  3109. net.WriteEntity(pl)
  3110. net.WriteUInt(plteam, 16)
  3111. net.Broadcast()
  3112. elseif attacker:IsPlayer() then
  3113. if assistpl then
  3114. net.Start("zs_pls_kill_pl")
  3115. net.WriteEntity(pl)
  3116. net.WriteEntity(attacker)
  3117. net.WriteEntity(assistpl)
  3118. net.WriteString(inflictor:GetClass())
  3119. net.WriteUInt(plteam, 16)
  3120. net.WriteUInt(attacker:Team(), 16) -- Assuming assistants are always on the same team.
  3121. net.WriteBit(headshot)
  3122. net.Broadcast()
  3123.  
  3124. gamemode.Call("PlayerKilledByPlayer", pl, assistpl, inflictor, headshot, dmginfo, true)
  3125. else
  3126. net.Start("zs_pl_kill_pl")
  3127. net.WriteEntity(pl)
  3128. net.WriteEntity(attacker)
  3129. net.WriteString(inflictor:GetClass())
  3130. net.WriteUInt(plteam, 16)
  3131. net.WriteUInt(attacker:Team(), 16)
  3132. net.WriteBit(headshot)
  3133. net.Broadcast()
  3134. end
  3135.  
  3136. gamemode.Call("PlayerKilledByPlayer", pl, attacker, inflictor, headshot, dmginfo)
  3137. else
  3138. net.Start("zs_death")
  3139. net.WriteEntity(pl)
  3140. net.WriteString(inflictor:GetClass())
  3141. net.WriteString(attacker:GetClass())
  3142. net.WriteUInt(plteam, 16)
  3143. net.Broadcast()
  3144. end
  3145. end
  3146.  
  3147. function GM:PlayerKilledByPlayer(pl, attacker, inflictor, headshot, dmginfo)
  3148. end
  3149.  
  3150. function GM:PlayerCanPickupWeapon(pl, ent)
  3151. if pl:Team() == TEAM_UNDEAD then return ent:GetClass() == pl:GetZombieClassTable().SWEP end
  3152.  
  3153. return not ent.ZombieOnly and ent:GetClass() ~= "weapon_stunstick"
  3154. end
  3155.  
  3156. function GM:PlayerFootstep(pl, vPos, iFoot, strSoundName, fVolume, pFilter)
  3157. end
  3158.  
  3159. function GM:PlayerStepSoundTime(pl, iType, bWalking)
  3160. local fStepTime = 350
  3161.  
  3162. if iType == STEPSOUNDTIME_NORMAL or iType == STEPSOUNDTIME_WATER_FOOT then
  3163. local fMaxSpeed = pl:GetMaxSpeed()
  3164. if fMaxSpeed <= 100 then
  3165. fStepTime = 400
  3166. elseif fMaxSpeed <= 300 then
  3167. fStepTime = 350
  3168. else
  3169. fStepTime = 250
  3170. end
  3171. elseif iType == STEPSOUNDTIME_ON_LADDER then
  3172. fStepTime = 450
  3173. elseif iType == STEPSOUNDTIME_WATER_KNEE then
  3174. fStepTime = 600
  3175. end
  3176.  
  3177. if pl:Crouching() then
  3178. fStepTime = fStepTime + 50
  3179. end
  3180.  
  3181. return fStepTime
  3182. end
  3183.  
  3184. concommand.Add("zsdropweapon", function(sender, command, arguments)
  3185. if GAMEMODE.ZombieEscape then return end
  3186.  
  3187. if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) or CurTime() < (sender.NextWeaponDrop or 0) or GAMEMODE.ZombieEscape then return end
  3188. sender.NextWeaponDrop = CurTime() + 0.15
  3189.  
  3190. local currentwep = sender:GetActiveWeapon()
  3191. if currentwep and currentwep:IsValid() then
  3192. local ent = sender:DropWeaponByType(currentwep:GetClass())
  3193. if ent and ent:IsValid() then
  3194. local shootpos = sender:GetShootPos()
  3195. local aimvec = sender:GetAimVector()
  3196. 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)
  3197. ent:SetAngles(sender:GetAngles())
  3198. end
  3199. end
  3200. end)
  3201.  
  3202. concommand.Add("zsemptyclip", function(sender, command, arguments)
  3203. if GAMEMODE.ZombieEscape then return end
  3204.  
  3205. if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) then return end
  3206.  
  3207. sender.NextEmptyClip = sender.NextEmptyClip or 0
  3208. if sender.NextEmptyClip <= CurTime() then
  3209. sender.NextEmptyClip = CurTime() + 0.1
  3210.  
  3211. local wep = sender:GetActiveWeapon()
  3212. if wep:IsValid() and not wep.NoMagazine then
  3213. local primary = wep:ValidPrimaryAmmo()
  3214. if primary and 0 < wep:Clip1() then
  3215. sender:GiveAmmo(wep:Clip1(), primary, true)
  3216. wep:SetClip1(0)
  3217. end
  3218. local secondary = wep:ValidSecondaryAmmo()
  3219. if secondary and 0 < wep:Clip2() then
  3220. sender:GiveAmmo(wep:Clip2(), secondary, true)
  3221. wep:SetClip2(0)
  3222. end
  3223. end
  3224. end
  3225. end)
  3226.  
  3227. concommand.Add("zsgiveammo", function(sender, command, arguments)
  3228. if GAMEMODE.ZombieEscape then return end
  3229.  
  3230. if not sender:IsValid() or not sender:Alive() or sender:Team() ~= TEAM_HUMAN then return end
  3231.  
  3232. local ammotype = arguments[1]
  3233. if not ammotype or #ammotype == 0 or not GAMEMODE.AmmoCache[ammotype] then return end
  3234.  
  3235. local count = sender:GetAmmoCount(ammotype)
  3236. if count <= 0 then
  3237. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3238. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_spare_ammo_to_give")
  3239. return
  3240. end
  3241.  
  3242. local ent
  3243. local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
  3244. if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
  3245. ent = dent
  3246. end
  3247.  
  3248. if not ent then
  3249. ent = sender:MeleeTrace(48, 2).Entity
  3250. end
  3251.  
  3252. if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
  3253. local desiredgive = math.min(count, GAMEMODE.AmmoCache[ammotype])
  3254. if desiredgive >= 1 then
  3255. sender:RemoveAmmo(desiredgive, ammotype)
  3256. ent:GiveAmmo(desiredgive, ammotype)
  3257.  
  3258. if CurTime() >= (sender.NextGiveAmmoSound or 0) then
  3259. sender.NextGiveAmmoSound = CurTime() + 1
  3260. sender:PlayGiveAmmoSound()
  3261. end
  3262.  
  3263. sender:RestartGesture(ACT_GMOD_GESTURE_ITEM_GIVE)
  3264.  
  3265. return
  3266. end
  3267. else
  3268. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3269. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
  3270. end
  3271. end)
  3272.  
  3273. concommand.Add("zsgiveweapon", function(sender, command, arguments)
  3274. if GAMEMODE.ZombieEscape then return end
  3275.  
  3276. if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) or GAMEMODE.ZombieEscape then return end
  3277.  
  3278. local currentwep = sender:GetActiveWeapon()
  3279. if currentwep and currentwep:IsValid() then
  3280. local ent
  3281. local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
  3282. if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
  3283. ent = dent
  3284. end
  3285.  
  3286. if not ent then
  3287. ent = sender:MeleeTrace(48, 2).Entity
  3288. end
  3289.  
  3290. if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
  3291. if not ent:HasWeapon(currentwep:GetClass()) then
  3292. sender:GiveWeaponByType(currentwep, ent, false)
  3293. else
  3294. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3295. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "person_has_weapon")
  3296. end
  3297. else
  3298. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3299. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
  3300. end
  3301. end
  3302. end)
  3303.  
  3304. concommand.Add("zsgiveweaponclip", function(sender, command, arguments)
  3305. if GAMEMODE.ZombieEscape then return end
  3306.  
  3307. if not (sender:IsValid() and sender:Alive() and sender:Team() == TEAM_HUMAN) then return end
  3308.  
  3309. local currentwep = sender:GetActiveWeapon()
  3310. if currentwep and currentwep:IsValid() then
  3311. local ent
  3312. local dent = Entity(tonumbersafe(arguments[2] or 0) or 0)
  3313. if GAMEMODE:ValidMenuLockOnTarget(sender, dent) then
  3314. ent = dent
  3315. end
  3316.  
  3317. if not ent then
  3318. ent = sender:MeleeTrace(48, 2).Entity
  3319. end
  3320.  
  3321. if ent and ent:IsValid() and ent:IsPlayer() and ent:Team() == TEAM_HUMAN and ent:Alive() then
  3322. if not ent:HasWeapon(currentwep:GetClass()) then
  3323. sender:GiveWeaponByType(currentwep, ent, true)
  3324. else
  3325. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3326. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "person_has_weapon")
  3327. end
  3328. else
  3329. sender:SendLua("surface.PlaySound(\"buttons/button10.wav\")")
  3330. sender:PrintTranslatedMessage(HUD_PRINTCENTER, "no_person_in_range")
  3331. end
  3332. end
  3333. end)
  3334.  
  3335. concommand.Add("zsdropammo", function(sender, command, arguments)
  3336. if GAMEMODE.ZombieEscape then return end
  3337.  
  3338. if not sender:IsValid() or not sender:Alive() or sender:Team() ~= TEAM_HUMAN or CurTime() < (sender.NextDropClip or 0) then return end
  3339.  
  3340. sender.NextDropClip = CurTime() + 0.2
  3341.  
  3342. local wep = sender:GetActiveWeapon()
  3343. if not wep:IsValid() then return end
  3344.  
  3345. local ammotype = arguments[1] or wep:GetPrimaryAmmoTypeString()
  3346. if GAMEMODE.AmmoNames[ammotype] and GAMEMODE.AmmoCache[ammotype] then
  3347. local ent = sender:DropAmmoByType(ammotype, GAMEMODE.AmmoCache[ammotype] * 2)
  3348. if ent and ent:IsValid() then
  3349. ent:SetPos(sender:EyePos() + sender:GetAimVector() * 8)
  3350. ent:SetAngles(sender:GetAngles())
  3351. local phys = ent:GetPhysicsObject()
  3352. if phys:IsValid() then
  3353. phys:Wake()
  3354. phys:SetVelocityInstantaneous(sender:GetVelocity() * 0.85)
  3355. end
  3356. end
  3357. end
  3358. end)
  3359.  
  3360. local VoiceSetTranslate = {}
  3361. VoiceSetTranslate["models/player/alyx.mdl"] = "alyx"
  3362. VoiceSetTranslate["models/player/barney.mdl"] = "barney"
  3363. VoiceSetTranslate["models/player/breen.mdl"] = "male"
  3364. VoiceSetTranslate["models/player/combine_soldier.mdl"] = "combine"
  3365. VoiceSetTranslate["models/player/combine_soldier_prisonguard.mdl"] = "combine"
  3366. VoiceSetTranslate["models/player/combine_super_soldier.mdl"] = "combine"
  3367. VoiceSetTranslate["models/player/eli.mdl"] = "male"
  3368. VoiceSetTranslate["models/player/gman_high.mdl"] = "male"
  3369. VoiceSetTranslate["models/player/kleiner.mdl"] = "male"
  3370. VoiceSetTranslate["models/player/monk.mdl"] = "monk"
  3371. VoiceSetTranslate["models/player/mossman.mdl"] = "female"
  3372. VoiceSetTranslate["models/player/odessa.mdl"] = "male"
  3373. VoiceSetTranslate["models/player/police.mdl"] = "combine"
  3374. VoiceSetTranslate["models/player/brsp.mdl"] = "female"
  3375. VoiceSetTranslate["models/player/moe_glados_p.mdl"] = "female"
  3376. VoiceSetTranslate["models/grim.mdl"] = "combine"
  3377. VoiceSetTranslate["models/jason278-players/gabe_3.mdl"] = "monk"
  3378. function GM:PlayerSpawn(pl)
  3379. pl:StripWeapons()
  3380. pl:RemoveStatus("confusion", false, true)
  3381.  
  3382. if pl:GetMaterial() ~= "" then
  3383. pl:SetMaterial("")
  3384. end
  3385.  
  3386. pl:UnSpectate()
  3387.  
  3388. pl.StartCrowing = nil
  3389. pl.StartSpectating = nil
  3390. pl.NextSpawnTime = nil
  3391. pl.Gibbed = nil
  3392.  
  3393. pl.SpawnNoSuicide = CurTime() + 1
  3394. pl.SpawnedTime = CurTime()
  3395.  
  3396. pl:ShouldDropWeapon(false)
  3397.  
  3398. pl:SetLegDamage(0)
  3399. pl:SetLastAttacker()
  3400.  
  3401. if pl:Team() == TEAM_UNDEAD then
  3402. pl:RemoveStatus("overridemodel", false, true)
  3403.  
  3404. if not pl.Revived then
  3405. pl.DamagedBy = {}
  3406. end
  3407.  
  3408. pl.LifeBarricadeDamage = 0
  3409. pl.LifeHumanDamage = 0
  3410. pl.LifeBrainsEaten = 0
  3411.  
  3412. if self:GetEscapeSequence() and self:GetEscapeStage() >= ESCAPESTAGE_BOSS then
  3413. local bossindex = pl:GetBossZombieIndex()
  3414. if bossindex ~= -1 then
  3415. pl:SetZombieClass(bossindex)
  3416. end
  3417. elseif pl.DeathClass and self:GetWaveActive() then
  3418. pl:SetZombieClass(pl.DeathClass)
  3419. pl.DeathClass = nil
  3420. end
  3421.  
  3422. local classtab = pl:GetZombieClassTable()
  3423. pl:DoHulls(pl:GetZombieClass(), TEAM_UNDEAD)
  3424.  
  3425. if classtab.Model then
  3426. pl:SetModel(classtab.Model)
  3427. elseif classtab.UsePlayerModel then
  3428. local desiredname = pl:GetInfo("cl_playermodel")
  3429. if #desiredname == 0 then
  3430. pl:SelectRandomPlayerModel()
  3431. else
  3432. pl:SetModel(player_manager.TranslatePlayerModel(desiredname))
  3433. end
  3434. elseif classtab.UsePreviousModel then
  3435. local curmodel = string.lower(pl:GetModel())
  3436. if table.HasValue(self.RestrictedModels, curmodel) or string.sub(curmodel, 1, 14) ~= "models/player/" then
  3437. pl:SelectRandomPlayerModel()
  3438. end
  3439. elseif classtab.UseRandomModel then
  3440. pl:SelectRandomPlayerModel()
  3441. else
  3442. pl:SetModel("models/player/zombie_classic.mdl")
  3443. end
  3444.  
  3445. local numundead = team.NumPlayers(TEAM_UNDEAD)
  3446. if self.OutnumberedHealthBonus <= numundead or classtab.Boss then
  3447. pl:SetHealth(classtab.Health)
  3448. else
  3449. pl:SetHealth(classtab.Health * 1.5)
  3450. end
  3451.  
  3452. if classtab.SWEP then
  3453. pl:Give(classtab.SWEP)
  3454. end
  3455.  
  3456. pl:SetNoTarget(true)
  3457. pl:SetMaxHealth(1)
  3458.  
  3459. pl:ResetSpeed()
  3460. pl:SetCrouchedWalkSpeed(classtab.CrouchedWalkSpeed or 0.70)
  3461.  
  3462. if not pl.Revived or not self:GetWaveActive() or CurTime() > self:GetWaveEnd() then
  3463. pl.StartCrowing = 0
  3464. end
  3465.  
  3466. if pl.ForceSpawnAngles then
  3467. pl:SetEyeAngles(pl.ForceSpawnAngles)
  3468. pl.ForceSpawnAngles = nil
  3469. end
  3470.  
  3471. if pl.SpawnedOnSpawnPoint and not pl.DidntSpawnOnSpawnPoint and not pl.Revived and not pl:GetZombieClassTable().NeverAlive then
  3472. pl:GiveStatus("zombiespawnbuff", 3)
  3473. end
  3474. pl.DidntSpawnOnSpawnPoint = nil
  3475. pl.SpawnedOnSpawnPoint = nil
  3476.  
  3477. pl:CallZombieFunction("OnSpawned")
  3478. else
  3479. pl.m_PointQueue = 0
  3480. pl.PackedItems = {}
  3481.  
  3482. local desiredname = pl:GetInfo("cl_playermodel")
  3483. local modelname = player_manager.TranslatePlayerModel(#desiredname == 0 and self.RandomPlayerModels[math.random(#self.RandomPlayerModels)] or desiredname)
  3484. local lowermodelname = string.lower(modelname)
  3485. if table.HasValue(self.RestrictedModels, lowermodelname) then
  3486. modelname = "models/player/alyx.mdl"
  3487. lowermodelname = modelname
  3488. end
  3489. pl:SetModel(modelname)
  3490.  
  3491. -- Cache the voice set.
  3492. if VoiceSetTranslate[lowermodelname] then
  3493. pl.VoiceSet = VoiceSetTranslate[lowermodelname]
  3494. elseif string.find(lowermodelname, "female", 1, true) then
  3495. pl.VoiceSet = "female"
  3496. else
  3497. pl.VoiceSet = "male"
  3498. end
  3499.  
  3500. pl.HumanSpeedAdder = nil
  3501.  
  3502. pl.BonusDamageCheck = CurTime()
  3503.  
  3504. pl:ResetSpeed()
  3505. pl:SetJumpPower(DEFAULT_JUMP_POWER)
  3506. pl:SetCrouchedWalkSpeed(0.65)
  3507.  
  3508. pl:SetNoTarget(false)
  3509. pl:SetMaxHealth(100)
  3510.  
  3511. if self.ZombieEscape then
  3512. pl:Give("weapon_zs_zeknife")
  3513. pl:Give("weapon_zs_zegrenade")
  3514. pl:Give(table.Random(self.ZombieEscapeWeapons))
  3515. elseif self.StartingLoadout then
  3516. self:GiveStartingLoadout(pl)
  3517. elseif pl.m_PreRedeem then
  3518. if self.RedeemLoadout then
  3519. for _, class in pairs(self.RedeemLoadout) do
  3520. pl:Give(class)
  3521. end
  3522. else
  3523. pl:Give("weapon_zs_redeemers")
  3524. pl:Give("weapon_zs_swissarmyknife")
  3525. end
  3526. end
  3527.  
  3528. local oldhands = pl:GetHands()
  3529. if IsValid(oldhands) then
  3530. oldhands:Remove()
  3531. end
  3532.  
  3533. local hands = ents.Create("zs_hands")
  3534. if hands:IsValid() then
  3535. hands:DoSetup(pl)
  3536. hands:Spawn()
  3537. end
  3538. end
  3539.  
  3540. pl:DoMuscularBones()
  3541. pl:DoNoodleArmBones()
  3542.  
  3543. local pcol = Vector(pl:GetInfo("cl_playercolor"))
  3544. pcol.x = math.Clamp(pcol.x, 0, 2.5)
  3545. pcol.y = math.Clamp(pcol.y, 0, 2.5)
  3546. pcol.z = math.Clamp(pcol.z, 0, 2.5)
  3547. pl:SetPlayerColor(pcol)
  3548.  
  3549. local wcol = Vector(pl:GetInfo("cl_weaponcolor"))
  3550. wcol.x = math.Clamp(wcol.x, 0, 2.5)
  3551. wcol.y = math.Clamp(wcol.y, 0, 2.5)
  3552. wcol.z = math.Clamp(wcol.z, 0, 2.5)
  3553. pl:SetWeaponColor(wcol)
  3554.  
  3555. pl.m_PreHurtHealth = pl:Health()
  3556. end
  3557.  
  3558. function GM:SetWave(wave)
  3559. local previouslylocked = {}
  3560. local UnlockedClasses = {}
  3561. for i, classtab in ipairs(GAMEMODE.ZombieClasses) do
  3562. if not gamemode.Call("IsClassUnlocked", classid) then
  3563. previouslylocked[i] = true
  3564. end
  3565. end
  3566.  
  3567. SetGlobalInt("wave", wave)
  3568.  
  3569. for classid in pairs(previouslylocked) do
  3570. if gamemode.Call("IsClassUnlocked", classid) then
  3571. local classtab = self.ZombieClasses[classid]
  3572. if not classtab.UnlockedNotify then
  3573. classtab.UnlockedNotify = true
  3574. table.insert(UnlockedClasses, classid)
  3575. end
  3576.  
  3577. for _, ent in pairs(ents.FindByClass("logic_classunlock")) do
  3578. local classname = GAMEMODE.ZombieClasses[classid].Name
  3579. if ent.Class == string.lower(classname) then
  3580. ent:Input("onclassunlocked", ent, ent, classname)
  3581. end
  3582. end
  3583. end
  3584. end
  3585.  
  3586. if #UnlockedClasses > 0 then
  3587. for _, pl in pairs(player.GetAll()) do
  3588. local classnames = {}
  3589. for __, classid in pairs(UnlockedClasses) do
  3590. table.insert(classnames, translate.ClientGet(pl, self.ZombieClasses[classid].TranslationName))
  3591. end
  3592. net.Start("zs_classunlock")
  3593. net.WriteString(string.AndSeparate(classnames))
  3594. net.Send(pl)
  3595. end
  3596. end
  3597. end
  3598.  
  3599. GM.NextEscapeDamage = 0
  3600. function GM:WaveStateChanged(newstate)
  3601. if newstate then
  3602. if self:GetWave() == 0 then
  3603. self:SetClosestsToZombie()
  3604.  
  3605. local humans = {}
  3606. for _, pl in pairs(player.GetAll()) do
  3607. if pl:Team() == TEAM_HUMAN and pl:Alive() then
  3608. table.insert(humans, pl)
  3609. end
  3610. end
  3611.  
  3612. if #humans >= 1 then
  3613. for _, pl in pairs(humans) do
  3614. gamemode.Call("GiveDefaultOrRandomEquipment", pl)
  3615. pl.BonusDamageCheck = CurTime()
  3616. end
  3617. end
  3618.  
  3619. -- We should spawn a crate in a random spawn point if no one has any.
  3620. if not self.ZombieEscape and #ents.FindByClass("prop_arsenalcrate") == 0 then
  3621. local have = false
  3622. for _, pl in pairs(humans) do
  3623. if pl:HasWeapon("weapon_zs_arsenalcrate") then
  3624. have = true
  3625. break
  3626. end
  3627. end
  3628.  
  3629. if not have and #humans >= 1 then
  3630. local spawn = self:PlayerSelectSpawn(humans[math.random(#humans)])
  3631. if spawn and spawn:IsValid() then
  3632. local ent = ents.Create("prop_arsenalcrate")
  3633. if ent:IsValid() then
  3634. ent:SetPos(spawn:GetPos())
  3635. ent:Spawn()
  3636. ent:DropToFloor()
  3637. ent:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER) -- Just so no one gets stuck in it.
  3638. ent.NoTakeOwnership = true
  3639. end
  3640. end
  3641. end
  3642. end
  3643. end
  3644.  
  3645. local prevwave = self:GetWave()
  3646.  
  3647. if self:GetUseSigils() and prevwave >= self:GetNumberOfWaves() then return end
  3648.  
  3649. gamemode.Call("SetWave", prevwave + 1)
  3650. gamemode.Call("SetWaveStart", CurTime())
  3651. if self.ZombieEscape then
  3652. gamemode.Call("SetWaveEnd", -1)
  3653. SetGlobalInt("numwaves", -1)
  3654. else
  3655. gamemode.Call("SetWaveEnd", self:GetWaveStart() + self:GetWaveOneLength() + (self:GetWave() - 1) * (GetGlobalBool("classicmode") and self.TimeAddedPerWaveClassic or self.TimeAddedPerWave))
  3656. end
  3657.  
  3658. net.Start("zs_wavestart")
  3659. net.WriteInt(self:GetWave(), 16)
  3660. net.WriteFloat(self:GetWaveEnd())
  3661. net.Broadcast()
  3662.  
  3663. for _, pl in pairs(player.GetAll()) do
  3664. if pl:Team() == TEAM_UNDEAD then
  3665. pl.m_LastWaveStartSpawn = CurTime()
  3666. if pl:GetZombieClassTable().Name == "Crow" then
  3667. pl:SetZombieClass(pl.DeathClass or 1)
  3668. pl:UnSpectateAndSpawn()
  3669. elseif not pl:Alive() and not pl.Revive then
  3670. pl:UnSpectateAndSpawn()
  3671. end
  3672. end
  3673. end
  3674.  
  3675. local curwave = self:GetWave()
  3676. for _, ent in pairs(ents.FindByClass("logic_waves")) do
  3677. if ent.Wave == curwave or ent.Wave == -1 then
  3678. ent:Input("onwavestart", ent, ent, curwave)
  3679. end
  3680. end
  3681. for _, ent in pairs(ents.FindByClass("logic_wavestart")) do
  3682. if ent.Wave == curwave or ent.Wave == -1 then
  3683. ent:Input("onwavestart", ent, ent, curwave)
  3684. end
  3685. end
  3686. elseif self:GetWave() >= self:GetNumberOfWaves() then -- Last wave is over
  3687. if self:GetUseSigils() then
  3688. if self:GetEscapeStage() == ESCAPESTAGE_BOSS then
  3689. self:SetEscapeStage(ESCAPESTAGE_DEATH)
  3690.  
  3691. PrintMessage(3, "Escape sequence death fog stage")
  3692.  
  3693. gamemode.Call("SetWaveEnd", -1)
  3694. elseif self:GetEscapeStage() == ESCAPESTAGE_ESCAPE then
  3695. self:SetEscapeStage(ESCAPESTAGE_BOSS)
  3696.  
  3697. -- 2 minutes to get out with everyone spawning as bosses.
  3698. gamemode.Call("SetWaveEnd", CurTime() + 120)
  3699.  
  3700. PrintMessage(3, "Escape sequence boss stage")
  3701.  
  3702. -- Start spawning boss zombies.
  3703. elseif self:GetEscapeStage() == ESCAPESTAGE_NONE then
  3704. -- If we're using sigils, remove them all and spawn the doors.
  3705. for _, sigil in pairs(ents.FindByClass("prop_obj_sigil")) do
  3706. local ent = ents.Create("prop_obj_exit")
  3707. if ent:IsValid() then
  3708. ent:SetPos(sigil.NodePos or sigil:GetPos())
  3709. ent:SetAngles(sigil:GetAngles())
  3710. ent:Spawn()
  3711. end
  3712.  
  3713. sigil:Destroy()
  3714. end
  3715.  
  3716. --[[net.Start("zs_waveend")
  3717. net.WriteInt(self:GetWave(), 16)
  3718. net.WriteFloat(CurTime())
  3719. net.Broadcast()]]
  3720. PrintMessage(3, "Escape sequence started")
  3721.  
  3722. -- 2 minutes to escape.
  3723. gamemode.Call("SetWaveActive", true)
  3724. gamemode.Call("SetWaveEnd", CurTime() + 120)
  3725. self:SetEscapeStage(ESCAPESTAGE_ESCAPE)
  3726.  
  3727. local curwave = self:GetWave()
  3728. for _, ent in pairs(ents.FindByClass("logic_waves")) do
  3729. if ent.Wave == curwave or ent.Wave == -1 then
  3730. ent:Input("onwaveend", ent, ent, curwave)
  3731. end
  3732. end
  3733. for _, ent in pairs(ents.FindByClass("logic_waveend")) do
  3734. if ent.Wave == curwave or ent.Wave == -1 then
  3735. ent:Input("onwaveend", ent, ent, curwave)
  3736. end
  3737. end
  3738. end
  3739. else
  3740. -- If not using sigils then humans all win.
  3741. gamemode.Call("EndRound", TEAM_HUMAN)
  3742.  
  3743. local curwave = self:GetWave()
  3744. for _, ent in pairs(ents.FindByClass("logic_waves")) do
  3745. if ent.Wave == curwave or ent.Wave == -1 then
  3746. ent:Input("onwaveend", ent, ent, curwave)
  3747. end
  3748. end
  3749. for _, ent in pairs(ents.FindByClass("logic_waveend")) do
  3750. if ent.Wave == curwave or ent.Wave == -1 then
  3751. ent:Input("onwaveend", ent, ent, curwave)
  3752. end
  3753. end
  3754. end
  3755. else
  3756. gamemode.Call("SetWaveStart", CurTime() + (GetGlobalBool("classicmode") and self.WaveIntermissionLengthClassic or self.WaveIntermissionLength))
  3757.  
  3758. net.Start("zs_waveend")
  3759. net.WriteInt(self:GetWave(), 16)
  3760. net.WriteFloat(self:GetWaveStart())
  3761. net.Broadcast()
  3762.  
  3763. for _, pl in pairs(player.GetAll()) do
  3764. if pl:Team() == TEAM_HUMAN and pl:Alive() then
  3765. if self.EndWaveHealthBonus > 0 then
  3766. pl:SetHealth(math.min(pl:GetMaxHealth(), pl:Health() + self.EndWaveHealthBonus))
  3767. end
  3768. elseif pl:Team() == TEAM_UNDEAD and not pl:Alive() and not pl.Revive then
  3769. local curclass = pl.DeathClass or pl:GetZombieClass()
  3770. local crowindex = GAMEMODE.ZombieClasses["Crow"].Index
  3771. pl:SetZombieClass(crowindex)
  3772. pl:DoHulls(crowindex, TEAM_UNDEAD)
  3773. pl.DeathClass = nil
  3774. pl:UnSpectateAndSpawn()
  3775. pl.DeathClass = curclass
  3776. end
  3777.  
  3778. pl.SkipCrow = nil
  3779. end
  3780.  
  3781. local curwave = self:GetWave()
  3782. for _, ent in pairs(ents.FindByClass("logic_waves")) do
  3783. if ent.Wave == curwave or ent.Wave == -1 then
  3784. ent:Input("onwaveend", ent, ent, curwave)
  3785. end
  3786. end
  3787. for _, ent in pairs(ents.FindByClass("logic_waveend")) do
  3788. if ent.Wave == curwave or ent.Wave == -1 then
  3789. ent:Input("onwaveend", ent, ent, curwave)
  3790. end
  3791. end
  3792. end
  3793.  
  3794. gamemode.Call("OnWaveStateChanged")
  3795. end
  3796.  
  3797. function GM:PlayerSwitchFlashlight(pl, newstate)
  3798. if pl:Team() == TEAM_UNDEAD then
  3799. if pl:Alive() then
  3800. pl:SendLua("gamemode.Call(\"ToggleZombieVision\")")
  3801. end
  3802.  
  3803. return false
  3804. end
  3805.  
  3806. return true
  3807. end
  3808.  
  3809. function GM:PlayerStepSoundTime(pl, iType, bWalking)
  3810. return 350
  3811. end
  3812.  
  3813. concommand.Add("zs_class", function(sender, command, arguments)
  3814. if sender:Team() ~= TEAM_UNDEAD or sender.Revive or GAMEMODE.PantsMode or GAMEMODE:IsClassicMode() or GAMEMODE:IsBabyMode() or GAMEMODE.ZombieEscape then return end
  3815.  
  3816. local classname = arguments[1]
  3817. local suicide = arguments[2] == "1"
  3818. local classtab = GAMEMODE.ZombieClasses[classname]
  3819. if not classtab or classtab.Hidden and not (classtab.CanUse and classtab:CanUse(sender)) then return end
  3820.  
  3821. if not gamemode.Call("IsClassUnlocked", classname) then
  3822. sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "class_not_unlocked_will_be_unlocked_x", classtab.Wave))
  3823. elseif sender:GetZombieClassTable().Name == classname and not sender.DeathClass then
  3824. sender:CenterNotify(COLOR_RED, translate.ClientFormat(sender, "you_are_already_a_x", translate.ClientGet(sender, classtab.TranslationName)))
  3825. else
  3826. sender.DeathClass = classtab.Index
  3827. sender:CenterNotify(translate.ClientFormat(sender, "you_will_spawn_as_a_x", translate.ClientGet(sender, classtab.TranslationName)))
  3828.  
  3829. if suicide and sender:Alive() and not sender:GetZombieClassTable().Boss and gamemode.Call("CanPlayerSuicide", sender) then
  3830. sender:Kill()
  3831. end
  3832. end
  3833. end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement