Module:Vehicle

local vehicle = {}

local metatable = {} local methodtable = {}

metatable.__index = methodtable

local common = require( 'Module:Common' ) local api = require( 'Module:Common/Api' ) local log = require( 'Module:Log' ) local manufacturer = require( 'Module:Manufacturer' )._manufacturer

local lang = mw.getContentLanguage

--- Request Api Data --- Using current subpage name without vehicle type suffix --- @return table or nil function methodtable.getApiDataForCurrentPage( self ) local name = self.frameArgs.uuid or self.name

local json = mw.text.jsonDecode( mw.ext.Apiunto.get_raw( 'v2/vehicles/' .. name, { include = { 'hardpoints', --'components', 'shops', },       locale = 'en_EN' } ) )

if api.checkResponseStructure( json, true, false ) == false then return end

self.apiData = json[ 'data' ] self.apiData = api.makeAccessSafe( self.apiData )

return self.apiData end

--- Set Vehicle Semantic Properties function methodtable.setSemanticProperties( self ) common.checkSmwResult( self:setBaseSemanticProperties ) self:setExtraSemanticProperties end

--- Base Properties that are shared across all Vehicles --- @return table SMW Result function methodtable.setBaseSemanticProperties( self ) -- Set properties with Template param local setData = { [ 'Name' ] = self.name, [ 'Production state' ] = self.frameArgs[ 'productionstate' ], [ 'Role' ] = self.frameArgs[ 'role' ], [ 'Series' ] = self.frameArgs[ 'series' ], --- Pledge info [ 'Pledge availability' ] = self.frameArgs[ 'pledgeavailability' ], [ 'Pledge price' ] = self.frameArgs[ 'pledgecost' ], [ 'Original pledge price' ] = self.frameArgs[ 'originalpledgecost' ], [ 'Warbond pledge price' ] = self.frameArgs[ 'warbondcost' ], [ 'Original warbond pledge price' ] = self.frameArgs[ 'originalwarbondcost' ], --- Crew [ 'Minimum crew' ] = self.frameArgs[ 'mincrew' ], [ 'Maximum crew' ] = self.frameArgs[ 'maxcrew' ], --- Cargo [ 'Cargo capacity' ] = self.frameArgs[ 'cargocapacity' ], --- Speed [ 'SCM speed' ] = self.frameArgs[ 'combatspeed' ], [ 'Maximum speed' ] = self.frameArgs[ 'maxspeed' ], --- Dimensions [ 'Entity length' ] = self.frameArgs[ 'length' ], [ 'Entity width' ] = self.frameArgs[ 'width' ], [ 'Entity height' ] = self.frameArgs[ 'height' ], [ 'Retracted length' ] = self.frameArgs[ 'retractedlength' ], [ 'Retracted width' ] = self.frameArgs[ 'retractedwidth' ], [ 'Retracted height' ] = self.frameArgs[ 'retractedheight' ], [ 'Mass' ] = self.frameArgs[ 'mass' ], --- Lore [ 'Lore release date' ] = self.frameArgs[ 'releasedate' ], [ 'Lore retirement date' ] = self.frameArgs[ 'retiredate' ], --- Development [ 'Concept announcement date' ] = self.frameArgs[ 'conceptdate' ], [ 'Concept sale date' ] = self.frameArgs[ 'saledate' ], --- Official sites [ 'Galactapedia URL' ] = self.frameArgs[ 'galactapediaurl' ], [ 'Pledge store URL' ] = self.frameArgs[ 'rsistoreurl' ], [ 'Brochure URL' ] = self.frameArgs[ 'brochureurl' ], [ 'Trailer URL' ] = self.frameArgs[ 'trailerurl' ], [ 'Whitleys Guide URL' ] = self.frameArgs[ 'whitleysguideurl' ], }

local manufacturerArg = self.frameArgs[ 'manufacturer' ] if manufacturerArg then setData[ 'Manufacturer' ] = manufacturer( manufacturerArg ).name or manufacturerArg end

--- Handle numbered parameters local dataArgMap = { [ 'Presentation URL' ] = 'presentationurl', [ 'Q and A URL' ] = 'qandaurl' }

for dataKey, argKey in pairs( dataArgMap ) do		local dataValues = { self.frameArgs[ argKey ] }		for i = 1, 5 do local argValue = self.frameArgs[ argKey .. i ] if argValue then table.insert( dataValues, argValue ) end end setData[ dataKey ] = dataValues end

-- Set properties with API data if self.apiData ~= nil then -- RSI website data setData[ 'Ship matrix name' ] = self.apiData.name -- Sizes are lowercased setData[ 'Ship matrix size' ] = lang:ucfirst( self.apiData.size ) -- Loaner vehicles local loaners = {} if type( self.apiData.loaner ) == 'table' then for _, loaner in pairs( self.apiData.loaner ) do				table.insert( loaners, string.format( '%s', loaner.name ) ) end end if #loaners > 0 then setData[ 'Loaner vehicle' ] = loaners end

-- Flight ready vehicles --- Override template parameter with in-game data if self.apiData.uuid ~= nil then setData[ 'UUID' ] = self.apiData.uuid setData[ 'Class name' ] = self.apiData.class_name setData[ 'Size' ] = self.apiData.size_class setData[ 'Mass' ] = self.apiData.mass setData[ 'Cargo capacity' ] = common.formatNum( self.apiData.cargo_capacity ) setData[ 'Vehicle inventory' ] = common.formatNum( self.apiData.vehicle_inventory ) setData[ 'Maximum speed' ] = common.formatNum( self.apiData.speed.max ) setData[ 'Zero to Maximum speed time' ] = common.formatNum( self.apiData.speed.zero_to_max ) setData[ 'Maximum speed to zero time' ] = common.formatNum( self.apiData.speed.max_to_zero )

--- Ground vehicle-specific data --- Lazy way to differeniate ground vehicle --- This is ship matrix data though so it might be inconsistent if self.apiData.size == 'vehicle' then setData[ 'Reverse speed' ] = common.formatNum( self.apiData.speed.reverse ) --- Spaceship-specific data else setData[ 'SCM speed' ] = common.formatNum( self.apiData.speed.scm ) setData[ 'Zero to SCM speed time' ] = common.formatNum( self.apiData.speed.zero_to_scm ) setData[ 'SCM speed to zero time' ] = common.formatNum( self.apiData.speed.scm_to_zero ) setData[ 'Roll rate' ] = common.formatNum( self.apiData.agility.roll or nil, nil ) setData[ 'Pitch rate' ] = common.formatNum( self.apiData.agility.pitch or nil, nil ) setData[ 'Yaw rate' ] = common.formatNum( self.apiData.agility.yaw or nil, nil ) end

--- Insurance if self.apiData.insurance ~= nil then setData[ 'Insurance claim time' ] = common.formatNum( self.apiData.insurance.claim_time or 0 ) setData[ 'Insurance expedite time' ] = common.formatNum( self.apiData.insurance.expedite_time or 0 ) setData[ 'Insurance expedite cost' ] = common.formatNum( self.apiData.insurance.expedite_cost or 0 ) end

--- Components --- From game data if self.apiData.hardpoints ~= nil and type( self.apiData.hardpoints ) == 'table' and #self.apiData.hardpoints > 0 then local hardpoint = require( 'Module:VehicleHardpoint' ):new( self.frameArgs[ 'name' ] or mw.title.getCurrentTitle.fullText ) hardpoint:setHardPointObjects( self.apiData.hardpoints ) end end end

return mw.smw.set( setData ) end

--- Extra Properties that are Vehicle (Type) Specific --- @param apiData table function methodtable.setExtraSemanticProperties( self, apiData )

end

--- Queries the SMW Store --- @return table function methodtable.getSmwData( self ) -- Cache multiple calls if self.smwData ~= nil then return self.smwData end local queryName = self.frameArgs[ 'name' ] or mw.title.getCurrentTitle.fullText local data = mw.smw.ask( {       ' ' .. queryName .. ' ',        '?Name#-',        '?Manufacturer#-',        '?Production state#-',        '?Role#-',        '?Ship matrix size#-',        '?Size#-',        '?Series#-',        '?Loaner vehicle',        '?Minimum crew#-',        '?Maximum crew#-',        '?Cargo capacity',        '?Vehicle inventory',        '?Pledge price',        '?Original pledge price',        '?Warbond pledge price',        '?Original warbond pledge price',        '?Pledge availability#-',        '?Insurance claim time#-n',        '?Insurance expedite time#-n',        '?Insurance expedite cost',        '?Entity length',        '?Retracted length',        '?Entity width',        '?Retracted width',        '?Entity height',        '?Retracted height',        '?Mass',        '?SCM speed',        '?Zero to SCM speed time',        '?SCM speed to zero time',        '?Maximum speed', '?Zero to Maximum speed time', '?Maximum speed to zero time', '?Reverse speed', '?Roll rate', '?Pitch rate', '?Yaw rate', '?Lore release date', '?Lore retirement date', '?Concept announcement date', '?Concept sale date', '?Galactapedia URL#-', '?Pledge store URL#-', '?Presentation URL#-', '?Portfolio URL#-', '?Whitleys Guide URL#-', '?Brochure URL#-', '?Trailer URL#-', '?Q and A URL#-', '?UUID', '?Class name', '?Ship matrix name', } )

if data == nil or data[ 1 ] == nil then return '' .. log.info( 'SMW data has not loaded yet, this will take a few minutes.' ) end

self.smwData = data[ 1 ]

return self.smwData end

--- Creates the infobox function methodtable.getInfobox( self ) local data = self:getSmwData

local infobox = require( 'Module:InfoboxNeue' ) local tabber = require( 'Module:Tabber' ).renderTabber local infoboxTable = {} local sectionTable = {} --- SMW Data load error --- Infobox data should always have Name property if type( data ) ~= 'table' or data[ 'Name' ] == nil then return infobox.renderInfobox( infobox.renderMessage( { title = 'Infobox data temporary unavaliable', desc = 'SMW data has not loaded yet, this will take a few minutes.' } ) )	end local function getIndicatorClass if data[ 'Production state' ] == nil then return end local classMap = { [ 'Flight ready' ] = 'green', [ 'In production' ] = 'yellow', [ 'Active for Squadron 42' ] = 'yellow', [ 'In concept' ] = 'red' }		for matcher, class in pairs( classMap ) do			if string.match( data[ 'Production state' ], matcher ) ~= nil then return 'infobox__indicator--' .. class end end end local function getManufacturer if data[ 'Manufacturer' ] == nil then return end local mfu = manufacturer( data[ 'Manufacturer' ] ) if mfu == nil then return data[ 'Manufacturer' ] end return infobox.showDescIfDiff(			table.concat( { , mfu.name,  } ),			mfu.code		) end

infoboxTable = { infobox.renderImage( self.frameArgs[ 'image' ] or 'Placeholderv2.png' ), infobox.renderIndicator( {			data = data[ 'Production state' ],			desc = self.frameArgs[ 'productionstatedesc' ],			class = getIndicatorClass		} ), infobox.renderHeader( {			title = data[ 'Name' ],			--- e.g. Aegis Dynamics (AEGS)			subtitle = getManufacturer		} ) }

local function getSize if data[ 'Size' ] == nil then return data[ 'Ship matrix size' ] end local codes = { 'XXS', 'XS', 'S', 'M', 'L', 'XL' } return infobox.showDescIfDiff(			data[ 'Ship matrix size' ],			table.concat( { 'S', data[ 'Size' ], '/', codes[ data[ 'Size' ] ] } ) 		) end local function getSeries if data[ 'Series' ] == nil then return end return string.format(			'%s',			data[ 'Series' ], data[ 'Series' ]		) end sectionTable = { infobox.renderItem( {			label = 'Role',			data = data[ 'Role' ],		} ), infobox.renderItem( {			label = 'Size',			data = getSize,		} ), infobox.renderItem( {			label = 'Series',			data = getSeries,		} ), infobox.renderItem( {			label = 'Loaner',			data = infobox.tableToCommaList( data[ 'Loaner vehicle' ] ),		} ), }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), col = 2 } ) )

--- Capacity section local function getCrew if data[ 'Minimum crew' ] and data[ 'Maximum crew' ] == nil then return end if data[ 'Minimum crew' ] and data[ 'Maximum crew' ] and data[ 'Minimum crew' ] ~= data[ 'Maximum crew' ] then return table.concat( { data[ 'Minimum crew' ], ' – ', data[ 'Maximum crew' ] } ) end return data[ 'Minimum crew' ] or data[ 'Maximum crew' ] end

sectionTable = { infobox.renderItem( {			label = 'Crew',			data = getCrew,		} ), infobox.renderItem( {			label = 'Cargo',			data = data[ 'Cargo capacity' ],		} ), infobox.renderItem( {			label = 'Stowage',			data = data[ 'Vehicle inventory' ],		} ), }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), title = 'Capacity', col = 3 } ) )

--- Cost section local function getCostSection local tabberData = {} sectionTable = { infobox.renderItem( {				label = 'Standalone',				data = infobox.showDescIfDiff( data[ 'Pledge price' ], data[ 'Original pledge price' ] ),			} ), infobox.renderItem( {				label = 'Warbond',				data = infobox.showDescIfDiff( data[ 'Warbond pledge price' ], data[ 'Original warbond pledge price' ] ),			} ), infobox.renderItem( {				label = 'Avaliblity',				data = data[ 'Pledge availability' ],			} ), }		tabberData['label1'] = 'Pledge' tabberData['content1'] = infobox.renderSection( { content = table.concat( sectionTable ),			col = 2		} ) local function makeTimeReadable( t ) if t ~= nil then t = lang:formatDuration( t * 60 )

local regex = { [ '%shours*' ] = 'h', [ '%sminutes*' ] = 'm', [ '%sseconds*' ] = 's', [ 'and%s'] = '' }				for pattern, replace in pairs( regex ) do					t = string.gsub( t, pattern, replace ) end end return t		end sectionTable = { infobox.renderItem( {				label = 'Claim',				data = makeTimeReadable( data[ 'Insurance claim time' ] ),			} ), infobox.renderItem( {				label = 'Expedite',				data = makeTimeReadable( data[ 'Insurance expedite time' ] ),			} ), infobox.renderItem( {				label = 'Expedite fee',				data = data[ 'Insurance expedite cost' ],				colspan = 2			} ), }

tabberData['label2'] = 'Insurance' tabberData['content2'] = infobox.renderSection( { content = table.concat( sectionTable ),			col = 4		} ) --- TODO: Move this back up to the first tab when we fix universe cost sectionTable = {}

--tabberData['label3'] = 'Universe' --tabberData['content3'] = infobox.renderMessage( {		--	title = 'Persistent Universe data not avaliable',		--	desc = 'We are integrating the wiki with game data at the moment. It is coming soon™.'		--} ) --tabberData['content3'] = infobox.renderSection( { content = table.concat( sectionTable ), col = 2 } )

--tabberData['label4'] = 'Arena Commander' --tabberData['content4'] = infobox.renderMessage( {		--	title = 'Arena Commander data not avaliable',		--	desc = 'We are integrating the wiki with game data at the moment. It is coming soon™.'		--} ) --tabberData['content4'] = infobox.renderSection( { content = table.concat( sectionTable ), col = 2 } )

return tabber( tabberData ) end

sectionTable = { getCostSection }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), title = 'Cost', class = 'infobox__section--tabber' } ) )

--- Specifications section local function getSpecificationsSection local tabberData = {} sectionTable = { infobox.renderItem( {				label = 'Length',				data = infobox.showDescIfDiff( data[ 'Entity length' ], data[ 'Retracted length' ] ),			} ), infobox.renderItem( {				label = 'Width',				data = infobox.showDescIfDiff( data[ 'Entity width' ], data[ 'Retracted width' ] ),			} ), infobox.renderItem( {				label = 'Height',				data = infobox.showDescIfDiff( data[ 'Entity height' ], data[ 'Retracted height' ] ),			} ), infobox.renderItem( {				label = 'Mass',				data = data[ 'Mass' ],			} ), }		tabberData['label1'] = 'Dimensions' tabberData['content1'] = infobox.renderSection( { content = table.concat( sectionTable ),			col = 3		} ) sectionTable = { infobox.renderItem( {				label = 'SCM speed',				data = data[ 'SCM speed' ]			} ), infobox.renderItem( {				label = '0 to SCM',				data = data[ 'Zero to SCM speed time' ]			} ), infobox.renderItem( {				label = 'SCM to 0',				data = data[ 'SCM speed to zero time' ]			} ), infobox.renderItem( {				label = 'Max speed',				data = data[ 'Maximum speed' ]			} ), infobox.renderItem( {				label = '0 to max',				data = data[ 'Zero to Maximum speed time' ]			} ), infobox.renderItem( {				label = 'Max to 0',				data = data[ 'Maximum speed to zero time' ]			} ), infobox.renderItem( {				label = 'Reverse speed',				data = data[ 'Reverse speed' ]			} ), infobox.renderItem( {				label = 'Roll rate',				data = data[ 'Roll rate' ]			} ), infobox.renderItem( {				label = 'Pitch rate',				data = data[ 'Pitch rate' ]			} ), infobox.renderItem( {				label = 'Yaw rate',				data = data[ 'Yaw rate' ]			} ), }		tabberData['label2'] = 'Speed' tabberData['content2'] = infobox.renderSection( { content = table.concat( sectionTable ),			col = 3		} ) return tabber( tabberData ) end

sectionTable = { getSpecificationsSection }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), title = 'Specifications', class = 'infobox__section--tabber' } ) )

--- Lore section sectionTable = { infobox.renderItem( {				label = 'Released',				data = data[ 'Lore release date' ]		} ), infobox.renderItem( {				label = 'Retired',				data = data[ 'Lore retirement date' ]		} ), }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), title = 'Lore', col = 2 } ) )	--- Development section sectionTable = { infobox.renderItem( {				label = 'Announced',				data = data[ 'Concept announcement date' ]		} ), infobox.renderItem( {				label = 'Concept sale',				data = data[ 'Concept sale date' ]		} ), }

table.insert( infoboxTable, infobox.renderSection( { content = table.concat( sectionTable ), title = 'Development', col = 2 } ) )

--- Other sites local function getOfficialSites return table.concat( {			infobox.renderLinkButton( { label = 'Galactapedia', link = data[ 'Galactapedia URL' ] } ),			infobox.renderLinkButton( { label = 'Pledge store', link = data[ 'Pledge store URL' ] } ),			infobox.renderLinkButton( { label = 'Presentation', link = data[ 'Presentation URL' ] } ),			infobox.renderLinkButton( { label = 'Portfolio', link = data[ 'Portfolio URL' ] } ),			infobox.renderLinkButton( { label = 'Whitley\'s Guide', link = data[ 'Whitleys Guide URL' ] } ),			infobox.renderLinkButton( { label = 'Brochure', link = data[ 'Brochure URL' ] } ),			infobox.renderLinkButton( { label = 'Trailer', link = data[ 'Trailer URL' ] } ),			infobox.renderLinkButton( { label = 'Q&A', link = data[ 'Q and A URL' ] } ),		} )	end local function getCommunitySites local links = '' local query

if data[ 'UUID' ] ~= nil then links = table.concat( { links, table.concat( { infobox.renderLinkButton( {					label = 'Universal Item Finder',					link = string.format( 'https://finder.cstone.space/search/%s', data[ 'UUID' ] )				} )			} ) } )		end if data[ 'Class name' ] ~= nil then query = data[ 'Class name' ]:lower links = table.concat( { links, table.concat( { infobox.renderLinkButton( {					label = '#DPSCalculator',					link = string.format( 'https://www.erkul.games/ship/%s', query )				} ), infobox.renderLinkButton( {					label = 'SPViewer',					link = string.format( 'https://www.spviewer.eu/pages/ships/%s', query )				} ), infobox.renderLinkButton( {					label = 'TIS Ship Map',					link = string.format( 'https://tradein.space/#/ship_maps/%s', query )				} ), } ) } )		end if data[ 'Ship matrix name' ] ~= nil then query = mw.uri.encode( data[ 'Ship matrix name' ], 'PATH' ) links = table.concat( { links, table.concat( { infobox.renderLinkButton( {					label = 'StarShip 42',					link = string.format( 'https://www.starship42.com/fleetview/single/?source=Star%%20Citizen%%20Wiki&type=matrix&style=colored&s=%s', query )				} ), infobox.renderLinkButton( {					label = 'FleetYards',					link = string.format( 'https://fleetyards.net/ships/%s', query )				} ), } ) } )		end

return links end

sectionTable = { infobox.renderItem( {				label = 'Official sites',				data = getOfficialSites		} ), infobox.renderItem( {				label = 'Community sites',				data = getCommunitySites		} ), }

table.insert( infoboxTable, infobox.renderFooterButton( { icon = 'WikimediaUI-Globe.svg', label = 'Other sites', type = 'popup', content = infobox.renderSection( {			content = table.concat( sectionTable ),			class = 'infobox__section--linkButtons'		} ) } ) )

return infobox.renderInfobox( table.concat( infoboxTable ), data[ 'name' ] ) end

--- Set the frame and load args --- @param frame table function methodtable.setFrame( self, frame ) self.currentFrame = frame self.frameArgs = require( 'Module:Arguments' ).getArgs( frame ) end

--- Save Api Data to SMW store function methodtable.saveApiData( self ) --- Used by getApiDataForCurrentPage and setSemanticProperties self.name = self.frameArgs[ 'name' ] or common.removeTypeSuffix(       mw.title.getCurrentTitle.rootText,        self.vehicleType    ) local data = self:getApiDataForCurrentPage

self:setSemanticProperties

return data end

--- New Instance --- @param type string Term used remove suffix from page title function vehicle.new( self, type ) if type == nil then error( 'Required argument "type" missing.' ) end

local instance = { vehicleType = type, categories = {} }

setmetatable( instance, metatable )

return instance end

return vehicle