Module:VehicleHardpoint

local VehicleHardPoint = {}

local metatable = {} local methodtable = {}

local common = require( 'Module:Common' ) local data = mw.loadData( 'Module:VehicleHardpoint/Data' ) local hatnote = require( 'Module:Hatnote' )._hatnote

metatable.__index = methodtable

-- Local functions

--- Checks if an entry contains a 'child' key with further entries --- --- @return boolean local function hasChildren( row ) return row.children ~= nil and type( row.children ) == 'table' and #row.children > 0 end

--- Creates a key to be used in 'setHardPointObjects' --- This allows to sum the total count of each similar object --- --- @param row table - API Data --- @param hardpointData table - Data from getHardpointData --- @param parent table|nil - Parent hardpoint --- @param root string|nil - Root hardpoint --- @return string Key local function makeKey( row, hardpointData, parent, root ) local key

if type( row.item ) == 'table' then if row.type == 'ManneuverThruster' or          row.type == 'MainThruster' or           row.type == 'WeaponDefensive' or           row.type == 'WeaponLocker' or           row.type == 'ArmorLocker' or           row.type == 'Bed' or           row.type == 'CargoGrid' then key = row.type .. row.sub_type else key = row.type .. row.sub_type .. row.item.uuid end else key = hardpointData.class .. hardpointData.type end

if row.type ~= 'WeaponDefensive' then if parent ~= nil then key = key .. parent[ 'Hardpoint' ] end

if root ~= nil and not string.match( key, root ) and ( hardpointData.class == 'Weapons' ) then key = key .. root end end

if hardpointData.class == 'Weapons' and row.name ~= nil and row.type == 'MissileLauncher' then key = key .. row.name end

--mw.log(string.format('Key: %s', key)) return key end

--- Tries to fix hardpoints that have no item, but everything set on the 'child' key --- --- @param row table - API Data --- @return table - Fixed entry local function fixChild( row ) if row.item == nil and hasChildren( row ) and #row.children == 1 then local item = row.children[ 1 ]

local children = {}

if hasChildren( item ) then children = item.children if item.item ~= nil and item.item.children ~= nil then item.item.children = {} end end

row.name = item.name row.type = item.type row.sub_type = item.sub_type row.item = item.item if #children > 1 then row.children = children else row.children = { data = {} } end end

return row end

--- Get pre-defined hardpoint data for a given hardpoint type or name --- --- @param hardpointType string --- @return table|nil function methodtable.getHardpointData( self, hardpointType ) if type( data.hardPointMappings[ hardpointType ] ) == 'table' then return data.hardPointMappings[ hardpointType ] end

for hType, mappingData in pairs( data.hardPointMappings ) do       if hardpointType == hType then return mappingData elseif type( mappingData.matches ) == 'table' then for _, matcher in pairs( mappingData.matches ) do               if string.match( hardpointType, matcher ) ~= nil then return mappingData end end end end

return nil end

--- Creates a settable SMW Subobject --- --- @param row table - API Data --- @param hardpointData table - Data from getHardpointData --- @param parent table|nil - Parent hardpoint --- @param root string|nil - Root hardpoint --- @return table function methodtable.makeObject( self, row, hardpointData, parent, root ) local object = {}

if hardpointData == nil then hardpointData = self:getHardpointData( row.type or row.name ) end

if hardpointData == nil then return nil end

object[ 'Hardpoint' ] = row.name --object[ 'From game data' ] = true object[ 'Hardpoint minimum size' ] = row.min_size object[ 'Hardpoint maximum size' ] = row.max_size object[ 'Vehicle hardpoints template group' ] = hardpointData.class

if data.hardPointNames[ row.type ] ~= nil then object[ 'Hardpoint type' ] = data.hardPointNames[ row.type ] else object[ 'Hardpoint type' ] = hardpointData.type end

if data.hardPointNames[ row.sub_type ] ~= nil then object[ 'Hardpoint subtype' ] = data.hardPointNames[ row.sub_type ] else object[ 'Hardpoint subtype' ] = hardpointData.type end

if hardpointData.item ~= nil then if type( hardpointData.item.name ) == 'string' then object[ 'Name' ] = hardpointData.item.name end end

if type( row.item ) == 'table' then local itemObj = row.item if itemObj.name ~= '<= PLACEHOLDER =>' then local match = string.match( row.class_name or '', 'Destruct_(%d+s)') if row.type == 'SelfDestruct' and match ~= nil then object[ 'Name' ] = 'Self destruct (' .. match .. ')' else object[ 'Name' ] = row.item.name end end

if itemObj.type == 'WeaponDefensive' and type( itemObj.counter_measure ) == 'table' then object[ 'Magazine capacity' ] = itemObj.counter_measure.capacity end

if ( itemObj.type == 'Cargo' or itemObj.type == 'SeatAccess' or itemObj.type == 'CargoGrid' or itemObj.type == 'Container' ) and type( itemObj.inventory ) == 'table' then object[ 'Inventory' ] = common.formatNum( (itemObj.inventory.scu or nil), nil ) end

if object[ 'Hardpoint minimum size' ] == nil then object[ 'Hardpoint minimum size' ] = itemObj.size object[ 'Hardpoint maximum size' ] = itemObj.size end

object[ 'UUID' ] = row.item.uuid end

if parent ~= nil then object[ 'Parent hardpoint UUID' ] = parent[ 'UUID' ] object[ 'Parent hardpoint' ] = parent[ 'Hardpoint' ] end

if root ~= nil and root ~= row.name then object[ 'Root hardpoint' ] = root end

-- Remove SeatAccess Hardpoints without storage if row.item ~= nil and row.item.type == 'SeatAccess' and object[ 'Inventory' ] == nil then object = nil end

return object; end

--- Sets all available hardpoints as sub-objects --- This is the main method called by others --- --- @param hardpoints table API Hardpoint data function methodtable.setHardPointObjects( self, hardpoints ) if type( hardpoints ) ~= 'table' then error( 'Hardpoints need to be a table' ) end

local out = {}

local function addToOut( object, key ) if object == nil then return end

if type( out[ key ] ) ~= 'table' then if object ~= nil then out[ key ] = object out[ key ][ 'Item quantity' ] = 1 end else out[ key ][ 'Item quantity' ] = out[ key ][ 'Item quantity' ] + 1

if type( out[ key ][ 'Magazine capacity' ] ) == 'number' then out[ key ][ 'Magazine capacity' ] = out[ key ][ 'Magazine capacity' ] + object[ 'Magazine capacity' ] end

if object[ 'Hardpoint type' ] == 'Cargo grid' then out[ key ][ 'Item quantity' ] = 1 if out[ key ][ 'Inventory' ] ~= nil and object[ 'Inventory' ] ~= nil then out[ key ][ 'Inventory' ] = tonumber(out[ key ][ 'Inventory' ]) + tonumber(object[ 'Inventory' ]) end end end end

local depth = 1

local function addHardpoints( hardpoints, parent, root ) for _, hardpoint in pairs( hardpoints ) do           hardpoint.name = string.lower( hardpoint.name ) hardpoint = fixChild( hardpoint )

if depth == 1 then root = hardpoint.name --mw.log(string.format('Root: %s', root)) end

hardpoint = VehicleHardPoint.fixTypes( hardpoint )

local hardpointData = self:getHardpointData( hardpoint.type or hardpoint.name )

if hardpointData ~= nil then local key = makeKey( hardpoint, hardpointData, parent, root )

local obj = self:makeObject( hardpoint, hardpointData, parent, root )

addToOut( obj, key )

if hasChildren( hardpoint ) then depth = depth + 1 addHardpoints( hardpoint.children, obj, root ) end end end

depth = depth - 1

if depth < 1 then depth = 1 root = nil end end

addHardpoints( hardpoints )

--mw.logObject(out)

for _, subobject in pairs( out ) do       mw.smw.subobject( subobject ) end end

--- Queries the SMW store for all available hardpoint subobjects for a given page --- --- @param page string - The page to query --- @return table hardpoints function methodtable.querySmwStore( self, page ) -- Cache multiple calls if self.smwData ~= nil then return self.smwData end

local smwData = mw.smw.ask( {       '-Has subobject::' .. page .. 'Hardpoint type::+Vehicle hardpoints template group::+',        '?Item quantity#-=count',        '?Hardpoint minimum size#-=min_size',        '?Hardpoint maximum size#-=max_size',        '?Vehicle hardpoints template group=class',        '?Hardpoint type=type',        '?Hardpoint subtype=sub_type',        '?Name#-=name',        '?Inventory#-n=scu',        '?UUID#-=uuid',        '?Hardpoint#-=hardpoint',        '?Magazine capacity#-=magazine_size',        '?Parent hardpoint#-=parent_hardpoint',        '?Root hardpoint#-=root_hardpoint',        '?Parent UUID#-=parent_uuid',        '?Name.Grade#-=item_grade',        '?Name.Class#-=item_class',        '?Name.Size#-=item_size',        '?Name.Manufacturer#-=manufacturer',        'sort=Vehicle hardpoints template group,Hardpoint type,Hardpoint maximum size,Item quantity', 'order=asc,asc,asc,asc', 'limit=1000' } )

if smwData == nil or smwData[ 1 ] == nil then return nil end

mw.logObject( smwData )

self.smwData = smwData

return self.smwData end

--- Group Hardpoints by Class and type --- --- @param smwData table SMW data - Requires a 'class' key on each row --- @return table function methodtable.group( self, smwData ) local grouped = {}

if type( smwData ) ~= 'table' then return {} end

for _, row in self.spairs( smwData ) do       if not row.isChild and row.class ~= nil and row.type ~= nil then if type( grouped[ row.class ] ) ~= 'table' then grouped[ row.class ] = {} end

if type( grouped[ row.class ][ row.type ] ) ~= 'table' then grouped[ row.class ][ row.type ] = {} end

table.insert( grouped[ row.class ][ row.type ], row ) end end

--mw.logObject( grouped )

return grouped end

--- Adds children to the according parents --- --- @param smwData table All available Hardpoint objects for this page --- @return table The stratified table function methodtable.createDataStructure( self, smwData ) -- Maps object id to key in array local idMapping = {}

for key, object in pairs( smwData ) do       if object.hardpoint ~= nil then local keyMap = ( object.root_hardpoint or object.hardpoint ) .. object.hardpoint

idMapping[ keyMap ] = key end end

local function stratify( toStratify ) for _, object in pairs( toStratify ) do           if object.parent_hardpoint ~= nil then local parentEl = toStratify[ idMapping[ (object.root_hardpoint or '') .. object.parent_hardpoint ] ]

if parentEl ~= nil then if parentEl.children == nil then parentEl.children = {} end

object.isChild = true

table.insert( parentEl.children, object ) end end end end

stratify( smwData )

return smwData end

--- Generate the output --- --- @param groupedData table Grouped SMW data --- @return table function methodtable.makeOutput( self, groupedData ) local classOutput = {}

-- An item with potential children local function makeEntry( item, depth ) -- Info if data stems from ship-matrix or game files if classOutput.info == nil then local text ---if item.from_gamedata == true then text = 'Data extracted from game data.' ---else ---   text = 'Data extracted from ship matrix.' ---end

classOutput.info = hatnote( text ) end

depth = depth or 1

local row = mw.html.create( 'div' ) :addClass( 'template-component' ) :addClass( string.format( 'template-component--level-%d', depth) ) :tag('div') :addClass('template-component__connectors') :tag('div'):addClass('template-component__connectorX'):done :tag('div'):addClass('template-component__connectorY'):done :done

if item.magazine_size ~= nil then item.count = item.magazine_size end

local size = 'N/A' local prefix = 'S'

---if item.from_gamedata == true or item.class == 'Weapons' then ---   prefix = 'S'        ---end

if item.item_size ~= nil then size = string.format( '%s%s', prefix, item.item_size ) else size = string.format( '%s%s', prefix, item.max_size ) end

local nodeSizeCount = mw.html.create( 'div' ) :addClass('template-component__port') :tag( 'div' ) :addClass( 'template-component__count' ) :wikitext( string.format( '%dx', item.count ) ) :done

if item.class ~= 'Cargo grid' then nodeSizeCount :tag( 'div' ) :addClass( 'template-component__size' ) :wikitext( size ) :done end

nodeSizeCount = nodeSizeCount:allDone

local title = item.sub_type or item.type if item.name ~= nil then if data.nameFixes[ item.name ] ~= nil then title = string.format( '%s', data.nameFixes[ item.name ], item.name ) else title = string.format( '%s', item.name ) end end

local subtitle = item.manufacturer or 'N/A' if item.manufacturer ~= nil and item.manufacturer ~= 'N/A' then subtitle = string.format( '%s', item.manufacturer ) end

-- Show SCU in subtitle if item.type == 'Cargo grid' then subtitle = item.scu .. ' SCU' or 'N/A' elseif item.type == 'Personal storage' then subtitle = item.scu * 100 .. 'K µSCU' or 'N/A' end

local nodeItemManufacturer = mw.html.create( 'div' ) :addClass( 'template-component__item' ) :tag( 'div' ) :addClass( 'template-component__title' ) :wikitext( title ) :done :tag( 'div' ) :addClass( 'template-component__subtitle' ) :wikitext( subtitle ) :done :allDone

row:tag('div') :addClass('template-component__card') :node( nodeSizeCount ) :node( nodeItemManufacturer ) :done

row = tostring( row )

if type( item.children ) == 'table' then depth = depth + 1 for _, child in self.spairs( item.children ) do               row = row .. makeEntry( child, depth ) end end

return row end

-- Items of a given class e.g. avionics local function makeSection( types ) local out = ''

for classType, items in self.spairs( types ) do       	local label = classType

-- Label override if data.sectionLabelFixes[ classType ] ~= nil then label = data.sectionLabelFixes[ classType ] end

local icon = string.format( '', string.lower( label ) ) -- Disable label missing icons for now for _, labelMissingIcon in pairs( data.labelsMissingIcon ) do           	if label == labelMissingIcon then icon = '' end end

local section = mw.html.create( 'div' ) :addClass( 'template-components__section') :tag( 'div' ) :addClass( 'template-components__label' ) :wikitext( string.format( '%s %s', icon, label ) )               :done :tag( 'div' ):addClass( 'template-components__group' )

local str = ''

for _, item in self.spairs( items ) do               if not item.isChild then local subGroup = mw.html.create('div') :addClass( 'template-components__subgroup' ) :node( makeEntry( item ) ) :allDone str = str .. tostring( subGroup ) end end

out = out .. tostring( section:node( str ):allDone ) end

return out end

for class, types in self.spairs( groupedData ) do       classOutput[ class ] = makeSection( types ) end

---mw.logObject(classOutput)

return classOutput end

--- Generates tabber output function methodtable.out( self ) local smwData = self:querySmwStore( self.page )

if smwData == nil then return hatnote( 'Could not load SMW data from ' .. self.page .. '.' ) end

smwData = self:createDataStructure( smwData ) smwData = self:group( smwData )

local output = self:makeOutput( smwData )

--- Class corresponds to Module:VehicleHardpoint/Data local avSys = ( tostring( output[ 'Avionics' ] or '' ) ) .. ( tostring( output[ 'Systems' ] or '' ) ) local prThr = ( tostring( output[ 'Propulsion' ] or '' ) ) .. ( tostring( output[ 'Thrusters' ] or '' ) ) local weapo = ( tostring( output[ 'Weapons' ] or '' ) ) local cargo = ( tostring( output[ 'Cargo grid' ] or '' ) )

if #avSys > 0 then avSys = avSys .. ( output.info or '' ) else avSys = hatnote( 'No avionics or systems available' ) end

if #prThr > 0 then prThr = prThr .. ( output.info or '' ) else prThr = hatnote( 'No propulsion or thrusters available.' ) end

if #weapo > 0 then weapo = weapo .. ( output.info or '' ) else weapo = hatnote( 'No weaponry present.' ) end

if #cargo > 0 then cargo = cargo .. ( output.info or '' ) else cargo = hatnote( 'No cargo grid information found.' ) end

local format = string.format( %s=%s %s=%s %s=%s %s=%s ,			'Avionics & Systems',           avSys,            'Propulsion & Thrusters',            prThr,            'Weaponry',            weapo,            'Cargo',            cargo    )

return mw.getCurrentFrame:extensionTag{ name = 'tabber', content = format } .. mw.getCurrentFrame:extensionTag{ name = 'templatestyles', args = { src = 'Template:Vehicle hardpoints/styles.css' } } end

--- Manually fix some (sub_)types by checking the hardpoint name --- --- @param hardpoint table Entry from the api --- @return table The fixed entry function VehicleHardPoint.fixTypes( hardpoint ) if hardpoint.type == 'ManneuverThruster' or hardpoint.type == 'MainThruster' then if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and string.match( string.lower( hardpoint.name ), 'vtol' ) ~= nil then hardpoint.sub_type = 'VtolThruster' end

if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and string.match( string.lower( hardpoint.name ), 'retro' ) ~= nil then hardpoint.sub_type = 'RetroThruster' end

if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and string.match( string.lower( hardpoint.name ), 'retro' ) ~= nil then hardpoint.sub_type = 'RetroThruster' end if ( hardpoint.sub_type == 'JointThruster' or hardpoint.sub_type == 'UNDEFINED' ) and string.match( string.lower( hardpoint.name ), 'grav' ) ~= nil then hardpoint.sub_type = 'GravLev' end

if hardpoint.type == 'MainThruster' then hardpoint.sub_type = 'Main' .. hardpoint.sub_type end end

if hardpoint.type == 'WeaponDefensive' then if ( hardpoint.sub_type == 'CountermeasureLauncher' or hardpoint.sub_type == 'UNDEFINED' ) and ( string.match( string.lower( hardpoint.class_name ), 'decoy' ) ~= nil or                 string.match( string.lower( hardpoint.class_name ), 'flare' ) ~= nil) then hardpoint.sub_type = 'DecoyLauncher' end

if ( hardpoint.sub_type == 'CountermeasureLauncher' or hardpoint.sub_type == 'UNDEFINED' ) and ( string.match( string.lower( hardpoint.class_name ), 'chaff' ) ~= nil or              string.match( string.lower( hardpoint.class_name ), 'noise' ) ~= nil) then hardpoint.sub_type = 'NoiseLauncher' end

if type( hardpoint.item ) == 'table' and hardpoint.item ~= nil then hardpoint.item.name = '<= PLACEHOLDER =>' end end

if hardpoint.type == 'FuelTank' or hardpoint.type == 'QuantumFuelTank' then local prefix = '' if hardpoint.type == 'QuantumFuelTank' then prefix = 'Quantum' end

if string.match( string.lower( hardpoint.class_name ), 'small' ) ~= nil then hardpoint.sub_type = prefix .. 'FuelTankSmall' end

if string.match( string.lower( hardpoint.class_name ), 'large' ) ~= nil then hardpoint.sub_type = prefix .. 'FuelTankLarge' end end

if hardpoint.type == 'Turret' then if ( hardpoint.sub_type == 'GunTurret' or hardpoint.sub_type == 'UNDEFINED' ) and string.match( string.lower( hardpoint.class_name ), 'mining' ) ~= nil then hardpoint.sub_type = 'MiningTurret' end if hardpoint.sub_type == 'Turret' and string.match( string.lower( hardpoint.class_name ), 'remote' ) ~= nil then hardpoint.sub_type = 'GunTurret' end end

-- Manual mapping defined in Module:VehicleHardpoint/Data if type( hardpoint.item ) == 'table' and hardpoint.item ~= nil then for _, mapping in pairs( data.hardPointTypeFixes ) do	   	for _, matcher in pairs( data.hardPointMappings[mapping]['matches'] ) do	    		if string.match( hardpoint.name, matcher ) ~= nil then hardpoint.type = mapping return hardpoint end end end end

return hardpoint end

--- New Instance --- --- @return table VehicleHardPoint function VehicleHardPoint.new( self, page ) local instance = { page = page or nil, spairs = require( 'Module:Common' ).spairs }

setmetatable( instance, metatable )

return instance end

--- Parser call for generating the table function VehicleHardPoint.outputTable( frame ) local args = require( 'Module:Arguments' ).getArgs( frame ) local page = args[ 1 ] or args[ 'Name' ] or mw.title.getCurrentTitle.rootText

local instance = VehicleHardPoint:new( page )

if args['debug'] ~= nil then local smwData = instance:querySmwStore(page) local struct = instance:createDataStructure( smwData ) local group = instance:group( struct ) return mw.dumpObject(smwData) .. mw.dumpObject(struct) .. mw.dumpObject(group) end return instance:out end

return VehicleHardPoint