Restricting/preventing players movement

How can I restrict player movement from “stepping into specific box”, efficiently?

For example:
If active map is gm_flagrass, I want to prevent players from nocliping/going into skybox.
Way I am doing it now:

  1. I find minimum and maximum world space vectors of the area I want to restrict movement to.
    Playing on gm_flatgrass map. Skybox min=Vector( -8191.96875, -8191.96875, -15871.96875 ), max=Vector( 8191.96875, 8191.96875, -13312.03125 ).
  2. Then, I put things together (gist):

--[[-------------------------------------------------Author:		OmegaExtern <omegaextern@live.com>
License:	MIT <https://opensource.org/licenses/MIT>
-----------------------------------------------------
To install, save this file in the following folder (create a folder if it does not exist, it is case-sensitive):
	(Steam installation directory)\steamapps\common\GarrysMod\garrysmod\addons\protect-skybox-area\lua\autorun\server


Supported maps:
	gm_flatgrass
---------------------------------------------------]]
local SupportedMaps, ActiveMap = {
	--[[---------------------------------------------
	local abs = math.abs
	local Size = Vector( abs( Min.x - Max.x ), abs( Min.y - Max.y ), abs( Min.z - Max.z ) )
	local Extents = Vector( Size.x / 2, Size.y / 2, Size.z / 2 )
	local Center = Vector( Min.x + Extents.x, Min.y + Extents.y, Min.z + Extents.z )
	-----------------------------------------------]]
	gm_flatgrass = { max = Vector( 8191.96875, 8191.96875, -13312.03125 ), min = Vector( -8191.96875, -8191.96875, -15871.96875 ) },
	gm_construct = { max = Vector( 15615.968750, 15103.968750, 15231.169922 ), min = Vector( -15103.968750, -15103.968750, 10367.232422 ) }
	-- Feel free to add more maps...
}, game.GetMap()
if ( !SERVER || !SupportedMaps[ ActiveMap ] ) then
	ErrorNoHalt( "Not serverside or unsupported map.
" )
	return
end
do
	local PlayerMeta = FindMetaTable( "Player" )
	if ( !PlayerMeta ) then
		ErrorNoHalt( "Could not find Player metatable.
" )
		return
	end


	--[[---------------------------------------------
		! DO NOT EDIT ABOVE /!\ DO NOT EDIT ABOVE !
	-----------------------------------------------]]


	local AllowAdmins		= false	-- Boolean. Set to true to allow (super)admins to go into skybox; otherwise false.
	local PrintNotification	= true	-- Boolean. Set to true to print a notification message that indicates whether a skybox is protected or not; otherwise false.


	--[[---------------------------------------------
		! DO NOT EDIT BELOW /!\ DO NOT EDIT BELOW !
	-----------------------------------------------]]


	local getlocal, getinfo, ActiveMapSkyboxBounds = debug.getlocal, debug.getinfo, SupportedMaps[ ActiveMap ]


	function PlayerMeta.IntersectsWithSkybox( self )
		assert( IsValid( self ) && isfunction( self.IsPlayer ) && self:IsPlayer(), Format( "bad argument #%i (%s) to '%s' (Player expected, got %s)", 1, getlocal( 1, 1 ), getinfo( 1, "n" ).name, type( self ) ) )
		local plyMin, plyMax = self:LocalToWorld( self:OBBMins() ), self:LocalToWorld( self:OBBMaxs() )
		return ( ( ( ( ( ActiveMapSkyboxBounds.min.x <= plyMax.x ) && ( ActiveMapSkyboxBounds.max.x >= plyMin.x ) ) && ( ( ActiveMapSkyboxBounds.min.y <= plyMax.y ) && ( ActiveMapSkyboxBounds.max.y >= plyMin.y ) ) ) && ( ActiveMapSkyboxBounds.min.z <= plyMax.z ) ) && ( ActiveMapSkyboxBounds.max.z >= plyMin.z ) )
	end


	local CreateTwoStateServerConVar = function( name, default, flags, helpText, funcOff, funcOn, notify, persist, forceExecution )
		if ( !SERVER ) then
			return
		end
		local funcName = getinfo( 1, 'n' ).name
		assert( isstring( name ) && name:len() > 1, Format( "bad argument %d# to '%s' (string expected, got %s)", 1, funcName, type( name ) ) )
		if ( !default || type( default ) == "nil" ) then
			default = 0
		elseif ( isstring( default ) ) then
			default = tonumber( default ) || 0
		elseif ( isfunction( default ) ) then
			default = default()
		end
		assert( isnumber( default ), Format( "bad argument %d# to '%s' (number expected, got %s)", 2, funcName, type( default ) ) )
		default = math.Clamp( math.floor( default ), 0, 1 )
		assert( isnumber( flags ) || istable( flags ), Format( "bad argument %d# to '%s' (number or table expected, got %s)", 3, funcName, type( flags ) ) )
		assert( isstring( helpText ), Format( "bad argument %d# to '%s' (number or table expected, got %s)", 4, funcName, type( helpText ) ) )
		local cvar = CreateConVar( name, default, flags, helpText )
		cvars.RemoveChangeCallback( cvar:GetName(), cvar:GetName() )
		table.Empty( cvars.GetConVarCallbacks( cvar:GetName(), false ) )
		cvars.AddChangeCallback( cvar:GetName(), function( convar_name, value_old, value_new )
			value_old = math.floor( tonumber( value_old ) || default )
			value_new = math.floor( tonumber( value_new ) || default )
			if ( ( value_old < 0 && value_new < 0 ) || ( value_old < 0 && value_new == 0 ) || ( value_old == 0 && value_new < 0 ) || ( value_old == 0 && value_new == 0 ) ) then
				RunConsoleCommand( convar_name, '0' )
				return
			end
			if ( ( value_old > 1 && value_new > 1 ) || ( value_old > 1 && value_new == 1 ) || ( value_old == 1 && value_new > 1 ) || ( value_old == 1 && value_new == 1 ) ) then
				RunConsoleCommand( convar_name, '1' )
				return
			end
			if ( ( value_old < 0 && value_new > 1 ) || ( value_old < 0 && value_new == 1 ) || ( value_old == 0 && value_new > 1 ) || ( value_old == 0 && value_new == 1 ) ) then
				if ( isfunction( funcOn ) ) then
					funcOn( cvar, convar_name, value_old, value_new )
				end
				if ( isbool( notify ) && notify ) then
					print( Format( "Server cvar \"%s\" changed to 1.", convar_name ) )
				end
				RunConsoleCommand( convar_name, '1' )
				return
			end
			if ( ( value_old > 1 && value_new < 0 ) || ( value_old == 1 && value_new < 0 ) || ( value_old > 1 && value_new == 0 ) || ( value_old == 1 && value_new == 0 ) ) then
				if ( isfunction( funcOff ) ) then
					funcOff( cvar, convar_name, value_old, value_new )
				end
				if ( isbool( notify ) && notify ) then
					print( Format( "Server cvar \"%s\" changed to 0.", convar_name ) )
				end
				RunConsoleCommand( convar_name, '0' )
				return
			end
			RunConsoleCommand( convar_name, tostring( math.Clamp( value_new, 0, 1 ) ) )
		end, cvar:GetName() )
		if ( isbool( persist ) && persist ) then
			local value = math.Clamp( cvar:GetInt(), 0, 1 )
			if ( forceExecution ) then
				if ( value == 1 ) then
					if ( isfunction( funcOn ) ) then
						funcOn( cvar, cvar:GetName(), value, 1 )
					end
					if ( isbool( notify ) && notify ) then
						print( Format( "Server cvar \"%s\" changed to 1.", cvar:GetName() ) )
					end
				else
					if ( isfunction( funcOff ) ) then
						funcOff( cvar, cvar:GetName(), value, 0 )
					end
					if ( isbool( notify ) && notify ) then
						print( Format( "Server cvar \"%s\" changed to 0.", cvar:GetName() ) )
					end
				end
			end
			RunConsoleCommand( cvar:GetName(), tostring( value ) )
		else
			if ( forceExecution ) then
				if ( default == 1 ) then
					if ( isfunction( funcOn ) ) then
						funcOn( cvar, cvar:GetName(), cvar:GetInt(), 1 )
					end
					if ( isbool( notify ) && notify ) then
						print( Format( "Server cvar \"%s\" changed to 1.", cvar:GetName() ) )
					end
				else
					if ( isfunction( funcOff ) ) then
						funcOff( cvar, cvar:GetName(), cvar:GetInt(), 0 )
					end
					if ( isbool( notify ) && notify ) then
						print( Format( "Server cvar \"%s\" changed to 0.", cvar:GetName() ) )
					end
				end
			end
			RunConsoleCommand( cvar:GetName(), tostring( default ) )
		end
		return cvar
	end


	local OnChangePrintNotification = function( sv_protectskybox )
		if ( !PrintNotification ) then
			return
		end
		if ( sv_protectskybox:GetBool() ) then
			MsgC( Color( 0, 128, 64 ), "Skybox is protected!
" )
		else
			MsgC( Color( 128, 32, 32 ), "Skybox is not protected!
" )
		end
	end


	local sv_protectskybox_allowadmins = CreateTwoStateServerConVar( "sv_protectskybox_allowadmins", ( AllowAdmins && 1 ) || 0, FCVAR_ARCHIVE, "Allow or disallow (super)admins to enter the skybox.", nil, nil, true, false, false )
	local sv_protectskybox = CreateTwoStateServerConVar( "sv_protectskybox", 1, FCVAR_ARCHIVE, "Enable or disable protection for the skybox area.", function( cvar )
		hook.Remove( "SkyboxIntersection", "FooBarSkyboxIntersections" )
		hook.Remove( "Think", "FooBarThinks" )
		OnChangePrintNotification( cvar )
	end, function( cvar )
		--
		-- Stop players from going into skybox area/space.
		--
		hook.Add( "SkyboxIntersection", "FooBarSkyboxIntersections", function( ply )
			-- Allow admins and super-admins to go into skybox.
			if ( sv_protectskybox_allowadmins:GetBool() && ( ply:IsAdmin() || ply:IsSuperAdmin() ) ) then
				return
			end
			-- Called when a player intersects with skybox.
			-- If non-admin player enters the skybox:
			ply:ChatPrint( "Sorry, you are not allowed to enter the skybox!" )
			ply:KillSilent() -- Kills a player, silently.
			-- If I just wanted to prevent, how can I find intersection vector?
			--ply:SetPos( ? IntersectionVector ? )
		end )
		hook.Add( "Think", "FooBarThinks", function()
			for _, ply in pairs( player.GetAll() ) do
				-- Skip over dead players. I am not sure about vehicles.
				if ( !ply:Alive() ) then
					continue
				end
				if ( ply:IntersectsWithSkybox() ) then
					hook.Call( "SkyboxIntersection", nil, ply )
				end
			end
		end )
		OnChangePrintNotification( cvar )
	end, true, true, true )


	--OnChangePrintNotification( sv_protectskybox )
end

Now this works. But, I feel unhappy about doing this inside Think hook. Improvements are welcome.
Also, another question from code, “how can I find intersection vector?”. I would like to learn and do that, the right way (and avoid doing tons of if-statements “check coords individually against every box-face/plane” and then set pos… < that’s newb way, I mean really).

Greetings from OmegaExtern :weeb:

Heres my take at it:



--core file
local PLAYER = FindMetaTable("Player")

local blockTable ={}

function AddToBlock(tablesToAdd)
	if(!istable(tablesToAdd)) then return false end
	
	local tablesToAddNames = {}
	for a = #tablesToAdd,1,-1 do
		tablesToAddNames[tablesToAdd[a]['name']] = true
	end
	for a = #blockTable,1,-1 do
		if(tablesToAddNames[blockTable[a]['name']]) then
			table.remove(blockTable,a)
			a=a-1
		end
	end
	for a = #tablesToAdd,1,-1 do
		table.insert(blockTable,tablesToAdd[a])
	end
	return true
end

function ClearBlock()
	table.Empty(blockTable)
end

function RemoveFromBlock(tablesToRemove)
	if(!istable(tablesToRemove)) then return false end
	local tablesToRemoveNames = {}
	for a = #tablesToRemove,1,-1 do
		tablesToRemoveNames[tablesToRemove[a]['name']] = true
	end
	for a = #blockTable,1,-1 do
		if(tablesToRemoveNames[blockTable[a]['name']]) then
			table.remove(blockTable,a)
			a=a-1
		end
	end
	return true
end

function PLAYER:LastValidPosition()
	if(self.lastValidPos) then
		self.moveData:SetVelocity(Vector(0,0,0))
		self.moveData:SetMaxSpeed(0)
		self.moveData:SetForwardSpeed(0)
		self.moveData:SetSideSpeed(0)
		self.moveData:SetUpSpeed(0)	
		self.moveData:SetOrigin(self.lastValidPos)
		return true
	end
	return false
end

function CompareVectors(vec1,vec2)
	local returnValue = nil
	if((vec1-vec2):IsZero()) then
		returnValue = 0
	elseif((vec1.x<vec2.x) and (vec1.y<vec2.y) and (vec1.z<vec2.z)) then
		returnValue = -1
	elseif((vec1.x>vec2.x) and (vec1.y>vec2.y) and (vec1.z>vec2.z)) then
		returnValue = 1
	end
	return returnValue	
end


function PLAYER:IsValidPosition()
	local thisEntry = {}
	for a = #blockTable,1,-1 do
		thisEntry=blockTable[a]
		if(thisEntry['name'] and thisEntry['type']) then
			if(thisEntry['type']=="rectangle") then
				if(isvector(thisEntry['min']) and isvector(thisEntry['max'])) then
					if(isvector(thisEntry['origin'])) then
						if( ( CompareVectors( (thisEntry['origin']-thisEntry['min']), self.targetPos ) == -1 ) and ( CompareVectors( (thisEntry['origin']+thisEntry['max']), self.targetPos) == 1 )) then
							return false	
						end	
					else
						if( ( CompareVectors( thisEntry['min'], self.targetPos) == -1 ) and ( CompareVectors(thisEntry['max'], self.targetPos) == 1)) then
							return false
						end
					end
				end

			elseif(thisEntry['type']=="circle") then
				if(isvector(thisEntry['origin']) and isnumber(thisEntry['radius'])) then
					if((self.targetPos:Distance(thisEntry['origin'])-thisEntry['radius']) <= 0) then
						return false
					end
				end
			end
		end
	end
	if(self:IsOnGround()) then
		self.lastValidPos = self.moveData:GetOrigin()
	end
	return true
end



hook.Add("FinishMove","MoveBlock",function(ply,moveData)
	local targetPos = moveData:GetOrigin()	
	if(ply.targetPos!=targetPos) then
		ply.targetPos = targetPos
		ply.moveData = moveData
		hook.Call("PlayerPositionChanged",nil,ply)
	end
end)


Then to block positions do:



local initTable = {
	{
	name="rect1",
	type="rectangle",
	min=Vector(-100,-100,-100),
	max=Vector(100,100,100)
	},
	{
	name="rect2",
	type="rectangle",
	origin=Vector(0,0,0),
	min=Vector(-100,-100,-100),
	max=Vector(100,100,100)
	},
	{
	name="circle1",
	type="circle",
	origin=Vector(0,0,0),
	radius=500
	}
}


AddToBlock(initTable)


And to detect if a player is in a blocked position and act:



hook.Add("PlayerPositionChanged","CheckIfPositionIsValid",function(ply)
	if(!ply:IsValidPosition()) then
		ply:LastValidPosition()
	end
end)


:smile:

[editline]1st May 2016[/editline]

You can add/remove places at anytime. The blocked places are added to a table with a name.

To remove a entry you just:



local initTable = {
	{
	name="rect1"
	},
	{
	name="rect2"
	},
	{
	name="circle1"
	}
}


Uses same structure as the Add, to make removing using a variable simple.

[editline]1st May 2016[/editline]

Oops frogot to add:



RemoveFromBlock(initTable)


to the edit above.

You are going to want to use FinishMove, not think.