Modul:CelestialObject

Aus Steel Beasts Wiki
Version vom 24. Juni 2023, 21:42 Uhr von sc>FoXFTW
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Die Dokumentation für dieses Modul kann unter Modul:CelestialObject/Doku erstellt werden

local CelestialObject = {}

local metatable = {}
local methodtable = {}

metatable.__index = methodtable

local objectData = mw.loadData( 'Module:CelestialObject/Data' )

-- Extensions
local common = require( 'Module:Common' )
local localized = require( 'Module:Localized' )
local quelle = require( 'Module:Quelle' )
local log = require( 'Module:Log' )


--- Sets the star system category
--- Checks if a category with (Sternensystem) Suffix exists
--- Uses the suffixed name when found
local function setSystemCategory( celestialObject )
    local systemName = celestialObject.parentObjectData.page

    local category = mw.title.new( systemName .. ' (Sternensystem)', 14 )

    if category.exists then
        table.insert( celestialObject.categories, '[[Category:' .. category.text .. '|' .. celestialObject.pageName .. ']]' )
    else
        table.insert( celestialObject.categories, '[[Category:' .. systemName .. '|' .. celestialObject.pageName .. ']]' )
    end
end


--- Returns the starmap code for a given name or nil if not found
--- @return string|nil
function CelestialObject.getStarmapCodeFromName( name )
    for _, typeData in pairs( objectData.starmapCodeMap ) do
        if type( typeData ) == 'table' then
            for page, object in pairs( typeData ) do
                if page == name then
                    return object.code
                end
            end
        end
    end

    return nil
end


--- Returns the 'old' name from a celestial object name
--- Example: Th.us’ūng (Pallas) II
--- Returns: Pallas II
--- If param 'new' is true, returns Th.us’ūng II
---
--- @param name string The name
--- @param new boolean Flag to return old or new name
--- @return string
function CelestialObject.getName( name, new )
    local split = mw.text.split( name, '(', true )

    if type( split ) == 'table' and split[ 2 ] ~= nil then
        -- Form: Lorem) IV
        local subSplit = mw.text.split( split[ 2 ], ')', true )

        if subSplit[ 1 ] ~= nil and subSplit[ 2 ] ~= nil then
            if new == true then
                return mw.text.trim( split[ 1 ] ) .. ' ' .. mw.text.trim( subSplit[ 2 ] )
            end

            return mw.text.trim( subSplit[ 1 ] ) .. ' ' .. mw.text.trim( subSplit[ 2 ] )
        end
    end

    return name
end


--- Returns an icon for a given object type or nil
---
--- @param type string The object type
--- @return string|nil
function CelestialObject.getIcon( objectType )
    if objectData.iconMap[ objectType ] ~= nil then
        return objectData.iconMap[ objectType ]
    end

    return nil
end

--- Reverse maps a type to text
--- @return string|nil
function CelestialObject.reverseMapType( type )
    for key, value in pairs( objectData.mappings ) do
        if value == type then
            return key
        end
    end

    return nil
end

--- Reverse maps a type to text
--- @return string|nil
function methodtable.reverseMapType( type )
    return CelestialObject.reverseMapType( type )
end


--- Output 'Umkreist' if the type orbits, else 'Standort'
--- @return string
function methodtable.getPositionLabel( self, type )
    for _, v in pairs( objectData.orbitables ) do
        if v == type then
            return 'Umkreist'
        end
    end

    return 'Standort'
end

--- Sets the main category for this object
function methodtable.setCategories( self )
    table.insert( self.categories, '[[Category:' .. self.objectType .. '|' .. self.pageName .. ']]' )

    -- Only Celestial Objects have a system_id
    -- If no systemid is set on the parent, or the object is a planet it gets categorized into the system
    if self.parentObjectData.system_id == nil or self.celestialObjectData.type == 'PLANET' then
        setSystemCategory( self )
    else
        -- Designation _MAY_ be buggy
        table.insert( self.categories, '[[Category:' .. self.parentObjectData.designation .. ']]' )
    end
end

--- Add a singular category
--- @param category string
function methodtable.addCategory( self, category )
    table.insert( self.categories, '[[Category:' .. mw.text.trim( category, '%[%]' ) .. ']]' )
end

--- Parent object semantic data
--- Either the Starsystem or parent celestial object
--- @return table Parent SMW Data
function methodtable.getParentObjectData( self )
    if self.celestialObjectData == nil then
        error( 'You need to call "getCelestialObjectData" first.' )
    end

    if self.parentObjectData ~= nil then
        return self.parentObjectData
    end

    local askData = CelestialObject.getSmwBaseAskData()

    askData.mainlabel = '-'
    askData.limit = 1
    askData.sort = 'Starmap Code'
    askData.order = 'asc'

    local id = self.celestialObjectData.parent_id
    if id == nil then
        table.insert( askData, 1, '[[Has subobject::<q>[[Starmap Code::' .. self.celestialObjectData.code .. ']]</q>]]' )
    else
        table.insert( askData, 1, '[[ID::' .. self.celestialObjectData.parent_id .. '|+depth=0]]' )
    end

    local data = mw.smw.ask( askData )

    if id == nil and (data == nil or data[ 1 ] == nil) then
        table.remove( askData, 1 )
        table.insert( askData, 1, '[[Has subobject::<q>[[Starmap Code::' .. self.celestialObjectData.code .. ']]</q>]]' )

        data = mw.smw.ask( askData )
    end

    if data == nil or data[ 1 ] == nil then
        error( 'Keine Systemdaten für Starmap Code ' .. tostring( id ) .. ' oder Elternid ' .. tostring( self.celestialObjectData.parent_id ) .. ' gefunden.', 0 )
    end

    self.parentObjectData = data[ 1 ]

    if data[ 1 ].affiliation == nil then
        self.parentObjectData.affiliation = {}
    end

    return self.parentObjectData
end

--- Queries the SMW Store for the current object
--- @param identifier string|number Starmap Code / Name / ID of the object
--- @return table SMW Data
function methodtable.getCelestialObjectData( self, identifier )
    -- Cache multiple calls
    if self.celestialObjectData ~= nil then
        return self.celestialObjectData
    end

    -- We'll try to get the code by pagename
    if identifier == nil then
        identifier = CelestialObject.getStarmapCodeFromName( localized.getMainTitle() )

        if identifier == nil and self.frameArgs[ 'Starmap Code' ] ~= nil then
            identifier = self.frameArgs[ 'Starmap Code' ]
        end
    end

    local selector = {
        '[[Starmap Code::' .. identifier .. ']]',
        '[[Bezeichnung::' .. identifier .. ']]',
        '[[Bezeichnung::' .. localized.getMainTitle() .. ']]',
    }

    if type( identifier ) == 'number' then
        table.insert( selector, '[[ID::' .. identifier .. ']]' )
    end

    local askData = CelestialObject.getSmwBaseAskData()

    askData.mainlabel = '-'
    askData.limit = 1

    table.insert( askData, 1, table.concat( selector, '||' ) )

    local data = mw.smw.ask( askData )

    if data == nil or data[ 1 ] == nil then
        error( 'Semantische Daten zu "' .. identifier .. '" noch nicht geladen. Bei neuen Seiten kann dieser Vorgang mehrere Minuten dauern.' )
    end

    self.celestialObjectData = data[ 1 ]

    if data[ 1 ].affiliation == nil then
        self.celestialObjectData.affiliation = {}
    end

    return self.celestialObjectData
end


--- Creates the base infobox for a celestial object
--- TODO Clean
--- @return string Infobox
function methodtable.makeInfoBox( self )
    if self.celestialObjectData == nil or self.celestialObjectData.page == nil or self.hideBox == true then
        -- Faulty SMW data
        return ''
    end

    local system = require( 'Module:Sternensystem' )

    if self.celestialObjectData.habitable ~= nil then
        self.celestialObjectData.habitable = common.booleanToText( self.celestialObjectData.habitable )
    end

    local box = require( 'Module:InfoboxNeue' ):new( {
        removeEmpty = true,
        emptyString = '-',
        placeholderImage = 'Platzhalter Starmap.webp',
    } )

    local boxTitle = localized.getMainTitle()
    if boxTitle ~= self.pageName then
        boxTitle = self.pageName
    end
    boxTitle = common.removeTypeSuffix( boxTitle, self.typeSuffixes )

    -- Image
    box:renderImage( common.getImage( {
        common.removeTypeSuffix( localized.getMainTitle(), self.typeSuffixes ),
        boxTitle,
        'Galactapedia_' .. boxTitle .. '.png',
        self.frameArgs.image,
    } ) )

    local parentPage = ''
    local starmapRef = ''
    local subtitle = ''

    if self.parentObjectData.page ~= nil then
        parentPage = mw.text.split( self.parentObjectData.page, '#', true )[ 1 ]

        local systemCode = mw.text.split( ( self.parentObjectData.code or '' ), '.', true )[ 1 ]
        quelle = quelle:new()

        starmapRef = quelle:format( {
            url = 'https://robertsspaceindustries.com/starmap?location=' .. self.celestialObjectData.code .. '&system=' .. systemCode,
            title = 'Ark Starmap: ' .. self.pageName,
            wrap = true,
            ref_name = 'starmap',
        } )

        subtitle = '[[' .. parentPage .. '|' .. common.removeTypeSuffix( parentPage, 'Sternensystem' ) .. ' System]]'
    end

    box:renderHeader( {
    	title = boxTitle,
    	subtitle = subtitle,
    } )

    local section = {}

    for _, data in ipairs( objectData.infoboxAstronomicalDataSmwMappings ) do
        if self.celestialObjectData[ data.key ] ~= nil then
            table.insert( section, box:renderItem( data.value, self.celestialObjectData[ data.key ] ) )
        end
    end

    if self.parentObjectData.type ~= nil and self.parentObjectData.planet_count == nil then
        local orbits = self.parentObjectData.designation

        if self.parentObjectData.type == 'STAR' then
            orbits = orbits .. ' (Stern)'
        end

        table.insert( section, box:renderItem( self:getPositionLabel( self.celestialObjectData.type ), '[[' .. orbits .. ']]' ) )
    end
    
    box:renderSection( {
    	title = 'Astronomische Daten' .. starmapRef,
		col = 2,
		content = section
    })
    
    section = {}

    for _, data in ipairs( objectData.infoboxPoliticsDataSmwMappings ) do
        if self.celestialObjectData[ data.key ] ~= nil then
            if data.key == 'affiliation' then
                table.insert( section, box:renderItem( data.value, system.getAffiliationLines( self.celestialObjectData.affiliation ) ) )
            else
                table.insert( section, box:renderItem( data.value, self.celestialObjectData[ data.key ] ) )
            end
        end
    end

	box:renderSection( {
		title = 'Politik und Wirtschaft',
		col = 2,
		content = section,
	} )

    return tostring( box ) .. tostring( common.generateInterWikiLinks( localized.getMainTitle() ) )
end

--- Entrypoint for {{#seo:}}
function methodtable.setSeoData( self )
    if self.currentFrame == nil then
        error( 'No frame set.', 0 )
    end

    if self.celestialObjectData == nil or self.celestialObjectData.page == nil then
        -- Faulty SMW data, don't call #seo
        return
    end

    if type( self.celestialObjectData.affiliation ) == 'string' then
        self.celestialObjectData.affiliation = { self.celestialObjectData.affiliation }
    end

    local image = common.getImage( {
        common.removeTypeSuffix( localized.getMainTitle(), self.typeSuffixes ),
        self.frameArgs.image
    } )

    -- Call to {{#seo:}}
    mw.ext.seo.set{
        author = self.currentFrame:preprocess( '{{SERVER}}/Benutzer:{{urlencode:{{REVISIONUSER}}|WIKI}}' ),
        section = 'Sternensystem',
        url = tostring( mw.uri.fullUrl( self.celestialObjectData.page ) ),
        title = table.concat( {
            self.pageName,
            self.objectType,
            self.currentFrame:preprocess( '{{SITENAME}}' )
        }, ' - '),
        title_mode = 'replace',
        keywords = table.concat( {
            self.objectType,
            self.pageName,
            table.concat( self.celestialObjectData.affiliation, ', '),
        }, ', '),
        description = self.frameArgs.description or nil,
        image = image or '',
        image_alt = 'Bild des ' .. self.pageName .. ' ' .. self.objectType,
        locale = 'de_DE',
        type = 'article',
    }
end

--- Creates a settable subobject table from args
--- @param args table Frame args
--- @return table Subobject
function methodtable.getSubObjectData( self, args )
    if args[ 'Starmap Code' ] == nil then
        return
    end

    local requiredKeys = {
        'Sternensystemid',
        'Starmap Code',
        'Bezeichnung',
        'Typ',
        'ID',
        'Elternid',
    }

    local setObj = {
        [ 'Sternensystemid' ]   = args[ 'Sternensystemid' ]   or nil,
        [ 'Starmap Code' ]      = args[ 'Starmap Code' ]      or nil,
        [ 'Bezeichnung' ]       = args[ 'Bezeichnung' ]       or nil,
        [ 'Typ' ]               = args[ 'Typ' ]               or nil,
        [ 'Kontrolle' ]         = args[ 'Kontrolle' ]         or nil,
        [ 'ID' ]                = args[ 'ID' ]                or nil,
        [ 'Elternid' ]          = args[ 'Elternid' ]          or nil,
        [ 'Wirtschaft' ]        = args[ 'Wirtschaft' ]        or nil,
        [ 'Bevölkerungsgröße' ] = args[ 'Bevölkerungsgröße' ] or nil,
        [ 'Gefahrenalge' ]      = args[ 'Gefahrenalge' ]      or nil,
        [ 'Habitabel' ]         = args[ 'Habitabel' ]         or nil,
    }

    for _, v in pairs( requiredKeys ) do
        if setObj[ v ] == nil then
            error( 'Eingegebene Daten nicht valide.' )
            return
        end
        
        if v == 'Typ' then
        	setObj[ v ] = setObj[ v ] .. '@en'
    	end
    end

    if setObj[ 'ID' ] ~= nil then
        setObj[ 'ID' ] = mw.ustring.gsub( setObj[ 'ID' ], '%.', '' )
    end

    if setObj[ 'Elternid' ] ~= nil then
        setObj[ 'Elternid' ] = mw.ustring.gsub( setObj[ 'Elternid' ], '%.', '' )
    end

    return setObj
end

--- WIP: Create a manual subobject
--- See: Vorlage:CelestialObject
--- @param args table - The frame arguments
--- @param typeOverride string|nil - Optional type override used in modules
function methodtable.addManual( self, args, typeOverride )
    if args[ 'Starmap Code' ] == nil then
        return
    end

    local setObj = self:getSubObjectData( args )

    if setObj == nil then
        return
    end

    if typeOverride ~= nil then
        setObj[ 'Typ' ] = typeOverride
    end

    setObj[ 'Typ' ] = mw.text.trim( mw.ustring.upper( setObj[ 'Typ' ] ) ) .. '@en'

    mw.smw.subobject( setObj )
end

--- Load data and output the infobox
--- @param frame table The current frame
--- @return string Infobox and categories
function methodtable.run( self, frame )
    self.currentFrame = frame
    self.frameArgs = require( 'Module:Arguments' ).getArgs( frame )
    if type( self.frameArgs[ 'Bild' ] ) == 'string' then
        self.frameArgs[ 'image' ] = self.frameArgs[ 'Bild' ]
    end

    if self.frameArgs[ 1 ] ~= nil then
        self.objectType = self.frameArgs[ 1 ]
    end

    -- Add manual submodule if code is set
	self:addManual(
        self.frameArgs,
        self.frameArgs[ 'Typ' ] or objectData.mappings[ self.objectType ] or self.objectType
    )

    local dataSuccess, coE = pcall(
        self.getCelestialObjectData, self, self.frameArgs.code or self.frameArgs.name or self.frameArgs[ 'Starmap Code' ]
    )

    local parentSuccess, poE = pcall(self.getParentObjectData, self)

    if not dataSuccess or not parentSuccess then
    	--errors = '<span style="display:none;">' .. ( coE or '' ) .. '<br>' .. ( poE or '' ) .. '</span>'
        local errors= ''
        return log.info( 'Daten werden im semantic Wiki aktualisiert, dies kann mehrere Minuten dauern.' .. errors )
    end

    self.pageName = common.removeTypeSuffix( localized.getMainTitle(), self.typeSuffixes )

    if self.celestialObjectData.name ~= nil and self.pageName ~= self.celestialObjectData.name then
        self.pageName = self.pageName .. ': ' .. self.celestialObjectData.name
    end

    if mw.title.getCurrentTitle().namespace == 0 then
        self:setCategories()
    end

    common.setDisplayTitle( self.currentFrame, self.pageName )

    self:setSeoData()

    return tostring( self:makeInfoBox() ) .. tostring( table.concat( self.categories ) )
end


--- Wrapper to allow method calls
--- @see CelestialObject.getSmwBaseAskData
function methodtable.getSmwBaseAskData( self )
    return CelestialObject.getSmwBaseAskData()
end


--- Returns a copy of data that is queried for each object from the smw store
--- @return table
function CelestialObject.getSmwBaseAskData()
    local askData = {}

    for k, v in pairs( objectData.smwAskData ) do
        askData[ k ] = v
    end

    return askData
end


--- WIP: Create a manual subobject
--- See: Vorlage:CelestialObject
--- @param frame table - The current frame
--- @param typeOverride string|nil - Optional type override used in modules
function CelestialObject.addManualFromFrame( frame, typeOverride )
    local instance = CelestialObject:new( '' )

    instance:addManual( require( 'Module:Arguments' ).getArgs( frame ), typeOverride )
end

--- New Instance
--- @return table CelestialObject
function CelestialObject.new( self, objectType )
    local instance = {
        objectType = '',
        parentType = '',

        currentFrame = {},
        frameArgs = {},

        pageName = {},
        categories = {},

        celestialObjectData = nil,
        parentObjectData = nil,

        typeSuffixes = {
            'Sternensystem',
            'Stern',
            'Planet',
            'Mond',
            'Raumstation',
            'Stadt',
            'Lagrange Punkt',
        },
    }

    setmetatable( instance, metatable )

    if objectType == nil then
        error( 'Required argument "objectType" missing.' )
    end

    instance.objectType = objectType
    table.insert( instance.typeSuffixes, objectType )

    return instance
end

--- For direct invocation
function CelestialObject.newFromFrame( frame )
    local instance = CelestialObject:new( frame.args[ 'type' ] )

    return instance:run( frame )
end

return CelestialObject