Modul:CharArmor

Aus Steel Beasts Wiki
Version vom 3. September 2023, 08:02 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:CharArmor/Doku erstellt werden

local CharArmor = {}

local metatable = {}
local methodtable = {}

metatable.__index = methodtable

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

-- Extensions
local common = require( 'Module:Common' )
local api = require( 'Module:Common/Api' )
local localized = require( 'Module:Localized' )
local TNT = require( 'Module:Translate' ):new()
local log = require( 'Module:Log' )
local item = require( 'Module:Item' )
local commodity = require( 'Module:Commodity' )


--- Sorts all attachments into pre-defined groups
--- Outputs a table containing the min and max size for the attachment port
--- As well as the overall count of attachments for a group
--- @param attachments table
--- @return table
local function makeArmorAttachmentTable( attachments )
    local out = {}
    local sortedOut = {}

    for _, attachment in pairs( attachments ) do
        local attachmentName = CharArmor.translateArmorAttachment( attachment[ 'Halterungsname' ] )

        local minNum = tonumber( attachment[ 'Minimalgröße' ] )
        if minNum == nil then
            minNum = 0
        end

        local maxNum = tonumber( attachment[ 'Maximalgröße' ] )
        if maxNum == nil then
            maxNum = 0
        end

        if attachmentName ~= nil then
            if out[ attachmentName ] == nil then
                out[ attachmentName ] = {
                    name = attachmentName,
                    min = minNum,
                    max = maxNum,
                    count = 1
                }
            else
                out[ attachmentName ].count = out[ attachmentName ].count + 1

                if minNum < out[ attachmentName ].min then
                    out[ attachmentName ].min = minNum
                end

                if maxNum > out[ attachmentName ].max then
                    out[ attachmentName ].max = maxNum
                end
            end
        end
    end

    for _, class in ipairs( objectData.attachmentOrder ) do
        if out[ class ] ~= nil then
            table.insert(sortedOut, out[ class ])
        end
    end

    --[[ TODO remove comment to show all attachments
    for name, attachment in pairs( out ) do
        if order[ name ] == nil then
            table.insert( sortedOut, attachment )
        end
    end
    ]]--

    return sortedOut
end

--- Add manual smw data
function methodtable.addManual( self )
    if self.frameArgs == nil then
        return
    end

    local setObj = {
        [ 'Name' ]                                = self.frameArgs.Name                                    or nil,
        [ 'Beschreibung' ]                        = self.frameArgs.Beschreibung                            or nil,
        [ 'Größe' ]                               = self.frameArgs[ 'Größe' ]                              or nil,
        [ 'Hersteller' ]                          = self.frameArgs[ 'Hersteller' ]                         or nil,
        [ 'Rüstungstyp' ]                         = self.frameArgs[ 'Rüstungstyp' ]                        or nil,
        [ 'Rüstungsklasse' ]                      = self.frameArgs[ 'Rüstungsklasse' ]                     or nil,
        [ 'Ist Basisversion' ]                    = self.frameArgs[ 'Ist Basisversion' ]                   or nil,
        [ 'Basisversion UUID' ]                   = self.frameArgs[ 'Basisversion UUID' ]                  or nil,
        [ 'SP' ]                                  = self.frameArgs[ 'SP' ]                                 or nil,
        [ 'Temperaturresistenz Minmal' ]          = self.frameArgs[ 'Temperaturresistenz Minmal' ]         or nil,
        [ 'Temperaturresistenz Maximal' ]         = self.frameArgs[ 'Temperaturresistenz Maximal' ]        or nil,
        [ 'Resistenzmultiplikator Physisch' ]     = self.frameArgs[ 'Resistenzmultiplikator Physisch' ]    or nil,
        [ 'Resistenzmultiplikator Energie' ]      = self.frameArgs[ 'Resistenzmultiplikator Energie' ]     or nil,
        [ 'Resistenzmultiplikator Distortion' ]   = self.frameArgs[ 'Resistenzmultiplikator Distortion' ]  or nil,
        [ 'Resistenzmultiplikator Thermisch' ]    = self.frameArgs[ 'Resistenzmultiplikator Thermisch' ]   or nil,
        [ 'Resistenzmultiplikator Biochemisch' ]  = self.frameArgs[ 'Resistenzmultiplikator Biochemisch' ] or nil,
        [ 'Resistenzmultiplikator Betäubung' ]    = self.frameArgs[ 'Resistenzmultiplikator Betäubung' ]   or nil,
        [ 'Spielversion' ]                        = self.frameArgs[ 'Spielversion' ]                       or nil,
    }

    mw.smw.set( setObj )
end

--- Request Api Data
--- Using current subpage name
--- @return table
function methodtable.getApiDataForCurrentPage( self )
    local name = self.frameArgs[ 'uuid' ] or self.frameArgs[ 'name' ] or mw.title.getCurrentTitle().text

    local json = mw.text.jsonDecode( mw.ext.Apiunto.get_raw( 'v2/armor/' .. name, {
        include = {
            'shops.items'
        },
    } ) )

    api.checkResponseStructure( json, true, false )

    self.apiData = json[ 'data' ]

    return self.apiData
end

--- Request armor attachments from SMW
--- @param name string
--- @return table
function methodtable.getArmorAttachments( self, name )
    local query = {
        '[[-Has subobject::' .. name .. ']][[Typ::Halterung]]',
        '?Halterungsname#-',
        '?Minimalgröße#-',
        '?Maximalgröße#-',
        'mainlabel=-'
    }

    return mw.smw.ask( query )
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 localized.getMainTitle()

    local data = mw.smw.ask( {
        '[[' .. queryName .. ']][[Hersteller::+]]',
        '?#-=page',
        '?Name#-',
        '?Größe#-',
        '?Hersteller#-',
        '?Beschreibung', '+lang=' .. common.getLocaleForPage(),
        '?Schadensreduktion#-',
        '?SP',
        '?Rüstungstyp=type', '+lang=' .. common.getLocaleForPage(),
        '?Rüstungstyp=type_de', '+lang=de',
        '?Rüstungsklasse=class', '+lang=' .. common.getLocaleForPage(),
        '?Rüstungsklasse=class_de', '+lang=de',
        '?Temperaturresistenz#-n',
        '?Resistenzmultiplikator Physisch#-n',
        '?Resistenzmultiplikator Energie#-n',
        '?Resistenzmultiplikator Distortion#-n',
        '?Resistenzmultiplikator Thermisch#-n',
        '?Resistenzmultiplikator Biochemisch#-n',
        '?Resistenzmultiplikator Betäubung#-n',
        '?Länge',
        '?Breite',
        '?Höhe',
        '?Volumen',
        '?Spielversion#-=version',
        'mainlabel=-'
    } )

    if data == nil or data[ 1 ] == nil then
        return TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'msg_smw_loading' )
    end

    self.smwData = data[ 1 ]

    self.smwData.attachments = self:getArmorAttachments( queryName )

    self.smwData.price = commodity.getPrice( queryName )
    if self.smwData.price ~= nil then
        self.smwData.price = self.smwData.price[ 1 ].price
    end

    return self.smwData
end

--- Base Properties that are shared across all Vehicles
--- @return table SMW Result
function methodtable.setSemanticProperties( self )
    -- Api Error, don't set anything
    if self.apiData == nil then
        return
    end

    local item = item:new( '', self.apiData )
    local setObj = item:makeSmwObject()

    setObj[ 'Set' ]               = CharArmor.getArmorSet( self.apiData.name )
    setObj[ 'Schadensreduktion' ] = self.apiData.clothing.damage_reduction or nil
    setObj[ 'Rüstungstyp' ]       = api.mapTranslation( CharArmor.translateArmorType( self.apiData.clothing.armor_type or nil ) )
    setObj[ 'Rüstungsklasse' ]    = api.mapTranslation( CharArmor.translateClass( self.apiData.type or nil ) )
    setObj[ 'Temperaturresistenz' ] = {
        CharArmor.formatTemperature( self.apiData.clothing.temp_resistance_min ),
        CharArmor.formatTemperature( self.apiData.clothing.temp_resistance_max ),
    }


    if type( self.apiData.clothing.resistances ) == 'table' then
        for _, resistance in pairs( self.apiData.clothing.resistances ) do
            if resistance.type == 'physical' then
                setObj['Resistenzmultiplikator Physisch'] = common.formatNum( resistance.multiplier )
            end
            if resistance.type == 'energy' then
                setObj['Resistenzmultiplikator Energie'] = common.formatNum( resistance.multiplier )
            end
            if resistance.type == 'distortion' then
                setObj['Resistenzmultiplikator Distortion'] = common.formatNum( resistance.multiplier )
            end
            if resistance.type == 'thermal' then
                setObj['Resistenzmultiplikator Thermisch'] = common.formatNum( resistance.multiplier )
            end
            if resistance.type == 'biochemical' then
                setObj['Resistenzmultiplikator Biochemisch'] = common.formatNum( resistance.multiplier )
            end
            if resistance.type == 'stun' then
                setObj['Resistenzmultiplikator Betäubung'] = common.formatNum( resistance.multiplier )
            end
        end
    end

    local result = mw.smw.set( setObj )

    if self.apiData.ports ~= nil and type( self.apiData.ports ) == 'table' then
        for _, attachment in pairs( self.apiData.ports ) do
            if attachment.name ~= nil then
                mw.smw.subobject( {
                    [ 'Typ' ] = {
                        'Halterung@de',
                        'Attachment@en',
                    },
                    [ 'Halterungsname' ] = attachment.name,
                    [ 'Minimalgröße' ] = attachment.min_size or 0,
                    [ 'Maximalgröße' ] = attachment.max_size or 0,
                } )
            end
        end
    end

    commodity:new():addShopData( self.apiData )

    return result
end


--- Sets the main categories for this object
function methodtable.setCategories( self, smwData )
    if smwData.Hersteller == nil then
        table.insert( self.categories, '[[Category:Seiten mit Skriptfehlern]]' )
        return
    end

    if self.lang == 'de' then
        table.insert( self.categories, '[[Category:' .. smwData.Hersteller .. '|' .. smwData.Name .. ' ]]' )

        if smwData.class ~= nil then
            table.insert( self.categories, string.format('[[Kategorie:%s|%s]]', smwData.class_de, smwData.Name ) )
        end

        if smwData.type ~= nil and smwData.type ~= 'Unbekannter Typ' then
            table.insert( self.categories, string.format('[[Kategorie:%s|%s]]', smwData.type_de, smwData.Name ) )
        end
    else
        table.insert( self.categories, '[[Category:' .. smwData.Name ..'|' .. smwData.Name .. ' ]]' )
    end
end


--- Entrypoint for {{#seo:}}
function methodtable.setSeoData( self )
    local data = self:getSmwData()

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

    require( 'Module:SEO' ).set(
            TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'seo_section' ),
            data.page,
            table.concat({
                data.Name,
                data.Hersteller,
                self.currentFrame:preprocess( '{{SITENAME}}' )
            }, ' - '),
            'replace',
            {
                data.Name,
                data.type or '',
                data.class or '',
                data.Hersteller,
                require( 'Module:Hersteller' ).getCodeFromName( data.Hersteller ),
                TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'seo_section' ),
            },
            nil,
            self.frameArgs[ 'image' ],
            data.page
    )
end

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

    if type( data ) == 'string' then
        return log.info( data )
    end

    if nil == data.Hersteller then
        data.Hersteller = 'Unbekannter Hersteller'
    end

    -- Set Title
    common.setDisplayTitle( self.currentFrame, data.Name )

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

    local nameNormalized, _ = mw.ustring.gsub( data.Name, "[^%w-]", ' ' )
    nameNormalized, _ = mw.ustring.gsub( nameNormalized, "%s+", ' ' )

    local nameCleaned, _ = mw.ustring.gsub( nameNormalized, "%(", '' )
    nameCleaned, _ = mw.ustring.gsub( nameCleaned, "%)", '' )

    box:renderImage( common.getImage( {
        self.frameArgs[ 'image' ],
        mw.text.trim( nameNormalized ) .. '.jpg',
        mw.text.trim( nameCleaned ) .. '.jpg',
    } ) )

    box:renderHeader( {
    	title = data.Name,
    	subtitle = '[[' .. data.Hersteller .. ']]' .. api.getVersionInfoRef( data.version, self.lang ),
    } )

    local tempMinRes, tempMaxRes = CharArmor.temperatureResistanceToText( data[ 'Temperaturresistenz' ] )

    local armorClass = data.class
    if armorClass ~= nil then
        armorClass = string.format('[[:Kategorie:%s|%s]]', data.class_de, armorClass)
    end

    local armorType = data.type
    if armorType ~= nil then
        armorType = string.format('[[:Kategorie:%s|%s]]', data.type_de, armorType)
    end

    local set = CharArmor.getArmorSet( data.Name )
    if set ~= nil then
        table.insert( self.categories, string.format('[[Kategorie:%s|%s]]', set, data.Name ) )
        set = mw.ustring.format( '[[:Kategorie:%s|%s]]', set, set )
    end

    local damageReduction = data[ 'Schadensreduktion' ]
    if damageReduction ~= nil then
        damageReduction = (100 * damageReduction) .. '%'
    else
        damageReduction = '-'
    end

    box:renderSection( {
    	col = 2,
    	content = {
    		box:renderItem( TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'lbl_type' ), armorType or '-' ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'lbl_price' ), data.price or TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'txt_cant_buy' ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'lbl_carrying_capacity' ), data[ 'SP' ] or '-' ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'lbl_weight' ), data[ 'Volumen' ] or '-' ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_damage_reduction' ), damageReduction ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:Common/i18n.json', 'lbl_class' ), armorClass or '-' ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_set' ), set or '-' ),
    	}
    } )

    box:renderSection( {
    	title = TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_temperature_resistance' ),
    	col = 2,
    	content = {
    		box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_temperature_resistance_min' ), tempMinRes or '-' ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_temperature_resistance_max' ), tempMaxRes or '-' ),
    	}
    } )

    box:renderSection( {
    	title = TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_resistances' ),
    	col = 3,
    	content = {
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_physical' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Physisch' ] ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_energy' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Physisch' ] ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_distortion' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Distortion' ] ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_thermal' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Thermisch' ] ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_biochemical' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Biochemisch' ] ) ),
			box:renderItem( TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_stun' ), CharArmor.resistanceToString( data[ 'Resistenzmultiplikator Betäubung' ] ) ),
    	}
    } )

    if data.attachments ~= nil and type( data.attachments ) == 'table' and #data.attachments > 0 then
        local attachTable = makeArmorAttachmentTable( data.attachments )

        if #attachTable > 0 then
            local attachs = {}

            for _, attachment in ipairs( attachTable ) do
                local size = ''
                if attachment.min ~= 0 and attachment.max ~= 0 then
                    if attachment.min ~= attachment.max then
                        size = string.format( '(S%s - S%s)', attachment.min, attachment.max )
                    end
                elseif attachment.min ~= nil and attachment.min ~= 1 then
                    size = string.format( '(S%s)', attachment.min )
                end

                if attachment.name == 'Abwerfbar' then
                    attachment.name = 'Abwerfbar ' ..  mw.smw.info( 'Granaten, Knicklichter, etc.' )
                end

                table.insert( attachs, box:renderItem( attachment.name, string.format( '%dx %s', attachment.count, size ) ) )
            end
            
            box:renderSection( {
            	col = 3,
            	title = TNT.formatInLanguage( self.lang, 'Module:CharArmor/i18n.json', 'lbl_attachments' ),
            	content = attachs
            } )
        end
    end

    return tostring( box )
end

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

    if type( self.frameArgs[ 'Bild' ] ) == 'string' then
        self.frameArgs[ 'image' ] = self.frameArgs[ 'Bild' ]
    end
end

--- Get categories
function methodtable.getCategories( self )
    return tostring( table.concat( self.categories ) )
end

--- Save Api Data to SMW store
function methodtable.saveApiData( self )
    if self.currentFrame == nil then
        error( 'No frame set. Call "setFrame" first.', 0 )
    end

    local data = self:getApiDataForCurrentPage()

    self:setSemanticProperties()

    return data
end


--[[

Char Armor Methods

]]--

--- @param name string
--- @return string
function CharArmor.getArmorSet( name )
    for _, setName in ipairs( objectData.armorSets ) do
        if mw.ustring.find( name, setName, 1, true ) then
            return setName
        end
    end

    return nil
end

--- @param class string
--- @return table
function CharArmor.translateClass( class )
    if objectData.itemClassTranslations[ class ] == nil then
        return {
            de_DE = class,
            en_EN = class
        }
    end

    return objectData.itemClassTranslations[ class ]
end

--- @param type string
--- @return table
function CharArmor.translateArmorType( type )
    return {
        de_DE = objectData.armorTypeTranslations[ type ] or type,
        en_EN = type
    }
end

--- Calculates the resistance
--- @return number
function CharArmor.calculateResistance( resistance )
    if resistance == nil then
        return nil
    end

    return 100 * ( 1 - resistance )
end

--- Resistance to string
--- @return string
function CharArmor.resistanceToString( resistance )
    local res = CharArmor.calculateResistance( resistance )
    local span = mw.html.create( 'span' )

    if res == nil then
        return '-'
    end

    if res > 100 then
        -- Armor has negative resistance
        span:addClass( 'resistance-negative' )
    else
        span:addClass( 'resistance-positive' )
    end

    span:wikitext( res .. '%' )

    return tostring( span:allDone() )
end

--- Returns temperature resistances as strings containing °C
--- @param resTable table containing an arbitrary amount of temperatures
--- @return string, string
function CharArmor.temperatureResistanceToText( resTable )
    local tempMinRes = math.huge
    local tempMaxRes = -math.huge

    if resTable ~= nil and type( resTable ) == 'table' then
        for _, res in pairs( resTable ) do
            res = common.toNumber( res, 0 )

            if res < tempMinRes then
                tempMinRes = res
            end

            if res > tempMaxRes then
                tempMaxRes = res
            end
        end
    end

    if tempMinRes == math.huge then
        tempMinRes = '-'
    else
        tempMinRes = tempMinRes .. '°C'
    end

    if tempMaxRes == -math.huge then
        tempMaxRes = '-'
    else
        tempMaxRes = tempMaxRes .. '°C'
    end

    return tempMinRes, tempMaxRes
end

--- Translates an internal attachment name to text
--- @param name string The internal name
--- @return string
function CharArmor.translateArmorAttachment( name )
    if name == nil then
        return name
    end

    local matchAttachName = mw.ustring.match(name, "(%w+)_attach_%d")
    local matchStockedName = mw.ustring.match(name, "(%w+)_stocked_%d")

    if matchAttachName ~= nil then
        name = matchAttachName
    elseif matchStockedName ~= nil then
        name = matchStockedName
    end

    if objectData.armorAttachmentTranslations[ name ] ~= nil then
        if common.getLocaleForPage() == 'de' then
            return objectData.armorAttachmentTranslations[ name ].de_DE
        end

        return objectData.armorAttachmentTranslations[ name ].en_EN
    end

    return name
end

--- Format the carrying capacity
--- @param capacity string|number|nil
--- @return string
function CharArmor.formatCarryingCapacity( capacity )
    if capacity == nil then
        return nil
    end

    if type( capacity ) == 'string' then
        capacity = mw.ustring.gsub( capacity, 'K SP', '' )
        capacity = mw.ustring.gsub( capacity, 'K µSCU', '' )

        capacity = common.toNumber( capacity )
        if capacity ~= nil then
            capacity = capacity * 1000
        end
    end

    capacity = common.toNumber( capacity )
    if capacity == nil then
        return nil
    end

    return common.formatNum( capacity ) .. ' µSCU'
end

--- Format the emperature resistance
--- @param temperature string|number|nil
--- @return string
function CharArmor.formatTemperature( temperature )
    if temperature == nil then
        return nil
    end

    temperature = common.toNumber(temperature, false )
    if temperature == nil then
        return nil
    end

    return common.formatNum( temperature ) .. ' °C'
end

--- Template entry
function CharArmor.main( frame )
    local instance = CharArmor:new()
    instance:setFrame( frame )

    if not localized.isLanguagePage() then
        if instance.frameArgs[ 'Manuell' ] ~= nil then
            instance:addManual()
        else
            instance:saveApiData()
        end
    end

    instance:setCategories( instance:getSmwData() )

    instance:setSeoData()

    return tostring( instance:getInfoBox() ) .. instance:getCategories()
end

--- New Instance
function CharArmor.new( self, name )
    local instance = {
        categories = {},
        frameArgs = {
            [ 'name' ] = name
        },
        lang = require( 'Module:Template translation' )._getLanguageSubpage( mw.title.getCurrentTitle() )
    }

    setmetatable( instance, metatable )

    return instance
end

return CharArmor