2014-10-02 08:49:54 +08:00
GM.Name = " Zombie Survival "
GM.Author = " William \" JetBoom \" Moodhe "
GM.Email = " williammoodhe@gmail.com "
GM.Website = " http://www.noxiousnet.com "
-- No, adding a gun doesn't make your name worth being here.
GM.Credits = {
{ " William \" JetBoom \" Moodhe " , " williammoodhe@gmail.com (www.noxiousnet.com) " , " Creator / Programmer " } ,
{ " 11k " , " tjd113@gmail.com " , " Zombie view models " } ,
{ " Eisiger " , " k2deseve@gmail.com " , " Zombie kill icons " } ,
{ " Austin \" Little Nemo \" Killey " , " austin_odyssey@yahoo.com " , " Ambient music " } ,
{ " Zombie Panic: Source " , " http://www.zombiepanic.org/ " , " Melee weapon sounds " } ,
2018-05-02 06:32:59 +08:00
{ " Samuel " , " samuel_games@hotmail.com " , " Board Kit models " } ,
{ " Typhon " , " lukas-tinel@hotmail.com " , " Fear-o-meter textures " } ,
{ " Benjy, The Darker One, Raox, Scott " , " " , " Code contributions " } ,
2014-10-02 08:49:54 +08:00
{ " Mr. Darkness " , " " , " Russian translation " } ,
{ " honsal " , " " , " Korean translation " } ,
{ " rui_troia " , " " , " Portuguese translation " } ,
{ " Shinyshark " , " " , " Dutch translation " } ,
{ " Kradar " , " " , " Italian translation " } ,
{ " Raptor " , " " , " German translation " } ,
{ " The Special Duckling " , " " , " Danish translation " } ,
2018-05-02 06:32:59 +08:00
{ " ptown, Dr. Broly " , " " , " Spanish translation " } ,
{ " Anyone else on GitHub or who I've forgotten " , " " , " Various contributions " } ,
2014-10-02 08:49:54 +08:00
}
2018-05-02 06:32:59 +08:00
if file.Exists ( GM.FolderName .. " /gamemode/maps/ " .. game.GetMap ( ) .. " .lua " , " LUA " ) then
include ( " maps/ " .. game.GetMap ( ) .. " .lua " )
end
2014-10-02 08:49:54 +08:00
function GM : GetNumberOfWaves ( )
local default = GetGlobalBool ( " classicmode " ) and 10 or self.NumberOfWaves
local num = GetGlobalInt ( " numwaves " , default ) -- This is controlled by logic_waves.
return num == - 2 and default or num
end
function GM : GetWaveOneLength ( )
return GetGlobalBool ( " classicmode " ) and self.WaveOneLengthClassic or self.WaveOneLength
end
2018-05-02 06:32:59 +08:00
include ( " obj_vector_extend.lua " )
include ( " obj_entity_extend.lua " )
include ( " obj_player_extend.lua " )
include ( " obj_weapon_extend.lua " )
2014-10-02 08:49:54 +08:00
include ( " sh_translate.lua " )
include ( " sh_colors.lua " )
include ( " sh_serialization.lua " )
include ( " sh_util.lua " )
2018-05-02 06:32:59 +08:00
include ( " skillweb/sh_skillweb.lua " )
2014-10-02 08:49:54 +08:00
include ( " sh_options.lua " )
include ( " sh_zombieclasses.lua " )
include ( " sh_animations.lua " )
include ( " sh_sigils.lua " )
2015-03-10 02:26:27 +08:00
include ( " sh_channel.lua " )
2018-05-02 06:32:59 +08:00
include ( " sh_weaponquality.lua " )
2014-10-02 08:49:54 +08:00
include ( " noxapi/noxapi.lua " )
2018-05-02 06:32:59 +08:00
include ( " vault/shared.lua " )
2014-10-02 08:49:54 +08:00
include ( " workshopfix.lua " )
2018-05-02 06:32:59 +08:00
include_library ( " perf " )
include_library ( " player_movement " )
include_library ( " inventory " )
include_library ( " ammoexpand " )
2014-10-02 08:49:54 +08:00
----------------------
GM.EndRound = false
GM.StartingWorth = 100
GM.ZombieVolunteers = { }
team.SetUp ( TEAM_ZOMBIE , " The Undead " , Color ( 0 , 255 , 0 , 255 ) )
team.SetUp ( TEAM_SURVIVORS , " Survivors " , Color ( 0 , 160 , 255 , 255 ) )
local validmodels = player_manager.AllValidModels ( )
validmodels [ " tf01 " ] = nil
validmodels [ " tf02 " ] = nil
vector_tiny = Vector ( 0.001 , 0.001 , 0.001 )
-- ogg/mp3 still doesn't work with SoundDuration() function
GM.SoundDuration = {
[ " zombiesurvival/music_win.ogg " ] = 33.149 ,
[ " zombiesurvival/music_lose.ogg " ] = 45.714 ,
[ " zombiesurvival/lasthuman.ogg " ] = 120.503 ,
[ " zombiesurvival/beats/defaulthuman/1.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/2.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/3.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/4.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/5.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/6.ogg " ] = 14.222 ,
[ " zombiesurvival/beats/defaulthuman/7.ogg " ] = 14.222 ,
[ " zombiesurvival/beats/defaulthuman/8.ogg " ] = 7.111 ,
[ " zombiesurvival/beats/defaulthuman/9.ogg " ] = 14.222 ,
[ " zombiesurvival/beats/defaultzombiev2/1.ogg " ] = 8 ,
[ " zombiesurvival/beats/defaultzombiev2/2.ogg " ] = 8 ,
[ " zombiesurvival/beats/defaultzombiev2/3.ogg " ] = 8 ,
[ " zombiesurvival/beats/defaultzombiev2/4.ogg " ] = 8 ,
[ " zombiesurvival/beats/defaultzombiev2/5.ogg " ] = 8 ,
[ " zombiesurvival/beats/defaultzombiev2/6.ogg " ] = 6.038 ,
[ " zombiesurvival/beats/defaultzombiev2/7.ogg " ] = 6.038 ,
[ " zombiesurvival/beats/defaultzombiev2/8.ogg " ] = 6.038 ,
[ " zombiesurvival/beats/defaultzombiev2/9.ogg " ] = 6.038 ,
[ " zombiesurvival/beats/defaultzombiev2/10.ogg " ] = 6.038
}
2018-05-02 06:32:59 +08:00
local SERVER = SERVER
local CLIENT = CLIENT
local HITGROUP_HEAD = HITGROUP_HEAD
local HITGROUP_LEFTARM = HITGROUP_LEFTARM
local HITGROUP_RIGHTARM = HITGROUP_RIGHTARM
local HITGROUP_GEAR = HITGROUP_GEAR
local HITGROUP_STOMACH = HITGROUP_STOMACH
local HITGROUP_LEFTLEG = HITGROUP_LEFTLEG
local HITGROUP_RIGHTLEG = HITGROUP_RIGHTLEG
local PTeam = FindMetaTable ( " Player " ) . Team
2014-10-02 08:49:54 +08:00
function GM : AddCustomAmmo ( )
2018-05-02 06:32:59 +08:00
game.AddAmmoType ( { name = " dummy " } )
2014-10-02 08:49:54 +08:00
game.AddAmmoType ( { name = " pulse " } )
2018-05-02 06:32:59 +08:00
game.AddAmmoType ( { name = " impactmine " } )
game.AddAmmoType ( { name = " chemical " } )
game.AddAmmoType ( { name = " scrap " } )
2014-10-02 08:49:54 +08:00
game.AddAmmoType ( { name = " stone " } )
2018-05-02 06:32:59 +08:00
game.AddAmmoType ( { name = " flashbomb " } )
game.AddAmmoType ( { name = " betty " } )
game.AddAmmoType ( { name = " molotov " } )
game.AddAmmoType ( { name = " corgasgrenade " } )
game.AddAmmoType ( { name = " crygasgrenade " } )
game.AddAmmoType ( { name = " bloodshot " } )
2014-10-02 08:49:54 +08:00
game.AddAmmoType ( { name = " spotlamp " } )
game.AddAmmoType ( { name = " manhack " } )
game.AddAmmoType ( { name = " manhack_saw " } )
game.AddAmmoType ( { name = " drone " } )
2018-05-02 06:32:59 +08:00
game.AddAmmoType ( { name = " pulse_cutter " } )
game.AddAmmoType ( { name = " drone_hauler " } )
game.AddAmmoType ( { name = " rollermine " } )
game.AddAmmoType ( { name = " sigilfragment " } )
game.AddAmmoType ( { name = " corruptedfragment " } )
game.AddAmmoType ( { name = " mediccloudbomb " } )
game.AddAmmoType ( { name = " nanitecloudbomb " } )
game.AddAmmoType ( { name = " repairfield " } )
game.AddAmmoType ( { name = " zapper " } )
game.AddAmmoType ( { name = " zapper_arc " } )
game.AddAmmoType ( { name = " remantler " } )
game.AddAmmoType ( { name = " turret_buckshot " } )
game.AddAmmoType ( { name = " turret_assault " } )
game.AddAmmoType ( { name = " turret_rocket " } )
game.AddAmmoType ( { name = " camera " } )
game.AddAmmoType ( { name = " tv " } )
game.AddAmmoType ( { name = " foodwatermelon " } )
game.AddAmmoType ( { name = " foodorange " } )
game.AddAmmoType ( { name = " foodbanana " } )
game.AddAmmoType ( { name = " foodsoda " } )
game.AddAmmoType ( { name = " foodmilk " } )
game.AddAmmoType ( { name = " foodtakeout " } )
game.AddAmmoType ( { name = " foodwater " } )
end
GM.Food = { }
function GM : RegisterFood ( )
self.Food = { }
for k , v in pairs ( weapons.GetList ( ) ) do
if v.Base == " weapon_zs_basefood " then
table.insert ( self.Food , v.ClassName )
end
end
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
function GM : RefreshMapIsObjective ( )
local mapname = string.lower ( game.GetMap ( ) )
if string.find ( mapname , " _obj_ " , 1 , true ) or string.find ( mapname , " objective " , 1 , true ) then
self.ObjectiveMap = true
else
self.ObjectiveMap = false
end
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
function GM : AssignItemProperties ( )
for _ , tab in ipairs ( self.Items ) do
if tab.SWEP then
local sweptab = self.ZSInventoryItemData [ tab.SWEP ] or weapons.Get ( tab.SWEP )
if sweptab then
if not tab.Description then
tab.Description = sweptab.Description
end
if not tab.Tier then
tab.Tier = sweptab.Tier
end
if not tab.MaxStock then
tab.MaxStock = sweptab.MaxStock
end
if tab.Name == " ? " then
tab.Name = sweptab.PrintName or tab.Name
end
end
end
end
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
-- Utility function to setup a weapon's DefaultClip.
function GM : SetupDefaultClip ( tab )
tab.DefaultClip = math.ceil ( tab.ClipSize * self.SurvivalClips * ( tab.ClipMultiplier or 1 ) )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
-- Some weapons are derived from weapon_base and try to make use of .Owner
function GM : FixWeaponBase ( )
local base = weapons.GetStored ( " weapon_base " )
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
base.TranslateActivity = function ( me )
if me.ActivityTranslate [ act ] ~= nil then
return me.ActivityTranslate [ act ]
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
return - 1
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
base.TakePrimaryAmmo = function ( me , num )
if me.Weapon : Clip1 ( ) <= 0 then
if me : Ammo1 ( ) <= 0 then return end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
me : GetOwner ( ) : RemoveAmmo ( num , me.Weapon : GetPrimaryAmmoType ( ) )
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
return
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
me.Weapon : SetClip1 ( me.Weapon : Clip1 ( ) - num )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
base.Ammo1 = function ( me )
return me : GetOwner ( ) : GetAmmoCount ( me.Weapon : GetPrimaryAmmoType ( ) )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
function GM : GetRedeemBrains ( )
return GetGlobalInt ( " redeembrains " , self.DefaultRedeem )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
function GM : PlayerIsAdmin ( pl )
return pl : IsAdmin ( )
end
function GM : GetFallDamage ( pl , fallspeed )
return 0 -- Handled in OnPlayerHitGround
2014-10-02 08:49:54 +08:00
end
function GM : ValidMenuLockOnTarget ( pl , ent )
2018-05-02 06:32:59 +08:00
if ent and ent : IsValidLivingHuman ( ) then
2014-10-02 08:49:54 +08:00
local startpos = pl : EyePos ( )
local endpos = ent : NearestPoint ( startpos )
2018-05-02 06:32:59 +08:00
if startpos : DistToSqr ( endpos ) <= 2304 and TrueVisible ( startpos , endpos ) then -- 48^2
2014-10-02 08:49:54 +08:00
return true
end
end
return false
end
function GM : GetHandsModel ( pl )
2018-05-02 06:32:59 +08:00
return player_manager.TranslatePlayerHands ( player_manager.TranslateToPlayerModelName ( pl : GetModel ( ) ) )
end
function GM : GetBestAvailableZombieClass ( baseclass_id )
if self : ShouldUseBetterVersionSystem ( ) then
local baseclass
while true do
baseclass = self.ZombieClasses [ baseclass_id ]
if baseclass and baseclass.BetterVersion and self : IsClassUnlocked ( baseclass.BetterVersion ) then
baseclass_id = baseclass.BetterVersion
else
break
end
end
end
return self.ZombieClasses [ baseclass_id ] . Index
end
function GM : ShouldUseBetterVersionSystem ( )
return not self.ObjectiveMap
2014-10-02 08:49:54 +08:00
end
local playerheight = Vector ( 0 , 0 , 72 )
local playermins = Vector ( - 17 , - 17 , 0 )
local playermaxs = Vector ( 17 , 17 , 4 )
local SkewedDistance = util.SkewedDistance
GM.DynamicSpawnDistVisOld = 2048
GM.DynamicSpawnDistOld = 640
function GM : DynamicSpawnIsValidOld ( zombie , humans , allplayers )
-- I didn't make this check where trigger_hurt entities are. Rather I made it check the time since the last time you were hit with a trigger_hurt.
-- I'm not sure if it's possible to check if a trigger_hurt is enabled or disabled through the Lua bindings.
if SERVER and zombie.LastHitWithTriggerHurt and CurTime ( ) < zombie.LastHitWithTriggerHurt + 2 then
return false
end
2018-05-02 06:32:59 +08:00
local hpos , nearest , dist
2014-10-02 08:49:54 +08:00
-- Optional caching for these.
if not humans then humans = team.GetPlayers ( TEAM_HUMAN ) end
if not allplayers then allplayers = player.GetAll ( ) end
local pos = zombie : GetPos ( ) + Vector ( 0 , 0 , 1 )
if zombie : Alive ( ) and zombie : GetMoveType ( ) == MOVETYPE_WALK and zombie : OnGround ( )
and not util.TraceHull ( { start = pos , endpos = pos + playerheight , mins = playermins , maxs = playermaxs , mask = MASK_SOLID , filter = allplayers } ) . Hit then
local vtr = util.TraceHull ( { start = pos , endpos = pos - playerheight , mins = playermins , maxs = playermaxs , mask = MASK_SOLID_BRUSHONLY } )
if not vtr.HitSky and not vtr.HitNoDraw then
local valid = true
for _ , human in pairs ( humans ) do
2018-05-02 06:32:59 +08:00
hpos = human : GetPos ( )
nearest = zombie : NearestPoint ( hpos )
dist = SkewedDistance ( hpos , nearest , 2.75 ) -- We make it so that the Z distance between a human and a zombie is skewed if the zombie is below the human.
2014-10-02 08:49:54 +08:00
if dist <= self.DynamicSpawnDistOld or dist <= self.DynamicSpawnDistVisOld and WorldVisible ( hpos , nearest ) then -- Zombies can't be in radius of any humans. Zombies can't be clearly visible by any humans.
valid = false
break
end
end
return valid
end
end
return false
end
function GM : GetBestDynamicSpawnOld ( pl , pos )
local spawns = self : GetDynamicSpawnsOld ( pl )
if # spawns == 0 then return end
return self : GetClosestSpawnPoint ( spawns , pos or self : GetTeamEpicentre ( TEAM_HUMAN ) ) or table.Random ( spawns )
end
function GM : GetDynamicSpawnsOld ( pl )
local tab = { }
local allplayers = player.GetAll ( )
local humans = team.GetPlayers ( TEAM_HUMAN )
for _ , zombie in pairs ( team.GetPlayers ( TEAM_UNDEAD ) ) do
if zombie ~= pl and self : DynamicSpawnIsValidOld ( zombie , humans , allplayers ) then
table.insert ( tab , zombie )
end
end
return tab
end
2018-05-02 06:32:59 +08:00
GM.DynamicSpawnDist = 512
GM.DynamicSpawnDistVis = 2048
GM.CreeperNestDist = 150
GM.CreeperNestDistBuild = 420
GM.CreeperNestDistBuildNest = 192
GM.CreeperNestDistBuildZSpawn = 256
local trace_dynspawn = { mins = playermins , maxs = playermaxs , mask = MASK_SOLID }
local trace_dynspawn_skybox = { mins = playermins , maxs = playermaxs , mask = MASK_SOLID_BRUSHONLY }
function GM : DynamicSpawnIsValid ( ent , humans , allplayers )
2014-10-02 08:49:54 +08:00
if self : ShouldUseAlternateDynamicSpawn ( ) then
2018-05-02 06:32:59 +08:00
return self : DynamicSpawnIsValidOld ( ent , humans , allplayers )
2014-10-02 08:49:54 +08:00
end
-- Optional caching for these.
if not humans then humans = team.GetPlayers ( TEAM_HUMAN ) end
2018-05-02 06:32:59 +08:00
if not allplayers then allplayers = player.GetAll ( ) end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
local pos = ent : GetPos ( ) + Vector ( 0 , 0 , 1 )
local is_nest = ent : GetClass ( ) == " prop_creepernest "
local required_distance = is_nest and self.CreeperNestDist or self.DynamicSpawnDist -- Nests have a shorter distance and no visibility requirement.
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
--if ent.GetNestBuilt and ent:GetNestBuilt() and not util.TraceHull({start = pos, endpos = pos + playerheight, mins = playermins, maxs = playermaxs, mask = MASK_SOLID_BRUSHONLY}).Hit then
if is_nest and not ent : GetNestBuilt ( ) then
return false
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
-- Check if not enough room.
trace_dynspawn.start = pos
trace_dynspawn.endpos = pos + playerheight
trace_dynspawn.filter = allplayers
table.insert ( trace_dynspawn.filter , ent )
local tr = util.TraceHull ( trace_dynspawn )
if tr.Hit then
return false
end
-- No need to check this. You shouldn't be able to build a nest on top of nodraw/skybox
-- -- Check if on top of a nodraw / skybox.
-- trace_dynspawn_skybox.start = pos
-- trace_dynspawn_skybox.endpos = pos - playerheight
-- local vtr = util.TraceHull(trace_dynspawn_skybox)
-- if vtr.HitSky or vtr.HitNoDraw then
-- return false
-- end
-- vtr = util.TraceLine(trace_dynspawn_skybox)
-- if vtr.HitSky or vtr.HitNoDraw then
-- return false
-- end
-- Check if too close to a human.
local nearest , dist
for _ , human in pairs ( humans ) do
nearest = human : NearestPoint ( pos )
dist = SkewedDistance ( nearest , pos , 2.75 )
if dist <= required_distance then -- We make it so that the Z distance between a human and an ent is skewed if the ent is below the human.
return false
end
-- Check if visible by any human.
if not is_nest and dist <= self.DynamicSpawnDistVis and WorldVisible ( nearest , pos ) then
return false
2014-10-02 08:49:54 +08:00
end
end
2018-05-02 06:32:59 +08:00
return true
2014-10-02 08:49:54 +08:00
end
function GM : GetBestDynamicSpawn ( pl , pos )
if self : ShouldUseAlternateDynamicSpawn ( ) then
return self : GetBestDynamicSpawnOld ( pl , pos )
end
local spawns = self : GetDynamicSpawns ( pl )
if # spawns == 0 then return end
return self : GetClosestSpawnPoint ( spawns , pos or self : GetTeamEpicentre ( TEAM_HUMAN ) ) or table.Random ( spawns )
end
function GM : GetDynamicSpawns ( pl )
if self : ShouldUseAlternateDynamicSpawn ( ) then
return self : GetDynamicSpawnsOld ( pl )
end
local tab = { }
local humans = team.GetPlayers ( TEAM_HUMAN )
for _ , nest in pairs ( ents.FindByClass ( " prop_creepernest " ) ) do
2018-05-02 06:32:59 +08:00
if self : DynamicSpawnIsValid ( nest , humans ) then
2014-10-02 08:49:54 +08:00
table.insert ( tab , nest )
end
end
return tab
end
function GM : GetDesiredStartingZombies ( )
2018-05-02 06:32:59 +08:00
local numplayers = # player.GetAllActive ( )
return math.Clamp ( math.ceil ( numplayers * self.WaveOneZombies ) , 1 , numplayers - 1 )
2014-10-02 08:49:54 +08:00
end
function GM : GetEndRound ( )
return self.RoundEnded
end
function GM : PrecacheResources ( )
util.PrecacheSound ( " physics/body/body_medium_break2.wav " )
util.PrecacheSound ( " physics/body/body_medium_break3.wav " )
util.PrecacheSound ( " physics/body/body_medium_break4.wav " )
for name , mdl in pairs ( player_manager.AllValidModels ( ) ) do
util.PrecacheModel ( mdl )
end
2018-05-02 06:32:59 +08:00
game.AddParticles ( " particles/vman_explosion.pcf " )
PrecacheParticleSystem ( " dusty_explosion_rockets " )
2014-10-02 08:49:54 +08:00
end
function GM : ShouldCollide ( enta , entb )
2018-05-02 06:32:59 +08:00
local snca = enta.ShouldNotCollide
if snca and snca ( enta , entb ) then return false end
local sncb = entb.ShouldNotCollide
if sncb and sncb ( entb , enta ) then return false end
--[[if enta.ShouldNotCollide and enta:ShouldNotCollide(entb) or entb.ShouldNotCollide and entb:ShouldNotCollide(enta) then
2014-10-02 08:49:54 +08:00
return false
2018-05-02 06:32:59 +08:00
end ] ]
2014-10-02 08:49:54 +08:00
return true
end
2018-05-02 06:32:59 +08:00
function GM : DoChangeDeploySpeed ( wep )
if wep : IsValid ( ) and wep.SetDeploySpeed and not wep.NoDeploySpeedChange then
local owner = wep : GetOwner ( )
wep : SetDeploySpeed ( self.BaseDeploySpeed * ( owner : IsValid ( ) and owner.DeploySpeedMultiplier or 1 ) * ( owner : IsValid ( ) and owner : GetStatus ( " frost " ) and 0.7 or 1 ) )
2014-10-02 08:49:54 +08:00
end
end
function GM : OnPlayerHitGround ( pl , inwater , hitfloater , speed )
if inwater then return true end
2018-05-02 06:32:59 +08:00
if speed > 64 then
pl.LandSlow = true
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
local isundead = PTeam ( pl ) == TEAM_UNDEAD
if isundead and pl : GetZombieClassTable ( ) . NoFallDamage then return true end
local threshold_mul
local slowdown_mul
local recovery_mul
local damage_mul
2014-12-19 13:52:24 +08:00
if isundead then
speed = math.max ( 0 , speed - 200 )
2018-05-02 06:32:59 +08:00
threshold_mul = 1
slowdown_mul = 1
recovery_mul = 1
damage_mul = 1
else
threshold_mul = pl.FallDamageThresholdMul or 1
slowdown_mul = pl.FallDamageSlowDownMul or 1
recovery_mul = pl.FallDamageRecoveryMul or 1
damage_mul = pl.FallDamageDamageMul or 1
2014-12-19 13:52:24 +08:00
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
local damage = ( 0.1 * ( speed - 525 * threshold_mul ) ) ^ 1.45
2014-12-19 13:52:24 +08:00
if hitfloater then damage = damage / 2 end
2018-05-02 06:32:59 +08:00
if SERVER then
local groundent = pl : GetGroundEntity ( )
if groundent : IsValid ( ) and groundent : IsPlayer ( ) and PTeam ( groundent ) == TEAM_UNDEAD and pl : HasTrinket ( " curbstompers " ) then
if groundent : IsHeadcrab ( ) then
groundent : TakeSpecialDamage ( groundent : Health ( ) + 70 , DMG_DIRECT , pl , pl , pl : GetPos ( ) )
elseif groundent : IsTorso ( ) then
groundent : TakeSpecialDamage ( 50 , DMG_CLUB , pl , pl , pl : GetPos ( ) )
end
if math.floor ( damage ) > 0 then
groundent : TakeSpecialDamage ( damage * 5 , DMG_CLUB , pl , pl , pl : GetPos ( ) )
return true
end
2014-12-19 13:52:24 +08:00
end
2018-05-02 06:32:59 +08:00
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
if math.floor ( damage ) > 0 then
2014-12-19 13:52:24 +08:00
if SERVER then
2018-05-02 06:32:59 +08:00
local h = pl : Health ( )
pl : TakeSpecialDamage ( damage * damage_mul , DMG_FALL , game.GetWorld ( ) , game.GetWorld ( ) , pl : GetPos ( ) )
damage = h - pl : Health ( )
if not self.ZombieEscape and damage >= 5 and pl : Health ( ) > 0 then
if damage >= 30 then
pl : KnockDown ( damage * 0.05 * recovery_mul )
end
if not isundead or not pl : GetZombieClassTable ( ) . NoFallSlowdown then
pl : RawCapLegDamage ( CurTime ( ) + math.min ( 2 , damage * 0.038 * slowdown_mul ) )
end
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
2014-10-02 08:49:54 +08:00
pl : EmitSound ( " player/pl_fallpain " .. ( math.random ( 2 ) == 1 and 3 or 1 ) .. " .wav " )
2018-05-02 06:32:59 +08:00
elseif not self.ZombieEscape and damage >= 5 and ( not isundead or not pl : GetZombieClassTable ( ) . NoFallSlowdown ) then
pl : RawCapLegDamage ( CurTime ( ) + math.min ( 2 , damage * 0.038 * slowdown_mul ) )
2014-10-02 08:49:54 +08:00
end
end
return true
end
function GM : PlayerCanBeHealed ( pl )
2018-05-02 06:32:59 +08:00
local maxhp = pl : IsSkillActive ( SKILL_D_FRAIL ) and math.floor ( pl : GetMaxHealth ( ) * 0.25 ) or pl : GetMaxHealth ( )
return pl : Health ( ) < maxhp or pl : GetPoisonDamage ( ) > 0 or pl : GetBleedDamage ( ) > 0
2014-10-02 08:49:54 +08:00
end
function GM : PlayerCanPurchase ( pl )
2018-05-02 06:32:59 +08:00
if CLIENT and self.CanPurchaseCacheTime and self.CanPurchaseCacheTime >= CurTime ( ) then
return self.CanPurchaseCache
end
local canpurchase = PTeam ( pl ) == TEAM_HUMAN and self : GetWave ( ) > 0 and pl : Alive ( ) and pl : NearArsenalCrate ( )
if CLIENT then
self.CanPurchaseCache = canpurchase
self.CanPurchaseCacheTime = CurTime ( ) + 0.5
end
return canpurchase
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
-- This is actually only called by the engine on the server but it's here in case the client wants to know.
2015-03-10 01:45:47 +08:00
local TEAM_SPECTATOR = TEAM_SPECTATOR
2014-10-02 08:49:54 +08:00
function GM : PlayerCanHearPlayersVoice ( listener , talker )
2018-05-02 06:32:59 +08:00
return PTeam ( listener ) == PTeam ( talker ) or PTeam ( listener ) == TEAM_SPECTATOR , false
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
GM.PlayerCanHearPlayersVoiceDefault = GM.PlayerCanHearPlayersVoice
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
function GM : PlayerCanHearPlayersVoiceAllTalk ( listener , talker )
return true , false
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
cvars.AddChangeCallback ( " sv_alltalk " , function ( cvar , old , new )
GAMEMODE.PlayerCanHearPlayersVoice = new ~= " 1 " and GAMEMODE.PlayerCanHearPlayersVoiceDefault or GAMEMODE.PlayerCanHearPlayersVoiceAllTalk
end )
GM.PlayerCanHearPlayersVoice = GetConVar ( " sv_alltalk " ) : GetBool ( ) and GM.PlayerCanHearPlayersVoiceAllTalk or GM.PlayerCanHearPlayersVoiceDefault
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
function GM : PlayerTraceAttack ( pl , dmginfo , dir , trace )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
function GM : GetDamageResistance ( fearpower )
if self.MaxSigils > 0 and self : GetUseSigils ( ) then
return fearpower * 0.1 + self : NumSigilsCorrupted ( ) / self.MaxSigils * 0.2
2014-12-18 10:00:45 +08:00
end
2014-10-02 08:49:54 +08:00
2018-05-02 06:32:59 +08:00
return fearpower * 0.15
2014-10-02 08:49:54 +08:00
end
function GM : FindUseEntity ( pl , ent )
if not ent : IsValid ( ) then
2018-05-02 06:32:59 +08:00
local e = pl : TraceLine ( 90 , MASK_SOLID , pl : GetDynamicTraceFilter ( ) ) . Entity
2014-10-02 08:49:54 +08:00
if e : IsValid ( ) then return e end
end
return ent
end
function GM : ShouldUseAlternateDynamicSpawn ( )
return self.ZombieEscape or self : IsClassicMode ( ) or self.PantsMode or self : IsBabyMode ( )
end
function GM : GetZombieDamageScale ( pos , ignore )
2014-11-07 13:03:40 +08:00
if LASTHUMAN then return self.ZombieDamageMultiplier end
2014-10-02 08:49:54 +08:00
return self.ZombieDamageMultiplier * ( 1 - self : GetDamageResistance ( self : GetFearMeterPower ( pos , TEAM_UNDEAD , ignore ) ) )
end
local temppos
local function SortByDistance ( a , b )
2018-05-02 06:32:59 +08:00
return a : GetPos ( ) : DistToSqr ( temppos ) < b : GetPos ( ) : DistToSqr ( temppos )
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
local function GetSortedSpawnPoints ( teamid , pos )
2014-10-02 08:49:54 +08:00
temppos = pos
local spawnpoints
if type ( teamid ) == " table " then
spawnpoints = teamid
else
spawnpoints = team.GetValidSpawnPoint ( teamid )
end
2018-05-02 06:32:59 +08:00
2014-10-02 08:49:54 +08:00
table.sort ( spawnpoints , SortByDistance )
2018-05-02 06:32:59 +08:00
return spawnpoints
2014-10-02 08:49:54 +08:00
end
2018-05-02 06:32:59 +08:00
function GM : GetClosestSpawnPoint ( teamid , pos )
return GetSortedSpawnPoints ( teamid , pos ) [ 1 ]
end
function GM : GetFurthestSpawnPoint ( teamid , pos )
local spawnpoints = GetSortedSpawnPoints ( teamid , pos )
return spawnpoints [ # spawnpoints ]
end
local FEAR_RANGE = 768 ^ 2
2014-10-02 08:49:54 +08:00
local FEAR_PERINSTANCE = 0.075
local RALLYPOINT_THRESHOLD = 0.3
local function GetEpicenter ( tab )
local vec = Vector ( 0 , 0 , 0 )
if # tab == 0 then return vec end
for k , v in pairs ( tab ) do
vec = vec + v : GetPos ( )
end
return vec / # tab
end
function GM : GetTeamRallyGroups ( teamid )
local groups = { }
local ingroup = { }
local plys = team.GetPlayers ( teamid )
2018-05-02 06:32:59 +08:00
local plpos , group
2014-10-02 08:49:54 +08:00
for _ , pl in pairs ( plys ) do
if not ingroup [ pl ] and pl : Alive ( ) then
2018-05-02 06:32:59 +08:00
plpos = pl : GetPos ( )
group = { pl }
2014-10-02 08:49:54 +08:00
for __ , otherpl in pairs ( plys ) do
2018-05-02 06:32:59 +08:00
if otherpl ~= pl and not ingroup [ otherpl ] and otherpl : Alive ( ) and otherpl : GetPos ( ) : DistToSqr ( plpos ) <= FEAR_RANGE then
2014-10-02 08:49:54 +08:00
group [ # group + 1 ] = otherpl
end
end
if # group * FEAR_PERINSTANCE >= RALLYPOINT_THRESHOLD then
for k , v in pairs ( group ) do
ingroup [ v ] = true
end
groups [ # groups + 1 ] = group
end
end
end
return groups
end
function GM : GetTeamRallyPoints ( teamid )
local points = { }
for _ , group in pairs ( self : GetTeamRallyGroups ( teamid ) ) do
points [ # points + 1 ] = { GetEpicenter ( group ) , math.min ( 1 , ( # group * FEAR_PERINSTANCE - RALLYPOINT_THRESHOLD ) / ( 1 - RALLYPOINT_THRESHOLD ) ) }
end
return points
end
local CachedEpicentreTimes = { }
local CachedEpicentres = { }
function GM : GetTeamEpicentre ( teamid , nocache )
if not nocache and CachedEpicentres [ teamid ] and CurTime ( ) < CachedEpicentreTimes [ teamid ] then
return CachedEpicentres [ teamid ]
end
local plys = team.GetPlayers ( teamid )
local vVec = Vector ( 0 , 0 , 0 )
2018-05-02 06:32:59 +08:00
local considered = 0
2014-10-02 08:49:54 +08:00
for _ , pl in pairs ( plys ) do
2018-05-02 06:32:59 +08:00
if pl : Alive ( ) and pl : GetAuraRange ( ) == 2048 then
2014-10-02 08:49:54 +08:00
vVec = vVec + pl : GetPos ( )
2018-05-02 06:32:59 +08:00
considered = considered + 1
2014-10-02 08:49:54 +08:00
end
end
2018-05-02 06:32:59 +08:00
local epicentre = vVec / considered
2014-10-02 08:49:54 +08:00
if not nocache then
CachedEpicentreTimes [ teamid ] = CurTime ( ) + 0.5
CachedEpicentres [ teamid ] = epicentre
end
return epicentre
end
GM.GetTeamEpicenter = GM.GetTeamEpicentre
function GM : GetCurrentEquipmentCount ( id )
local count = 0
local item = self.Items [ id ]
if item then
if item.Countables then
if type ( item.Countables ) == " table " then
for k , v in pairs ( item.Countables ) do
count = count + # ents.FindByClass ( v )
end
else
count = count + # ents.FindByClass ( item.Countables )
end
end
if item.SWEP then
count = count + # ents.FindByClass ( item.SWEP )
end
end
return count
end
function GM : GetFearMeterPower ( pos , teamid , ignore )
if LASTHUMAN then return 1 end
2018-05-02 06:32:59 +08:00
local dist
2014-10-02 08:49:54 +08:00
local power = 0
for _ , pl in pairs ( player.GetAll ( ) ) do
2018-05-02 06:32:59 +08:00
if pl ~= ignore and PTeam ( pl ) == teamid and not pl : CallZombieFunction0 ( " DoesntGiveFear " ) and pl : Alive ( ) then
dist = pl : GetPos ( ) : DistToSqr ( pos )
2014-10-02 08:49:54 +08:00
if dist <= FEAR_RANGE then
2018-05-02 06:32:59 +08:00
power = power + ( 1 - dist / FEAR_RANGE ) * ( pl : GetZombieClassTable ( ) . FearPerInstance or FEAR_PERINSTANCE )
2014-10-02 08:49:54 +08:00
end
end
end
return math.min ( 1 , power )
end
function GM : GetRagdollEyes ( pl )
local Ragdoll = pl : GetRagdollEntity ( )
if not Ragdoll then return end
local att = Ragdoll : GetAttachment ( Ragdoll : LookupAttachment ( " eyes " ) )
if att then
att.Pos = att.Pos + att.Ang : Forward ( ) * - 2
att.Ang = att.Ang
return att.Pos , att.Ang
end
end
function GM : PlayerNoClip ( pl , on )
if pl : IsAdmin ( ) then
if SERVER then
PrintMessage ( HUD_PRINTCONSOLE , translate.Format ( on and " x_turned_on_noclip " or " x_turned_off_noclip " , pl : Name ( ) ) )
end
if SERVER then
pl : MarkAsBadProfile ( )
end
return true
end
return false
end
function GM : IsSpecialPerson ( pl , image )
local img , tooltip
if pl : SteamID ( ) == " STEAM_0:1:3307510 " then
img = " VGUI/steam/games/icon_sourcesdk "
tooltip = " JetBoom \n Creator of Zombie Survival! "
elseif pl : IsAdmin ( ) then
img = " VGUI/servers/icon_robotron "
tooltip = " Admin "
elseif pl : IsNoxSupporter ( ) then
img = " noxiousnet/noxicon.png "
tooltip = " Nox Supporter "
end
if img then
if CLIENT then
image : SetImage ( img )
image : SetTooltip ( tooltip )
end
return true
end
return false
end
function GM : GetWaveEnd ( )
return GetGlobalFloat ( " waveend " , 0 )
end
2018-05-02 06:32:59 +08:00
function GM : SetWaveEnd ( time )
SetGlobalFloat ( " waveend " , time )
2014-10-02 08:49:54 +08:00
end
function GM : GetWaveStart ( )
return GetGlobalFloat ( " wavestart " , self.WaveZeroLength )
end
2018-05-02 06:32:59 +08:00
function GM : SetWaveStart ( time )
SetGlobalFloat ( " wavestart " , time )
2014-10-02 08:49:54 +08:00
end
function GM : GetWave ( )
return GetGlobalInt ( " wave " , 0 )
end
if GM : GetWave ( ) == 0 then
GM : SetWaveStart ( GM.WaveZeroLength )
GM : SetWaveEnd ( GM.WaveZeroLength + GM : GetWaveOneLength ( ) )
end
function GM : GetWaveActive ( )
return GetGlobalBool ( " waveactive " , false )
end
function GM : SetWaveActive ( active )
if self.RoundEnded then return end
if self : GetWaveActive ( ) ~= active then
SetGlobalBool ( " waveactive " , active )
if SERVER then
gamemode.Call ( " WaveStateChanged " , active )
end
end
end
if not FixedSoundDuration then
FixedSoundDuration = true
local OldSoundDuration = SoundDuration
function SoundDuration ( snd )
if snd then
local ft = string.sub ( snd , - 4 )
if ft == " .mp3 " then
return OldSoundDuration ( snd ) * 2.25
end
if ft == " .ogg " then
return OldSoundDuration ( snd ) * 3
end
end
return OldSoundDuration ( snd )
end
end
2015-01-23 22:54:50 +08:00
function GM : VehicleMove ( )
end