792 lines
26 KiB
Lua
792 lines
26 KiB
Lua
|
-- Ship
|
||
|
resource.AddWorkshop("417565863")
|
||
|
|
||
|
-- Send required file to clients
|
||
|
AddCSLuaFile("sh_init.lua")
|
||
|
AddCSLuaFile("sh_player.lua")
|
||
|
AddCSLuaFile("cl_tauntwindow.lua")
|
||
|
AddCSLuaFile("cl_init.lua")
|
||
|
AddCSLuaFile("cl_hud_mask.lua")
|
||
|
AddCSLuaFile("cl_hud.lua")
|
||
|
AddCSLuaFile("cl_menu.lua")
|
||
|
AddCSLuaFile("cl_targetid.lua")
|
||
|
AddCSLuaFile("cl_autotaunt.lua")
|
||
|
AddCSLuaFile("cl_credits.lua")
|
||
|
|
||
|
-- Include the required lua files
|
||
|
include("sv_networkfunctions.lua")
|
||
|
include("sh_init.lua")
|
||
|
include("sh_config.lua")
|
||
|
include("sv_admin.lua")
|
||
|
include("sv_autotaunt.lua")
|
||
|
include("sv_tauntwindow.lua")
|
||
|
|
||
|
include("sv_bbox_addition.lua")
|
||
|
|
||
|
-- Server only constants
|
||
|
PHE.EXPLOITABLE_DOORS = {
|
||
|
"func_door",
|
||
|
"prop_door_rotating",
|
||
|
"func_door_rotating"
|
||
|
}
|
||
|
|
||
|
-- Voice Control Constant init
|
||
|
PHE.VOICE_IS_END_ROUND = 0
|
||
|
|
||
|
-- Update cvar to variables changes every so seconds
|
||
|
PHE.UPDATE_CVAR_TO_VARIABLE = 0
|
||
|
|
||
|
-- Spectator check
|
||
|
PHE.SPECTATOR_CHECK = 0
|
||
|
|
||
|
-- Player Join/Leave message
|
||
|
gameevent.Listen( "player_connect" )
|
||
|
hook.Add( "player_connect", "AnnouncePLJoin", function( data )
|
||
|
for k, v in pairs( player.GetAll() ) do
|
||
|
v:PrintMessage( HUD_PRINTTALK, data.name .. " has connected to the server." )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
gameevent.Listen( "player_disconnect" )
|
||
|
hook.Add( "player_disconnect", "AnnouncePLLeave", function( data )
|
||
|
for k,v in pairs( player.GetAll() ) do
|
||
|
v:PrintMessage( HUD_PRINTTALK, data.name .. " has left the server (Reason: " .. data.reason ..")" )
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- Force Close taunt window function, determined whenever the round ends, or team winning.
|
||
|
local function ForceCloseTauntWindow(num)
|
||
|
if num == 1 then
|
||
|
net.Start("PH_ForceCloseTauntWindow")
|
||
|
net.Broadcast()
|
||
|
elseif num == 0 then
|
||
|
net.Start("PH_AllowTauntWindow")
|
||
|
net.Broadcast()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Called alot
|
||
|
function GM:CheckPlayerDeathRoundEnd()
|
||
|
if !GAMEMODE.RoundBased || !GAMEMODE:InRound() then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local Teams = GAMEMODE:GetTeamAliveCounts()
|
||
|
|
||
|
if table.Count(Teams) == 0 then
|
||
|
GAMEMODE:RoundEndWithResult(1001, "Draw, everyone loses!")
|
||
|
PHE.VOICE_IS_END_ROUND = 1
|
||
|
ForceCloseTauntWindow(1)
|
||
|
|
||
|
net.Start("PH_RoundDraw_Snd")
|
||
|
net.Broadcast()
|
||
|
|
||
|
hook.Call("PH_OnRoundDraw", nil)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if table.Count(Teams) == 1 then
|
||
|
local TeamID = table.GetFirstKey(Teams)
|
||
|
-- debug
|
||
|
MsgAll("Round Result: "..team.GetName(TeamID).." ("..TeamID..") Wins!\n")
|
||
|
-- End Round
|
||
|
GAMEMODE:RoundEndWithResult(TeamID, team.GetName(TeamID).." win!")
|
||
|
PHE.VOICE_IS_END_ROUND = 1
|
||
|
ForceCloseTauntWindow(1)
|
||
|
|
||
|
-- send the win notification
|
||
|
if TeamID == TEAM_HUNTERS then
|
||
|
net.Start("PH_TeamWinning_Snd")
|
||
|
net.WriteString(PHE.WINNINGSOUNDS[TEAM_HUNTERS])
|
||
|
net.Broadcast()
|
||
|
elseif TeamID == TEAM_PROPS then
|
||
|
net.Start("PH_TeamWinning_Snd")
|
||
|
net.WriteString(PHE.WINNINGSOUNDS[TEAM_PROPS])
|
||
|
net.Broadcast()
|
||
|
end
|
||
|
|
||
|
hook.Call("PH_OnRoundWinTeam", nil, TeamID)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- Player Voice & Chat Control to prevent Metagaming. (As requested by some server owners/suggestors.)
|
||
|
-- You can disable this feature by typing 'sv_alltalk 1' in console to make everyone can hear.
|
||
|
|
||
|
-- Control Player Voice
|
||
|
function GM:PlayerCanHearPlayersVoice(listen, speaker)
|
||
|
|
||
|
local alltalk_cvar = GetConVar("sv_alltalk"):GetInt()
|
||
|
if (alltalk_cvar > 0) then return true, false end
|
||
|
|
||
|
-- prevent Loopback check.
|
||
|
if (listen == speaker) then return false, false end
|
||
|
|
||
|
-- Only alive players can listen other living players.
|
||
|
if listen:Alive() && speaker:Alive() then return true, false end
|
||
|
|
||
|
-- Event: On Round Start. Living Players don't listen to dead players.
|
||
|
if PHE.VOICE_IS_END_ROUND == 0 && listen:Alive() && !speaker:Alive() then return false, false end
|
||
|
|
||
|
-- Listen to all dead players while you dead.
|
||
|
if !listen:Alive() && !speaker:Alive() then return true, false end
|
||
|
|
||
|
-- However, Living players can be heard from dead players.
|
||
|
if !listen:Alive() && speaker:Alive() then return true, false end
|
||
|
|
||
|
-- Event: On Round End/Time End. Listen to everyone.
|
||
|
if PHE.VOICE_IS_END_ROUND == 1 && listen:Alive() && !speaker:Alive() then return true, false end
|
||
|
|
||
|
-- Spectator can only read from themselves.
|
||
|
if listen:Team() == TEAM_SPECTATOR && listen:Alive() && speaker:Alive() then return false, false end
|
||
|
|
||
|
-- This is for ULX "Permanent Gag". Uncomment this if you have some issues.
|
||
|
-- if speaker:GetPData( "permgagged" ) == "true" then return false, false end
|
||
|
|
||
|
-- does return true, true required here?
|
||
|
end
|
||
|
|
||
|
-- Control Players Chat
|
||
|
function GM:PlayerCanSeePlayersChat(txt, onteam, listen, speaker)
|
||
|
|
||
|
if ( onteam ) then
|
||
|
-- Generic Specific OnTeam chats
|
||
|
if ( !IsValid( speaker ) || !IsValid( listen ) ) then return false end
|
||
|
if ( listen:Team() != speaker:Team() ) then return false end
|
||
|
|
||
|
-- ditto, this is same as below.
|
||
|
if listen:Alive() && speaker:Alive() then return true end
|
||
|
if PHE.VOICE_IS_END_ROUND == 0 && listen:Alive() && !speaker:Alive() then return false end
|
||
|
if !listen:Alive() && !speaker:Alive() then return true end
|
||
|
if !listen:Alive() && speaker:Alive() then return true end
|
||
|
if PHE.VOICE_IS_END_ROUND == 1 && listen:Alive() && !speaker:Alive() then return true end
|
||
|
if listen:Team() == TEAM_SPECTATOR && listen:Alive() && speaker:Alive() then return false end
|
||
|
end
|
||
|
|
||
|
local alltalk_cvar = GetConVar("sv_alltalk"):GetInt()
|
||
|
if (alltalk_cvar > 0) then return true end
|
||
|
|
||
|
-- Generic Checks
|
||
|
if ( !IsValid( speaker ) || !IsValid( listen ) ) then return false end
|
||
|
|
||
|
-- Only alive players can see other living players.
|
||
|
if listen:Alive() && speaker:Alive() then return true end
|
||
|
|
||
|
-- Event: On Round Start. Living Players don't see dead players' chat.
|
||
|
if PHE.VOICE_IS_END_ROUND == 0 && listen:Alive() && !speaker:Alive() then return false end
|
||
|
|
||
|
-- See Chat to all dead players while you dead.
|
||
|
if !listen:Alive() && !speaker:Alive() then return true end
|
||
|
|
||
|
-- However, Living players' chat can be seen from dead players.
|
||
|
if !listen:Alive() && speaker:Alive() then return true end
|
||
|
|
||
|
-- Event: On Round End/Time End. See Chat to everyone.
|
||
|
if PHE.VOICE_IS_END_ROUND == 1 && listen:Alive() && !speaker:Alive() then return true end
|
||
|
|
||
|
-- Spectator can only read from themselves.
|
||
|
if listen:Team() == TEAM_SPECTATOR && listen:Alive() && speaker:Alive() then return false end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- Called when an entity takes damage
|
||
|
function EntityTakeDamage(ent, dmginfo)
|
||
|
local att = dmginfo:GetAttacker()
|
||
|
|
||
|
-- Code from: https://facepunch.com/showthread.php?t=1500179 , Special thanks from AlcoholicoDrogadicto(http://steamcommunity.com/profiles/76561198082241865/) for suggesting this.
|
||
|
if GAMEMODE:InRound() && ent && ent:IsPlayer() && ent:Alive() && ent:Team() == TEAM_PROPS && ent.ph_prop then
|
||
|
-- Prevent Prop 'Friendly Fire'
|
||
|
if ( dmginfo:GetAttacker():IsPlayer() && dmginfo:GetAttacker():Team() == ent:Team() )
|
||
|
then printVerbose("DMGINFO::ATTACKED!!-> "..tostring(dmginfo:GetAttacker())..", DMGTYPE: "..dmginfo:GetDamageType())
|
||
|
return
|
||
|
end
|
||
|
--Debug purpose.
|
||
|
printVerbose("!! " .. ent:Name() .. "'s PLAYER entity appears to have taken damage, we can redirect it to the prop! (Model is: " .. ent.ph_prop:GetModel() .. ")")
|
||
|
ent.ph_prop:TakeDamageInfo(dmginfo)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if GAMEMODE:InRound() && ent && (ent:GetClass() != "ph_prop" && ent:GetClass() != "func_breakable" && ent:GetClass() != "prop_door_rotating" && ent:GetClass() != "prop_dynamic*") && !ent:IsPlayer() && att && att:IsPlayer() && att:Team() == TEAM_HUNTERS && att:Alive() then
|
||
|
if att:Armor() >= 5 && GetConVar("ph_hunter_fire_penalty"):GetInt() >= 5 then
|
||
|
att:SetHealth(att:Health() - (math.Round(GetConVar("ph_hunter_fire_penalty"):GetInt()/2)))
|
||
|
att:SetArmor(att:Armor() - 15)
|
||
|
if att:Armor() < 0 then att:SetArmor(0) end
|
||
|
else
|
||
|
att:SetHealth(att:Health() - GetConVar("ph_hunter_fire_penalty"):GetInt())
|
||
|
end
|
||
|
if att:Health() <= 0 then
|
||
|
MsgAll(att:Name() .. " felt guilty for hurting so many innocent props and committed suicide\n")
|
||
|
att:Kill()
|
||
|
|
||
|
hook.Call("PH_HunterDeathPenalty", nil, att)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
hook.Add("EntityTakeDamage", "PH_EntityTakeDamage", EntityTakeDamage)
|
||
|
|
||
|
-- Called when player tries to pickup a weapon
|
||
|
function GM:PlayerCanPickupWeapon(pl, ent)
|
||
|
if pl:Team() != TEAM_HUNTERS then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function PH_ResetCustomTauntWindowState()
|
||
|
-- Force close any taunt menu windows
|
||
|
ForceCloseTauntWindow(0)
|
||
|
-- Extra additional
|
||
|
PHE.VOICE_IS_END_ROUND = 0
|
||
|
-- Reset Player's Height
|
||
|
end
|
||
|
hook.Add("PostCleanupMap", "PH_ResetCustomTauntWindow", PH_ResetCustomTauntWindowState)
|
||
|
|
||
|
-- Make a variable for 4 unique combines.
|
||
|
-- Clean up, sorry btw.
|
||
|
local playerModels = {
|
||
|
"combine",
|
||
|
"combineprison",
|
||
|
"combineelite",
|
||
|
"police"
|
||
|
-- you may add more here.
|
||
|
}
|
||
|
|
||
|
function GM:PlayerSetModel(pl)
|
||
|
|
||
|
-- player actual model to prevent multi-damage hitbox.
|
||
|
local player_model = "models/props_idbs/phenhanced/box.mdl"
|
||
|
|
||
|
if GetConVar("ph_use_custom_plmodel"):GetBool() then
|
||
|
-- Use a delivered player model info from cl_playermodel ConVar.
|
||
|
-- This however will use a custom player selection. It'll immediately apply once it is selected.
|
||
|
local mdlinfo = pl:GetInfo("cl_playermodel")
|
||
|
local mdlname = player_manager.TranslatePlayerModel(mdlinfo)
|
||
|
|
||
|
if pl:Team() == TEAM_HUNTERS then
|
||
|
player_model = mdlname
|
||
|
end
|
||
|
else
|
||
|
-- Otherwise, Use Random one based from a table above.
|
||
|
local customModel = table.Random(playerModels)
|
||
|
local customMdlName = player_manager.TranslatePlayerModel(customModel)
|
||
|
|
||
|
if pl:Team() == TEAM_HUNTERS then
|
||
|
player_model = customMdlName
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- precache and Set the model.
|
||
|
util.PrecacheModel(player_model)
|
||
|
pl:SetModel(player_model)
|
||
|
end
|
||
|
|
||
|
-- The [E] & Mouse Click 1 behaviour is now moved in here!
|
||
|
function GM:PlayerExchangeProp(pl, ent)
|
||
|
|
||
|
if !IsValid(pl) then return; end
|
||
|
if !IsValid(ent) then return; end
|
||
|
|
||
|
if pl:Team() == TEAM_PROPS && pl:IsOnGround() && !pl:Crouching() && table.HasValue(PHE.USABLE_PROP_ENTITIES, ent:GetClass()) && ent:GetModel() then
|
||
|
if table.HasValue(PHE.BANNED_PROP_MODELS, ent:GetModel()) then
|
||
|
pl:ChatPrint("[PH: Enhanced] Notice: That prop has been banned from the server.")
|
||
|
elseif IsValid(ent:GetPhysicsObject()) && (pl.ph_prop:GetModel() != ent:GetModel() || pl.ph_prop:GetSkin() != ent:GetSkin()) then
|
||
|
local ent_health = math.Clamp(ent:GetPhysicsObject():GetVolume() / 250, 1, 200)
|
||
|
local new_health = math.Clamp((pl.ph_prop.health / pl.ph_prop.max_health) * ent_health, 1, 200)
|
||
|
pl.ph_prop.health = new_health
|
||
|
|
||
|
pl.ph_prop.max_health = ent_health
|
||
|
pl.ph_prop:SetModel(ent:GetModel())
|
||
|
pl.ph_prop:SetSkin(ent:GetSkin())
|
||
|
pl.ph_prop:SetSolid(SOLID_VPHYSICS)
|
||
|
pl.ph_prop:SetPos(pl:GetPos() - Vector(0, 0, ent:OBBMins().z))
|
||
|
pl.ph_prop:SetAngles(pl:GetAngles())
|
||
|
|
||
|
pl:SetHealth(new_health)
|
||
|
|
||
|
if GetConVar("ph_sv_enable_obb_modifier"):GetBool() && ent:GetNWBool("hasCustomHull",false) then
|
||
|
local hmin = ent.m_Hull[1]
|
||
|
local hmax = ent.m_Hull[2]
|
||
|
local dmin = ent.m_dHull[1]
|
||
|
local dmax = ent.m_dHull[2]
|
||
|
|
||
|
if hmax.z < 24 || dmax.z < 24 then
|
||
|
pl:SetViewOffset(Vector(0,0,24))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,24))
|
||
|
elseif hmax.z > 84 || dmax.z > 84 then --what the heck Duck Size is 84? BigMomma.mdl?
|
||
|
pl:SetViewOffset(Vector(0,0,84))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,84))
|
||
|
else
|
||
|
pl:SetViewOffset(Vector(0,0,hmax.z))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,dmax.z))
|
||
|
end
|
||
|
|
||
|
pl:SetHull(hmin,hmax)
|
||
|
pl:SetHullDuck(dmin,dmax)
|
||
|
|
||
|
net.Start("SetHull")
|
||
|
net.WriteInt(math.Round(math.Max(hmax.x,hmax.y)),32)
|
||
|
net.WriteInt(hmax.z,32)
|
||
|
net.WriteInt(dmax.z,32)
|
||
|
net.WriteInt(new_health,9)
|
||
|
net.Send(pl)
|
||
|
else
|
||
|
local hullxymax = math.Round(math.Max(ent:OBBMaxs().x, ent:OBBMaxs().y))
|
||
|
local hullxymin = hullxymax * -1
|
||
|
local hullz = math.Round(ent:OBBMaxs().z - ent:OBBMins().z)
|
||
|
|
||
|
local dhullz = hullz
|
||
|
if hullz > 10 && hullz <= 30 then
|
||
|
dhullz = hullz-(hullz*0.5)
|
||
|
elseif hullz > 30 && hullz <= 40 then
|
||
|
dhullz = hullz-(hullz*0.2)
|
||
|
elseif hullz > 40 && hullz <= 50 then
|
||
|
dhullz = hullz-(hullz*0.1)
|
||
|
else
|
||
|
dhullz = hullz
|
||
|
end
|
||
|
|
||
|
if hullz < 24 then
|
||
|
pl:SetViewOffset(Vector(0,0,24))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,24))
|
||
|
elseif hullz > 84 then
|
||
|
pl:SetViewOffset(Vector(0,0,84))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,84))
|
||
|
else
|
||
|
pl:SetViewOffset(Vector(0,0,hullz))
|
||
|
pl:SetViewOffsetDucked(Vector(0,0,dhullz))
|
||
|
end
|
||
|
|
||
|
pl:SetHull(Vector(hullxymin, hullxymin, 0), Vector(hullxymax, hullxymax, hullz))
|
||
|
pl:SetHullDuck(Vector(hullxymin, hullxymin, 0), Vector(hullxymax, hullxymax, dhullz))
|
||
|
|
||
|
net.Start("SetHull")
|
||
|
net.WriteInt(hullxymax, 32)
|
||
|
net.WriteInt(hullz, 32)
|
||
|
net.WriteInt(dhullz, 32)
|
||
|
net.WriteInt(new_health, 9)
|
||
|
net.Send(pl)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
hook.Call("PH_OnChangeProp", nil, pl, ent)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- Called when a player tries to use an object. By default this pressed ['E'] button. MouseClick 1 will be mentioned below at line @351
|
||
|
function GM:PlayerUse(pl, ent)
|
||
|
if !pl:Alive() || pl:Team() == TEAM_SPECTATOR || pl:Team() == TEAM_UNASSIGNED then return false; end
|
||
|
|
||
|
-- Prevent Execution Spam by holding ['E'] button too long.
|
||
|
if pl.UseTime <= CurTime() then
|
||
|
|
||
|
local hmx, hz = ent:GetPropSize()
|
||
|
if GetConVar("phe_check_props_boundaries"):GetBool() && !pl:CheckHull(hmx, hmx, hz) then
|
||
|
pl:SendLua("chat.AddText(Color(235, 10, 15), \"[PH: Enhanced]\", Color(220, 220, 220), \" There is no room to change that prop!\")")
|
||
|
else
|
||
|
self:PlayerExchangeProp(pl, ent)
|
||
|
end
|
||
|
|
||
|
pl.UseTime = CurTime() + 1
|
||
|
|
||
|
end
|
||
|
|
||
|
-- Prevent the door exploit
|
||
|
if table.HasValue(PHE.EXPLOITABLE_DOORS, ent:GetClass()) && pl.last_door_time && pl.last_door_time + 1 > CurTime() then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
pl.last_door_time = CurTime()
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
net.Receive("CL2SV_ExchangeProp", function(len, ply)
|
||
|
local Prop = net.ReadEntity()
|
||
|
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "-=* NOTICE *=-")
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "Hello! We've noticed you tried using the \"CL2SV_ExchangeProp\" net message.")
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "Sad news is that this net message is no longer used (due to exploits). Shame, isn't it?")
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "")
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "This net message will still respond, but you will receive this message instead.")
|
||
|
ply:PrintMessage(HUD_PRINTCONSOLE, "-=* NOTICE *=-")
|
||
|
|
||
|
--[[
|
||
|
if ply.UseTime <= CurTime() then
|
||
|
|
||
|
if !ply:IsHoldingEntity() then
|
||
|
local hmx,hz = Prop:GetPropSize()
|
||
|
if (GetConVar("phe_check_props_boundaries"):GetBool() && !ply:CheckHull(hmx,hmx,hz)) then
|
||
|
ply:SendLua("chat.AddText(Color(235,10,15), \"[PH: Enhanced]\", Color(220,220,220), \" There is no room to change that prop!\")")
|
||
|
else
|
||
|
GAMEMODE:PlayerExchangeProp(ply, Prop)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
ply.UseTime = CurTime() + 1
|
||
|
|
||
|
end
|
||
|
]]
|
||
|
|
||
|
-- OBSOLETE : THIS IS COMMENTED OUT BECAUSE THIS METHOD IS SILLY AND SHOULD NOT BE USED. --yeah kind of my fault! >.<
|
||
|
end)
|
||
|
|
||
|
-- Called when player presses [F3]. Plays a taunt for their team
|
||
|
function GM:ShowSpare1(pl)
|
||
|
if (GetConVar("ph_enable_custom_taunts"):GetInt() == 1) && GAMEMODE:InRound() then
|
||
|
pl:ConCommand("ph_showtaunts")
|
||
|
end
|
||
|
|
||
|
if ((GetConVar("ph_enable_custom_taunts"):GetInt() == 0) or (GetConVar("ph_enable_custom_taunts"):GetInt() == 2)) && GAMEMODE:InRound() && pl:Alive() && (pl:Team() == TEAM_HUNTERS || pl:Team() == TEAM_PROPS) && pl.last_taunt_time + GetConVar("ph_normal_taunt_delay"):GetInt() <= CurTime() && (table.Count(PHE.PROP_TAUNTS) > 1 && table.Count(PHE.HUNTER_TAUNTS) > 1) then
|
||
|
local curTeamTaunt = {
|
||
|
hunter = PHE:GetAllTeamTaunt(TEAM_HUNTERS),
|
||
|
prop = PHE:GetAllTeamTaunt(TEAM_PROPS)
|
||
|
}
|
||
|
|
||
|
-- play the taunts based on listed curCustTaunt available.
|
||
|
repeat
|
||
|
if pl:Team() == TEAM_HUNTERS then
|
||
|
rand_taunt = table.Random(curTeamTaunt.hunter)
|
||
|
else
|
||
|
rand_taunt = table.Random(curTeamTaunt.prop)
|
||
|
end
|
||
|
until rand_taunt != pl.last_taunt
|
||
|
|
||
|
pl.last_taunt_time = CurTime() + GetConVar("ph_normal_taunt_delay"):GetInt()
|
||
|
pl.last_taunt = rand_taunt
|
||
|
|
||
|
pl:EmitSound(rand_taunt, 100)
|
||
|
pl:SetNWFloat("LastTauntTime", CurTime())
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Called when a player leaves
|
||
|
function PlayerDisconnected(pl)
|
||
|
pl:RemoveProp()
|
||
|
end
|
||
|
hook.Add("PlayerDisconnected", "PH_PlayerDisconnected", PlayerDisconnected)
|
||
|
|
||
|
-- Set specific variable for checking in player initial spawn, then use Player:IsHoldingEntity()
|
||
|
hook.Add("PlayerInitialSpawn", "PHE.SetupInitData", function(ply)
|
||
|
ply.LastPickupEnt = NULL
|
||
|
ply.UseTime = 0
|
||
|
end)
|
||
|
hook.Add("AllowPlayerPickup", "PHE.IsHoldingEntity", function(ply,ent)
|
||
|
ply.LastPickupEnt = ent
|
||
|
ent.LastPickupPly = ply
|
||
|
end)
|
||
|
|
||
|
-- Spray Controls
|
||
|
hook.Add( "PlayerSpray", "PH.GeneralSprayFunc", function( ply )
|
||
|
if ( ( !ply:Alive() ) || ( ply:Team() == TEAM_SPECTATOR ) ) then
|
||
|
return true
|
||
|
end
|
||
|
end )
|
||
|
|
||
|
-- Called when the players spawns
|
||
|
function PlayerSpawn(pl)
|
||
|
pl:SetNWBool("PlayerLockedRotation", false)
|
||
|
pl:SetNWBool("InFreezeCam", false)
|
||
|
pl:SetNWEntity("PlayerKilledByPlayerEntity", nil)
|
||
|
pl:Blind(false)
|
||
|
pl:RemoveProp()
|
||
|
pl:SetColor(Color(255,255,255,255))
|
||
|
pl:SetRenderMode(RENDERMODE_TRANSALPHA)
|
||
|
pl:UnLock()
|
||
|
pl:ResetHull()
|
||
|
pl:SetNWFloat("LastTauntTime", CurTime())
|
||
|
pl.last_taunt_time = 0
|
||
|
|
||
|
net.Start("ResetHull")
|
||
|
net.Send(pl)
|
||
|
|
||
|
net.Start("DisableDynamicLight")
|
||
|
net.Send(pl)
|
||
|
|
||
|
pl:SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR)
|
||
|
pl:CollisionRulesChanged()
|
||
|
|
||
|
if pl:Team() == TEAM_HUNTERS then
|
||
|
pl:SetJumpPower(160)
|
||
|
elseif pl:Team() == TEAM_PROPS then
|
||
|
pl:SetJumpPower(160 * GetConVar("ph_prop_jumppower"):GetFloat())
|
||
|
end
|
||
|
|
||
|
-- Listen server host
|
||
|
if !game.IsDedicated() then
|
||
|
pl:SetNWBool("ListenServerHost", pl:IsListenServerHost())
|
||
|
end
|
||
|
end
|
||
|
hook.Add("PlayerSpawn", "PH_PlayerSpawn", PlayerSpawn)
|
||
|
|
||
|
|
||
|
-- Called when round ends
|
||
|
function RoundEnd()
|
||
|
-- Unblind the hunters
|
||
|
for _, pl in pairs(team.GetPlayers(TEAM_HUNTERS)) do
|
||
|
pl:Blind(false)
|
||
|
pl:UnLock()
|
||
|
end
|
||
|
|
||
|
-- Stop autotaunting
|
||
|
net.Start("AutoTauntRoundEnd")
|
||
|
net.Broadcast()
|
||
|
end
|
||
|
hook.Add("PH_RoundEnd", "PH.ForceHuntersUnblind", RoundEnd)
|
||
|
|
||
|
|
||
|
-- This is called when the round time ends (props win)
|
||
|
function GM:RoundTimerEnd()
|
||
|
if !GAMEMODE:InRound() then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
GAMEMODE:RoundEndWithResult(TEAM_PROPS, "Props win!")
|
||
|
PHE.VOICE_IS_END_ROUND = 1
|
||
|
ForceCloseTauntWindow(1)
|
||
|
|
||
|
net.Start("PH_TeamWinning_Snd")
|
||
|
net.WriteString(PHE.WINNINGSOUNDS[TEAM_PROPS])
|
||
|
net.Broadcast()
|
||
|
|
||
|
hook.Call("PH_OnTimerEnd", nil)
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Called before start of round
|
||
|
function GM:OnPreRoundStart(num)
|
||
|
game.CleanUpMap()
|
||
|
|
||
|
if GetGlobalInt("RoundNumber") != 1 && (GetConVar("ph_swap_teams_every_round"):GetInt() == 1 || ((team.GetScore(TEAM_PROPS) + team.GetScore(TEAM_HUNTERS)) > 0)) then
|
||
|
for _, pl in pairs(player.GetAll()) do
|
||
|
if pl:Team() == TEAM_PROPS || pl:Team() == TEAM_HUNTERS then
|
||
|
if pl:Team() == TEAM_PROPS then
|
||
|
pl:SetTeam(TEAM_HUNTERS)
|
||
|
else
|
||
|
pl:SetTeam(TEAM_PROPS)
|
||
|
if GetConVar("ph_notice_prop_rotation"):GetBool() then
|
||
|
timer.Simple(0.5, function() pl:SendLua( [[notification.AddLegacy("You are in Prop Team with Rotate support! You can rotate the prop around by moving your mouse.", NOTIFY_UNDO, 20 )]] ) end)
|
||
|
pl:SendLua( [[notification.AddLegacy("Additionally you can toggle lock rotation by pressing R key!", NOTIFY_GENERIC, 18 )]] )
|
||
|
pl:SendLua( [[surface.PlaySound("garrysmod/content_downloaded.wav")]] )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
pl:ChatPrint("Teams have been swapped!")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Props will gain a Bonus Armor points Hunter teams has more than 4 players in it. The more player, the more armor they get.
|
||
|
timer.Simple(1, function()
|
||
|
local NumHunter = table.Count(team.GetPlayers(TEAM_HUNTERS))
|
||
|
if NumHunter >= 4 && NumHunter <= 8 then
|
||
|
for _,prop in pairs(team.GetPlayers(TEAM_PROPS)) do
|
||
|
if IsValid(prop) then prop:SetArmor(math.random(1,3) * 15) end
|
||
|
end
|
||
|
elseif NumHunter > 8 then
|
||
|
for _,prop in pairs(team.GetPlayers(TEAM_PROPS)) do
|
||
|
if IsValid(prop) then prop:SetArmor(math.random(3,7) * 15) end
|
||
|
end
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
hook.Call("PH_OnPreRoundStart", nil, GetConVar("ph_swap_teams_every_round"):GetInt())
|
||
|
end
|
||
|
|
||
|
UTIL_StripAllPlayers()
|
||
|
UTIL_SpawnAllPlayers()
|
||
|
UTIL_FreezeAllPlayers()
|
||
|
end
|
||
|
|
||
|
|
||
|
-- Called every server tick.
|
||
|
function GM:Think()
|
||
|
-- Prop spectating is a bit messy so let us clean it up a bit
|
||
|
if PHE.SPECTATOR_CHECK < CurTime() then
|
||
|
for _, pl in pairs(team.GetPlayers(TEAM_PROPS)) do
|
||
|
if IsValid(pl) && !pl:Alive() && pl:GetObserverMode() == OBS_MODE_IN_EYE then
|
||
|
hook.Call("ChangeObserverMode", GAMEMODE, pl, OBS_MODE_ROAMING)
|
||
|
end
|
||
|
end
|
||
|
PHE.SPECTATOR_CHECK = CurTime() + PHE.SPECTATOR_CHECK_ADD
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Bonus Drop :D
|
||
|
function PH_Props_OnBreak(ply, ent)
|
||
|
if GetConVar("ph_enable_lucky_balls"):GetBool() then
|
||
|
local pos = ent:GetPos()
|
||
|
if math.random() < 0.08 then -- 8% Chance of drops.
|
||
|
local dropent = ents.Create("ph_luckyball")
|
||
|
dropent:SetPos(Vector(pos.x, pos.y, pos.z + 32)) -- to make sure the Lucky Ball didn't fall underground.
|
||
|
dropent:SetAngles(Angle(0,0,0))
|
||
|
dropent:SetColor(Color(math.Round(math.random(0,255)),math.Round(math.random(0,255)),math.Round(math.random(0,255)),255))
|
||
|
dropent:Spawn()
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
hook.Add("PropBreak", "Props_OnBreak_WithDrops", PH_Props_OnBreak)
|
||
|
|
||
|
-- Force Close the Taunt Menu whenever the prop is being killed.
|
||
|
function close_PlayerKilledSilently(ply)
|
||
|
if ply:Team() == TEAM_PROPS then
|
||
|
net.Start( "PH_ForceCloseTauntWindow" )
|
||
|
net.Send(ply)
|
||
|
end
|
||
|
end
|
||
|
hook.Add("PlayerSilentDeath", "SilentDed_ForceClose", close_PlayerKilledSilently)
|
||
|
|
||
|
-- Flashlight toggling
|
||
|
function GM:PlayerSwitchFlashlight(pl, on)
|
||
|
if pl:Alive() && pl:Team() == TEAM_HUNTERS then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
if pl:Alive() && pl:Team() == TEAM_PROPS then
|
||
|
net.Start("PlayerSwitchDynamicLight")
|
||
|
net.Send(pl)
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
-- Round Control
|
||
|
cvars.AddChangeCallback("ph_min_waitforplayers", function(cvar, old, new)
|
||
|
if tonumber(new) < 1 then
|
||
|
RunConsoleCommand("ph_min_waitforplayers", "1")
|
||
|
print("[PH:E] Warning: Value must not be 0! Use ph_waitforplayers 0 to disable.")
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
local bAlreadyStarted = false
|
||
|
function GM:OnRoundEnd( num )
|
||
|
-- Check if GetConVar("ph_waitforplayers"):GetBool() is true
|
||
|
-- This is a fast implementation for a waiting system
|
||
|
-- Make optimisations if needed
|
||
|
if ( GetConVar("ph_waitforplayers"):GetBool() ) then
|
||
|
-- Take away a round number quickly before it adds another when there are not enough players
|
||
|
-- Set to false
|
||
|
if ( ( team.NumPlayers( TEAM_HUNTERS ) < GetConVar("ph_min_waitforplayers"):GetInt() ) || ( team.NumPlayers( TEAM_PROPS ) < GetConVar("ph_min_waitforplayers"):GetInt() ) ) then
|
||
|
bAlreadyStarted = false
|
||
|
end
|
||
|
|
||
|
-- Set to true
|
||
|
if ( ( team.NumPlayers( TEAM_HUNTERS ) >= GetConVar("ph_min_waitforplayers"):GetInt() ) && ( team.NumPlayers( TEAM_PROPS ) >= GetConVar("ph_min_waitforplayers"):GetInt() ) ) then
|
||
|
bAlreadyStarted = true
|
||
|
end
|
||
|
|
||
|
-- Check if the round was already started before so we count it as a fully played round
|
||
|
if ( !bAlreadyStarted ) then
|
||
|
SetGlobalInt( "RoundNumber", GetGlobalInt("RoundNumber") - 1 )
|
||
|
end
|
||
|
end
|
||
|
|
||
|
hook.Call("PH_OnRoundEnd", nil, num)
|
||
|
|
||
|
end
|
||
|
|
||
|
function GM:RoundStart()
|
||
|
|
||
|
local roundNum = GetGlobalInt( "RoundNumber" );
|
||
|
local roundDuration = GAMEMODE:GetRoundTime( roundNum )
|
||
|
|
||
|
GAMEMODE:OnRoundStart( roundNum )
|
||
|
|
||
|
timer.Create( "RoundEndTimer", roundDuration, 0, function() GAMEMODE:RoundTimerEnd() end )
|
||
|
timer.Create( "CheckRoundEnd", 1, 0, function() GAMEMODE:CheckRoundEnd() end )
|
||
|
|
||
|
SetGlobalFloat( "RoundEndTime", CurTime() + roundDuration );
|
||
|
|
||
|
-- Check if GetConVar("ph_waitforplayers"):GetBool() is true
|
||
|
-- This is a fast implementation for a waiting system
|
||
|
-- Make optimisations if needed
|
||
|
if ( GetConVar("ph_waitforplayers"):GetBool() ) then
|
||
|
|
||
|
-- Pause these timers if there are not enough players on the teams in the server
|
||
|
if ( ( team.NumPlayers( TEAM_HUNTERS ) < GetConVar("ph_min_waitforplayers"):GetInt() ) || ( team.NumPlayers( TEAM_PROPS ) < GetConVar("ph_min_waitforplayers"):GetInt() ) ) then
|
||
|
|
||
|
if ( timer.Exists( "RoundEndTimer" ) && timer.Exists( "CheckRoundEnd" ) ) then
|
||
|
|
||
|
timer.Pause( "RoundEndTimer" )
|
||
|
timer.Pause( "CheckRoundEnd" )
|
||
|
|
||
|
SetGlobalFloat( "RoundEndTime", -1 );
|
||
|
|
||
|
PrintMessage( HUD_PRINTTALK, "There's not enough players to start the game!" )
|
||
|
-- Reset the team score
|
||
|
team.SetScore(TEAM_PROPS, 0)
|
||
|
team.SetScore(TEAM_HUNTERS, 0)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- Send this as a global boolean
|
||
|
SetGlobalBool( "RoundWaitForPlayers", GetConVar("ph_waitforplayers"):GetBool() )
|
||
|
|
||
|
hook.Call("PH_RoundStart", nil)
|
||
|
|
||
|
end
|
||
|
-- End of Round Control Override
|
||
|
|
||
|
-- Player pressed a key
|
||
|
function PlayerPressedKey(pl, key)
|
||
|
-- Use traces to select a prop
|
||
|
local min,max = pl:GetHull()
|
||
|
if pl && pl:IsValid() && pl:Alive() && pl:Team() == TEAM_PROPS then
|
||
|
plhullz = max.z
|
||
|
|
||
|
if key == IN_ATTACK then
|
||
|
local trace = {}
|
||
|
if plhullz < 24 then
|
||
|
trace.start = pl:EyePos() + Vector(0, 0, plhullz + (24- plhullz))
|
||
|
trace.endpos = pl:EyePos() + Vector(0, 0, plhullz + (24 - plhullz)) + pl:EyeAngles():Forward() * 100
|
||
|
elseif plhullz > 84 then
|
||
|
trace.start = pl:EyePos() + Vector(0, 0, plhullz - 84)
|
||
|
trace.endpos = pl:EyePos() + Vector(0, 0, plhullz - 84) + pl:EyeAngles():Forward() * 300
|
||
|
else
|
||
|
trace.start = pl:EyePos() + Vector(0, 0, 8)
|
||
|
trace.endpos = pl:EyePos() + Vector(0, 0, 8) + pl:EyeAngles():Forward() * 100
|
||
|
end
|
||
|
trace.filter = ents.FindByClass("ph_prop")
|
||
|
|
||
|
local trace2 = util.TraceLine(trace)
|
||
|
if trace2.Entity && trace2.Entity:IsValid() && table.HasValue(PHE.USABLE_PROP_ENTITIES, trace2.Entity:GetClass()) then
|
||
|
if pl.UseTime <= CurTime() then
|
||
|
if !pl:IsHoldingEntity() then
|
||
|
local hmx, hz = trace2.Entity:GetPropSize()
|
||
|
if GetConVar("phe_check_props_boundaries"):GetBool() && !pl:CheckHull(hmx, hmx, hz) then
|
||
|
pl:SendLua("chat.AddText(Color(235, 10, 15), \"[PH: Enhanced]\", Color(220, 220, 220), \" There is no room to change that prop!\")")
|
||
|
else
|
||
|
GAMEMODE:PlayerExchangeProp(pl, trace2.Entity)
|
||
|
end
|
||
|
end
|
||
|
pl.UseTime = CurTime() + 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Prop rotation lock key
|
||
|
if pl && pl:IsValid() && pl:Alive() && pl:Team() == TEAM_PROPS then
|
||
|
if key == IN_RELOAD then
|
||
|
if pl:GetPlayerLockedRot() then
|
||
|
pl:SetNWBool("PlayerLockedRotation", false)
|
||
|
pl:PrintMessage(HUD_PRINTCENTER, "Prop Rotation Lock: Disabled")
|
||
|
net.Start("PHE.rotateState")
|
||
|
net.WriteInt(0, 2)
|
||
|
net.Send(pl)
|
||
|
else
|
||
|
pl:SetNWBool("PlayerLockedRotation", true)
|
||
|
pl:PrintMessage(HUD_PRINTCENTER, "Prop Rotation Lock: Enabled")
|
||
|
net.Start("PHE.rotateState")
|
||
|
net.WriteInt(1, 2)
|
||
|
net.Send(pl)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
hook.Add("KeyPress", "PlayerPressedKey", PlayerPressedKey)
|