Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebalance Spawnpoints #26

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
628f673
Add spawnpoint limit (default 1)
legokidlogan Aug 1, 2024
b517bbe
Reorganize; de-clutter global namespace
legokidlogan Aug 1, 2024
79762f1
Lint/cleanup
legokidlogan Aug 1, 2024
fc6e40e
Simplify
legokidlogan Aug 1, 2024
56f0a2e
Clarity
legokidlogan Aug 1, 2024
0056efc
Remove extra arg
legokidlogan Aug 1, 2024
dd3f80d
Fix logic
legokidlogan Aug 1, 2024
496146e
Move linking checks to a hook
legokidlogan Aug 1, 2024
61507fa
Enforce naming convention on instance vars
legokidlogan Aug 1, 2024
feb3100
Remove unused
legokidlogan Aug 1, 2024
aab0ac8
Add hook for denying spawnpoint creation
legokidlogan Aug 1, 2024
0f1c768
Update README
legokidlogan Aug 1, 2024
95d49f5
Update README
legokidlogan Aug 1, 2024
7bec030
Add cooldowns from players and points being spawned
legokidlogan Aug 1, 2024
cffebc5
Lint
legokidlogan Aug 1, 2024
9fc2fcd
Improve effect; add effect on spawnpoint cooldown done
legokidlogan Aug 1, 2024
b1e5f69
Fix typo
legokidlogan Aug 1, 2024
c01e0de
Improve audiovisual feedback to (un)linking
legokidlogan Aug 1, 2024
5969a7d
Add high-level sound when removed with a link, to inform nearby players
legokidlogan Aug 1, 2024
e88bcc6
Fix weird floating bug
legokidlogan Aug 1, 2024
d9a6185
Fix effects being outside of the world and not rendering
legokidlogan Aug 25, 2024
03dcf1b
Don't use the -0.1 hack, it's messier and breaks spawn legality
legokidlogan Aug 25, 2024
af6f61b
Move friendliness check to a function
legokidlogan Aug 26, 2024
f18a0bc
Make friendliness check available in both realms
legokidlogan Aug 26, 2024
5021f65
Use 3D2D text for ultimate clarity and signposting
legokidlogan Aug 26, 2024
2666ba2
Tweak message display
legokidlogan Aug 26, 2024
12a91c3
Sync cooldown ignore with message display
legokidlogan Aug 26, 2024
be8b780
Only apply cooldowns if a point was recently removed
legokidlogan Aug 27, 2024
e46b4ce
Don't play the charged effect if the cooldown is being ignored
legokidlogan Aug 27, 2024
a77cf8c
Block color and material changing
legokidlogan Aug 28, 2024
a17b619
Fully block sf/e2/wire from color and material changing
legokidlogan Aug 28, 2024
2dddc09
Return true
legokidlogan Sep 20, 2024
278dbbf
Work with auto-refresh
legokidlogan Sep 20, 2024
a40ad3e
Change default interact cooldown
legokidlogan Sep 20, 2024
23efc01
Use an inernal health system
legokidlogan Sep 20, 2024
11474c5
Inform player of when creation cooldown is over if they trigger it
legokidlogan Sep 20, 2024
fd0e442
Also show the time remaining in the denial hint
legokidlogan Sep 20, 2024
9b2c345
Tweak message text
legokidlogan Sep 20, 2024
efe3689
Clarity
legokidlogan Sep 20, 2024
add4817
Add text outline
legokidlogan Sep 20, 2024
62c2475
Simplify text
legokidlogan Sep 22, 2024
bd696a6
Simplify text
legokidlogan Sep 22, 2024
233d16a
Simplify text
legokidlogan Sep 22, 2024
29ee834
Simplify text
legokidlogan Sep 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
# cfc_mobile_spawnpoint_2
CFC's refactor of the CFC Mobile SpawnPoint 2


## Server Convars

| Convar | Description | Default |
| :---: | :---: | :---: |
| sbox_maxsent_spawnpoint | The max number of spawn points per player. | 1 |
| cfc_spawnpoints_cooldown_on_ply_spawn | When a player spawns, they must wait this many seconds before they can create/link spawn points. | 10 |
| cfc_spawnpoints_cooldown_on_point_spawn | When a spawn point is created, it cannot be linked to for this many seconds. | 5 |
| cfc_spawnpoints_removal_window | Player/point cooldowns only apply if a previous spawn point was removed in the past X seconds. 0 to not alter cooldowns. | 30 |
| cfc_spawnpoints_interact_cooldown | Per-player interaction cooldown for spawn points. | 0.5 |


## Shared Functions

- `friendly, failReason = CFC_SpawnPoints.IsFriendly( spawnPoint, ply )`
- Determines if a player is 'friendly' with a spawn point.
- i.e. they can link to it, if no cooldowns or other restrictions block them.
- You can override this during the `InitPostEntity` hook if you need a different check (e.g. a squad/faction system).
- Be sure to override it in both the server and client realms.
- Unfriendly players will automatically be denied from using the spawn point.


## Server Hooks

- `denyReason = CFC_SpawnPoints_DenyLink( spawnPoint, ply )`
- Return true or a string to prevent a player from linking to a spawn point.
- Returning a string will display it to the player to show why they couldn't do the link.
- `denyReason = CFC_SpawnPoints_DenyCreation( ply, data )`
- Return true or a string to prevent a player from creating a spawn point.
- Returning a string will display it to the player to show why they couldn't create the spawn point.
- `data` is a table with keys `Pos` and `Angle`, for where the spawn point will be located.


## Shared Hooks

- `ignoreCooldown = CFC_SpawnPoints_IgnorePlayerSpawnCooldown( ply )`
- Allow a player to ignore the spawn point creation/linking cooldown from the player spawning in.
- For example, make builders ignore it if you have a build/pvp system.
- `ignoreCooldown = CFC_SpawnPoints_IgnorePointSpawnCooldown( spawnPoint, ply )`
- Allow a player to ignore the linking cooldown of a recently-spawned spawn point.
- For example, make builders ignore it if you have a build/pvp system.
16 changes: 16 additions & 0 deletions lua/autorun/client/cl_cfc_spawnpoint.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

net.Receive( "CFC_SpawnPoints_CreationDenied", function()
local reason = net.ReadString()

notification.AddLegacy( reason, NOTIFY_ERROR, 5 )
surface.PlaySound( "buttons/button10.wav" )
end )

net.Receive( "CFC_SpawnPoints_LinkDenySound", function()
surface.PlaySound( "npc/roller/code2.wav" )
end )

net.Receive( "CFC_SpawnPoints_CreationCooldownOver", function()
notification.AddLegacy( "You can create spawnpoints again!", NOTIFY_GENERIC, 5 )
surface.PlaySound( "buttons/button3.wav" )
end )
171 changes: 165 additions & 6 deletions lua/autorun/server/sv_cfc_spawnpoint.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,177 @@
resource.AddWorkshop( "3114959065" )

local HOOK_CANTOOL = "CFC_Spawnpoint2_BannedTools"
local bannedTools = {
CFC_SpawnPoints = CFC_SpawnPoints or {}

CFC_SpawnPoints.COMMANDS = {
["unlinkSpawnPoint"] = {
["!unlinkspawn"] = true,
["!unlinkspawnpoint"] = true
},
["unlinkThisSpawnPoint"] = {
["!unlinkthis"] = true
}
}

CFC_SpawnPoints.BANNED_TOOLS = {
["nocollideeverything"] = true,
["nocollide"] = true,
["material"] = true,
["submaterial"] = true,
["fadingdoor"] = true,
["fading_door"] = true,
["colour"] = true,
}
hook.Remove( "CanTool" , HOOK_CANTOOL )
hook.Add( "CanTool" , HOOK_CANTOOL, function( ply, tr, tool )

local COOLDOWN_ON_PLY_SPAWN

local commands = CFC_SpawnPoints.COMMANDS
local bannedTools = CFC_SpawnPoints.BANNED_TOOLS

local heightOfSpawnPointPlusOne = 16

util.AddNetworkString( "CFC_SpawnPoints_CreationDenied" )
util.AddNetworkString( "CFC_SpawnPoints_LinkDenySound" )
util.AddNetworkString( "CFC_SpawnPoints_CreationCooldownOver" )


----- SETUP -----

local function localizeConvars()
COOLDOWN_ON_PLY_SPAWN = GetConVar( "cfc_spawnpoints_cooldown_on_ply_spawn" )
end


hook.Add( "InitPostEntity", "CFC_SpawnPoints_Setup", localizeConvars )
if Entity( 0 ) ~= NULL then localizeConvars() end -- Work with auto-refresh

hook.Add( "PlayerSpawn", "SpawnPointHook", function( ply )
local spawnPoint = ply:GetNWEntity( "CFC_SpawnPoints_LinkedSpawnPoint" )
if not spawnPoint or not spawnPoint:IsValid() then return end
if not spawnPoint:IsInWorld() then
ply:ChatPrint( "Your linked spawn point is in an invalid location" )
return
end

local spawnPos = spawnPoint:GetPos() + Vector( 0, 0, heightOfSpawnPointPlusOne )
ply:SetPos( spawnPos )
end )

hook.Add( "PlayerSpawn", "CFC_SpawnPoints_ApplyCooldownFromPlayerSpawn", function( ply )
local cooldown = COOLDOWN_ON_PLY_SPAWN:GetFloat()

ply:SetNWFloat( "CFC_SpawnPoints_SpawnCooldownEndTime", CurTime() + cooldown )
end )

hook.Add( "CanTool", "CFC_Spawnpoint2_BannedTools", function( ply, tr, tool )
if not tr.Hit then return end
if not IsValid( tr.Entity ) then return end
if tr.Entity:GetClass() ~= "sent_spawnpoint" then return end

if bannedTools[tool] then
ply:ChatPrint( string.format( "You cant use '%s' on a spawnpoint", tool) )
ply:ChatPrint( string.format( "You cant use '%s' on a spawnpoint", tool ) )
return false
end
end)
end )

hook.Add( "PlayerDisconnected", "UnlinkPlayerOnDisconnect", function( ply )
local spawnPoint = ply:GetNWEntity( "CFC_SpawnPoints_LinkedSpawnPoint" )
if not IsValid( spawnPoint ) then return end
if not spawnPoint.UnlinkPlayer then return end

spawnPoint:UnlinkPlayer( ply )
end )

hook.Add( "CFC_SpawnPoints_DenyCreation", "CFC_SpawnPoints_EnforcePlayerSpawnCooldown", function( ply, data )
local cooldownEndTime = ply:GetNWFloat( "CFC_SpawnPoints_SpawnCooldownEndTime", 0 )

local timeLeft = cooldownEndTime - CurTime()

if timeLeft > 0 then
if hook.Run( "CFC_SpawnPoints_IgnorePlayerSpawnCooldown", ply ) then return end

-- If this cooldown stops a creation attempt, notify the player when it's over.
timer.Create( "CFC_SpawnPoints_NotifyPlayerSpawnCooldownOver_" .. ply:SteamID(), timeLeft + 0.1, 1, function()
if not IsValid( ply ) then return end

-- Don't notify if something else is still blocking spawnpoint creation.
if not hook.Run( "CFC_SpawnPoints_DenyCreation", ply, data ) then
net.Start( "CFC_SpawnPoints_CreationCooldownOver" )
net.Send( ply )
end
end )

return "You must wait " .. math.ceil( timeLeft ) .. " second(s) before creating a new Spawn Point"
end
end )

hook.Add( "CFC_SpawnPoints_DenyLink", "CFC_SpawnPoints_EnforcePlayerSpawnCooldown", function( _, ply )
local cooldownEndTime = ply:GetNWFloat( "CFC_SpawnPoints_SpawnCooldownEndTime", 0 )

if CurTime() < cooldownEndTime then
if hook.Run( "CFC_SpawnPoints_IgnorePlayerSpawnCooldown", ply ) then return end

return "You must wait after spawning to make any links."
end
end )

hook.Add( "CFC_SpawnPoints_DenyLink", "CFC_SpawnPoints_EnforcePointSpawnCooldown", function( spawnPoint, ply )
local cooldownEndTime = spawnPoint:GetCreationCooldownEndTime()

if CurTime() < cooldownEndTime then
if hook.Run( "CFC_SpawnPoints_IgnorePointSpawnCooldown", spawnPoint, ply ) then return end

return "The spawn point is not ready to be linked to yet."
end
end )

-- Denies linking based on CFC_SpawnPoints.IsFriendly()
hook.Add( "CFC_SpawnPoints_DenyLink", "CFC_SpawnPoints_FriendCheck", function( spawnPoint, ply )
local friendly, failReason = CFC_SpawnPoints.IsFriendly( spawnPoint, ply )

if not friendly then return failReason end
end )


----- CHAT COMMANDS -----

hook.Add( "PlayerSay", "UnlinkSpawnPointCommand", function( ply, txt )
local text = string.lower( txt ):gsub( "%s+", "" ) -- Remove whitespace
local unlinkSpawnCommands = commands.unlinkSpawnPoint
if not unlinkSpawnCommands[text] then return end

local spawnPoint = ply:GetNWEntity( "CFC_SpawnPoints_LinkedSpawnPoint" )
if not IsValid( spawnPoint ) then
ply:PrintMessage( 4, "You are not linked to a Spawn Point" )

return
end

spawnPoint:UnlinkPlayer( ply )
ply:PrintMessage( 4, "Spawn Point unlinked" )
end )

hook.Add( "PlayerSay", "UnlinkThisSpawnPointCommand", function( ply, txt )
local text = string.lower( txt ):gsub( "%s+", "" ) -- Remove whitespace
local unlinkThisSpawnCommands = commands.unlinkThisSpawnPoint
if not unlinkThisSpawnCommands[text] then return end

local spawnPoint = ply:GetEyeTraceNoCursor().Entity
if not IsValid( spawnPoint ) then return end

if spawnPoint:GetClass() ~= "sent_spawnpoint" then
ply:PrintMessage( 4, "You must be looking at a Spawn Point to use this command" )

return
end

local owner = spawnPoint:CPPIGetOwner()

if owner ~= ply and not ply:IsAdmin() then
ply:PrintMessage( 4, "That's not yours! You can't unlink others from this Spawn Point" )

return
end

spawnPoint:UnlinkAllPlayersExcept( { [owner] = true } )
ply:PrintMessage( 4, "All players except the owner have been unlinked from this Spawn Point" )
end )
99 changes: 99 additions & 0 deletions lua/autorun/sh_cfc_spawnpoint.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
CFC_SpawnPoints = CFC_SpawnPoints or {}

if cleanup then
cleanup.Register( "sent_spawnpoint" )
end

CreateConVar( "sbox_maxsent_spawnpoint", 1, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "The max number of spawn points per player.", 0, 100 )
CreateConVar( "cfc_spawnpoints_cooldown_on_ply_spawn", 10, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "When a player spawns, they must wait this many seconds before they can create/link spawn points.", 0, 1000 )
CreateConVar( "cfc_spawnpoints_cooldown_on_point_spawn", 5, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "When a spawn point is created, it cannot be linked to for this many seconds.", 0, 1000 )
CreateConVar( "cfc_spawnpoints_interact_cooldown", 0.5, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "Per-player interaction cooldown for spawn points.", 0, 1000 )
CreateConVar( "cfc_spawnpoints_health_max", 1500, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "Max health of spawnpoints. 0 to disable.", 0, 10000 )
CreateConVar( "cfc_spawnpoints_health_regen", 200, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "Health regenerated per second by spawnpoints. 0 to disable.", 0, 10000 )
CreateConVar( "cfc_spawnpoints_health_regen_cooldown", 10, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "If a spawnpoint takes damage, it must wait this long before it can start regenerating. 0 to disable.", 0, 10000 )

local REMOVAL_WINDOW = CreateConVar( "cfc_spawnpoints_removal_window", 30, { FCVAR_ARCHIVE, FCVAR_REPLICATED }, "Player/point cooldowns only apply if a previous spawn point was removed in the past X seconds. 0 to not alter cooldowns.", 0, 1000 )


----- GLOBAL FUNCTIONS -----

--[[
- Determines whether or not a player is considered 'friendly' to a spawn point.
- i.e. they can link to it, if no cooldowns or other restrictions block them.
- Returns friendly, failReason
- You can override this function in InitPostEntity if you need a different 'friendliness' check.
--]]
function CFC_SpawnPoints.IsFriendly( spawnPoint, ply )
if not CPPI then
if spawnPoint:GetCreatingPlayer() == ply then return true end

return false, "You can only link to your own Spawn Points."
end

local owner = spawnPoint:CPPIGetOwner()
if ply == owner then return true end

local friends = owner.CPPIGetFriends and owner:CPPIGetFriends()

if not friends or friends == CPPI.CPPI_DEFER or friends == CPPI.CPPI_NOTIMPLEMENTED then
return false, "You can only link to your own Spawn Points."
end

if table.HasValue( friends, ply ) then return true end

return false, "You are not buddied with the Spawn Point's owner."
end


----- PRIVATE FUNCTIONS -----

local function ignoreCooldownsDueToRemovalWindow( ply )
if REMOVAL_WINDOW:GetFloat() <= 0 then return false end

local lastRemoval = ply:GetNWFloat( "CFC_SpawnPoints_LastRemovedTime", 0 )
local hasBeenALongTime = CurTime() - lastRemoval > REMOVAL_WINDOW:GetFloat()

return hasBeenALongTime
end


----- SETUP -----

hook.Add( "CFC_SpawnPoints_IgnorePlayerSpawnCooldown", "CFC_SpawnPoints_RemovalWindow", function( ply )
if ignoreCooldownsDueToRemovalWindow( ply ) then return true end
end )

hook.Add( "CFC_SpawnPoints_IgnorePointSpawnCooldown", "CFC_SpawnPoints_RemovalWindow", function( _spawnPoint, ply )
if ignoreCooldownsDueToRemovalWindow( ply ) then return true end
end )

hook.Add( "InitPostEntity", "CFC_SpawnPoints_BlockInvisibility", function()
local entityMeta = FindMetaTable( "Entity" )

local setColor = entityMeta.SetColor
function entityMeta:SetColor( color )
if color and color.a ~= 255 and self:GetClass() == "sent_spawnpoint" then
setColor( self, Color( color.r, color.g, color.b, 255 ) )
else
setColor( self, color )
end
end

local setMaterial = entityMeta.SetMaterial
function entityMeta:SetMaterial( material )
if self:GetClass() == "sent_spawnpoint" then
setMaterial( self, "" )
else
setMaterial( self, material )
end
end

local setSubMaterial = entityMeta.SetSubMaterial
function entityMeta:SetSubMaterial( index, material )
if self:GetClass() == "sent_spawnpoint" then
material = ""
end

setSubMaterial( self, index, material )
end
end )
Loading