Author Topic: Zoom script  (Read 17514 times)

0 Members and 2 Guests are viewing this topic.

Offline m!m

  • 211
hmm...

Is it possible to blur the outer regions of the screen via scripting? Then it would be possible to get this "scope feeling" while zooming  ;7.

When there's time I'll implement the weapon restriction...

 

Offline The E

  • He's Ebeneezer Goode
  • 213
  • Nothing personal, just tech support.
    • Steam
    • Twitter
Post processing effects like that are possible (I believe, anyway), but hideously expensive in terms of processing required. Better wait for a better shader management system that integrates shaders and lua.
If I'm just aching this can't go on
I came from chasing dreams to feel alone
There must be changes, miss to feel strong
I really need lifе to touch me
--Evergrey, Where August Mourns

 

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
it probibly might just work to take a large bitmap of some set dimension. essentially gray with a transparent blob in the middle and maybe a crosshair graphic on top of that. youd want to stretch it to cover the screen and then render the crosshair graphic as a polygon at the center. its not as polished as a shader but it should look pretty good if you do it right.
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN

 

Offline The E

  • He's Ebeneezer Goode
  • 213
  • Nothing personal, just tech support.
    • Steam
    • Twitter
Yeah, that would work as well, I think
If I'm just aching this can't go on
I came from chasing dreams to feel alone
There must be changes, miss to feel strong
I really need lifе to touch me
--Evergrey, Where August Mourns

 

Offline m!m

  • 211
So, I implemented a config file system and an allowed/restricted Ship/Weapon system but now I have got a problem with the Weapon banks.
To check if the current selected weapon is allowed I need to access the primary/secondary weapon bank. Now here is my problem:
I want to access the weapon class but it seams that there is no operator or function that gives me access to these. The scripting documentation states something as followed:

weaponbank [] number Index
Array of weapon banks
Returns: Weapon bank, or invalid handle on failure

suggesting that the weaponbanktype itself is a table containing the weaponbank handles but when i access it like a table in a for-loop FreeSpace (or rather Lua) gives me an "attempt to call a userdata value" error.

Does anyone know how I can get the currently selected weapon class or what I'm doing wrong?

Thank you for your help,

m!m

 

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
weaponbank is a table (technically its userdata but it works like a table) you can use the # operator to figure out the length of the table, and iterate through them to determine what weapon is mounted and whether its armed or not.

Code: [Select]
--get the weapon bank type object, with this you can see if dual fire or linked mode is selected
pbanks = hv.player.PrimaryBanks
link = pbanks.Linked
--iterate through primary banks
numpbanks = #pbanks
for i=1, numpbanks do
  --get a specific bank
  pbank = pbanks[i]
  --do stuff with bank here, like check to see if the weapon is armed or what it is or how much ammo there is
  wep = pbank.WeaponClass
  arm = pbank.Armed
  ammo = pbank.AmmoLeft
end


*edited for sanity, errors*
*edited because lua starts indexing at 1 not 0*
« Last Edit: July 26, 2010, 02:07:32 pm by Nuke »
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN

  

Offline m!m

  • 211
Ahh, I tried to iterate throug it using a for i in bank do loop rather than using indexes.

Thank you Nuke :)

 

Offline m!m

  • 211
Soooo, new version with some new features including:
  • A configuration file mechanism
  • The ability to restrict (or allow) the use of zoom based on weapon and/or ship class

Here's the script, paste it into your data/tables directory in a file called zoom-sct.tbm:
Code: [Select]
#Conditional Hooks

$Application: FS2_Open
$On Game Init:
[
-------------------------------------------------------------
--------              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
-------------------------------------------------------------
---- 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 setZoomFactor(value)
local val = tonumber(value)
if val == nil then
ba.warning("Non numeric value given to 'Zoom Factor'. Skipping...")
return
end
zoomValue = val
end

local function setTransTime(value)
local val = tonumber(value)
if val == nil then
ba.warning("Non numeric value given to 'Transition Time'. Skipping...")
return
end
transitionTime = val
end

local function setSens(value)
local val = tonumber(value)
if val == nil then
ba.warning("Non numeric value given to 'Sensitivity'. Skipping...")
return
end
sensitivity = val
end

local function setLinkedZoom(value)
local yesKey = "yes"
value = value:lower()
if value == yesKey then
linkedZoom = true
else
linkedZoom = false
end
end

local function addAllowedShipClass(value)
if isAllIdent(value) then
allowedShips = {}
allowedShips[1] = value
return
end
if allowedShips[1] == nil or not isAllIdent(allowedShips[1]) then
local ship = tb.ShipClasses[value]
if ship ~= nil and ship:isValid() then
table.insert(allowedShips, value)
else
ba.warning("Specified ship class '" .. value .. "' does not exist.")
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
local ship = tb.ShipClasses[value]
if ship ~= nil and ship:isValid() then
table.insert(restrictedShips, value)
else
ba.warning("Specified ship class '" .. value .. "' does not exist.")
end
end
end

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

local function addRestrictedWeaponClass(value)
if isAllIdent(value) then
restrictedWeapons = {}
restrictedWeapons[1] = value
return
end
if restrictedWeapons[1] == nil or not isAllIdent(restrictedWeapons[1]) then
local weapon = tb.WeaponClasses[value]
if weapon ~= nil and weapon:isValid() then
table.insert(restrictedWeapons, value)
else
ba.warning("Specified weapon class '" .. value .. "' does not exist.")
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
ba.warning("Malformatted line: " .. line .. "\nSkipping...")
return
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
ba.warning("Keyword '" .. keyword .. "' hasn't specified a value. Skipping...")
return
end
end

keyword = keyword:lower() -- make the keyword lowercase

local val = line:sub(val_s, line:len())

if val ~= nil then
val = trim(val)
local setFunc = getSetFunc(keyword)
if setFunc == nil then
ba.warning("Unknown keyword: " .. keyword)
return
end
setFunc(val)
else
return
end
end

local function initSetTable()
setTable = {}
setTable["Zoom Factor"]=setZoomFactor
setTable["Transition Time"]=setTransTime
setTable["Sensitivity"]=setSens
setTable["Allowed Ship Class"]=addAllowedShipClass
setTable["Restricted Ship Class"]=addRestrictedShipClass
setTable["Allowed Weapon Class"]=addWeaponClass
setTable["Restricted Weapon Class"]=addRestrictedWeaponClass
setTable["Linked Zoom"]=setLinkedZoom
end

local function readConfig()
if cf.fileExists(configFile) then
local config = cf.openFile(configFile)
if config:isValid() then
initSetTable()
local parse = true
while parse do
local line = getNextLine(config)
if line == nil then
parse = false
break
end
parseLine(line) -- parse the found line
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

-------------------------------------------------------------
------      defining functions used in the script      ------
-------------------------------------------------------------
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 zooming then
if hv.Player:isValid() then
if 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: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)
end
end
end
end

function drawProgress()
progressString = getProgressString()

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 = true
useConfig = true

zoomValue = 0.1
normalFOV = 0.75
transitionTime = 2
zooming = false
defaultZoomSet = false
cameraSet = false
configFile = "data/config/zoom_config.cfg"
zoomOutOverride = false
linkedZoom = true

-- 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**"}
restrictedShips = {}

-- Weapons
allowedWeapons = {"**all**"}
restrictedWeapons = {}
end

-- Initialize the global variables
initVars()

-- Initialize the rest
init()
]

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

$On Frame:
[
if runZoomScript then
if zooming then
handleControls()

drawProgress()
end
end
]

$State: GS_STATE_GAME_PLAY
$KeyPress: d
$On Key Pressed:
[
if runZoomScript then
if not cameraSet then
if #gr.Cameras > 0 then
cam = gr.Cameras[1]
end
end
zoomIn()
end
]

$On Key Released:
[
if runZoomScript then
zoomOut()
end
]

#End

The needed config file has to be placed in the data/config directory (create if not existing) and it has to be named zoom_config.cfg (this can be edited in the script)
Code: [Select]
-- Setting standart values
Zoom Factor: 0.1
Transition Time: 2
Sensitivity: 0.25
Linked Zoom: YES

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

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

Explanation of entries:
Code: [Select]
"--" Are comments
Zoom Factor:      The zoom factor, given in fraction of normal FOV (0.1 will result in 10% FOV of normal)
Transition Time:  How long should zooming in take
Sensitivity:      The sensitivity will be reduced to this value where the normal flight mode is a sensitivity of 1
Linked Zoom:      Should zooming be allowed even when the weapon banks are linked

Allowed Ship Class:     This shipclass will be allowed to zoom (more than 1 statement is possible)
Restricted Ship Class:  This shipclass will not be able to zoom (again more than 1 can be given)

Allowed Weapon Class:   When this weapon is used zooming will be possible (more than 1 possible)
Restricted Weapon Class:Zooming will not be possible if this weaponclass is selected (more than 1 possible)

Giving "**all**" in the Class restriction section will result in allowing/restricting all classes to zoom
When a class is in allowed and in restricted it will be allowed to zoom

If there are no further bugs I'll update the wiki shortly.
« Last Edit: September 08, 2010, 04:59:58 am by m!m »

 

Offline Droid803

  • Trusted poster of legit stuff
  • 213
  • /人 ◕ ‿‿ ◕ 人\ Do you want to be a Magical Girl?
    • Skype
    • Steam
Awesome stuff.
Just tested it out. Didn't find anything wrong, though I'm no scripter.
Hold D zooms in, let go, zoom out.  :cool:
(´・ω・`)
=============================================================

 

Offline T-Man

  • 210
  • I came... I saw... I had a cuppa!
Is anyone else thinking of BP's Uriel and its Archer when they read this? *cue evil cackling*.

Hats off to you m!m, and everyone else whose contributed :yes:. I've always preferred precision over spamming shots (yeah i'm a sniper geek on FPSs :rolleyes:) so a feature like this is music to my ears. Some players might like it for observation missions or precision bombing runs too.

Would it be possible to add an option to drop the sensitivity of the player's controls as he zooms further in, so the ship turns less when you press the controls? I use the keyboard to play and i've found it quite hard to target guns at range, at sniper ranges with guns like the Archer or maxim it'd likely be impossible.

Hold D zooms in, let go, zoom out.  :cool:
Sounds like a good system; quick and easy. Perhaps there could also be an 'alt-d' command that toggles a constant zoom mode, with maybe the directional buttons on the keyboard\mouse wheel/secondary joystick knob\etc) to zoom in and out. Then players could choose between the 'just d' quick zoom in/out system or a more focused 'sniper mode' depending on the situation.

*Still cackling thinking about the Archer :lol:*
Also goes by 'Murasaki-Tatsu' outside of Hard-Light

UEF fanboy. Rabid Imagination.

 

Offline Droid803

  • Trusted poster of legit stuff
  • 213
  • /人 ◕ ‿‿ ◕ 人\ Do you want to be a Magical Girl?
    • Skype
    • Steam
I prefer Steve-O's original archer with this.
More range + kills fighters faster :P (and it bumps them severely off course if it doesn't kill them for extra lulz)
(´・ω・`)
=============================================================

 

Offline m!m

  • 211
Some progress on this one.  :nod:
I added more config options including the possibility to specify the key which will zoom in and I also added a zoom lock feature (as proposed by T-Man) that will lock the zoom.

But the bigger feature is the FRED interface for forcing zoom-in or -out and adding allowed/restricted classes on the (mission) fly.
They are used as following:
Code: [Select]
when
 true
 eval-script
   function-name
where function-name is one of:

zoom_zoomIn():
Causes a zoom in. Locks all player zoom controls.
This is never automatically zoomed back so the FREDer has to take care of this!

zoom_zoomOut():
Causes a zoom out. Also gives back zoom controlls to the player.

zoom_alS(ship):
Allows the ship class specified with ship. For it to work specify the ship class escaped with ' because " crashes FRED.
Due to the SEXP string length restriction of FSO it may not be possible to use this with all ship classes.

zoom_alW(weapon):
Allows the specified Weapon class. Essentially the same as zoom_alS().

zoom_reS(ship):
Restricts the specified Ship class. Essentially the same as zoom_alS().

zoom_reW(weapon):
Restricts the specified Weapon class. Essentially the same as zoom_alS().

New version is in wiki: http://www.hard-light.net/wiki/index.php/Script_-_Zoom

Please report any bugs or unwanted side effect here.

 

Offline Mobius

  • Back where he started
  • 213
  • Porto l'azzurro Dolce Stil Novo nella fantascienza
    • Skype
    • Twitter
    • The Lightblue Ribbon | Cultural Project
Thank you a lot for adding these features! I hope to test them very soon and post feedback.

Potential addition: the possibility to choose a specific zooming time in FRED (via variable?), and/or setting different zooming times for each spacecraft/weapon (via config file?). :)
The Lightblue Ribbon

Inferno: Nostos - Alliance
Series Resurrecta: {{FS Wiki Portal}} -  Gehenna's Gate - The Spirit of Ptah - Serendipity (WIP) - <REDACTED> (WIP)
FreeSpace Campaign Restoration Project
A tribute to FreeSpace in my book: Riflessioni dall'Infinito

 

Offline m!m

  • 211
Thanks Mobius.

Setting individual values for each spacecraft/weapon is possible but it is overly complicated and another question is what value should be used? The one of the ship or the one of the weapon?
Specifying the zoom time via FRED would be easily implemented, I'll do that when I've got time.

But now for something different:
On a idea of Mobius I experimented with a tactical display showing when zooming and the current state is this:


Please provide feedback and I'm open for some new ideas :)

 

Offline Droid803

  • Trusted poster of legit stuff
  • 213
  • /人 ◕ ‿‿ ◕ 人\ Do you want to be a Magical Girl?
    • Skype
    • Steam
That's...a bit cluttered.
Don't think you need the IFF words there (pretty apparent due to color, no?)
(´・ω・`)
=============================================================

 

Offline The E

  • He's Ebeneezer Goode
  • 213
  • Nothing personal, just tech support.
    • Steam
    • Twitter
In addition, you are doing a LOT of drawing to the HUD. That's a significant resource drain right there.
If I'm just aching this can't go on
I came from chasing dreams to feel alone
There must be changes, miss to feel strong
I really need lifе to touch me
--Evergrey, Where August Mourns

 

Offline m!m

  • 211
Don't think you need the IFF words there (pretty apparent due to color, no?)
That's right but the problem is that I can't get the IFF color via scripting and I'm currently choosing the color from the name so it's limited to distinguish "Friendly", "Hostile" and all that's left over so it's only drawing in 3 colors.  :(
My question is: Is there a way to get the IFF color of a team so the brackets can be drawn with the right colors?
Edit: I solved this... :)

In addition, you are doing a LOT of drawing to the HUD. That's a significant resource drain right there.
I agree with you and it is overkill in it's current state but I'm experimenting with things so I just showed all available objects (also debris). Drawing only ships is the best thing and like that the "cluttering" of the HUD will be reduced.

Thank you for the feedback and I hope I can show something new shortly...
« Last Edit: September 13, 2010, 10:52:20 am by m!m »

 

Offline Mobius

  • Back where he started
  • 213
  • Porto l'azzurro Dolce Stil Novo nella fantascienza
    • Skype
    • Twitter
    • The Lightblue Ribbon | Cultural Project
I see the resource drain is considerable there, so it should definitely become optional so that FREDders will decide when to use it. :)
The Lightblue Ribbon

Inferno: Nostos - Alliance
Series Resurrecta: {{FS Wiki Portal}} -  Gehenna's Gate - The Spirit of Ptah - Serendipity (WIP) - <REDACTED> (WIP)
FreeSpace Campaign Restoration Project
A tribute to FreeSpace in my book: Riflessioni dall'Infinito

 

Offline m!m

  • 211
Just a small update that fixes bugs and adds new configuration values:
Progress Offset X: This specifies where the progress bar will be displayed on the x-axis (this means the top-left corner)
Progress Offset Y: Same as above but for Y-axis
Progress Height: Specifies the height of the bar
Progress Width: And the corresponding width...

New version is in the wiki. Get it while it's hot!

 

Offline Mobius

  • Back where he started
  • 213
  • Porto l'azzurro Dolce Stil Novo nella fantascienza
    • Skype
    • Twitter
    • The Lightblue Ribbon | Cultural Project
You're awesome! :yes:
The Lightblue Ribbon

Inferno: Nostos - Alliance
Series Resurrecta: {{FS Wiki Portal}} -  Gehenna's Gate - The Spirit of Ptah - Serendipity (WIP) - <REDACTED> (WIP)
FreeSpace Campaign Restoration Project
A tribute to FreeSpace in my book: Riflessioni dall'Infinito