Modul:Sternensystem

Aus Steel Beasts Wiki
Version vom 8. September 2023, 21:41 Uhr von Dr.Thodt (Diskussion | Beiträge) (1 Version importiert)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

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

local Sternensystem = {}

local metatable = {}
local methodtable = {}

metatable.__index = methodtable

-- Extensions
local api = mw.ext.Apiunto
local infobox = require( 'Module:InfoboxNeue' )
local systemData = require( 'Module:Sternensystem/Data' )
local common = require( 'Module:Common' )
local localized = require( 'Module:Localized' )
local quelle = require( 'Modul:Quelle' )


--- Filters celestial_objects by type key
--- Returns a table containing only the specified type
---
--- @param apiData table - Json API Data
--- @param objectType string - The type to filter
---
--- @return table
function Sternensystem.getCelestialObjectsForType( apiData, objectType )
    if apiData == nil or apiData.celestial_objects == nil then
        return {}
    end

    local objects = {}

    for _, object in pairs( apiData.celestial_objects ) do
        if object.type == objectType then
            table.insert( objects, object )
        end
    end

    return objects
end

--- Extracts the affiliations form api data
---
--- @param affiliationData table
--- @param key string
---
--- @return table
function Sternensystem.extractAffiliations( affiliationData, key )
    if type( affiliationData ) ~= 'table' or #affiliationData == 0 then
        return {}
    end

    local affiliations = {}

    for _, affiliation in pairs( affiliationData ) do
        if key ~= nil and affiliation[ key ] ~= nil then
            table.insert( affiliations, affiliation[ key ] )
        else
            table.insert( affiliations, {
                name = affiliation.name,
                code = affiliation.code,
            })
        end
    end

    return affiliations
end

--- Append 'Mrd. Jahre'
--- @param age number|string
---
--- @return string formatted age
function Sternensystem.formatAge( age )
    if age ~= nil and common.formatNum( age ) ~= nil then
        if type( age ) == 'string' then
            age = tonumber( age, 10 )
        end

        age = age * 1000000000

        return common.formatNum( age ) .. ' Jahre'
    end
end

--- Formats a table for sizes and appends 'km'
--- @param sizes table|nil
---
--- @return table|string
function Sternensystem.formatSize( sizes )
    if sizes == nil then
        return sizes
    end

    if type( sizes ) == 'table' then
        for k, v in pairs( sizes ) do
            if k ~= nil and v ~= nil then
                local formatted = common.formatNum( v )

                if formatted ~= nil then
                    sizes[ k ] = formatted .. ' km'
                end
            end
        end

        return sizes
    end

    local formatted = common.formatNum( sizes )

    if formatted ~= nil then
        return formatted .. ' km'
    end
end

--- Write celestial objects as subobject
---
--- @param celestialObjects table
function Sternensystem.setSubObjects( celestialObjects )
    for _, celestialObject in pairs( celestialObjects ) do
        if celestialObject.habitable ~= nil then
            if celestialObject.habitable == 1 or celestialObject.habitable == true then
                celestialObject.habitable = "Ja"
            else
                celestialObject.habitable = "Nein"
            end
        end

        local data = {
            [ 'ID' ]                = celestialObject.id,
            [ 'Starmap Code' ]      = celestialObject.code,
            [ 'Typ' ]               = celestialObject.type .. '@en',
            [ 'Name' ]              = celestialObject.name,
            [ 'Bezeichnung' ]       = celestialObject.designation,
            [ 'Alter' ]             = Sternensystem.formatAge( celestialObject.age ),
            [ 'Radius' ]            = Sternensystem.formatSize( celestialObject.size ),
            [ 'Habitabel' ]         = celestialObject.habitable,
            [ 'Beschreibung' ]      = common.mapTranslation( celestialObject.description ),
            [ 'Gefahrenlage' ]      = common.formatNum( celestialObject.sensor.danger ),
            [ 'Wirtschaft' ]        = common.formatNum( celestialObject.sensor.economy ),
            [ 'Bevölkerungsgröße' ] = common.formatNum( celestialObject.sensor.population ),
            [ 'Kontrolle' ]         = Sternensystem.extractAffiliations( celestialObject.affiliation, 'name' ),
            [ 'Umlaufzeit' ]        = common.formatNum( celestialObject.orbit_period ),
            [ 'Abstand' ]           = common.formatNum( celestialObject.distance ),
            [ 'Breitengrad' ]       = common.formatNum( celestialObject.latitude ),
            [ 'Längengrad' ]        = common.formatNum( celestialObject.longitude ),
            [ 'Elternid' ]          = celestialObject.parent_id,
            [ 'Sternensystemid' ]   = celestialObject.system_id,
        }

        if celestialObject.subtype ~= nil and type( celestialObject.subtype ) == 'table' and celestialObject.subtype.id ~= nil then
            data[ 'Subtyp' ] = celestialObject.subtype.type .. '@en'
            data[ 'Subtypname' ] = systemData.getTranslatedSubtype( celestialObject.subtype.name )
        end

        if celestialObject.type == 'JUMPPOINT' and celestialObject.jumppoints ~= nil and type( celestialObject.jumppoints ) == 'table' then
            local jumppoint = celestialObject.jumppoints

            data[ 'Größe' ] = jumppoint.size
            data[ 'Richtung' ] = jumppoint.direction

            local designation = ''

            if jumppoint.entry.system_id == celestialObject.system_id then
                designation = jumppoint.entry.designation
            elseif jumppoint.exit.system_id == celestialObject.system_id then
                designation = jumppoint.exit.designation
            end

            local destinations = mw.text.split( designation, ' - ', true )

            if #destinations == 2 then
                data[ 'Sprungpunkt Start' ] = Sternensystem.cleanJumppointDestination( destinations[ 1 ] )
                data[ 'Sprungpunkt Ziel' ]  = Sternensystem.cleanJumppointDestination( destinations[ 2 ] )
            end
        end

		mw.smw.subobject( data )
        --common.checkSmwResult( mw.smw.subobject( data ) )
    end
end

--- Returns the 'old' Starsystem name from a format of
--- Starsystem (New Name)
---
--- @param destination string
---
--- @return string
function Sternensystem.cleanJumppointDestination( destination )
    local clean = mw.text.split( destination, '(', true )

    if clean[ 1 ] ~= nil and type( clean[ 1 ] ) == 'string' then
        return mw.text.trim( clean[ 1 ] )
    end

    return destination
end

--- Returns small logo and link if available
---
--- @param affiliations string|table
---
--- @return string
function Sternensystem.getAffiliationLines( affiliations )
    local mapping = {
        BANU = '[[Banu]]',
        DEV = 'Developing',
        DEVELOPING = 'Developing',
        UEE = '[[Menschen]]',
        UNC = 'Unclaimed',
        UNCLAIMED = 'Unclaimed',
        VANDUUL = '[[Vanduul]]',
        [ 'XI\'AN' ] = '[[Xi\'An]]',
    }

    if type( affiliations ) == 'string' then
        affiliations = { affiliations }
    end

    local lines = {}

    for _, affiliation in pairs( affiliations ) do
        local line = '[[Datei:Systemlogo ' .. mw.ustring.upper( affiliation:gsub( '%\'', '' ) ) .. '.svg|frameless|link=|class=noviewer factionlogo|x15px]] '

        if mapping[ mw.ustring.upper( affiliation ) ] == nil then
            line = line .. mw.ustring.upper( affiliation )
        else
            line = line .. mapping[ mw.ustring.upper( affiliation ) ]
        end

        table.insert( lines, line )
    end

    if #lines == 0 then
        return '-'
    end

    return tostring( table.concat( lines, '<br>' ) )
end

--- Textual Affiliation
---
--- @param affiliations table|string
---
--- @return string
function Sternensystem.getAffiliationsAsText( affiliations )
    if affiliations == nil then
        return ''
    end

    if type( affiliations ) == 'string' then
        affiliations = { affiliations }
    end

    local line = ''

    for _, affiliation in pairs( affiliations ) do
        if #line > 0 then
            line = line .. ' und '
        end

        if affiliation == 'Developing' then
            line = line .. 'von niemandem, dieses System entwickelt sich derzeit'
        elseif affiliation == 'Unclaimend' then
            line = line .. 'von niemandem, keine Fraktion kontrolliert dieses System'
        elseif affiliation == 'UEE' then
            line = line .. 'des United Earth Empire'
        else
            line = line .. 'der ' .. affiliation
        end
    end

    return line .. '.'
end

---
--- Public Methods
---

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

    local data = self:getSmwData()

    if nil == data.Name then
        -- Faulty SMW data, don't call #seo
        return
    end

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

    local cleanTitle = common.removeTypeSuffix( data.page, 'Sternensystem' )

    local desc = table.concat( {
        'Das',
        cleanTitle,
        'Sternensystem ist ein System under der Kontrolle',
        Sternensystem.getAffiliationsAsText( data.affiliation )
    }, ' ' )

    -- Image
    local image = common.getImage( {
        self.pageName .. '_System.jpg',
        self.pageName .. '_System.png',
        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( data.page ) ),
        title = table.concat( {
            cleanTitle,
            'Sternensystem',
            self.currentFrame:preprocess( '{{SITENAME}}' )
        }, ' - '),
        title_mode = 'replace',
        keywords = table.concat( {
            'Sternensystem',
            cleanTitle,
            table.concat( data.affiliation, ', '),
        }, ', '),
        description = self.frameArgs.description or desc,
        image = image or '',
        image_alt = 'Bild des ' .. data.Name .. ' Systems',
        locale = 'de_DE',
        type = 'article',
    }
end

--- Request Api Data
--- Using current subpage name
---
--- @return table
function methodtable.getApiDataForCurrentPage( self )
    local name = self.pageName

    local config = systemData.new( self.pageName )

    if config.get( 'api_name' ) ~= '' then
        name = config.get( 'api_name' )
    end

    local json = mw.text.jsonDecode( api.get_starsystem( name, {
        include = {
            'celestialObjects',
        },
    } ) )

    common.checkApiResponse( json )

    self.apiData = json.data or nil

    return self.apiData
end

--- Returns the highest star age
---
--- @return number
function methodtable.getSystemAge( self )
    local stars = Sternensystem.getCelestialObjectsForType( self.apiData, 'STAR' )
    local age = 0

    for _, star in pairs( stars ) do
        if star.age ~= nil and tonumber( star.age, 10 ) >= age then
            age = tonumber( star.age, 10 )
        end
    end

    return age
end

--- Queries the SMW Store
---
--- @return table
function methodtable.getSmwData( self )
    -- Cache multiple calls
    if self.smwData ~= nil then
        return self.smwData
    end

    -- name from args or current page
    local queryName = self.frameArgs.name or mw.title.getCurrentTitle().rootText

    local query = {
        '[[ ' .. queryName .. ' ]]',
        '?#-=page',
        '?Name#-=name',
        '?Starmap Code=code',
        '?Status=status',
        '?Typ=type',
        '+lang=en',
        '?Sternensystem Größe=system_size',
        '?Sternensystem Alter=system_age',

        '?Kontrolle=affiliation',
        '?Bevölkerungsgröße=population_level',
        '?Wirtschaft=economic_level',
        '?Gefahrenlage=threat_level',

        '?Anzahl Sterne=star_count',
        '?Anzahl Planeten=planet_count',
        '?Anzahl Monde=moon_count',
        '?Anzahl Asteroidengürtel=asteroid_belt_count',
        '?Anzahl Sprungpunkte=jumppoint_count',
        'mainlabel=-'
    }

    local data = mw.smw.ask( query )

    if data == nil or data[ 1 ] == nil then
        error( 'Seite "' .. queryName .. '" besitzt keine semantischen Daten.', 0 )
    end

    self.smwData = data[ 1 ]

    return self.smwData
end


--- Queries the SMW Store
function methodtable.setSemanticProperties( self )
    -- Api Error, don't set anything
    if self.apiData == nil then
        return
    end

    local setData = {
        [ 'Name' ] = self.apiData.name,
        [ 'ID' ] = self.apiData.id,
        [ 'Starmap Code' ] = self.apiData.code,
        [ 'Status' ] = self.apiData.status,
        [ 'Typ' ] = self.apiData.type .. '@en',
        [ 'Sternensystem Größe' ] = common.formatNum( self.apiData.aggregated.size ),
        [ 'Sternensystem Alter' ] = common.formatNum( self:getSystemAge() ) .. ' Mrd.Jahre',

        [ 'Gefahrenlage' ] = common.formatNum( self.apiData.aggregated.danger ),
        [ 'Kontrolle' ] = Sternensystem.extractAffiliations( self.apiData.affiliation, 'name' ),
        [ 'Wirtschaft' ] = common.formatNum( self.apiData.aggregated.economy ),
        [ 'Bevölkerungsgröße' ] = common.formatNum( self.apiData.aggregated.population ),

        [ 'Frostlinie' ] = common.formatNum( self.apiData.aggregated.frost_line ),
        -- [ 'Habitable Zone (Innen)' ] = common.formatNum( apiData.aggregated.habitable_zone_inner ),
        -- [ 'Habitable Zone (Außen)' ] = common.formatNum( apiData.aggregated.habitable_zone_outer ),

        [ 'Anzahl Sterne' ] = self.apiData.aggregated.stars,
        [ 'Anzahl Planeten' ] = self.apiData.aggregated.planets,
        [ 'Anzahl Monde' ] = self.apiData.aggregated.moons,
        [ 'Anzahl Asteroidengürtel' ] = #Sternensystem.getCelestialObjectsForType( self.apiData, 'ASTEROID_BELT' ),
        [ 'Anzahl Sprungpunkte' ] = #Sternensystem.getCelestialObjectsForType( self.apiData, 'JUMPPOINT' ),

        [ 'Beschreibung' ] = common.mapTranslation( self.apiData.description ),
    }

    common.checkSmwResult( mw.smw.set( setData ) )

    if self.apiData.celestial_objects ~= nil and type( self.apiData.celestial_objects ) == 'table' then
        Sternensystem.setSubObjects( self.apiData.celestial_objects )
    end
end

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

    if data.page == nil then
        return 'SMW Daten noch nicht geladen, bitte Seite neu laden.'
    end

    local astronomicalData = {
        star_count = 'Sterne',
        system_size = 'Größe (AE)',
        system_age = 'Alter',
    }

    local politicsAndEconomy = {
        {
            key = 'affiliation',
            text = 'Zugehörigkeit',
        },
        {
            key = 'population_level',
            text = 'Bevölkerung',
        },
        {
            key = 'economic_level',
            text = 'Wirtschaft',
        },
        {
            key = 'threat_level',
            text = 'Gefahrenlage',
        },
    }

    local cleanTitle = self.pageName

    if cleanTitle == nil then
        cleanTitle = common.removeTypeSuffix( localized.getMainTitle(), 'Sternensystem' )
    end

    local box = infobox:new( {
        removeEmpty = true,
        emptyString = '-',
    } )

    -- Image
    box:renderImage( common.getImage( {
        self.pageName .. '_System.jpg',
        self.pageName .. '_System.png',
        self.frameArgs.image
    } ) )

    box:renderHeader( {
    	title = cleanTitle .. ' System',
    } )

    local section = {}
    
    for smwKey, value in pairs( astronomicalData ) do
        if data[ smwKey ] ~= nil then
            table.insert( section, box:renderItem( value, data[ smwKey ] ) )
        end
    end

	table.insert( section, box:renderItem( 'Planeten', data.planet_count or '-' ) )
	table.insert( section, box:renderItem( 'Monde', data.moon_count or '-' ) )
	table.insert( section, box:renderItem( 'Asteroidengürtel', data.asteroid_belt_count or '-' ) )
	table.insert( section, box:renderItem( 'Sprungpunkte', data.jumppoint_count or '-' ) )
	
	box:renderSection( {
		title = 'Astronomische Daten' .. (quelle:new()):format( {
	        url = 'https://robertsspaceindustries.com/starmap?location=' .. ( data.code or '' ),
	        title = 'Ark Starmap: ' .. self.pageName .. ' System',
	        wrap = true,
	        ref_name = 'system_starmap',
	    } ),
		col = 2,
		content = section,
	} )

    section = {}

    for _, obj in ipairs( politicsAndEconomy ) do
        if data[ obj.key ] ~= nil then
            if obj.key == 'affiliation' then
                table.insert( section, box:renderItem( obj.text, Sternensystem.getAffiliationLines( data.affiliation ) ) )
            else
                table.insert( section, box:renderItem( obj.text, data[ obj.key ] ) )
            end
        end
    end
    
    box:renderSection( {
    	title = 'Politik und Wirtschaft',
    	col = 2,
    	content = section
    } )

    return tostring( box ) .. tostring( common.generateInterWikiLinks( cleanTitle ) )
end

--- Ouput infobox and categories and set seo
--- Entrypoint for Template calls
function Sternensystem.infoBox( frame )
    local instance = Sternensystem:new()

    instance.currentFrame = frame
    instance.frameArgs = frame:getParent().args

    instance.pageName = instance.frameArgs.name or common.removeTypeSuffix( localized.getMainTitle(), 'Sternensystem' )

    if mw.title.getCurrentTitle().namespace == 0 then
        table.insert( instance.categories, '[[Category:Sternensystem|' .. instance.pageName .. ']]' )
    end

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

	if not mw.title.getCurrentTitle().isSubpage then
		instance:getApiDataForCurrentPage()
    	instance:setSemanticProperties()
	end
    instance:setSeoData()

    return tostring( instance:getInfoBox() ) ..
           tostring( table.concat( instance.categories ) ) ..
           tostring( common.generateInterWikiLinks( instance.pageName ) )
end

--- New instance
function Sternensystem.new( self )
    local instance = {
        currentFrame = nil,
        frameArgs = nil,
        pageName = nil,
        categories = {},
        apiData = nil,
        smwData = nil,
    }

    setmetatable( instance, metatable )

    return instance
end

return Sternensystem