模块:VehicleHardpoint:修订间差异

来自星际公民中文百科
无编辑摘要
(已还原LeonRay讨论)的编辑至最后由CxJuice修订的版本)
标签回退
 
第1行: 第1行:
local VehicleHardpoint = {}
local VehicleHardPoint = {}


local metatable = {}
local metatable = {}
local methodtable = {}
local methodtable = {}


metatable.__index = methodtable
local common = require( 'Module:Common' )
 
local data = mw.loadData( 'Module:VehicleHardpoint/Data' )
local TNT = require( 'Module:Translate' ):new()
local common = require( 'Module:Common' ) -- formatNum and spairs
local hatnote = require( 'Module:Hatnote' )._hatnote
local hatnote = require( 'Module:Hatnote' )._hatnote
local data = mw.loadJsonData( 'Module:VehicleHardpoint/data.json' )
local config = mw.loadJsonData( 'Module:VehicleHardpoint/config.json' )


metatable.__index = methodtable


--- Calls TNT with the given key
-- Local functions
---
--- @param key string The translation key
--- @param addSuffix boolean Adds a language suffix if config.smw_multilingual_text is true
--- @return string If the key was not found in the .tab page, the key is returned
local function translate( key, addSuffix )
    addSuffix = addSuffix or false
    local success, translation
 
    local function multilingualIfActive( input )
        if addSuffix and config.smw_multilingual_text == true then
            return string.format( '%s@%s', input, config.module_lang or mw.getContentLanguage():getCode() )
        end
 
        return input
    end
 
    if config.module_lang ~= nil then
        success, translation = pcall( TNT.formatInLanguage, config.module_lang, 'Module:VehicleHardpoint/i18n.json', key or '' )
    else
        success, translation = pcall( TNT.format, 'Module:VehicleHardpoint/i18n.json', key or '' )
    end
 
    if not success or translation == nil then
        return multilingualIfActive( key )
    end
 
    return multilingualIfActive( translation )
end
 


--- Checks if an entry contains a 'child' key with further entries
--- Checks if an entry contains a 'child' key with further entries
第51行: 第19行:
end
end


 
--- Creates a key to be used in 'setHardPointObjects'
--- Creates the object that is used to query the SMW store
--- This allows to sum the total count of each similar object
---
--- @param page string the vehicle page containing data
--- @return table
local function makeSmwQueryObject( page )
    local langSuffix = ''
    if config.smw_multilingual_text == true then
        langSuffix = '+lang=' .. ( config.module_lang or mw.getContentLanguage():getCode() )
    end
 
    return {
        string.format(
            '[[-Has subobject::' .. page .. ']][[%s::+]][[%s::+]]',
            translate( 'SMW_HardpointType' ),
            translate( 'SMW_VehicleHardpointsTemplateGroup' )
        ),
        string.format( '?%s#-=from_gamedata', translate( 'SMW_FromGameData' ) ),
        string.format( '?%s#-=count', translate( 'SMW_ItemQuantity' ) ),
        string.format( '?%s#-=min_size', translate( 'SMW_HardpointMinimumSize' ) ),
        string.format( '?%s#-=max_size', translate( 'SMW_HardpointMaximumSize' ) ),
        string.format( '?%s#-=class', translate( 'SMW_VehicleHardpointsTemplateGroup' ) ), langSuffix,
        string.format( '?%s#-=type', translate( 'SMW_HardpointType' ) ), langSuffix,
        string.format( '?%s#-=sub_type', translate( 'SMW_HardpointSubtype' ) ), langSuffix,
        string.format( '?%s#-=name', translate( 'SMW_Name' ) ),
        string.format( '?%s#-n=scu', translate( 'SMW_Inventory' ) ),
        string.format( '?UUID#-=uuid' ),
        string.format( '?%s#-=hardpoint', translate( 'SMW_Hardpoint' ) ) ,
        string.format( '?%s#-=class_name', translate( 'SMW_HardpointClassName' ) ) ,
        string.format( '?%s#-=magazine_capacity', translate( 'SMW_MagazineCapacity' ) ),
        string.format( '?%s=thrust_capacity', translate( 'SMW_ThrustCapacity' ) ),
        string.format( '?%s=damage', translate( 'SMW_Damage' ) ),
        string.format( '?%s=damage_radius', translate( 'SMW_DamageRadius' ) ),
        string.format( '?%s=fuel_capacity', translate( 'SMW_FuelCapacity' ) ),
        string.format( '?%s=fuel_intake_rate', translate( 'SMW_FuelIntakeRate' ) ),
        string.format( '?%s#-=parent_hardpoint', translate( 'SMW_ParentHardpoint' ) ),
        string.format( '?%s#-=root_hardpoint', translate( 'SMW_RootHardpoint' ) ),
        string.format( '?%s#-=parent_uuid', translate( 'SMW_ParentHardpointUuid' ) ),
        string.format( '?%s#-=icon', translate( 'SMW_Icon' ) ),
        string.format( '?%s=hp', translate( 'SMW_HitPoints' ) ),
        string.format( '?%s#-=position', translate( 'SMW_Position' ) ),
        -- These are subquery chains, they require that the 'Name' attribute is of type Page
        -- And that these pages contain SMW attributes
        '?' .. translate( 'SMW_Name' ) .. '.' .. translate( 'SMW_Grade' ) .. '#-=item_grade',
        '?' .. translate( 'SMW_Name' ) .. '.' .. translate( 'SMW_Class' ) .. '#-=item_class',
        '?' .. translate( 'SMW_Name' ) .. '.' .. translate( 'SMW_Size' ) .. '#-=item_size',
        '?' .. translate( 'SMW_Name' ) .. '.' .. translate( 'SMW_Manufacturer' ) .. '#-=manufacturer',
        string.format(
            'sort=%s,%s,%s,%s,%s',
            translate( 'SMW_VehicleHardpointsTemplateGroup' ),
            translate( 'SMW_Hardpoint' ),
            translate( 'SMW_HardpointType' ),
            translate( 'SMW_HardpointMaximumSize' ),
            translate( 'SMW_ItemQuantity' )
        ),
        'order=asc,desc,asc,asc,asc',
        'limit=1000'
    }
end
 
 
--- Creates a 'key' based on various data points found on the hardpoint and item
--- Based on this key, the count of some entries is generated
---
---
--- @param row table - API Data
--- @param row table - API Data
--- @param hardpointData table - Data from getHardpointData
--- @param hardpointData table - Data from getHardpointData
--- @param parent table|nil - Parent hardpoint (A settable SMW Subobject)
--- @param parent table|nil - Parent hardpoint
--- @param root string|nil - Root hardpoint
--- @param root string|nil - Root hardpoint
--- @return string Key
--- @return string Key
第123行: 第30行:
     local key
     local key


    -- If the hardpoint has an item attached
     if type( row.item ) == 'table' then
     if type( row.item ) == 'table' then
        -- List of item types that should always be grouped together
        -- i.e. their count is increased instead of them being displayed as separate boxes
         if row.type == 'ManneuverThruster' or
         if row.type == 'ManneuverThruster' or
           row.type == 'MainThruster' or
           row.type == 'MainThruster' or
          row.type == 'WeaponDefensive' or
          row.type == 'WeaponLocker' or
           row.type == 'ArmorLocker' or
           row.type == 'ArmorLocker' or
           row.type == 'Bed' or
           row.type == 'Bed' or
           row.type == 'CargoGrid' or
           row.type == 'CargoGrid'
          row.type == 'Cargo'
         then
         then
             key = row.type .. row.sub_type
             key = row.type .. row.sub_type
         else
         else
             local suffix = ( row.item.name or '' )
             key = row.type .. row.sub_type .. row.item.uuid
            if suffix == '<= PLACEHOLDER =>' then
                suffix = row.item.uuid
            end
 
            -- Adding the uuid to the key ensures separate boxes if the equipped item differs
            key = row.type .. row.sub_type .. suffix
         end
         end
     else
     else
        -- If no item is set, use the pre-defined class and type
         key = hardpointData.class .. hardpointData.type
         key = hardpointData.class .. hardpointData.type
     end
     end


    -- Appends the parent and root hardpoints in order to not mess up child counts
     if row.type ~= 'WeaponDefensive' then
    -- Without this, a vehicle with four turrets containing each one weapon would be listed as
        if parent ~= nil then
    -- having four turrets that each has four weapons (if the exact weapon is equipped on each turret)
            key = key .. parent[ 'Hardpoint' ]
     if parent ~= nil and parent[ translate( 'SMW_Name' ) ] ~= nil and
         end
      row.type ~= 'DecoyLauncherMagazine' and
      row.type ~= 'NoiseLauncherMagazine'
    then
        --key = key .. parent[ translate( 'SMW_Hardpoint' ) ]
         key = key .. ( parent[ translate( 'SMW_Name' ) ] or parent[ translate( 'SMW_Hardpoint' ) ] )
    end


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


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


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




--- Get pre-defined hardpoint data for a given hardpoint type
--- Tries to fix hardpoints that have no item, but everything set on the 'child' key
--- If no type is found, the hardpoint name is matched against the defined regexes until the first one matches
---
--- @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
--- @param hardpointType string
--- @return table|nil
--- @return table|nil
function methodtable.getHardpointData( self, hardpointType )
function methodtable.getHardpointData( self, hardpointType )
     if type( data.matches[ hardpointType ] ) == 'table' then
     if type( data.hardPointMappings[ hardpointType ] ) == 'table' then
         return data.matches[ hardpointType ]
         return data.hardPointMappings[ hardpointType ]
     end
     end


     for hType, mappingData in pairs( data.matches ) do
     for hType, mappingData in pairs( data.hardPointMappings ) do
         if hardpointType == hType then
         if hardpointType == hType then
             return mappingData
             return mappingData
第199行: 第121行:
end
end


 
--- Creates a settable SMW Subobject
--- Creates a child object for weapons and counter measure ammunitions
--- As well as weapon ports on armor locker
---
--- @param hardpoint table A hardpoint object form the API
--- @return void
local function addSubComponents( hardpoint )
    if type( hardpoint.item ) ~= 'table' then
        return
    end
 
    if type( hardpoint.children ) ~= 'table' then
        hardpoint.children = {}
    end
 
    if hardpoint.item.type == 'WeaponDefensive' or hardpoint.item.type == 'WeaponGun' then
        local item_type = 'Magazine'
        if mw.ustring.sub( hardpoint.class_name, -5 ) == 'chaff' then
            item_type = 'NoiseLauncherMagazine'
        elseif mw.ustring.sub( hardpoint.class_name, -5 ) == 'flare' then
            item_type = 'DecoyLauncherMagazine'
        end
 
        local capacity = {}
        local magazineName = translate( 'Magazine' )
        if hardpoint.item.type == 'WeaponGun' and type( hardpoint.item.vehicle_weapon ) == 'table' then
            table.insert( capacity, hardpoint.item.vehicle_weapon.capacity )
 
            -- This is a laser weapon, add another capacity of -1 to indicate that this weapon has infinite ammo
            if type( hardpoint.item.vehicle_weapon.regeneration ) == 'table' then
                table.insert( capacity, -1 )
                magazineName = translate( 'Capacitor' )
            end
        elseif type( hardpoint.item.counter_measure ) == 'table' then
            table.insert( capacity, hardpoint.item.counter_measure.capacity )
        end
 
        table.insert( hardpoint.children, {
            name = 'faux_hardpoint_magazine',
            class_name = 'FAUX_' .. item_type .. 'Magazine',
            type = item_type,
            sub_type = item_type,
            min_size = 1,
            max_size = 1,
            item = {
                name = magazineName,
                type = item_type,
                sub_type = item_type,
                magazine_capacity = capacity
            }
        } )
    end
 
    -- This seems to be a weapon rack
    if ( hardpoint.item.type == 'Usable' or hardpoint.item.type == 'Door' ) and type( hardpoint.item.ports ) == 'table' then
        local item_type = 'WeaponPort'
        for _, port in pairs( hardpoint.item.ports ) do
            -- Prevent stuff like mattress and pillow to count as weapon ports (I don't think SC let you hide weapons inside them :P)
            if ( mw.ustring.find( port.name, 'weapon', 1, true ) or mw.ustring.find( port.display_name, 'weapon', 1, true ) ) then
                local sub_type = item_type .. tostring( port.sizes.min or 0 ) .. tostring( port.sizes.max or 0 )
                local name = 'WeaponPort'
 
                if port.sizes.max == 5 or mw.ustring.find( port.display_name, 'launcher', 1, true ) then
                    name = name .. 'Launcher'
                elseif port.sizes.max == 4 or mw.ustring.find( port.display_name, 'rifle', 1, true ) then
                    name = name .. 'Rifle'
                elseif mw.ustring.find( port.display_name, 'multitool', 1, true ) then
                    name = name .. 'Multitool'
                elseif mw.ustring.find( port.display_name, 'addon', 1, true ) then
                    name = name .. 'Addon'
                -- Assume size 1 is pistol slot if it is not specified as multitool or addon
                elseif port.sizes.max == 1 or mw.ustring.find( port.display_name, 'pistol', 1, true ) then
                    name = name .. 'Pistol'
                end
 
                table.insert( hardpoint.children, {
                    name = 'faux_hardpoint_weaponport',
                    class_name = 'FAUX_WeaponPort',
                    type = item_type,
                    sub_type = sub_type,
                    min_size = port.sizes.min,
                    max_size = port.sizes.max,
                    item = {
                        name = translate( name ),
                        type = item_type,
                        sub_type = sub_type,
                    }
                } )
            end
        end
    end
end
 
 
--- Builds the object that is saved to SMW as a Subobject
---
---
--- @param row table - API Data
--- @param row table - API Data
第311行: 第139行:
     end
     end


     object[ translate( 'SMW_Hardpoint' ) ] = row.name
     object[ 'Hardpoint' ] = row.name
     object[ translate( 'SMW_FromGameData' ) ] = true
     --object[ 'From game data' ] = true
     object[ translate( 'SMW_HardpointMinimumSize' ) ] = row.min_size
     object[ 'Hardpoint minimum size' ] = row.min_size
     object[ translate( 'SMW_HardpointMaximumSize' ) ] = row.max_size
     object[ 'Hardpoint maximum size' ] = row.max_size
     object[ translate( 'SMW_VehicleHardpointsTemplateGroup' ) ] = translate( hardpointData.class, true )
     object[ 'Vehicle hardpoints template group' ] = hardpointData.class
    object[ translate( 'SMW_HitPoints' ) ] = row.damage_max
    object[ translate( 'SMW_Position' ) ] = row.position


     if type( row.class_name ) == 'string' then
     if data.hardPointNames[ row.type ] ~= nil then
         object[ translate( 'SMW_HardpointClassName' ) ] = row.class_name
        object[ 'Hardpoint type' ] = data.hardPointNames[ row.type ]
    else
         object[ 'Hardpoint type' ] = hardpointData.type
     end
     end


     object[ translate( 'SMW_HardpointType' ) ] = translate( hardpointData.type, true )
     if data.hardPointNames[ row.sub_type ] ~= nil then
    object[ translate( 'SMW_HardpointSubtype' ) ] = translate( hardpointData.type, true )
        object[ 'Hardpoint subtype' ] = data.hardPointNames[ row.sub_type ]
 
     else
    -- FIXME: Is there a way to use Lua table key directly instead of setting subtype separately in data.json?
         object[ 'Hardpoint subtype' ] = hardpointData.type
    -- For some components (e.g. missile), the key is the subtype of the component
     local function setTypeSubtype( match )
         if match ~= nil then
            if match.type ~= nil then
                object[ translate( 'SMW_HardpointType' ) ] = translate( match.type, true )
            end
            if match.subtype ~= nil then
                object[ translate( 'SMW_HardpointSubtype' ) ] = translate( match.subtype, true )
            end
        end
     end
     end


    setTypeSubtype( data.matches[ row.type ] )
     if hardpointData.item ~= nil then
    setTypeSubtype( data.matches[ row.sub_type ] )
    if type( hardpointData.item.name ) == 'string' then object[ 'Name' ] = hardpointData.item.name end
 
     if hardpointData.item ~= nil and type( hardpointData.item.name ) == 'string' then
        object[ translate( 'SMW_Name' ) ] = hardpointData.item.name
     end
     end


     if type( row.item ) == 'table' then
     if type( row.item ) == 'table' then
         local itemObj = row.item
         local itemObj = row.item
         if itemObj.name ~= '<= PLACEHOLDER =>' then
         if itemObj.name ~= '<= PLACEHOLDER =>' then
             local match = string.match( row.class_name or '', '[Dd]estruct_(%d+s)' )
             local match = string.match( row.class_name or '', 'Destruct_(%d+s)')
 
             if row.type == 'SelfDestruct' and match ~= nil then
             if row.type == 'SelfDestruct' and match ~= nil then
                 object[ translate( 'SMW_Name' ) ] = string.format( '%s (%s)', translate( 'SMW_SelfDestruct' ), match )
                 object[ 'Name' ] = 'Self destruct (' .. match .. ')'
                -- Set self-destruct stats
                -- FIXME: Do subquery instead when CIG properly implement self-destruct components
                if itemObj.self_destruct then
                    object[ translate( 'SMW_Damage' ) ] = itemObj.self_destruct.damage
                    object[ translate( 'SMW_DamageRadius' ) ] = itemObj.self_destruct.radius
                end
             else
             else
                 object[ translate( 'SMW_Name' ) ] = itemObj.name
                 object[ 'Name' ] = row.item.name
             end
             end
        else
            object[ translate( 'SMW_Name' ) ] = object[ translate( 'SMW_HardpointSubtype' ) ]
            -- Remove lang suffix
            local parts = mw.text.split( object[ translate( 'SMW_Name' ) ], '@', true )
            object[ translate( 'SMW_Name' ) ] = parts[ 1 ] or object[ translate( 'SMW_Name' ) ]
         end
         end


         object[ translate( 'SMW_MagazineCapacity' ) ] = itemObj.magazine_capacity
         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' )
         if ( itemObj.type == 'Cargo' or itemObj.type == 'SeatAccess' or itemObj.type == 'CargoGrid' or itemObj.type == 'Container' )
                and type( itemObj.inventory ) == 'table' then
        and type( itemObj.inventory ) == 'table' then
            object[ translate( 'SMW_Inventory' ) ] = common.formatNum( (itemObj.inventory.scu or nil ), nil )
        object[ 'Inventory' ] = common.formatNum( (itemObj.inventory.scu or nil), nil )
        end
    end


         if itemObj.thruster then
         if object[ 'Hardpoint minimum size' ] == nil then
            object[ translate( 'SMW_ThrustCapacity' ) ] = itemObj.thruster.thrust_capacity
             object[ 'Hardpoint minimum size' ] = itemObj.size
            --- Convert to per Newton since thrust capacity is in Newton
             object[ 'Hardpoint maximum size' ] = itemObj.size
            object[ translate( 'SMW_FuelBurnRate' ) ] = itemObj.thruster.fuel_burn_per_10k_newton / 10000
        end
 
        if itemObj.fuel_tank and itemObj.fuel_tank.capacity > 0 then
            object[ translate( 'SMW_FuelCapacity' ) ] = itemObj.fuel_tank.capacity
        end
 
        if itemObj.fuel_intake then
            object[ translate( 'SMW_FuelIntakeRate' ) ] = itemObj.fuel_intake.fuel_push_rate
        end
 
        if object[ translate( 'SMW_HardpointMinimumSize' ) ] == nil then
             object[ translate( 'SMW_HardpointMinimumSize' ) ] = itemObj.size
             object[ translate( 'SMW_HardpointMaximumSize' ) ] = itemObj.size
         end
         end


第400行: 第190行:


     if parent ~= nil then
     if parent ~= nil then
         object[ translate( 'SMW_ParentHardpointUuid' ) ] = parent[ 'UUID' ]
         object[ 'Parent hardpoint UUID' ] = parent[ 'UUID' ]
         object[ translate( 'SMW_ParentHardpoint' ) ] = parent[ translate( 'SMW_Name' ) ]
         object[ 'Parent hardpoint' ] = parent[ 'Hardpoint' ]
    end
 
    if root ~= nil then
        object[ translate( 'SMW_RootHardpoint' ) ] = root
    end
 
    -- Icon
    local icon = hardpointData.type
    if data.section_label_fixes[ hardpointData.class ] ~= nil or data.section_label_fixes[ hardpointData.type ] ~= nil then
        icon = data.section_label_fixes[ hardpointData.class ] or data.section_label_fixes[ hardpointData.type ]
     end
     end


     for hType, iconKey in pairs( data.icons ) do
     if root ~= nil and root ~= row.name then
        if hType == icon then
         object[ 'Root hardpoint' ] = root
            -- Disable label missing icons for now
            if iconKey == '' then
                icon = nil
                break
            end
            -- Apply icon key override
            icon = iconKey
        end
    end
 
    if icon ~= nil then
        if config.icon_name_localized == true then
            icon = translate( icon )
        end
 
        if config.icon_name_lowercase == true then
            icon = string.lower( icon )
        end
 
         object[ translate( 'SMW_Icon' ) ] = string.format( 'File:%s%s.svg', config.icon_prefix, icon )
     end
     end


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


     return object;
     return object;
end
end


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


     local objects = {}
     local out = {}
    local depth = 1
 
    local function cleanClassName( input )
        if string.find( input, 'turret', 1, true ) then
            local parts = mw.text.split( input, 'turret', true )
            input = parts[ 1 ] or input
        end
 
        for _, remove in pairs( { 'top', 'bottom', 'left', 'right', 'front', 'rear', 'bubble', 'side' } ) do
            input = string.gsub( input, '_' .. remove, '', 1 )
        end
 
        return input
    end


    -- Adds the subobject to the list of objects that should be saved to SMW
    -- Increases the item quantity / or combined cargo capacity for objects that have equal keys
     local function addToOut( object, key )
     local function addToOut( object, key )
         if object == nil then
         if object == nil then
第479行: 第222行:
         end
         end


        -- If this key (object) has not been seen before, save it to the list of subobjects
         if type( out[ key ] ) ~= 'table' then
         if type( objects[ key ] ) ~= 'table' then
             if object ~= nil then
             if object ~= nil then
                 objects[ key ] = object
                 out[ key ] = object
                 objects[ key ][ translate( 'SMW_ItemQuantity' ) ] = 1
                 out[ key ][ 'Item quantity' ] = 1
             end
             end
         else -- This key (object) has been seen before: Increase the quantity and any other cumulative metrics
         else
             objects[ key ][ translate( 'SMW_ItemQuantity' ) ] = objects[ key ][ translate( 'SMW_ItemQuantity' ) ] + 1
             out[ key ][ 'Item quantity' ] = out[ key ][ 'Item quantity' ] + 1
             if object[ translate( 'SMW_Position' ) ] ~= nil then
 
                if type( objects[ key ][ translate( 'SMW_Position' ) ] ) == 'table' then
             if type( out[ key ][ 'Magazine capacity' ] ) == 'number' then
                    table.insert( objects[ key ][ translate( 'SMW_Position' ) ], object[ translate( 'SMW_Position' ) ] )
                 out[ key ][ 'Magazine capacity' ] = out[ key ][ 'Magazine capacity' ] + object[ 'Magazine capacity' ]
                 else
                    objects[ key ][ translate( 'SMW_Position' ) ] = {
                        objects[ key ][ translate( 'SMW_Position' ) ],
                        object[ translate( 'SMW_Position' ) ]
                    }
                end
             end
             end


            local inventoryKey = translate( 'SMW_Inventory' )
if object[ 'Hardpoint type' ] == 'Cargo grid' then
            -- Accumulate the cargo capacities of all cargo grids
out[ key ][ 'Item quantity' ] = 1
            if object[ inventoryKey ] ~= nil then
if out[ key ][ 'Inventory' ] ~= nil and object[ 'Inventory' ] ~= nil then
                objects[ key ][ translate( 'SMW_ItemQuantity' ) ] = 1
            out[ key ][ 'Inventory' ] = tonumber(out[ key ][ 'Inventory' ]) + tonumber(object[ 'Inventory' ])
 
        end
                if objects[ key ][ inventoryKey ] ~= nil and object[ inventoryKey ] ~= nil then
end
                    local sucExisting, numExisting = pcall( tonumber, objects[ key ][ inventoryKey ], 10 )
                    local sucNew, numNew = pcall( tonumber, object[ inventoryKey ], 10 )
 
                    if sucExisting and sucNew and numExisting ~= nil and numNew ~= nil then
                        objects[ key ][ inventoryKey ] = numExisting + numNew
                    end
                end
            end
         end
         end
     end
     end


    local depth = 1


    -- Iterates through the list of hardpoints found on the API object
     local function addHardpoints( hardpoints, parent, root )
     local function addHardpoints( hardpoints, parent, root )
         for _, hardpoint in pairs( hardpoints ) do
         for _, hardpoint in pairs( hardpoints ) do
             hardpoint.name = string.lower( hardpoint.name )
             hardpoint.name = string.lower( hardpoint.name )
            hardpoint = fixChild( hardpoint )


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


             hardpoint = VehicleHardpoint.fixTypes( hardpoint, data.fixes )
             hardpoint = VehicleHardPoint.fixTypes( hardpoint )


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


             if hardpointData ~= nil then
             if hardpointData ~= nil then
                if depth == 1 then
                    if type( hardpoint.item ) == 'table' then
                        root = hardpoint.class_name or hardpoint.name
                        if root == '<= PLACEHOLDER =>' then
                            root = hardpointData.type
                        end
                    else
                        root = hardpoint.name
                    end
                    mw.logObject( string.format( 'Root: %s', root ), 'addHardpoints' )
                end
                addSubComponents( hardpoint )
                -- Based on the key, the hardpoint is either used as "standalone" (i.e. saved as a single subobject)
                -- or, if the key already exists, the count if increased by one (so no extra subobject is generated)
                 local key = makeKey( hardpoint, hardpointData, parent, root )
                 local key = makeKey( hardpoint, hardpointData, parent, root )


第553行: 第266行:
                 addToOut( obj, key )
                 addToOut( obj, key )


                -- Generate child subobjects
                 if hasChildren( hardpoint ) then
                 if hasChildren( hardpoint ) then
                     depth = depth + 1
                     depth = depth + 1
第571行: 第283行:
     addHardpoints( hardpoints )
     addHardpoints( hardpoints )


     mw.logObject( objects, 'setHardPointObjects' )
     --mw.logObject(out)
 
    for _, subobject in pairs( objects ) do
        mw.smw.subobject( subobject )
    end
end
 
 
--- Sets all available vehicle parts as SMW subobjects
--- This method should be called by the accompanying Vehicle Module
---
--- @param parts table API Hardpoint data
function methodtable.setParts(self, parts )
    if type( parts ) ~= 'table' then
        error( translate( 'msg_invalid_hardpoints_object' ) )
    end
 
    local objects = {}
    local depth = 1
 
    local partData = {
        class = 'VehiclePart',
        type = 'VehiclePart',
    }
 
    local function makeKey( row, parent )
        local key = row.name
 
        if parent ~= nil then
            key = key .. parent[ translate( 'SMW_Hardpoint' ) ]
        end
 
        mw.logObject( string.format( 'Key: %s', key ), 'makeKey' )
 
        return key
    end
 
 
    -- Adds the subobject to the list of objects that should be saved to SMW
    local function addToOut( object, key )
        if object == nil then
            return
        end
 
        -- If this key (object) has not been seen before, save it to the list of subobjects
        if type( objects[ key ] ) ~= 'table' then
            if object ~= nil then
                objects[ key ] = object
                objects[ key ][ translate( 'SMW_ItemQuantity' ) ] = 1
            end
        end
    end
 
 
    -- Iterates through the list of parts found on the API object
    local function addParts( parts, parent, root )
        for _, part in pairs( parts ) do
            part.type = 'VehiclePart'
            part.min_size = 1
            part.max_size = 1
            part.item = {
                name = part.display_name
            }
 
            if depth == 1 then
                root = part.name
                mw.logObject( string.format( 'Root: %s', root ), 'addParts' )
            end
 
            local key = makeKey( part, parent )
 
            local obj = self:makeObject( part, partData, parent, root )
 
            addToOut( obj, key )
 
            -- Generate child subobjects
            if hasChildren( part ) then
                depth = depth + 1
                addParts( part.children, obj, root )
            end
        end
 
        depth = depth - 1
 
        if depth < 1 then
            depth = 1
            root = nil
        end
    end
 
    addParts( parts )


    mw.logObject( objects, 'setParts' )
     for _, subobject in pairs( out ) do
 
     for _, subobject in pairs( objects ) do
         mw.smw.subobject( subobject )
         mw.smw.subobject( subobject )
     end
     end
end
end


--- Queries the SMW store for all available hardpoint subobjects for a given page
--- Queries the SMW store for all available hardpoint subobjects for a given page
第681行: 第300行:
     end
     end


    local smwData = mw.smw.ask( makeSmwQueryObject( page ) )
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
     if smwData == nil or smwData[ 1 ] == nil then
第687行: 第329行:
     end
     end


     --mw.logObject( smwData, 'querySmwStore' )
     mw.logObject( smwData )


     self.smwData = smwData
     self.smwData = smwData
第693行: 第335行:
     return self.smwData
     return self.smwData
end
end


--- Group Hardpoints by Class and type
--- Group Hardpoints by Class and type
第706行: 第347行:
     end
     end


     for _, row in ipairs( smwData ) do
     for _, row in self.spairs( smwData ) do
         if not row.isChild and row.class ~= nil and row.type ~= nil and
         if not row.isChild and row.class ~= nil and row.type ~= nil then
            -- Specifically hide manually added weapon ports that have no parent
            -- This should not be needed anymore if weapon lockers are found everywhere with an uuid
            row.type ~= translate( 'WeaponPort' ) and
            not mw.ustring.find( row.type, translate( 'Magazine' ), 1, true )
        then
             if type( grouped[ row.class ] ) ~= 'table' then
             if type( grouped[ row.class ] ) ~= 'table' then
                 grouped[ row.class ] = {}
                 grouped[ row.class ] = {}
第722行: 第358行:


             table.insert( grouped[ row.class ][ row.type ], row )
             table.insert( grouped[ row.class ][ row.type ], row )
            self.iconMap[ row.class ] = row.icon
            self.iconMap[ row.type ] = row.icon
         end
         end
     end
     end
第732行: 第365行:
     return grouped
     return grouped
end
end


--- Adds children to the according parents
--- Adds children to the according parents
第739行: 第371行:
--- @return table The stratified table
--- @return table The stratified table
function methodtable.createDataStructure( self, smwData )
function methodtable.createDataStructure( self, smwData )
     -- Maps a key to the index of the subobject, this way children can be set on their parent
     -- Maps object id to key in array
     local idMapping = {}
     local idMapping = {}


     for index, object in ipairs( smwData ) do
     for key, object in pairs( smwData ) do
        local keyMap
         if object.hardpoint ~= nil then
         if object.class == translate( 'VehiclePart' ) and object.name ~= nil then
             local keyMap = ( object.root_hardpoint or object.hardpoint ) .. object.hardpoint
             keyMap = object.name
 
        else
            idMapping[ keyMap ] = key
            keyMap = ( object.root_hardpoint or object.class_name or '' ) .. ( object.name or object.type or '' )
         end
         end
        idMapping[ keyMap ] = index
     end
     end


    -- Iterates through the list of SMW hardpoint subobjects
    -- If the 'parent_hardpoint' key is set (i.e. the hardpoint is a child), it is added as a child to the parent object
     local function stratify( toStratify )
     local function stratify( toStratify )
         for _, object in ipairs( toStratify ) do
         for _, object in pairs( toStratify ) do
             if object.parent_hardpoint ~= nil then
             if object.parent_hardpoint ~= nil then
                 local parentEl
                 local parentEl = toStratify[ idMapping[ (object.root_hardpoint or '') .. object.parent_hardpoint ] ]
                if object.class == translate( 'VehiclePart' ) and object.parent_hardpoint ~= nil then
                    parentEl = toStratify[ idMapping[ object.parent_hardpoint ] ]
                else
                    parentEl = toStratify[ idMapping[ ( ( object.root_hardpoint or '' ) .. object.parent_hardpoint ) ] ]
                end


                 if parentEl ~= nil then
                 if parentEl ~= nil then
第778行: 第400行:
     end
     end


    -- SMW outputs a "flat" List of objects, after this the output is more or less equal to that from the API
     stratify( smwData )
     stratify( smwData )


     return smwData
     return smwData
end
end
--- Creates the subtitle that is shown in the card
---
--- Show info based on importance to readers
--- When the first tier is not available, show the next tier
---
--- @param item table Item i.e. row from the smw query
--- @return string
function methodtable.makeSubtitle( self, item )
    local subtitle = {}
    -- Tier 1
    -- Component-specific stats that affects gameplay
    -- SCU
    if item.scu ~= nil then
        -- Fix for german number format
        if string.find( item.scu, ',', 1, true ) then
            item.scu = string.gsub( item.scu, ',', '.' )
        end
        if type( item.scu ) ~= 'number' then
            local success, scu = pcall( tonumber, item.scu, 10 )
            if success then
                item.scu = scu
            end
        end
        -- We need to use raw value from SMW to show scu in different units (SCU, K µSCU)
        -- So we need to format the number manually
        if item.type == translate( 'CargoGrid' ) then
            table.insert( subtitle,
                common.formatNum( item.scu ) .. ' SCU' or 'N/A'
            )
        elseif item.type == translate( 'PersonalStorage' ) then
            table.insert( subtitle,
                common.formatNum( item.scu * 1000 ) .. 'K µSCU' or 'N/A'
            )
        end
    end
    -- Components that don't have a wiki page currently
    -- Magazine Capacity
    if item.magazine_capacity ~= nil then
        if type( item.magazine_capacity ) == 'table' then
            table.insert( subtitle,
                string.format(
                    '%s/∞ %s',
                    item.magazine_capacity[ 1 ],
                    translate( 'Ammunition' )
                )
            )
        else
            table.insert( subtitle,
                string.format(
                    '%s/%s %s',
                    item.magazine_capacity,
                    item.magazine_capacity,
                    translate( 'Ammunition' )
                )
            )
        end
    end
    -- Parts
    if item.hp ~= nil then
        table.insert( subtitle,
            item.hp
        )
    end
    -- Fuel tanks
    if item.fuel_capacity ~= nil then
        table.insert( subtitle,
            item.fuel_capacity
        )
    end
    -- Fuel intake
    if item.fuel_intake_rate ~= nil then
        table.insert( subtitle,
            item.fuel_intake_rate
        )
    end
    -- Self destruct
    if item.damage ~= nil and item.damage_radius ~= nil then
        table.insert( subtitle,
            string.format(
                '%s · %s',
                item.damage,
                item.damage_radius
            )
        )
    end
    -- Thrusters
    if item.thrust_capacity ~= nil then
        table.insert( subtitle,
            item.thrust_capacity
        )
    end
    -- Weapon ports
    if item.type == translate( 'WeaponPort' ) then
        table.insert( subtitle,
            string.format(
                '%s (S%s – S%s)',
                translate( 'Weapon' ),
                item.min_size or 0,
                item.max_size or 0
            )
        )
    end
    -- Items with Grade and/or Class
    if item.item_grade ~= nil or item.item_class ~= nil then
        local grade_class = ''
        -- TODO can't use lang suffix for subquery properties
        if type( item.item_class ) == 'table' then
            local parts = mw.text.split( item.item_class[ 1 ], ' (', true )
            if #parts == 2 then
                grade_class = parts[ 1 ]
                item.item_class = parts[ 1 ]
            else
                grade_class = grade_class[ 1 ]
                item.item_class = item.item_class[ 1 ]
            end
        end
        if item.item_grade ~= nil and item.item_class ~= nil then
            grade_class = string.format( '%s (%s)', item.item_class, item.item_grade )
        elseif item.item_grade ~= nil then
            grade_class = item.item_grade
        end
        table.insert( subtitle,
            grade_class
        )
    end
    -- Tier 2
    -- Info that might affect gameplay but not as important
    if next( subtitle ) == nil then
        -- Position
        if item.position ~= nil then
            if type( item.position ) ~= 'table' then
                item.position = { item.position }
            end
   
            local converted = {}
            for _, position in ipairs( item.position ) do
                table.insert( converted, mw.text.trim( mw.getContentLanguage():ucfirst( string.gsub( position, '_', ' ' ) ) ) )
            end
            table.insert( subtitle,
                table.concat( converted, ', ' )
            )
        end
    end
    -- Tier 3
    -- Info that does not affect gameplay
    if next( subtitle ) == nil then
        -- Manufacturer
        if item.manufacturer ~= nil and item.manufacturer ~= 'N/A' then
            table.insert( subtitle,
                string.format( '[[%s]]', item.manufacturer )
            )
        end
    end
    -- Return if there are no information at all
    if next( subtitle ) == nil then
        return ''
    end
    return table.concat( subtitle, ' · ' )
end


--- Generate the output
--- Generate the output
第974行: 第412行:
     local classOutput = {}
     local classOutput = {}


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


             classOutput.info = hatnote( text, { icon = 'WikimediaUI-Robot.svg' } )
             classOutput.info = hatnote( text, { icon = 'WikimediaUI-Robot.svg' } )
第990行: 第428行:
         depth = depth or 1
         depth = depth or 1


        local row = mw.html.create( 'div' )
local row = mw.html.create( 'div' )
             :addClass( 'template-component' )
             :addClass( 'template-component' )
             :addClass( string.format( 'template-component--level-%d', depth ) )
             :addClass( string.format( 'template-component--level-%d', depth) )
              :tag( 'div' )
:tag('div')
                  :addClass( 'template-component__connectors' )
:addClass('template-component__connectors')
                      :tag( 'div' ):addClass( 'template-component__connectorX' ):done()
:tag('div'):addClass('template-component__connectorX'):done()
                      :tag( 'div' ):addClass( 'template-component__connectorY' ):done()
:tag('div'):addClass('template-component__connectorY'):done()
              :done()
:done()
 
        if item.magazine_size ~= nil then
            item.count = item.magazine_size
        end


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


         -- If Ship-Matrix components are not saved to SMW, always output the 'S' prefix
         ---if item.from_gamedata == true or item.class == 'Weapons' then
        if item.from_gamedata == nil then
        ---    prefix = 'S'
            prefix = 'S'
         ---end
        else
            if item.from_gamedata == true or
              item.from_gamedata == 1 or
              item.from_gamedata == '1' or -- For uninitialized attributes
              item.class == translate( 'Weapons' )
            then
                prefix = 'S'
            end
         end


         if item.item_size ~= nil then
         if item.item_size ~= nil then
第1,022行: 第455行:


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


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


         nodeSizeCount = nodeSizeCount:allDone()
         nodeSizeCount = nodeSizeCount:allDone()


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


         local nodeItem = mw.html.create( 'div' )
local subtitle = item.manufacturer or 'N/A'
              :addClass( 'template-component__item' )
        if item.manufacturer ~= nil and item.manufacturer ~= 'N/A' then
                    :tag( 'div' )
            subtitle = string.format( '[[%s]]', item.manufacturer )
                    :addClass( 'template-component__title' )
        end
                    :wikitext( name )
 
                    :done()
-- Show SCU in subtitle
       
if item.scu ~= nil then
        local subtitle = self:makeSubtitle( item )
        if item.type == 'Cargo grid' then
        if subtitle ~= '' then
        subtitle = item.scu .. ' SCU' or 'N/A'
             nodeItem:tag( 'div' )
        elseif item.type == 'Personal storage' then
    subtitle = item.scu * 100 .. 'K µSCU'  or 'N/A'
    end
    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' )
                 :addClass( 'template-component__subtitle' )
                 :wikitext( subtitle )
                 :wikitext( subtitle )
        end
                :done()
                :allDone()


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


         row = tostring( row )
         row = tostring( row )
第1,071行: 第516行:
         if type( item.children ) == 'table' then
         if type( item.children ) == 'table' then
             depth = depth + 1
             depth = depth + 1
             for _, child in ipairs( item.children ) do
             for _, child in self.spairs( item.children ) do
                 row = row .. makeEntry( child, depth )
                 row = row .. makeEntry( child, depth )
             end
             end
第1,080行: 第525行:




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


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


            -- Label override
-- Label override
            -- Note: This must be manually changed on the data.json page
        if data.sectionLabelFixes[ classType ] ~= nil then
            if data.section_label_fixes[ classType ] ~= nil then
                 label = data.sectionLabelFixes[ classType ]
                 label = data.section_label_fixes[ classType ]
             end
             end


             local icon = ''
             local icon = string.format( '[[File:Hardpoints icon %s.svg|20px|link=]]', string.lower( label ) )
            if self.iconMap[ classType ] ~= nil then
            -- Disable label missing icons for now
                icon = string.format( '[[%s|20px|link=]]', self.iconMap[ classType ] )
            for _, labelMissingIcon in pairs( data.labelsMissingIcon ) do
            if label == labelMissingIcon then icon = '' end
             end
             end


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


             local str = ''
             local str = ''


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


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


     mw.logObject( classOutput, 'makeOutput' )
     ---mw.logObject(classOutput)


     return classOutput
     return classOutput
end
end


--- Generates tabber output
--- Generates tabber output
第1,143行: 第588行:


     if smwData == nil then
     if smwData == nil then
         return hatnote( TNT.format( 'Module:VehicleHardpoint/i18n.json', 'msg_no_data', self.page ), { icon = 'WikimediaUI-Error.svg' } )
         return hatnote( 'SMW data not found on [[' .. self.page .. ']].', { icon = 'WikimediaUI-Error.svg' } )
     end
     end


第1,151行: 第596行:
     local output = self:makeOutput( smwData )
     local output = self:makeOutput( smwData )


     local tabberData = {}
--- 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 weUti = ( tostring( output[ 'Weapons' ] or '' ) ) .. ( tostring( output[ 'Utility' ] or '' ) )
    local caFac = ( tostring( output[ 'Cargo' ] or '' ) ) .. ( tostring( output[ 'Facilities' ] or '' ) )


     for i, grouping in ipairs( data.class_groupings ) do
     if #avSys > 0 then
         local key = grouping[ 1 ]
        avSys = avSys .. ( output.info or '' )
        local groups = grouping[ 2 ]
    else
         avSys = hatnote( 'No avionics or systems available' )
    end


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


         for _, group in ipairs( groups ) do
    if #weUti > 0 then
            groupContent = groupContent .. ( output[ translate( group ) ] or '' )
         weUti = weUti .. ( output.info or '' )
            table.insert( label, translate( group ) )
    else
        end
        weUti = hatnote( 'No weaponry or utility items present.' )
    end


        if #groupContent == 0 then
    if #caFac > 0 then
            groupContent = translate( 'empty_' .. key )
        caFac = caFac .. ( output.info or '' )
        end
    else
 
         caFac = hatnote( 'No cargo or facilities information found.' )
         tabberData[ 'label' .. i ] = table.concat( label, ' & ' )
        tabberData[ 'content' .. i ] = groupContent
     end
     end


     return require( 'Module:Tabber' ).renderTabber( tabberData ) .. mw.getCurrentFrame():extensionTag{
     local format = string.format([[
        name = 'templatestyles', args = { src = config.template_styles_page }
%s=%s
    }
|-|
end
%s=%s
 
|-|
 
%s=%s
--- Generates debug output
|-|
function methodtable.makeDebugOutput( self )
%s=%s
    local debug = require( 'Module:Common/Debug' )
]],
    self.smwData = nil
'Avionics & Systems',
    local smwData = self:querySmwStore( self.page )
            avSys,
    local struct = self:createDataStructure( smwData or {} )
            'Propulsion & Thrusters',
     local group = self:group( struct )
            prThr,
            'Weaponry & Utility',
            weUti,
            'Cargo & Facilities',
            caFac
     )


     return debug.collapsedDebugSections({
     return mw.getCurrentFrame():extensionTag{
         {
         name = 'tabber', content = format
            title = 'SMW Query',
    } .. mw.getCurrentFrame():extensionTag{
            content = debug.convertSmwQueryObject( makeSmwQueryObject( self.page ) ),
name = 'templatestyles', args = { src = 'Template:Vehicle hardpoints/styles.css' }
        },
}
        {
            title = 'SMW Data',
            content = smwData,
            tag = 'pre',
        },
        {
            title = 'Datastructure',
            content = struct,
            tag = 'pre',
        },
        {
            title = 'Grouped',
            content = group,
            tag = 'pre',
        },
        {
            title = 'Output',
            content = self:makeOutput( group ),
            tag = 'pre',
        },
    })
end
end


--- Manually fix some (sub_)types by checking the hardpoint name
--- Manually fix some (sub_)types by checking the hardpoint name
---
---
--- @param hardpoint table Entry from the api
--- @param hardpoint table Entry from the api
--- @param fixes table
--- @return table The fixed entry
--- @return table The fixed entry
function VehicleHardpoint.fixTypes( hardpoint, fixes )
function VehicleHardPoint.fixTypes( hardpoint )
     --- Assign key value pairs on a hardpoint
     if hardpoint.type == 'ManneuverThruster' or hardpoint.type == 'MainThruster' then
    --- @param kv table Table containing 'key=value' string pairs
         if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and
    local function assign( kv )
                string.match( string.lower( hardpoint.name ), 'vtol' ) ~= nil then
         for _, assignment in pairs( kv ) do
            hardpoint.sub_type = 'VtolThruster'
            local parts = mw.text.split( assignment, '=', true )
        end


            if #parts == 2 then
        if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and
                 if string.find( parts[ 2 ], '+', 1, true ) then
                 string.match( string.lower( hardpoint.name ), 'retro' ) ~= nil then
                    local valueParts = mw.text.split( parts[ 2 ], '+', true )
            hardpoint.sub_type = 'RetroThruster'
        end


                    parts[ 2 ] = valueParts[ 1 ] .. ( hardpoint[ valueParts[ 2 ] ] or '' )
        if ( hardpoint.sub_type == 'FixedThruster' or hardpoint.sub_type == 'UNDEFINED' ) and
                 end
                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


                hardpoint[ parts[ 1 ] ] = parts[ 2 ]
        if hardpoint.type == 'MainThruster' then
             end
             hardpoint.sub_type = 'Main' .. hardpoint.sub_type
         end
         end
     end
     end


     --- Set fixes on a hardpoint if tests evaluate to true
     if hardpoint.type == 'WeaponDefensive' then
    --- @param tests table
        if ( hardpoint.sub_type == 'CountermeasureLauncher' or hardpoint.sub_type == 'UNDEFINED' ) and
    local function fixHardpoint( tests )
                ( string.match( string.lower( hardpoint.class_name ), 'decoy' ) ~= nil or
        for _, test in ipairs( tests ) do
                  string.match( string.lower( hardpoint.class_name ), 'flare' ) ~= nil) then
            if VehicleHardpoint.evalRule( test[ 'if' ], hardpoint ) then
            hardpoint.sub_type = 'DecoyLauncher'
                local kv = test[ 'then' ]
        end
                if type( kv ) ~= 'table' then
 
                    kv = { kv }
        if ( hardpoint.sub_type == 'CountermeasureLauncher' or hardpoint.sub_type == 'UNDEFINED' ) and
                end
            ( 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


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


     for _, fix in ipairs( fixes ) do
     if hardpoint.type == 'FuelTank' or hardpoint.type == 'QuantumFuelTank' then
         if type( fix.type ) == 'table' then
        local prefix = ''
             for _, v in pairs( fix.type ) do
         if hardpoint.type == 'QuantumFuelTank' then
                if v == hardpoint.type then
             prefix = 'Quantum'
                    fixHardpoint( fix.modification )
        end
                    break
 
                end
        --- Fuel refinery (e.g. Starfarer)
            end
        if string.match( string.lower( hardpoint.class_name ), 'fuel_refinery' ) ~= nil then
         elseif type( fix.type ) == 'string' and fix.type == hardpoint.type then
            hardpoint.type = 'FuelRefinery'
             fixHardpoint( fix.modification )
        end
            break
 
        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
     end
     end


     -- Manual mapping defined in Module:VehicleHardpoint/Data
     if hardpoint.type == 'Turret' then
    if type( hardpoint.item ) == 'table' and hardpoint.item ~= nil then
    --- Gimbal mount
        -- If this is a noise launcher, but the class name says decoy, change Noise to Decoy
        if hardpoint.sub_type == 'GunTurret' and string.match( string.lower( hardpoint.class_name ), 'mount_gimbal' ) ~= nil then
         if string.find( hardpoint.item.name, 'Noise', 1, true ) and string.find( hardpoint.class_name, 'decoy', 1, true ) then
        hardpoint.type = 'WeaponGun'
            hardpoint.item.name = string.gsub( hardpoint.item.name, ' Noise ', ' Decoy ' )
        hardpoint.sub_type = 'GimbalMount'
    -- Pilot controllable weapon (e.g. F7CM, Mustang Delta)
         elseif hardpoint.sub_type == 'BallTurret' or hardpoint.sub_type == 'CanardTurret' then
        hardpoint.type = 'WeaponGun'
        -- Reclaimer remote salvage turret
        elseif hardpoint.sub_type == 'Utility' and string.match( string.lower( hardpoint.class_name ), 'salvage' ) ~= nil then
    hardpoint.type = 'UtilityTurret'
    hardpoint.sub_type = 'GunTurret'
    -- Fix remote turret designation
        elseif hardpoint.sub_type == 'Turret' and string.match( string.lower( hardpoint.class_name ), 'remote' ) ~= nil then
hardpoint.sub_type = 'RemoteTurret'
         end
         end
    end
   
    if hardpoint.type == 'ToolArm' then
    if hardpoint.sub_type == 'UNDEFINED' then
    if string.match( string.lower( hardpoint.class_name ), 'mining' ) ~= nil then
            hardpoint.sub_type = 'MiningArm'
    elseif string.match( string.lower( hardpoint.class_name ), 'salvage' ) ~= nil then
    hardpoint.sub_type = 'SalvageArm'
            end
    end
    end


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


     return hardpoint
     return hardpoint
end
end


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


第1,303行: 第777行:
     return instance
     return instance
end
end


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


     local instance = VehicleHardpoint:new( page )
     local instance = VehicleHardPoint:new( page )
    local out = instance:out()


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


 
return VehicleHardPoint
--- Set the hardpoints of the 300i as subobjects to the current page
function VehicleHardpoint.test( frame )
    local page = frame.args['Name'] or '300i'
    local json = mw.text.jsonDecode( mw.ext.Apiunto.get_raw( 'v2/vehicles/' .. page, {
        include = {
            'hardpoints',
            'parts'
        },
    } ) )
 
    local hardpoint = VehicleHardpoint:new( page )
    hardpoint:setHardPointObjects( json.data.hardpoints )
    hardpoint:setParts( json.data.parts )
end
 
 
--- Evaluates rules from 'data.fixes'
---
--- @param rules table A rules object from data.fixes
--- @param hardpoint table The hardpoint to evaluate
--- @param returnInvalid boolean|nil If invalid rules should be returned beneath the result
--- @return boolean (, table)
function VehicleHardpoint.evalRule( rules, hardpoint, returnInvalid )
    returnInvalid = returnInvalid or false
    local stepVal = {}
    local combination = {}
    local invalidRules = {}
 
    local function invalidRule( rule, index )
        table.insert( invalidRules, string.format( 'Invalid Rule found, skipping: <%s (Element %d)>', rule, index ) )
    end
 
    for index, rule in ipairs( rules ) do
        if type( rule ) == 'string' then
            -- mw.logObject( string.format( 'Evaluating rule %s', rule ), 'evalRule' )
 
            if string.find( rule, ':', 1, true ) ~= nil then
                local parts = mw.text.split( rule, ':', true )
 
                -- Simple check if a key equals a value
                if #parts == 2 then
                    local result = hardpoint[ parts[ 1 ] ] == parts[ 2 ]
                    -- mw.logObject( string.format( 'Rule <%s == %s>, equates to %s', hardpoint[ parts[ 1 ] ], parts[ 2 ], tostring( result ) ), 'evalRule' )
 
                    table.insert( stepVal, result )
                    -- String Match
                elseif #parts == 3 then
                    local key = parts[ 1 ]
                    local fn = parts[ 2 ]
 
                    -- Remove key and 'match' in order to combine the last parts again
                    table.remove( parts, 1 )
                    table.remove( parts, 1 )
 
                    local matcher = mw.ustring.lower( table.concat( parts, ':' ) )
 
                    local result = string[ fn ]( string.lower( hardpoint[ key ] ), matcher ) ~= nil
                    -- mw.logObject( string.format( 'Rule <%s matches %s>, equates to %s', hardpoint[ key ], matcher, tostring( result ) ), 'evalRule' )
 
                    table.insert( stepVal, result )
                else
                    invalidRule( rule, index )
                end
                -- A combination rule
            elseif rule == 'and' or rule == 'or' then
                table.insert( combination, rule )
            end
            -- A sub rule
        elseif type( rule ) == 'table' then
            local matches, invalid = VehicleHardpoint.evalRule( rule, hardpoint )
 
            table.insert( stepVal, matches )
 
            for _, v in ipairs( invalid or {} ) do
                table.insert( invalidRules, v )
            end
        else
            -- mw.logObject( 'Is invalid ' .. rule, 'evalRule' )
            invalidRule( rule, index )
        end
    end
 
    local ruleMatches = false
    for index, matched in ipairs( stepVal ) do
        if index == 1 then
            ruleMatches = matched
        else
            -- mw.logObject( 'test is ' .. combination[ index - 1 ], 'evalRule' )
            if combination[ index - 1 ] == 'and' then
                ruleMatches = ruleMatches and matched
            else
                ruleMatches = ruleMatches or matched
            end
        end
    end
 
    -- mw.logObject( 'Final rule result is ' .. tostring( ruleMatches ), 'evalRule' )
 
    if returnInvalid then
        return ruleMatches, invalidRules
    else
        return ruleMatches
    end
end
 
 
return VehicleHardpoint

2023年11月19日 (日) 11:04的最新版本

Module documentation[view][edit][history][purge]
This documentation is transcluded from 模块:VehicleHardpoint/doc. Changes can be proposed in the talk page.

This module saves and displays the hardpoints found on a vehicle. Implements {{Vehicle hardpoints}}.


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, { icon = 'WikimediaUI-Robot.svg' } )
        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|%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.scu ~= nil then
	        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
    	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( '[[File:Hardpoints icon %s.svg|20px|link=]]', 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( 'SMW data not found on [[' .. self.page .. ']].', { icon = 'WikimediaUI-Error.svg' } )
    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 weUti = ( tostring( output[ 'Weapons' ] or '' ) ) .. ( tostring( output[ 'Utility' ] or '' ) )
    local caFac = ( tostring( output[ 'Cargo' ] or '' ) ) .. ( tostring( output[ 'Facilities' ] 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 #weUti > 0 then
        weUti = weUti .. ( output.info or '' )
    else
        weUti = hatnote( 'No weaponry or utility items present.' )
    end

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

    local format = string.format([[
%s=%s
|-|
%s=%s
|-|
%s=%s
|-|
%s=%s
]],
			'Avionics & Systems',
            avSys,
            'Propulsion & Thrusters',
            prThr,
            'Weaponry & Utility',
            weUti,
            'Cargo & Facilities',
            caFac
    )

    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

        --- Fuel refinery (e.g. Starfarer)
        if string.match( string.lower( hardpoint.class_name ), 'fuel_refinery' ) ~= nil then
            hardpoint.type = 'FuelRefinery'
        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
    	--- Gimbal mount
        if hardpoint.sub_type == 'GunTurret' and string.match( string.lower( hardpoint.class_name ), 'mount_gimbal' ) ~= nil then
	        hardpoint.type = 'WeaponGun'
	        hardpoint.sub_type = 'GimbalMount'
	    -- Pilot controllable weapon (e.g. F7CM, Mustang Delta)
        elseif hardpoint.sub_type == 'BallTurret' or hardpoint.sub_type == 'CanardTurret' then
        	hardpoint.type = 'WeaponGun'
        -- Reclaimer remote salvage turret
        elseif hardpoint.sub_type == 'Utility' and string.match( string.lower( hardpoint.class_name ), 'salvage' ) ~= nil then
    		hardpoint.type = 'UtilityTurret'
    		hardpoint.sub_type = 'GunTurret'
    	-- Fix remote turret designation
        elseif hardpoint.sub_type == 'Turret' and string.match( string.lower( hardpoint.class_name ), 'remote' ) ~= nil then
			hardpoint.sub_type = 'RemoteTurret'
        end
    end
    
    if hardpoint.type == 'ToolArm' then
    	if hardpoint.sub_type == 'UNDEFINED' then
    		if string.match( string.lower( hardpoint.class_name ), 'mining' ) ~= nil then
            	hardpoint.sub_type = 'MiningArm'
    		elseif string.match( string.lower( hardpoint.class_name ), 'salvage' ) ~= nil then
    			hardpoint.sub_type = 'SalvageArm'
            end
    	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

Debug data: