User:EAi/freecam.lua

From Multi Theft Auto: Wiki
Jump to navigation Jump to search
-- state variables
local speed = 0
local strafespeed = 0
local rotX, rotY = 0,0

-- configurable parameters
local options = {
	normalMaxSpeed = 2,
	slowMaxSpeed = 0.2,
	fastMaxSpeed = 12,
	smoothMovement = true,
	acceleration = 0.3,
	decceleration = 0.15,
	mouseSensitivity = 1,
	maxYAngle = 188,
	key_fastMove = "lshift",
	key_slowMove = "lalt",
	key_forward = "w",
	key_backward = "s",
	key_left = "a",
	key_right = "d"
}

local mouseFrameDelay = 0

local rootElement = getRootElement()
local localPlayer = getLocalPlayer()

local getKeyState = getKeyState
do
	local mta_getKeyState = getKeyState
	function getKeyState(key)
		if isMTAWindowActive() then
			return false
		else
			return mta_getKeyState(key)
		end
	end
end

-- PRIVATE

local function freecamFrame ()
    -- work out an angle in radians based on the number of pixels the cursor has moved (ever)
    local cameraAngleX = rotX / 120
    local cameraAngleY = rotY / 120

    local freeModeAngleX = math.sin(cameraAngleX / 2)
    local freeModeAngleY = math.cos(cameraAngleX / 2) 
    local yangle = cameraAngleY / 1.5 -- the factor limits the ammount the camera can rotate, decrease it to increase the amount

    local freeModeAngleZ = math.sin(yangle)
	-- "local" removed by arc_ to make variables accessible to /freecampos command handler
    --[[local]] camPosX, camPosY, camPosZ = getCameraPosition()

    -- calculate a target based on the current position and an offset based on the angle
    --[[local]] camTargetX = camPosX + freeModeAngleX * 100
    --[[local]] camTargetY = camPosY + freeModeAngleY * 100
    --[[local]] camTargetZ = camPosZ + freeModeAngleZ * 100

	-- Calculate what the maximum speed that the camera should be able to move at.
    local mspeed = options.normalMaxSpeed
    if getKeyState ( options.key_fastMove ) then
        mspeed = options.fastMaxSpeed
	elseif getKeyState ( options.key_slowMove ) then
		mspeed = options.slowMaxSpeed
    end
	
	if options.smoothMovement then
		local acceleration = options.acceleration
		local decceleration = options.decceleration
	
	    -- Check to see if the forwards/backwards keys are pressed
	    local speedKeyPressed = false
	    if getKeyState ( options.key_forward ) then
			speed = speed + acceleration 
	        speedKeyPressed = true
	    end
		if getKeyState ( options.key_backward ) then
			speed = speed - acceleration 
	        speedKeyPressed = true
	    end

	    -- Check to see if the strafe keys are pressed
	    local strafeSpeedKeyPressed = false
		if getKeyState ( options.key_right ) then
	        if strafespeed > 0 then -- for instance response
	            strafespeed = 0
	        end
	        strafespeed = strafespeed - acceleration / 2
	        strafeSpeedKeyPressed = true
	    end
		if getKeyState ( options.key_left ) then
	        if strafespeed < 0 then -- for instance response
	            strafespeed = 0
	        end
	        strafespeed = strafespeed + acceleration / 2
	        strafeSpeedKeyPressed = true
	    end

	    -- If no forwards/backwards keys were pressed, then gradually slow down the movement towards 0
	    if speedKeyPressed ~= true then
			if speed > 0 then
				speed = speed - decceleration
			elseif speed < 0 then
				speed = speed + decceleration
			end
	    end

	    -- If no strafe keys were pressed, then gradually slow down the movement towards 0
	    if strafeSpeedKeyPressed ~= true then
			if strafespeed > 0 then
				strafespeed = strafespeed - decceleration
			elseif strafespeed < 0 then
				strafespeed = strafespeed + decceleration
			end
	    end

	    -- Check the ranges of values - set the speed to 0 if its very close to 0 (stops jittering), and limit to the maximum speed
	    if speed > -decceleration and speed < decceleration then
	        speed = 0
	    elseif speed > mspeed then
	        speed = mspeed
	    elseif speed < -mspeed then
	        speed = -mspeed
	    end
	 
	    if strafespeed > -(acceleration / 2) and strafespeed < (acceleration / 2) then
	        strafespeed = 0
	    elseif strafespeed > mspeed then
	        strafespeed = mspeed
	    elseif strafespeed < -mspeed then
	        strafespeed = -mspeed
	    end
	else
		speed = 0
		strafespeed = 0
		if getKeyState ( options.key_forward ) then speed = mspeed end
		if getKeyState ( options.key_backward ) then speed = -mspeed end
		if getKeyState ( options.key_left ) then strafespeed = mspeed end
		if getKeyState ( options.key_right ) then strafespeed = -mspeed end
	end

    -- Work out the distance between the target and the camera (should be 100 units)
    local camAngleX = camPosX - camTargetX
    local camAngleY = camPosY - camTargetY
    local camAngleZ = camPosZ - camTargetZ

    -- Calulcate the length of the vector
    local angleLength = math.sqrt(camAngleX*camAngleX+camAngleY*camAngleY+camAngleZ*camAngleZ)

    -- Normalize the vector, ignoring the Z axis, as the camera is stuck to the XY plane (it can't roll)
    local camNormalizedAngleX = camAngleX / angleLength
    local camNormalizedAngleY = camAngleY / angleLength
    local camNormalizedAngleZ = 0

    -- We use this as our rotation vector
    local normalAngleX = 0
    local normalAngleY = 0
    local normalAngleZ = 1

    -- Perform a cross product with the rotation vector and the normalzied angle
    local normalX = (camNormalizedAngleY * normalAngleZ - camNormalizedAngleZ * normalAngleY)
    local normalY = (camNormalizedAngleZ * normalAngleX - camNormalizedAngleX * normalAngleZ)
    local normalZ = (camNormalizedAngleX * normalAngleY - camNormalizedAngleY * normalAngleX)

    -- Update the camera position based on the forwards/backwards speed
    camPosX = camPosX + freeModeAngleX * speed
    camPosY = camPosY + freeModeAngleY * speed
    camPosZ = camPosZ + freeModeAngleZ * speed

    -- Update the camera position based on the strafe speed
    camPosX = camPosX + normalX * strafespeed
    camPosY = camPosY + normalY * strafespeed
    camPosZ = camPosZ + normalZ * strafespeed

    -- Update the target based on the new camera position (again, otherwise the camera kind of sways as the target is out by a frame)
    camTargetX = camPosX + freeModeAngleX * 100
    camTargetY = camPosY + freeModeAngleY * 100
    camTargetZ = camPosZ + freeModeAngleZ * 100

    -- Set the new camera position and target
    setCameraLookAt ( camTargetX, camTargetY, camTargetZ )
    setCameraPosition ( camPosX, camPosY, camPosZ )
end

local function freecamMouse (cX,cY,aX,aY)
	--ignore mouse movement if the cursor or MTA window is on
	--and do not resume it until at least 5 frames after it is toggled off
	--(prevents cursor mousemove data from reaching this handler)
	if isCursorShowing() or isMTAWindowActive() then
		mouseFrameDelay = 5
		return
	elseif mouseFrameDelay > 0 then
		mouseFrameDelay = mouseFrameDelay - 1
		return
	end

    local width, height = guiGetScreenSize()
    aX = aX - width / 2
    aY = aY - height / 2
    rotX = rotX + aX * options.mouseSensitivity
    rotY = rotY - aY * options.mouseSensitivity

    -- limit the camera to stop it going too far up or down
    if rotY < -options.maxYAngle then
        rotY = -options.maxYAngle
    elseif rotY > options.maxYAngle then
        rotY = options.maxYAngle
    end
end

-- PUBLIC

-- params: x, y, z  sets camera's position (optional)
-- param:  dontChangeFixedMode  leaves toggleCameraFixedMode alone if true, enables it if false or nil (optional)
function setFreecamEnabled (x, y, z, dontChangeFixedMode)
	if (x and y and z) then
	    setCameraPosition(x, y, z)
	end
	if (not dontChangeFixedMode) then
    	toggleCameraFixedMode(true)
	end
	addEventHandler("onClientRender", rootElement, freecamFrame)
	addEventHandler("onClientCursorMove",rootElement, freecamMouse)
	setElementData(localPlayer, "freecam:state", true)
	
	return true
end

-- param:  dontChangeFixedMode  leaves toggleCameraFixedMode alone if true, disables it if false or nil (optional)
function setFreecamDisabled(dontChangeFixedMode)
	if (not dontChangeFixedMode) then
    	toggleCameraFixedMode(false)
	end
	removeEventHandler("onClientRender", rootElement, freecamFrame)
	removeEventHandler("onClientCursorMove",rootElement, freecamMouse)
	setElementData(localPlayer, "freecam:state", false)
	
	return true
end

function isFreecamEnabled()
	return getElementData(localPlayer,"freecam:state")
end

function setFreecamOption(theOption, value)
	if options[theOption] ~= nil then
		options[theOption] = value
		return true
	else
		return false
	end
end

addEvent("doSetFreecamEnabled", true)
addEventHandler("doSetFreecamEnabled", rootElement, setFreecamEnabled)

addEvent("doSetFreecamDisabled", true)
addEventHandler("doSetFreecamDisabled", rootElement, setFreecamDisabled)

addEvent("doSetFreecamOption", true)
addEventHandler("doSetFreecamOption", rootElement, setFreecamOption)

-- added by arc_
addCommandHandler('freecam',
	function()
		if not isFreecamEnabled() then
			fadeCamera(true)
			setFreecamEnabled()
			outputConsole('Freecam is now enabled')
		else
			setFreecamDisabled()
			outputConsole('Freecam is now disabled')
		end
	end
)

addCommandHandler('freecampos',
	function(command, ...)
		if isFreecamEnabled() then
			outputConsole('setCameraPosition(' .. camPosX .. ', ' .. camPosY .. ', ' .. camPosZ .. ')')
			outputConsole('setCameraLookAt(' .. camTargetX .. ', ' .. camTargetY .. ', ' .. camTargetZ .. ')')
			triggerServerEvent('doFreecamPositionLog', localPlayer, camPosX, camPosY, camPosZ, camTargetX, camTargetY, camTargetZ, ...)
		end
	end
)