Event trigger.png We are conducting a survey to learn how we can improve the Paradox Wikis experience for our users. Please take the time to fill the Survey Event trigger.png

Talk:List of default ship variants

From Hearts of Iron 4 Wiki
Jump to navigation Jump to search


These tables have been generated by the following script. Feel free to update the script here if you make changes.

import re
from decimal import Decimal
from pyradox import parse_merge, parse_file, get_localisation
from functools import partial


get_localisation = partial(
    get_localisation, game='HoI4', process_substitutions=False)

# mapping stat names to l10n keys
EQUIPMENT_STATS = {stat: f'STAT_NAVY_{stat.upper()}' for stat in [
    "surface_detection", "sub_detection", "surface_visibility",
    "sub_visibility", "lg_attack", "lg_armor_piercing", "hg_attack",
    "hg_armor_piercing", "torpedo_attack", "sub_attack", "anti_air_attack",
    "naval_speed": 'STAT_NAVY_MAXIMUM_SPEED',
    "naval_range": 'STAT_NAVY_RANGE',
    "max_strength": 'STAT_COMMON_MAX_STRENGTH',
    "armor_value": 'STAT_COMMON_ARMOR',
    "reliability": 'STAT_COMMON_RELIABILITY',
    "fuel_consumption": 'STAT_COMMON_FUEL_CONSUMPTION',
    "carrier_size": 'STAT_CARRIER_SIZE',
    "build_cost_ic": 'STAT_COMMON_BUILD_COST_IC',

equipmentDB = parse_merge('common/units/equipment', game='HoI4', merge_levels=1)
equipments = equipmentDB['equipments']

def right(value):
    return f'<span style="float:right">{value}</span>'

# insert number into l10n string placeholder
def replace(number, tmpl, key='VALUE'):
    m = re.search(r'\$'+key.upper()+r'\b([^$]*)\$', tmpl)
    if not m:
        raise Exception(tmpl)
    result = ''
    judgement = 0
    options = m.group(1)
    if '+' in options:
        judgement = 1
    if '-' in options:
        judgement = -1
    if number == 0:
        judgement = 0
    elif number < 0:
        judgement = -judgement

    use_percent = '%' in options
    if judgement > 0:
        result += '{{green|'
    elif judgement < 0:
        result += '{{red|'
    if use_percent:
        number *= 100
        number = number.quantize(Decimal('0.1'))
    if number == number.to_integral():
        number = number.quantize(Decimal(1))
        number = number.normalize()
    result += f'{number:+}' if judgement else f'{number}'
    result += '%' if use_percent else ''
    if judgement:
        result += '}}'
    return re.sub(r'\$'+key.upper()+r'\b([^$]*)\$', result, tmpl)

moduleDB = parse_merge(

P = Decimal('0.001')  # precision of game calculations is limited to this

def get_attr(name, a):  # recursively find the first value
    if name is None:
        return None
    e = equipments[name]
    return e.find(a) or get_attr(e['parent'], a) or get_attr(e['archetype'], a)

def get_stat(name, attr, archetype=False):
    if name is None:
        return Decimal().quantize(P)
    e = equipments[name]
    if attr in e:
        return Decimal(e[attr]).quantize(P)
    if archetype or 'archetype' not in e:
        return Decimal().quantize(P)
    return get_stat(e['archetype'], attr, archetype=True)

def stat_l10n(label):
    label = get_localisation(EQUIPMENT_STATS[stat]).rstrip(': ')
    label = re.sub(r'£production_cost', '{{icon|production cost}}', label)
    return label

# collect all equipment names unlocked by naval techs into sections
sections = {
    'screen_ship': 'Screen ships',
    'capital_ship': 'Capital ships',
    'carrier': 'Carriers',
    'submarine': 'Submarines',
variants = { section: [] for section in sections }
techDB = parse_file('common/technologies/naval.txt', game='HoI4')
for tech in techDB['technologies'].values():
    for equipment_name in tech.find_all('enable_equipments'):
        variant = { 'name': equipment_name }
        # guess comparable hull name from archetype and equipment name
        quirks = {
            'torpedo_cruiser': 'ship_hull_torpedo_cruiser',
            'battle_cruiser_2': 'ship_hull_heavy_3',
            'carrier_1': 'ship_hull_carrier_conversion_bb',
            'carrier_2': 'ship_hull_carrier_1',
            'carrier_3': 'ship_hull_carrier_2',
            'carrier_4': 'ship_hull_carrier_3',
        variant['hull_name'] = equipments[equipment_name]['archetype'] + equipment_name[-2:]
        if equipment_name in quirks:
            variant['hull_name'] = quirks[equipment_name]
        variants[get_attr(equipment_name, 'type')].append(variant)

print('{{Version|%s}}' % GAME_VERSION)
# create one table per section
for section, section_name in sections.items():
    # pre-calculate stats for all rows to filter out empty columns
    for v in variants[section]:
        v['manpower'] = get_attr(v['name'], 'manpower')
        module_add = { stat: Decimal() for stat in EQUIPMENT_STATS }
        module_mult = { stat: Decimal(1) for stat in EQUIPMENT_STATS }
        module_avg = { stat: Decimal() for stat in EQUIPMENT_STATS }
        module_avg_count = { stat: 0 for stat in EQUIPMENT_STATS }
        for module_name in equipments[v['name']]['default_modules'].values():
            if module_name == "empty":
            module = moduleDB['equipment_modules'][module_name]
            v['manpower'] += module['manpower'] or 0
            for stat, val in module.find('add_stats', {}).items():
                module_add[stat] += Decimal(val).quantize(P)
            for stat, val in module.find('multiply_stats', {}).items():
                module_mult[stat] += Decimal(val).quantize(P)
            for stat, val in module.find('add_average_stats', {}).items():
                module_avg[stat] += Decimal(val).quantize(P)
                module_avg_count[stat] += 1

        for stat in EQUIPMENT_STATS:
            base = get_stat(v['name'], stat)
            base += module_add[stat]
            if module_avg_count[stat]:
                base += (module_avg[stat] / module_avg_count[stat]).quantize(P)
            v[stat] = (base * module_mult[stat]).quantize(P)

    include_mp = any(v['manpower'] for v in variants[section])
    stat_columns = {
        stat: 1
        for stat in EQUIPMENT_STATS
        if any(v[stat] for v in variants[section])

    print(f'== {section_name} ==')
    print('{{SVersion|%s}}' % GAME_VERSION)
    print('{| class="wikitable"\n! Equipment type\n! Equivalent hull in<br>'
          '{{icon|mtg|1}}\n! Extra stats<br>compared to hull\n! Modules')
    if include_mp:
        print('! <span style="writing-mode: vertical-lr">{{icon|manpower|1}}</span>')
    for stat in stat_columns:
        print('! <span style="writing-mode: vertical-lr">',
              stat_l10n(EQUIPMENT_STATS[stat]), '</span>')
    for v in variants[section]:
        equipment_l10n = get_localisation(v['name'])
        print(f'|- id="{equipment_l10n}"')
        print(f'| {equipment_l10n}')
        print('|', get_localisation(v['hull_name']))
        # compare base stats to hull
        if include_mp:
            mp_diff = get_attr(v['name'], 'manpower') - get_attr(v['hull_name'], 'manpower')
            if mp_diff:
                val = '{{%s|%d}}' % ('green' if mp_diff < 0 else 'red', mp_diff)
                print('* {{icon|manpower|1}}', right(val))
        for stat in EQUIPMENT_STATS:
            stat_diff = get_stat(v['name'], stat) - get_stat(v['hull_name'], stat)
            if stat_diff:
                diff_l10n = EQUIPMENT_STATS[stat] + '_DIFF'
                    right(replace(stat_diff, get_localisation(diff_l10n))),
        for name in equipments[v['name']]['default_modules'].values():
            if name == "empty":
            print('*', get_localisation(name + '_short') or get_localisation(name))
        if include_mp:
            print(f'| {right(v["manpower"])}')
        for stat in stat_columns:
            value_l10n = EQUIPMENT_STATS[stat] + '_VALUE'
            print(f'| {right(replace(v[stat], get_localisation(value_l10n)))}')

Bitmode (talk) 00:26, 29 January 2020 (UTC)