Difference between revisions of "Script - Zoom"

From FreeSpace Wiki
Jump to: navigation, search
(Minor code cleanup)
m (Table Entry: Fix error on recent FSO builds)
 
(12 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
This script enables you to zoom your view so precise aiming is easier.
 
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 [http://www.hard-light.net/forums/index.php?topic=70411.0 this thread].
 
== Table Entry ==
 
== Table Entry ==
* To use create an file called '''zoom-sct.tbm''' in your date/tables directory and paste the following code into it:
+
* To use create an file called '''zoom-sct.tbm''' in your ''date/tables'' directory and paste the following code into it:
 
<pre>
 
<pre>
 
#Conditional Hooks
 
#Conditional Hooks
 +
 +
$Application: FS2_Open
 +
$On Game Init:[[zoom_init.lua]]
 +
 +
$State: GS_STATE_GAME_PLAY
 +
$On Key Pressed: [[zoom_KeyDn.lua]]
 +
 +
$State: GS_STATE_GAME_PLAY
 +
$On Key Released: [[zoom_KeyUp.lua]]
  
 
$Application: FS2_Open
 
$Application: FS2_Open
Line 9: Line 20:
 
[
 
[
 
runZoomScript = true
 
runZoomScript = true
zoomValue = 0.1
 
normalFOV = 0.75
 
zooming = false
 
defaultZoomSet = false
 
 
]
 
]
  
$Application: FS2_Open
+
$On Mission End:
$On Mission End:  
 
 
[
 
[
runZoomScript = false
+
if runZoomScript then
 +
if defaultZoomSet and cam and cam:isValid() then
 +
cam:setFOV(normalFOV) -- Resetting the FOV in case we have not zoomed out completely
 +
end
 +
 +
resetZoomScript()
 +
 +
removeTemps()
 +
 +
runZoomScript = false
 +
end
 
]
 
]
  
 
$State: GS_STATE_GAME_PLAY
 
$State: GS_STATE_GAME_PLAY
$KeyPress: d
+
$On Frame:  
$On Key Pressed: [
+
[
if runZoomScript then  
+
if runZoomScript and zooming then
 +
handleControls()
 +
 +
if hu.HUDDrawn then
 +
drawProgress()
 +
end
 +
end
 +
]
 +
 
 +
#End
 +
</pre>
 +
 
 +
After this create the file '''zoom_init.lua''' int your ''data/scripts'' folder and paste the following into it:
 +
<pre>
 +
-------------------------------------------------------------
 +
--------              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
 +
zoomAllowedShips = {}
 +
zoomAllowedShips[1] = value
 +
return false
 +
end
 +
if zoomAllowedShips[1] == nil or not isAllIdent(zoomAllowedShips[1]) then
 +
 +
if indexOf(zoomAllowedShips,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(zoomAllowedShips, value)
 +
 +
useWeaponEnd = false
 +
 +
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
 +
zoomRestrictedShips = {}
 +
zoomRestrictedShips[1] = value
 +
return true
 +
end
 +
if zoomRestrictedShips[1] == nil or not isAllIdent(zoomRestrictedShips[1]) then
 +
 +
if indexOf(zoomRestrictedShips,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(zoomRestrictedShips, value)
 +
 +
useWeaponEnd = false
 +
 +
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
 +
zoomAllowedWeapons = {}
 +
zoomAllowedWeapons[1] = value
 +
return false
 +
end
 +
 +
if zoomAllowedWeapons[1] == nil or not isAllIdent(zoomAllowedWeapons[1]) then
 +
 +
if indexOf(zoomAllowedWeapons,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(zoomAllowedWeapons, value)
 +
 +
useWeaponEnd = false
 +
 +
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
 +
zoomRestrictedWeapons = {}
 +
zoomRestrictedWeapons[1] = value
 +
return false
 +
end
 +
if zoomRestrictedWeapons[1] == nil or not isAllIdent(zoomRestrictedWeapons[1]) then
 +
 +
if indexOf(zoomRestrictedWeapons,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(zoomRestrictedWeapons, value)
 +
 +
useWeaponEnd = false
 +
 +
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
 +
 +
line = trim(line)
 +
 +
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(".",key_e + 1)
 +
 +
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
 +
-- We have no value
 +
return "Keyword '" .. keyword .. "' hasn't specified a value. Skipping..."
 +
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 nil
 +
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"]=addAllowedWeaponClass
 +
 +
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
 +
 +
setTable["Weapon End"]=
 +
function(val)
 +
if val == nil then
 +
ba.warning("Invalid value given to 'Weapon End'!")
 +
return
 +
end
 +
weaponEnd = val
 +
useWeaponEnd = true
 +
end
 +
 +
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
 +
-- Prevent error on more recent FSO builds
 +
config:close()
 +
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 z_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 z_zoomOut()
 +
fredOverride = false
 +
zoomOut()
 +
end
 +
 
 +
function z_alS(ship)
 +
if addAllowedShipClass(ship) then
 +
table.insert(zoomTempAllowedShip, ship)
 +
end
 +
end
 +
 
 +
function z_alW(weapon)
 +
if addAllowedWeaponClass(weapon) then
 +
table.insert(zoomTempAllowedWeapon, weapon)
 +
end
 +
end
 +
 
 +
function z_reS(ship)
 +
if addRestrictedShipClass(ship) then
 +
table.insert(zoomTempRestrictedShip, ship)
 +
end
 +
end
 +
 
 +
function z_reW(weapon)
 +
if addRestrictedWeaponClass(weapon) then
 +
table.insert(zoomTempRestrictedWeapon, weapon)
 +
end
 +
end
 +
 
 +
 
 +
-------------------------------------------------------------
 +
------      defining functions used in the script      ------
 +
-------------------------------------------------------------
 +
function removeTemps()
 +
for i,v in ipairs(zoomTempAllowedShip) do
 +
index = indexOf(zoomAllowedShips,v)
 +
if index ~= nil then
 +
zoomAllowedShips[index]=nil
 +
end
 +
end
 +
 +
for i,v in ipairs(zoomTempRestrictedShip) do
 +
index = indexOf(zoomRestrictedShips,v)
 +
if index ~= nil then
 +
zoomRestrictedShips[index]=nil
 +
end
 +
end
 +
 +
for i,v in ipairs(zoomTempAllowedWeapon) do
 +
index = indexOf(zoomAllowedWeapons,v)
 +
if index ~= nil then
 +
zoomAllowedWeapons[index]=nil
 +
end
 +
end
 +
 +
for i,v in ipairs(zoomTempRestrictedWeapon) do
 +
index = indexOf(zoomRestrictedWeapons,v)
 +
if index ~= nil then
 +
zoomRestrictedWeapons[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 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
 +
return true
 +
end
 +
 
 +
function zoom_isAllowedShip(shipClass)
 +
if useWeaponEnd then
 +
return true
 +
else
 +
return checkTables(zoomAllowedShips,zoomRestrictedShips,shipClass)
 +
end
 +
end
 +
 
 +
function zoom_isAllowedWeapon(weaponClass)
 +
if useWeaponEnd then
 +
local name = weaponClass.Name
 +
local start = name:find("#", 0, true)
 +
if not start then
 +
return false
 +
end
 +
local substr = name:sub(start + 1, name:len())
 +
 +
return substr:lower() == weaponEnd:lower()
 +
else
 +
return checkTables(zoomAllowedWeapons,zoomRestrictedWeapons,weaponClass)
 +
end
 +
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 = zoom_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 zoom_isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
 +
allowedWeapon = true
 +
break
 +
else
 +
allowedWeapon = false
 +
end
 +
end
 +
end
 +
if (allowedWeapon and not useWeaponEnd) or (useWeaponEnd and not allowedWeapon) then
 +
local secWeaponBank = hv.Player.SecondaryBanks
 +
for i=0, #secWeaponBank do
 +
local v = secWeaponBank[i]
 +
if v.Armed then
 +
if zoom_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]
 +
break
 +
end
 +
end
 +
end
 +
 
if not zooming then
 
if not zooming then
plr = hv.Player
+
if hv.Player:isValid() then
if plr:isValid() then
+
if cam ~= nil and cam:isValid() then
if #gr.Cameras > 0 then
+
if isAllowedToZoom() then
cam = gr.Cameras[1]
+
if currentProgr > 0 and currentProgr < 100 then
if cam:isValid() then
+
stepWidth = width / currentProgr -- Setting the stepWidth in case the zoom may not have been completed
 +
end
 +
 +
zoomEndProgress = 100
 +
 
zooming = true
 
zooming = true
 +
 +
zoomingIn = true
 +
zoomedIn = false
 +
zoomedOut = false
 +
zoomingOut = false
 +
 
if not defaultZoomSet then
 
if not defaultZoomSet then
 
normalFOV = cam.FOV
 
normalFOV = cam.FOV
Line 37: Line 656:
 
end
 
end
 
zoom = normalFOV * zoomValue
 
zoom = normalFOV * zoomValue
cam:setFOV(zoom,2,1,0.5)
+
 +
ba.setControlMode(LUA_FULL_CONTROLS)
 +
 +
cam:setFOV(zoom,transitionTime,transitionTime / 2,transitionTime / 4)
 
end
 
end
 
end
 
end
Line 43: Line 665:
 
end
 
end
 
end
 
end
]
 
  
$On Key Released: [
+
function zoomOut()
if runZoomScript then
+
if zooming and not zoomOutOverride then
if zooming then
+
if hv.Player:isValid() then
plr = hv.Player
+
if cam ~= nil and cam:isValid() then
if plr:isValid() then
+
if #gr.Cameras > 0 then
+
stepWidth = width / 100
cam = gr.Cameras[1]
+
if cam:isValid() then
+
zoomingIn = false
zooming = false
+
zoomingOut = true
cam:setFOV(normalFOV,2,1,0.5)
+
zoomedIn = false
 +
zoomedOut = false
 +
 +
if currentProgr > 0 then
 +
zoomEndProgress = currentProgr -- Save the progress we made as we begin to zoom back
 
end
 
end
 +
 +
cam:setFOV(normalFOV,transitionTime,transitionTime / 2,transitionTime / 4)
 +
 +
ba.setControlMode(NORMAL_CONTROLS)
 +
 +
fredOverride = false
 
end
 
end
 
end
 
end
 
end
 
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
 +
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 allowed/restricted informations
 +
-- Ships
 +
zoomAllowedShips = {"**all**"}
 +
zoomTempAllowedShip = {}
 +
 +
zoomRestrictedShips = {}
 +
zoomTempRestrictedShip = {}
 +
 +
-- Weapons
 +
zoomAllowedWeapons = {"**all**"}
 +
zoomTempAllowedWeapon = {}
 +
 +
zoomRestrictedWeapons = {}
 +
zoomTempRestrictedWeapon = {}
 +
 +
-- or the equal weapon-postfix method
 +
weaponEnd = "zoom"
 +
useWeaponEnd = false
 +
end
 +
 
 +
function resetZoomScript()
 +
-- Reset camera in use...
 +
cam = nil
 +
end
 +
 
 +
-- Initialize the global variables
 +
initVars()
 +
 
 +
-- Initialize the rest
 +
init()
 +
 
 +
ba.print("Zoom Script initialized!\n")
 +
</pre>
 +
 
 +
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:'''
 +
<pre>
 +
if runZoomScript and not fredOverride then
 +
if zoomLockKey and hv.Key:lower() == zoomLockKey:lower() then
 +
if lockedZoom then
 +
zoomOut()
 +
lockedZoom = false
 +
return
 +
else
 +
zoomIn()
 +
lockedZoom = true
 +
return
 +
end
 +
end
 +
if hv.Key:lower() == zoomKey:lower() then
 +
zoomIn()
 +
end
 +
end
 +
</pre>
 +
 
 +
'''zoom_KeyUp.lua:'''
 +
<pre>
 +
if runZoomScript and not fredOverride then
 +
if not lockedZoom then
 +
if hv.Key:lower() == zoomKey:lower() then
 +
zoomOut()
 +
end
 +
end
 +
end
 +
</pre>
 +
 
 +
After this create a file named '''zoom_config.cfg''' in your ''data/config'' directory and paste the following text into it:
 +
<pre>
 +
-- Setting standard 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**
 +
 
 +
-- Now weapons with #zoom in the end will be allowed to zoom
 +
-- Uncomment this line to enable this behavior
 +
-- Weapon End: zoom
 
</pre>
 
</pre>
  
 
[[Category:Scripting Examples|Zoom]]
 
[[Category:Scripting Examples|Zoom]]

Latest revision as of 07:32, 30 April 2019

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]]

$State: GS_STATE_GAME_PLAY
$On Key Pressed: [[zoom_KeyDn.lua]]

$State: GS_STATE_GAME_PLAY
$On Key Released: [[zoom_KeyUp.lua]]

$Application: FS2_Open
$On Mission Start: 
[
runZoomScript = true
]

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

$State: GS_STATE_GAME_PLAY
$On Frame: 
[
if runZoomScript and zooming then
	handleControls()
	
	if hu.HUDDrawn then
		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
		zoomAllowedShips = {}
		zoomAllowedShips[1] = value
		return false
	end
	if zoomAllowedShips[1] == nil or not isAllIdent(zoomAllowedShips[1]) then
		
		if indexOf(zoomAllowedShips,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(zoomAllowedShips, value)
			
			useWeaponEnd = false
			
			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
		zoomRestrictedShips = {}
		zoomRestrictedShips[1] = value
		return true
	end
	if zoomRestrictedShips[1] == nil or not isAllIdent(zoomRestrictedShips[1]) then
		
		if indexOf(zoomRestrictedShips,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(zoomRestrictedShips, value)
			
			useWeaponEnd = false
			
			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
		zoomAllowedWeapons = {}
		zoomAllowedWeapons[1] = value
		return false
	end
	
	if zoomAllowedWeapons[1] == nil or not isAllIdent(zoomAllowedWeapons[1]) then
	
		if indexOf(zoomAllowedWeapons,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(zoomAllowedWeapons, value)
			
			useWeaponEnd = false
			
			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
		zoomRestrictedWeapons = {}
		zoomRestrictedWeapons[1] = value
		return false
	end
	if zoomRestrictedWeapons[1] == nil or not isAllIdent(zoomRestrictedWeapons[1]) then
		
		if indexOf(zoomRestrictedWeapons,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(zoomRestrictedWeapons, value)
			
			useWeaponEnd = false
			
			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
	
	line = trim(line)
		
	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(".",key_e + 1)
	
	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
		-- We have no value
		return "Keyword '" .. keyword .. "' hasn't specified a value. Skipping..."
	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 nil
	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"]=addAllowedWeaponClass
	
	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
	
	setTable["Weapon End"]=
	function(val)
		if val == nil then
			ba.warning("Invalid value given to 'Weapon End'!")
			return
		end	
		weaponEnd = val
		useWeaponEnd = true
	end
	
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
			-- Prevent error on more recent FSO builds
			config:close()
		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 z_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 z_zoomOut()
	fredOverride = false
	zoomOut()
end

function z_alS(ship)
	if addAllowedShipClass(ship) then
		table.insert(zoomTempAllowedShip, ship)
	end
end

function z_alW(weapon)
	if addAllowedWeaponClass(weapon) then
		table.insert(zoomTempAllowedWeapon, weapon)
	end
end

function z_reS(ship)
	if addRestrictedShipClass(ship) then
		table.insert(zoomTempRestrictedShip, ship)
	end
end

function z_reW(weapon)
	if addRestrictedWeaponClass(weapon) then
		table.insert(zoomTempRestrictedWeapon, weapon)
	end
end


-------------------------------------------------------------
------      defining functions used in the script      ------
-------------------------------------------------------------
function removeTemps()
	for i,v in ipairs(zoomTempAllowedShip) do
		index = indexOf(zoomAllowedShips,v)
		if index ~= nil then
			zoomAllowedShips[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempRestrictedShip) do
		index = indexOf(zoomRestrictedShips,v)
		if index ~= nil then
			zoomRestrictedShips[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempAllowedWeapon) do
		index = indexOf(zoomAllowedWeapons,v)
		if index ~= nil then
			zoomAllowedWeapons[index]=nil
		end
	end
	
	for i,v in ipairs(zoomTempRestrictedWeapon) do
		index = indexOf(zoomRestrictedWeapons,v)
		if index ~= nil then
			zoomRestrictedWeapons[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 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
	return true
end

function zoom_isAllowedShip(shipClass)
	if useWeaponEnd then
		return true
	else
		return checkTables(zoomAllowedShips,zoomRestrictedShips,shipClass)
	end
end

function zoom_isAllowedWeapon(weaponClass)
	if useWeaponEnd then
		local name = weaponClass.Name
		local start = name:find("#", 0, true)
		if not start then
			return false
		end
		local substr = name:sub(start + 1, name:len())
		
		return substr:lower() == weaponEnd:lower()
	else
		return checkTables(zoomAllowedWeapons,zoomRestrictedWeapons,weaponClass)
	end
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 = zoom_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 zoom_isAllowedWeapon(tb.WeaponClasses[v.WeaponClass.Name]) then
					allowedWeapon = true
					break
				else
					allowedWeapon = false
				end
			end
		end
		if (allowedWeapon and not useWeaponEnd) or (useWeaponEnd and not allowedWeapon) then
			local secWeaponBank = hv.Player.SecondaryBanks
			for i=0, #secWeaponBank do
				local v = secWeaponBank[i]
				if v.Armed then
					if zoom_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]
				break
			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
	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 allowed/restricted informations
	-- Ships
	zoomAllowedShips = {"**all**"}
	zoomTempAllowedShip = {}
	
	zoomRestrictedShips = {}
	zoomTempRestrictedShip = {}
	
	-- Weapons
	zoomAllowedWeapons = {"**all**"}
	zoomTempAllowedWeapon = {}	
	
	zoomRestrictedWeapons = {}
	zoomTempRestrictedWeapon = {}
	
	-- or the equal weapon-postfix method
	weaponEnd = "zoom"
	useWeaponEnd = false
end

function resetZoomScript()
	-- Reset camera in use...
	cam = nil
end

-- Initialize the global variables
initVars()

-- Initialize the rest
init()

ba.print("Zoom Script initialized!\n")

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 zoomLockKey and hv.Key:lower() == zoomLockKey:lower() then
		if lockedZoom then
			zoomOut()
			lockedZoom = false
			return
		else
			zoomIn()
			lockedZoom = true
			return
		end
	end
	if 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 standard 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**

-- Now weapons with #zoom in the end will be allowed to zoom
-- Uncomment this line to enable this behavior
-- Weapon End: zoom