Star Citizen Wiki Modul:VehicleHardpoint
Wir laden dich herzlich auf den Star Citizen Wiki Discord Server ein! Du kannst uns auch auf unserem neuen YouTube-Chanel finden!

Modul:VehicleHardpoint

From Star Citizen Wiki

Modulabhängigkeiten

Modulinfo

Dieses Modul ist ein Metamodul zur Speicherung und Abfrage von Fahrzeug Hardpoints.

Es gibt Komponenten in tabellarischer Form aus.


Diese Vorlage dient der Erstellung einer Übersicht von Fahrzeugkomponenten. Die verfügbaren Komponenten eines Fahrzeugs werden automatisch in tabellarischer Form dargestellt.

Wird kein Paramter angegeben, so wird der aktuelel Seitenname als Name des Fahrzeugs verwendet.

Kopiervorlage

{{FahrzeugKomponenten}}

Beispiele

{{FahrzeugKomponenten|300i}}
Antrieb
Hardpoints Icon Quantentreibstofftank.svg Quantentreibstofftank
1x1
QuantentreibstofftankUnbekannter Hersteller
Hardpoints Icon Sprungmodul.svg Sprungmodul
1x1
Hardpoints Icon Treibstoffeinlass.svg Treibstoffeinlass
2x1
TreibstoffeinlassUnbekannter Hersteller
1x1
TreibstoffeinlassOrigin Jumpworks GmbH
Hardpoints Icon Treibstofftank.svg Treibstofftank
2x1
TreibstofftankUnbekannter Hersteller
Triebwerk
Hardpoints Icon Manövriertriebwerk.svg Manövriertriebwerk
12x1
Feste ManövrierdüseOrigin Jumpworks GmbH
2x1
RetroschubdüseUnbekannter Hersteller
Hardpoints Icon Primärtriebwerk.svg Primärtriebwerk
1x4
Feste HauptschubdüseOrigin Jumpworks GmbH

Einträge stammen aus der Ship-Matrix

Bewaffnung
Hardpoints Icon Hilfsmittel.svg Hilfsmittel
5xS3
Täuschkörper (Geräusch)Origin Jumpworks GmbH
48xS3
Täuschkörper (Köder)Origin Jumpworks GmbH
Hardpoints Icon Flugkörper.svg Raketenwerfer
1xS2
1xS2
1xS2
1xS2
Hardpoints Icon Waffen.svg Waffen
1xS3
1xS3
1xS3

Einträge stammen aus der Ship-Matrix

Erzeugt eine tabellarische Übersicht der Komponenten eines Fahrzeugs

Template parameters

This template prefers inline formatting of parameters.

ParameterDescriptionTypeStatus
Fahrzeugname1

no description

Pagesuggested

local VehicleHardPoint = {}

local metatable = {}
local methodtable = {}

metatable.__index = methodtable

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


--- Calls TNT with the given key
local function translate( key )
	local success, translation = pcall( TNT.format, 'I18n/Module:VehicleHardpoint.tab', key or '' )

    if not success or translation == nil then
    	return key
    end

	return translation
end


-- 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.data > 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' then
            key = row.type .. row.sub_type
        else
            key = row.type .. row.sub_type .. row.item.data.uuid
        end
    else
        key = hardpointData.class.de_DE .. hardpointData.type.de_DE
    end

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

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

    if hardpointData.class.de_DE == 'Bewaffnung' 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.data == 1 then
        local item = row.children.data[ 1 ]

        local children = { data = {} }

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

        row.name = item.name
        row.type = item.type
        row.sub_type = item.sub_type
        row.item = item.item
        if #children.data > 1 then
            row.children = children
        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( matcher, hardpointType ) ~= 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[ 'Aus Spieldaten' ] = true
    object[ 'Minimalgröße' ] = row.min_size
    object[ 'Maximalgröße' ] = row.max_size
    object[ 'Komponentenklasse' ] = common.mapTranslation( hardpointData.class )

    if data.hardPointNames[ row.type ] ~= nil then
        object[ 'Typ' ] = common.mapTranslation( data.hardPointNames[ row.type ] )
    else
        object[ 'Typ' ] = common.mapTranslation( hardpointData.type )
    end

    if data.hardPointNames[ row.sub_type ] ~= nil then
        object[ 'Subtyp' ] = common.mapTranslation( data.hardPointNames[ row.sub_type ] )
    else
        object[ 'Subtyp' ] = common.mapTranslation( hardpointData.type )
    end

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

    if type( row.item ) == 'table' then
        local itemObj = row.item.data
        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' ] = 'Selbstzerstörung ' .. match
            else
                object[ 'Name' ] = row.item.data.name
            end
        end

        -- TODO: Use query chain when all items have accompanying pages instead of setting it on the subobject
        if itemObj.manufacturer ~= 'Unknown Manufacturer' then
            object[ 'Hersteller' ] = itemObj.manufacturer
        end

        if itemObj.type == 'Weapon Defensive' and type( itemObj.counterMeasure ) == 'table' then
            object[ 'Magazingröße' ] = itemObj.counterMeasure.data.max_ammo_count
        end

        if object[ 'Minimalgröße' ] == nil then
            object[ 'Minimalgröße' ] = itemObj.size
            object[ 'Maximalgröße' ] = itemObj.size
        end

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

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

    if root ~= nil and root ~= row.name then
        object[ 'Root Hardpoint' ] = root
    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' or #hardpoints == 0 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 ][ 'Anzahl' ] = 1
            end
        else
            out[ key ][ 'Anzahl' ] = out[ key ][ 'Anzahl' ] + 1

            if type( out[ key ][ 'Magazingröße' ] ) == 'number' then
                out[ key ][ 'Magazingröße' ] = out[ key ][ 'Magazingröße' ] + object[ 'Magazingröße' ]
            end
        end
    end

    local depth = 0

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

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

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

            if hardpointData ~= nil then
                hardpoint = VehicleHardPoint.fixTypes( hardpoint )

                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.data, obj, root )
                end
            end
        end

        depth = depth - 1

        if depth < 0 then
            depth = 0
            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 .. ']][[Typ::+]]',
        '?Aus Spieldaten#-=from_gamedata',
        '?Anzahl#-=count',
        '?Minimalgröße#-=min_size',
        '?Maximalgröße#-=max_size',
        '?Komponentenklasse=class', '+lang=' .. common.getLocaleForPage(),
        '?Komponentenklasse=class_de',
        '+lang=de',
        '?Typ=type', '+lang=' .. common.getLocaleForPage(),
        '?Typ=type_de', '+lang=de',
        '?Subtyp=sub_type', '+lang=' .. common.getLocaleForPage(),
        '?Subtyp=sub_type_de', '+lang=de',
        '?Name#-=name',
        '?UUID#-=uuid',
        '?Hardpoint#-=hardpoint',
        '?Magazingröße#-=magazine_size',
        '?Hersteller#-=manufacturer',
        '?Parent Hardpoint#-=parent_hardpoint',
        '?Root Hardpoint#-=root_hardpoint',
        '?Parent UUID#-=parent_uuid',
        '?Name.Grad#-=item_grade',
        '?Name.Klasse#-=item_class',
        '?Name.Größe#-=item_size',
        'sort=Komponentenklasse,Typ,Maximalgröße,Anzahl',
        'order=asc,asc,asc,asc',
    } )

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

    --mw.logObject( data )

    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 = {}

    local function makeEntry( item, depth )
        if classOutput.info == nil then
            local text
            if item.from_gamedata == true then
                text = translate( 'msg_from_gamedata' )
            else
                text = translate( 'msg_from_shipmatrix' )
            end

            classOutput.info = string.format( '<p class="hatnote">%s</p>', text )
        end

        depth = depth or 0
        local row = mw.html.create( 'tr' )
                :addClass( 'hardpoint--entry' )
                :addClass( string.format( 'depth-%d', depth) )

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

        local size = '-'
        local prefix = ''

        if item.from_gamedata == true or item.class_de == 'Bewaffnung' 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 )
--[[            if item.max_size ~= item.min_size then
                size = string.format( '<span class="hardpoint--entry__size-min">S%s</span>-<span class="hardpoint--entry__size-max">S%s</span>', item.min_size, item.max_size )
            end]]
        end

        local nodeSizeCount = mw.html.create( 'div' )
            :tag( 'span' )
                :addClass( 'hardpoint--entry__count' )
            :wikitext( string.format( '%dx', item.count ) )
                :done()
            :tag( 'span' )
                :addClass( 'hardpoint--entry__size' )
                    :wikitext( size )
                :done()
                :allDone()

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

        local manufacturer = item.manufacturer or 'Unbekannter Hersteller'
        if item.manufacturer ~= nil then
            manufacturer = string.format( '[[%s]]', item.manufacturer )
        end

        local nodeItemManufacturer = mw.html.create( 'div' )
            :tag( 'span' )
            :addClass( 'hardpoint--entry__item' )
            :wikitext( name )
            :done()
            :tag( 'span' )
                :addClass( 'hardpoint--entry__manufacturer' )
                :wikitext( manufacturer )
                :done()
                :allDone()

        row:tag('td')
            :addClass( 'hardpoint--size-count')
            :node( nodeSizeCount )
            :done()
        :tag('td')
            :addClass( 'hardpoint--item-manufacturer')
            :node( nodeItemManufacturer )
        :done()
        :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

    local function makeClassTable( types )
        local out = ''
        for classType, items in self.spairs( types ) do
            local icon = classType
            if icon == 'Raketenwerfer' then
                icon = 'Flugkörper'
            end

            local success
            success, icon = pcall( TNT.formatInLanguage, 'de', 'I18n/Module:VehicleHardpoint.tab', icon )

            if success then
                icon = string.format( '[[Datei:Hardpoints Icon %s.svg|20px|link=|class=hardpoint--class-icon__desaturated]]', icon )
                if classType == 'Selbstzerstörung' or classType == 'Scanner' then
                    icon = ''
                end
            else
                icon = ''
            end

            local row = mw.html.create( 'tr' )
                :addClass( 'hardpoint--type')
                :tag( 'td' )
                    :addClass( 'hardpoint--class-icon' )
                    :wikitext( string.format(
                        '%s %s',
                        icon,
                        translate( classType )
                    ) )
                :done()

            local inner = mw.html.create('table')
                    :addClass( 'hardpoint--entries')

            local str = ''
            for _, item in self.spairs( items ) do
                if not item.isChild then
                    str = str .. tostring( makeEntry( item ) )
                end
            end

            row:tag( 'td' )
                :node(inner:node(str):allDone())
            :done()
            :allDone()

            out = out .. tostring(row)
        end

        return out
    end


    for class, types in self.spairs( groupedData ) do
        local tbl = mw.html.create( 'table' ):addClass( 'hardpoint--class' )
        tbl:tag( 'tr' )
                :addClass( 'hardpoint--class__header' )
                :tag( 'th' )
                    :attr( 'colspan', 2 )
                    :wikitext( translate( class ) )
                :done()
            :done()

        tbl:node( makeClassTable( types ) )
        classOutput[ class ] = tbl:allDone()
    end

    mw.logObject(classOutput)

    return classOutput
end


function methodtable.test(self)
    local json = mw.text.jsonDecode( mw.ext.Apiunto.get_raw( 'ships/' .. self.page , {
        locale = 'de_DE',
        include = { 'hardpoints', 'shops.items' },
    } ) )

    json = json.data.hardpoints.data

    self:setHardPointObjects(json)
end


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

    if smwData == nil then
        return '<p class="hatnote">' .. translate( 'msg_no_data' ) .. '</p>'
    end

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

    local output = self:makeOutput( smwData )

    local avSys = ( tostring( output[ translate ('Avionik' ) ] or '' ) ) .. ( tostring( output[ translate( 'Systeme' ) ] or '' ) )
    local anTri = ( tostring( output[ translate ('Antrieb' ) ] or '' ) ) .. ( tostring( output[ translate( 'Triebwerk' ) ] or '' ) )
    local bewaf = ( tostring( output[ translate ('Bewaffnung' ) ] or '' ) )

    if #avSys > 0 then
        avSys = avSys .. ( output.info or '' )
    else
        avSys = translate( 'msg_no_systems' )
    end

    if #anTri > 0 then
        anTri = anTri .. ( output.info or '' )
    else
        anTri = translate( 'msg_no_thruster' )
    end

    if #bewaf > 0 then
        bewaf = bewaf .. ( output.info or '' )
    else
        bewaf = translate( 'msg_no_weapons' )
    end

    local format = string.format([[
%s=%s
|-|
%s=%s
|-|
%s=%s
]],
			translate( 'tab_avionics_systems' ),
            avSys,
            translate( 'tab_engine_propulsion' ),
            anTri,
            translate( 'tab_weapons' ),
            bewaf
    )

    return mw.getCurrentFrame():extensionTag{
        name = 'tabber',
        content = format,
    }
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.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.data ~= nil then
            hardpoint.item.data.name = '<= PLACEHOLDER =>'
        end
    end

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

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

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

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

    if type( hardpoint.item ) == 'table' and hardpoint.item.data ~= nil then
        if hardpoint.item.data.manufacturer == 'Talon' then
            hardpoint.item.data.manufacturer = 'Talon Weapon Systems'
        end

        if string.match( hardpoint.item.data.manufacturer, '%[PH%]' ) ~= nil then
            hardpoint.item.data.manufacturer = 'Unknown Manufacturer'
        end
    end

    return hardpoint
end


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

    setmetatable( instance, metatable )

    return instance
end


--- Parser call for setting hardpoints
function VehicleHardPoint.fromArgs( frame )
    local page = frame.args[ 1 ] or frame:getParent().args[ 1 ]

    local instance = VehicleHardPoint:new( page )

    instance:test()
end


--- Parser call for generating the table
function VehicleHardPoint.outputTable( frame )
    local page = frame.args[ 1 ] or frame:getParent().args[ 1 ] or mw.title.getCurrentTitle().rootText

    local instance = VehicleHardPoint:new( page )

    return instance:out()
end


return VehicleHardPoint
Cookies help us deliver our services. By using our services, you agree to our use of cookies.