Script - Zoom

From FreeSpace Wiki
Revision as of 17:52, 17 September 2010 by M!m (talk | contribs) (New config options and bug fixes)
Jump to: navigation, search

This script enables you to zoom your view so precise aiming is easier. It includes a config file that specifies the values the script will use. It also features a FRED SEXP interface so A FREDer can force a zoom-in (or -out) as needed. For more documentation check out this thread.

Table Entry

  • To use create an file called zoom-sct.tbm in your date/tables directory and paste the following code into it:
#Conditional Hooks

$Application: FS2_Open
$On Game Init:[[zoom_init.lua]]
$On Key Pressed: [[zoom_KeyDn.lua]]
$On Key Released: [[zoom_KeyUp.lua]]

$Application: FS2_Open
$On Mission Start: 
[
if not runZoomScript then
	runZoomScript = true
end
]

$On Mission End: 
[
if runZoomScript then
	runZoomScript = false
	if defaultZoomSet and cam:isValid() then
		cam:setFOV(normalFOV) -- Resetting the FOV in case we have not zoomed out completely
	end
	removeTemps()
end
]

$On Frame: 
[
if not hu.HUDDrawn then
	return -- Don't even think about drawing if HUD is not shown!
end
if runZoomScript then	
	if zooming then
		handleControls()
	
		drawProgress()
	end
end
]

#End

After this create the file zoom_init.lua int your data/scripts folder and paste the following into it:

-------------------------------------------------------------
--------              utility functions              --------
-------------------------------------------------------------
function trim(s)
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function isAllIdent(check)
	check = check:lower()
	if check == "**all**" then
		return true
	else
		return false
	end
end

function indexOf(t,val)
    for k,v in ipairs(t) do 
        if v == val then 
			return k 
		end
    end
end
-------------------------------------------------------------
---- defining functions used for parsing the config file ----
-------------------------------------------------------------

-- gets the next line out of the file
local function getNextLine(file)
	if file:isValid() then
		local line = file:read("*l")
		return line
	else
		ba.warning("Invalid file handle pased to readLine function")
		return nil
	end
end

-- looks up the set function which is connected to the specified keyword
local function getSetFunc(keyword)
	keyword = keyword:lower()
	for i,v in pairs(setTable) do
		index = i:lower()
		if index == keyword then
			return v
		end
	end
	return nil
end

local function addAllowedShipClass(value)
	if isAllIdent(value) then
		allowedShips = {}
		allowedShips[1] = value
		return false
	end
	if allowedShips[1] == nil or not isAllIdent(allowedShips[1]) then
		
		if indexOf(allowedShips,value) ~= nil then
			ba.warning("Ship " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local ship = tb.ShipClasses[value]
		if ship ~= nil and ship:isValid() then
			table.insert(allowedShips, value)
			return true
		else
			ba.warning("Specified ship class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addRestrictedShipClass(value)
	if isAllIdent(value) then
		restrictedShips = {}
		restrictedShips[1] = value
		return
	end
	if restrictedShips[1] == nil or not isAllIdent(restrictedShips[1]) then
		
		if indexOf(restrictedShips,value) ~= nil then
			ba.warning("Ship " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local ship = tb.ShipClasses[value]
		if ship ~= nil and ship:isValid() then
			table.insert(restrictedShips, value)
			return true
		else
			ba.warning("Specified ship class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addAllowedWeaponClass(value)
	if isAllIdent(value) then
		allowedWeapons = {}
		allowedWeapons[1] = value
		return false
	end
	
	if allowedWeapons[1] == nil or not isAllIdent(allowedWeapons[1]) then
	
		if indexOf(allowedWeapons,value) ~= nil then
			ba.warning("Weapon " .. value .. " was specified more than once. Skipping...")
			return false
		end
	
		local weapon = tb.WeaponClasses[value]
		if weapon ~= nil and weapon:isValid() then
			table.insert(allowedWeapons, value)
			return true
		else
			ba.warning("Specified weapon class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function addRestrictedWeaponClass(value)
	if isAllIdent(value) then
		restrictedWeapons = {}
		restrictedWeapons[1] = value
		return false
	end
	if restrictedWeapons[1] == nil or not isAllIdent(restrictedWeapons[1]) then
		
		if indexOf(restrictedWeapons,value) ~= nil then
			ba.warning("Weapon " .. value .. " was specified more than once. Skipping...")
			return false
		end
		
		local weapon = tb.WeaponClasses[value]
		if weapon ~= nil and weapon:isValid() then
			table.insert(restrictedWeapons, value)
			return true
		else
			ba.warning("Specified weapon class '" .. value .. "' does not exist.")
			return false
		end
	end
end

local function parseLine(line)
	if line == nil or line == "" then -- Check if this line needs to be parsed
		return
	end
		
	local comm_s, comm_e = line:find("--",1,true) 
	if comm_s ~= nil then
		line = line:sub(1,(comm_s - 1))
	end
	if line == "" then -- was the line fully commented away?
		return -- Nothing to be done...
	end
	local key_s, key_e = line:find(":",1,true)
	local val_s, val_e = line:find("%a",key_e)
	
	if key_s == nil then -- malformatted line
		return "Malformatted line: " .. line .. "\nSkipping..."
	end
	
	local keyword = line:sub(1,(key_e - 1))
	
	if val_s == nil then -- Maybe we_ve got a digit value
		val_s, val_e = line:find("%d",key_e) -- search for them
		if val_s == nil  then
			-- We have no value
			return "Keyword '" .. keyword .. "' hasn't specified a value. Skipping..."
		end
	end
	
	keyword = keyword:lower() -- make the keyword lowercase
	
	local val = line:sub(val_s, line:len())
	
	if val ~= nil then
		val = trim(val)
		keyword = trim(keyword)
		local setFunc = getSetFunc(keyword)
		if setFunc == nil then
			return "Unknown keyword: '" .. keyword .. "'"
		end
		setFunc(val)
	else
		return
	end
end

local function initSetTable()
	setTable = {}
	setTable["Zoom Factor"]=
	function(value) 
		local val = tonumber(value)
		if val == nil then
			return
			ba.warning("Non numeric value given to 'Zoom Factor'. Skipping...")
		end
		zoomValue = val
	end
	
	setTable["Transition Time"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Transition Time'. Skipping...")
			return
		end
		transitionTime = val
	end
	
	setTable["Sensitivity"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Sensitivity'. Skipping...")
			return
		end
		sensitivity = val
	end
	
	setTable["Allowed Ship Class"]=addAllowedShipClass
	
	setTable["Restricted Ship Class"]=addRestrictedShipClass
	
	setTable["Allowed Weapon Class"]=addWeaponClass
	
	setTable["Restricted Weapon Class"]=addRestrictedWeaponClass
	
	setTable["Linked Zoom"]=
	function(value)
		local yesKey = "yes"
		value = value:lower()
		if value == yesKey then
			linkedZoom = true
		else
			linkedZoom = false
		end
	end
	
	setTable["Key"]=
	function(key)
		if key == nil then
			ba.warning("Invalid value given for 'Key'. Skipping...")
			return
		end
		zoomKey = key
	end
	
	setTable["Zoom Lock Key"]=
	function(key)
		if key == nil then
			ba.warning("Invalid value given for 'Zoom Lock Key'. Skipping...")
			return
		end
		zoomLockKey = key
	end
	
	setTable["Progress Offset X"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Offset X'. Skipping...")
			return
		end
		offset_x = val
	end
	
	setTable["Progress Offset Y"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Offset >'. Skipping...")
			return
		end
		offset_Y = val
	end
	
	setTable["Progress Height"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Height'. Skipping...")
			return
		end
		heigth = val
	end
	
	setTable["Progress Width"]=
	function(value)
		local val = tonumber(value)
		if val == nil then
			ba.warning("Non numeric value given to 'Progress Width'. Skipping...")
			return
		end
		width = val
	end
	
	offset_x = 30
	offset_y = 70
	heigth = 8
	width = 80
end

local function readConfig()
	if cf.fileExists(configFile, "data/config", true) then
		local config = cf.openFile(configFile)
		if config:isValid() then
			initSetTable()
			local lineNumber = 0
			local parse = true
			while parse do
				lineNumber = lineNumber + 1
				local line = getNextLine(config)
				if line == nil then
					parse = false
					break
				end
				local error = parseLine(line) -- parse the found line
				if error ~= nil then
					ba.warning(configFile .. " [" .. tostring(lineNumber) .. "] : " .. error)
				end
			end
		else
			ba.warning("Handle to config file is invalid. Is the file readable?")
		end
	else
		ba.warning("No config file found. Returning to default")
	end
end

-------------------------------------------------------------
------these functions will be use for the FRED interface-----
-------------------------------------------------------------

function zoom_zoomIn()
	if defaultZoomSet and cam:isValid() then
		cam:setFOV(normalFOV) -- Resetting the FOV in case we have not zoomed out completely
	end
	fredOverride = true
	zoomIn()
end

function zoom_zoomOut()
	fredOverride = false
	zoomOut()
end

function zoom_alS(ship)
	if addAllowedShipClass(ship) then
		table.insert(tempAllowedShip, ship)
	end
end

function zoom_alW(weapon)
	if addAllowedWeaponClass(weapon) then
		table.insert(tempAllowedWeapon, weapon)
	end
end

function zoom_reS(ship)
	if addRestrictedShipClass(ship) then
		table.insert(tempRestrictedShip, ship)
	end
end

function zoom_reW(weapon)
	if addRestrictedWeaponClass(weapon) then
		table.insert(tempRestrictedWeapon, weapon)
	end
end


-------------------------------------------------------------
------      defining functions used in the script      ------
-------------------------------------------------------------
function removeTemps()
	for i,v in ipairs(tempAllowedShip) do
		index = indexOf(allowedShips,v)
		if index ~= nil then
			allowedShips[index]=nil
		end
	end
	
	for i,v in ipairs(tempRestrictedShip) do
		index = indexOf(restrictedShips,v)
		if index ~= nil then
			restrictedShips[index]=nil
		end
	end
	
	for i,v in ipairs(tempAllowedWeapon) do
		index = indexOf(allowedWeapons,v)
		if index ~= nil then
			allowedWeapons[index]=nil
		end
	end
	
	for i,v in ipairs(tempRestrictedWeapon) do
		index = indexOf(restrictedWeapons,v)
		if index ~= nil then
			restrictedWeapons[index]=nil
		end
	end
end

local function checkTables(allTbl,restrTbl, checkHandle)
	if checkHandle == nil then
		return false
	end
	if allTbl ~= nil and restrTbl ~= nil then
		if #allTbl > 0 then
			if isAllIdent(allTbl[1]) then
				if #restrTbl > 0 then
					if isAllIdent(restrTbl[1]) then
						return true
					end
				end
				for i,v in ipairs(restrTbl) do
				if checkHandle.Name == v then
						return false
					end
				end
				return true
			elseif isAllIdent(restrTbl[1]) then
				if #allTbl > 0 then
					if isAllIdent(allTbl[1]) then
						return true
					end
				end
				for i,v in ipairs(allTbl) do
					if checkHandle.Name == v then
						return true
					end
				end
				return false
			else
				local allowed = false
				for i,v in ipairs(allTbl) do
					if checkHandle.Name == v then
						allowed = true
						break
					end
				end
				for i,v in ipairs(restrTbl) do
					if checkHandle.Name == v then
						allowed = false
						break
					end
				end
				return allowed
			end
		end
	end
	return true
end

function isAllowedShip(shipClass)
	return checkTables(allowedShips,restrictedShips,shipClass)
end

function isAllowedWeapon(weaponClass)
	return checkTables(allowedWeapons,restrictedWeapons,weaponClass)
end

function getProgressString() 
	local progressString = "Zooming"
	if zoomingIn then
		progressString = progressString .. " in:"
	elseif zoomingOut then
		progressString = progressString .. " out:"
	elseif zoomedIn then
		progressString = "Zoomed in:"
	elseif zoomedOut then
		progressString = "Zoomed out:"
	else
		progressString = "Zoomed:"
	end
	return progressString
end

function handleControls()
	if zoomingIn or zoomedIn then
		if currentProgr > 0 then
			local tempSensValue = sensitivity * 100 / currentProgr
			
			if tempSensValue <= 1 then
				local ci = ba.getControlInfo()
				ci.Pitch = ci.Pitch * tempSensValue
				ci.Heading = ci.Heading * tempSensValue
				ci.Bank = ci.Bank * tempSensValue
			end
		end
	end
end

function isAllowedToZoom()
	local allowedShip = isAllowedShip(hv.Player.Class)
	
	local allowedWeapon = true
	local primWeaponBank = hv.Player.PrimaryBanks
	if not linkedZoom and primWeaponBank.Linked then
		allowedWeapon = false
	else
		for i=0, #primWeaponBank do
			local v = primWeaponBank[i]
			if v.Armed then
				if isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
					allowedWeapon = true
					break
				else
					allowedWeapon = false
				end
			end
		end
		if allowedWeapon then
			local secWeaponBank = hv.Player.SecondaryBanks
			for i=0, #secWeaponBank do
				local v = secWeaponBank[i]
				if v.Armed then
					if isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
						allowedWeapon = true
						break
					else
						allowedWeapon = false
					end
				end
			end
		end
	end
	return allowedShip and allowedWeapon
end

function zoomIn()
	if not cam or not cam:isValid() then
		for i=1,#gr.Cameras do
			if gr.Cameras[i].Name == "Main camera" then -- There is no better method than this :(
				cam = gr.Cameras[i]
			end
		end
	end
	if not zooming then
		if hv.Player:isValid() then
			if cam ~= nil and cam:isValid() then
				if isAllowedToZoom() then
					if currentProgr > 0 and currentProgr < 100 then
						stepWidth = width / currentProgr -- Setting the stepWidth in case the zoom may not have been completed
					end
					
					zoomEndProgress = 100
				
					zooming = true
					
					zoomingIn = true
					zoomedIn = false
					zoomedOut = false
					zoomingOut = false
					
					if not defaultZoomSet then
						normalFOV = cam.FOV
						defaultZoomSet = true
					end
					zoom = normalFOV * zoomValue
					
					ba.setControlMode(LUA_FULL_CONTROLS)
					
					cam:setFOV(zoom,transitionTime,transitionTime / 2,transitionTime / 4)
				end
			end
		end
	end
end

function zoomOut()
	if zooming and not zoomOutOverride then
		if hv.Player:isValid() then
			if cam ~= nil and cam:isValid() then
			
				stepWidth = width / 100
			
				zoomingIn = false
				zoomingOut = true
				zoomedIn = false
				zoomedOut = false
				
				if currentProgr > 0 then
					zoomEndProgress = currentProgr -- Save the progress we made as we begin to zoom back
				end
				
				cam:setFOV(normalFOV,transitionTime,transitionTime / 2,transitionTime / 4)
				
				ba.setControlMode(NORMAL_CONTROLS)
				
				fredOverride = false
			end
		end
	end
end

function drawProgress()
	progressString = getProgressString()
	
	gr.setColor(0,255,0)
	
	local stringWidth = gr.getStringWidth(progressString);
	gr.drawString(progressString,30,70)
		
	local realOffset_x = offset_x + stringWidth + 10
	gr.drawRectangle(realOffset_x, offset_y,realOffset_x + width, offset_y + heigth, zoomedIn)
	local frameTime = ba.getFrametime()
	
	local progressLineOffset_x = realOffset_x + stepWidth * currentProgr
	gr.drawLine(progressLineOffset_x, offset_y, progressLineOffset_x, offset_y + heigth)
	
	if not isAllowedToZoom() and not zoomOutOverride then
		if zoomingIn or zoomedIn then
			zoomOut()
			zoomOutOverride = true
		end
	end
	
	local thisFrameProg = (frameTime / transitionTime) * zoomEndProgress
	if zoomingIn then
		currentProgr = currentProgr + thisFrameProg
		if currentProgr >= 100 then
			currentProgr = 100
		zoomingIn = false
		zoomedIn = true
		end
	elseif zoomingOut then
		currentProgr = currentProgr - thisFrameProg
		if currentProgr <= 0 then
			currentProgr = 0
			zoomOutOverride = false
			zoomedOut = true
			zoomingOut = false
			zooming = false
		end
	end
end

local function init() 
	if useConfig then
		readConfig() -- read the config file if exists
	end
end

local function initVars() 
	-- Some things to tell the script to do something and some default values
	runZoomScript = false
	useConfig = true
	
	zoomValue = 0.1
	normalFOV = 0.75
	transitionTime = 2
	zooming = false
	defaultZoomSet = false
	configFile = "zoom_config.cfg"
	zoomOutOverride = false
	linkedZoom = true
	zoomKey = "d"
	zoomLockKey = nil
	
	fredOverride=false
	
	lockedZoom = false
		
	-- Setting the values to be used in the $On Frame: hook
	currentProgr = 0
	zoomEndProgress = 100

	zoomingIn = false
	zoomedIn = false

	zoomingOut = false
	zoomedOut = true

	-- Constants for drawing the progress
	offset_x = 30
	offset_y = 70
	heigth = 8
	width = 80

	stepWidth = width / 100

	-- Settings for the sensitivity
	sensitivity = 0.25

	-- The camera which is used for zooming (is initialized in the first $Key Pressed: hook)
	cam = nil
	
	-- Tables that hold the allowe/restricted informations
	-- Ships
	allowedShips = {"**all**"}
	tempAllowedShip = {}
	
	restrictedShips = {}
	tempRestrictedShip = {}
	
	-- Weapons
	allowedWeapons = {"**all**"}
	tempAllowedWeapon = {}	
	
	restrictedWeapons = {}
	tempRestrictedWeapon = {}
end

-- Initialize the global variables
initVars()

-- Initialize the rest
init()

ba.print("Zoom Script initialized!")

Then create the file zoom_KeyDn.lua and zoom_KeyUp.lua in your data/scripts directory and paste the following into then:

zoom_KeyDn.lua:

if runZoomScript and not fredOverride then
	if hv.Key:lower() == zoomLockKey:lower() then
		if lockedZoom then
			lockedZoom = false
			zoomOut()
		else
			lockedZoom = true
			zoomIn()
		end
	elseif hv.Key:lower() == zoomKey:lower() then
		zoomIn()
	end
end

zoom_KeyUp.lua:

if runZoomScript and not fredOverride then
	if not lockedZoom then
		if hv.Key:lower() == zoomKey:lower() then
			zoomOut()
		end
	end
end

After this create a file named zoom_config.cfg in your data/config directory and paste the following text into it:

-- Setting standart values
Zoom Factor: 0.1
Transition Time: 2
Sensitivity: 0.25
Linked Zoom: YES
Key: d
Zoom Lock Key: Alt-d

-- Displaying Values
Progress Offset X: 30
Progress Offset Y: 70
Progress Height: 8
Progress Width: 80

-- Setting ship class options
Allowed Ship Class: **all**

-- Setting weapon class options
Allowed Weapon Class: **all**