Modded Unit Support - dcs-retribution/dcs-retribution GitHub Wiki

Hey, can you add support for ?

Well, we could, but...

It takes a lot of time to add support for a mod in Retribution. We prefer using the free time we can allocate to Retribution to develop features that will benefit the whole community of Retribution users.

I'm willing to support the mod in Retribution myself!

Here is how to do it:

Pre-requisites:

  • A development environment for DCS Retribution, see Developer's Guide
  • Basic Python programming knowledge
  • Basic Lua programming knowledge
  • A DCS World open beta installation

Step 1: Pydcs extensions injection:

Adding support for a modded unit is much more complicated than adding its name in a faction file.

In order for a modded units to work with Retribution, we need to generate a data export for it to work with pydcs

Pydcs is the library we use to generate the .miz files, it is awesome, but it is not magical and can't support modded content natively if we don't tell it how do to do it. Its database also need to be updated every time DCS is updated.

Updating pydcs database is done by adding a script to DCS World to be executed on the game startup to export some aircraft data.

See the script here : https://github.com/dcs-retribution/pydcs/blob/retribution/tools/pydcs_export.lua;

Copy this script on your computer and do what is explained in the comments :

-- execute(dofile) this script at the end of
-- of 'DCS World\MissionEditor\modules\me_mission.lua'
-- base.dofile("C:\\Users\\peint\\Documents\\dcs\\tools\\pydcs_export.lua")

Modify the first line "export_path" so it match an existing folder on your computer :

local export_path = "D:\\Work\\DCS\\dcs\\dcs\\"

Running this script generate most of pydcs data in the target folder when launching DCS. If DCS stops loading at 10%, it's normal. If it doesn't start at all after 5 minutes and stay blocked at 10%, there might be an error in the script, so check <DCS World Save directory>/dcs.log for clues.

If you run it with mods installed, it generates a version of pydcs with support for the currently installed mods.

As pydcs does not have the ambition of supporting every existing mods, we do not push modded content to the official pydcs version, which only supports vanilla DCS.

So to add support for modded aircrafts in Retribution, we then have to manually extract the new aircraft from the generated modded pydcs version, and create an extension (which is added to Retribution code)

Aircraft data will be found in the file aircraft.py, vehicles/infantry in vehicles.py, ships in ships.py, etc. generated by pydcs. Search your unit class in this file, and add it to a custom file. Your mod might have added new weapons, they'll be found in weapons.py, and you have to fetch them as well.

Example of final file for the popular A-4E-C mod : https://github.com/dcs-retribution/dcs-retribution/blob/dev/pydcs_extensions/a4ec/a4ec.py Example of final file for the CurrentHill USA Military assets pack: https://github.com/dcs-retribution/dcs-retribution/blob/dev/pydcs_extensions/usamilitaryassetspack/usamilitaryassetspack.py

As you can see, extensions for mods are stored in this folder : https://github.com/dcs-retribution/dcs-retribution/blob/dev/pydcs_extensions

These file contains a lot of metadata we need to be able to generate missions, such as possible liveries, possible payloads for each pylons, specific weapons ids ... and so on.

Step 2: Pydcs extensions injection:

Once you've created your extension, you need to inject it's content into the pydcs version run by Retribution. In the Python file you created for the mod, decorate each unit type with either @planemod, @vehiclemod, or @helicoptermod. For example:

@planemod
class A_4E_C(PlaneType):
    ...

Any custom weapons with the mod should be included in the same file as the python class for the vehicle itself. After the weapons are defined, they must be injected into the pydcs weapons table. You do this by including the following line at the top of the file: from pydcs_extensions.weapon_injector import inject_weapons

and the following line after the weapons definition class: inject_weapons(CustomWeaponsClass) where CustomWeaponsClass is the name of the weapons class you created. For the A4EC listed above, that name is WeaponsA4EC.

See A-4E-C.yaml for reference.

Make sure your mod is imported in pydcs_extensions/__init__.py and in pydcs_extensions/<your mod extension>/__init__.py

Step 2 : Campaign start setup

For campaign start setup and mod settings support, add your plane to the files start_generator.py, faction.py, QNewGameWizard.py, and QGeneratorSettings.py

start_generator.py:

    a4_skyhawk: bool = False
    f22_raptor: bool = False
    hercules: bool = False
    jas39_gripen: bool = False
    su57_felon: bool = False
    frenchpack: bool = False

faction.py:

        # aircraft
        if not mod_settings.a4_skyhawk:
            self.remove_aircraft("A-4E-C")
        if not mod_settings.hercules:
            self.remove_aircraft("Hercules")
        if not mod_settings.f22_raptor:
            self.remove_aircraft("F-22A")
        if not mod_settings.jas39_gripen:
            self.remove_aircraft("JAS39Gripen")
            self.remove_aircraft("JAS39Gripen_AG")
        if not mod_settings.su57_felon:
            self.remove_aircraft("Su-57")

QNewGameWizard.py:

        mod_settings = ModSettings(
            a4_skyhawk=self.field("a4_skyhawk"),
            f22_raptor=self.field("f22_raptor"),
            f104_starfighter=self.field("f104_starfighter"),
            hercules=self.field("hercules"),
            jas39_gripen=self.field("jas39_gripen"),
            su57_felon=self.field("su57_felon"),
            frenchpack=self.field("frenchpack"),

QGeneratorSettings.py snippet:

        self.a4_skyhawk = QtWidgets.QCheckBox()
        self.registerField("a4_skyhawk", self.a4_skyhawk)
        self.a6a_intruder = QtWidgets.QCheckBox()
        self.registerField("a6a_intruder", self.a6a_intruder)
        self.a7e_corsair2 = QtWidgets.QCheckBox()
        self.registerField("a7e_corsair2", self.a7e_corsair2)
        self.ea6b_prowler = QtWidgets.QCheckBox()
        self.registerField("ea6b_prowler", self.ea6b_prowler)

QGeneratorSettings.py snippet:

            ("A-4E Skyhawk (v2.2.0)", self.a4_skyhawk),
            ("A-6A Intruder (v2.7.5.01)", self.a6a_intruder),
            ("A-7E Corsair II", self.a7e_corsair2),
            ("C-130J-30 Super Hercules (v6.8.2)", self.hercules),
            ("Cold War Assets mod (v1.0)", self.coldwarassets),

Step 3 : Retribution unit file setup:

DCS Retribution

The aircraft preferences per task are kept in each aircraft's yaml file in resources/units/aircraft/*.yaml. Each task is assigned a weight describing the aircraft's relative strength at the given task. Aircraft with a higher weight for a given task will be preferred over those with a lower weight. If a task is omitted from the yaml file, the aircraft will not be capable of performing that mission type. For example:

# From FA-18C_hornet.yaml
tasks:
  Anti-ship: 150
  BAI: 740
  BARCAP: 450
  CAS: 740
  DEAD: 440
  Escort: 450
  Fighter sweep: 450
  Intercept: 450
  OCA/Aircraft: 740
  OCA/Runway: 600
  SEAD: 160
  SEAD Escort: 160
  Strike: 600
  TARCAP: 450
# From F-16C_50.yaml
tasks:
  BAI: 750
  BARCAP: 460
  CAS: 750
  DEAD: 450
  Escort: 460
  Fighter sweep: 460
  Intercept: 460
  OCA/Aircraft: 750
  OCA/Runway: 610
  SEAD: 170
  SEAD Escort: 170
  Strike: 610
  TARCAP: 460

The hornet has a BAI weight of 740 while the viper has a BAI weight of 750. Retribution will prefer choosing vipers to hornets when deciding which aircraft is better suited to BAI.

Aircraft can have identical weights for a task. When multiple aircraft have the same weight for a task, they are treated as equal by Retribution. Selection criteria vary by many factors; one may be chosen randomly from the set of equal aircraft, one may be chosen arbitrarily, or a different but similar aircraft may be chosen. These weights are only guidelines. Use them to give Retribution hints about which aircraft are better at each task.

For an overview of all tasks and weights in Retribution, run dcs-retribution.exe dump-task-priorities (from the command line). That will dump Retribution/Debug/priorities.yaml to your DCS Saved Games directory.

See A-4E-C.yaml for reference.

Similarly for ground units, each unit has a yaml file in resources/units/ground_units/*.yaml. This file determines what type of unit it is, the price, and the name of available variants. It is important to note that the ID of the vehicle from the pydcs export must match the name of the unit yaml file. Example:

# From CH_KrAZ6322.yaml
class: Logistics
description: "The KrAZ-6322 is a Ukrainian off-road six-wheel drive truck intended for extreme 
  conditions. It has been produced since 1994 and is manufactured at the KrAZ factory in Kremenchuk, 
  Ukraine. It was first presented at the 1994 defence industry trade show in Kyiv."
introduced: 1994
manufacturer: KrAZ
origin: Ukraine
price: 3
role: Tactical Transport Vehicle
variants:
  "[CH] KrAZ-6322 Truck": {}

DCS Liberation 6 (info retained for archival purposes only)

Please note, that this is no longer required in DCS Retribution, as the capability list has been replaced by the task weights, detailed above. These instructions are only retained for DCS Liberation version 6 reference purposes.

Open ai_flight_planner_db.py

Add your plane to the lists it needs to be in, so DCS Liberation is aware of its capabilities. These lists are in priority order:

Example:

CAP_CAPABLE = [
#   ....
    Su_57,
    F_22A,
#   ....
]

Step 4: Create default payload and inject custom weapons

See Custom Loadouts Create default loadouts for the new plane.

Step 5: Factions

See Custom Factions and add the mods to the appropriate factions. If there are no appropriate factions, create a new one, but you don't need to worry about breaking existing factions by "requiring" the mod; Retribution will filter out mods that the player doesn't use.

Step 6: Layouts and groups

If you're implementing a mod pack with SAM or AntiShip site units, you may want to create a new group so they can be used for LORAD/MERAD/SHORAD. Example from MIM-104 Patriot:

name: MIM-104 Patriot (Mobile)
tasks:
  - LORAD
units:
  - "[CH] MIM-104 AN/MPQ-65 STR (HEMTT)"
  - "[CH] MIM-104 AN/MPQ-65A STR (HEMTT)"
  - "[CH] MIM-104 LTAMDS STR (HEMTT)"
  - "[CH] MIM-104 ECS (HEMTT)"
  - "[CH] MIM-104 EPP (HEMTT)"
  - "[CH] MIM-104 M903 PAC-2 GEM/T LN (HEMTT)"
  - "[CH] MIM-104 M903 PAC-3 MSE LN (HEMTT)"
  - "[CH] Oshkosh HEMTT M977"
layouts:
  - MIM-104 Patriot Battery

As you can see, each group has a layout. If possible, you should attempt to reuse existing layouts like 6 Launcher Circle. Sometimes these sites do require their own layout like the MIM-104. In the layout file you can specify the unit class or unit type you want to use if you need to be more specific. Example:

name: MIM-104 Patriot Battery
tasks:
  - LORAD
groups:
  - Patriot:
    - name: Patriot Battery 0
      unit_count:
        - 2
      unit_classes:
        - SearchTrackRadar
    - name: Patriot Battery 1
      unit_count:
        - 1
      unit_classes:
        - Logistics
    - name: Patriot Battery 2
      unit_count:
        - 1
      unit_types:
        - MIM104_ECS
    - name: Patriot Battery 3
      unit_count:
        - 1
      unit_classes:
        - Logistics
    - name: Patriot Battery 4
      unit_count:
        - 1
      unit_types:
        - MIM104_EPP
    - name: Patriot Battery 5
      unit_count:
        - 8
      unit_classes:
        - Launcher
  - PD:
    - name: Patriot Battery 7
      optional: true
      sub_task: PointDefense
      unit_count:
        - 1
        - 2
      unit_classes:
        - SHORAD
    - name: Patriot Battery 6
      sub_task: AAA
      optional: true
      unit_count:
        - 1
        - 2
      unit_classes:
        - AAA
layout_file: resources/layouts/anti_air/Patriot_Battery.miz

This layout file points to the Patriot_Battery.miz file which the normal patriot site is using. Since the patriot site layout was very similar but a little too specific, the contributor chose to create a new layout file instead.

Note: In the groups file you use the variant name of the unit while in the layout file you use the ID of the unit.

Step 7: Update radar db:

Like before, if you implemented a SAM site or a ship that has a radar, you want to include it in the radar db. Example:

from pydcs_extensions import usamilitaryassetspack as usamap

TELARS/Track Radars/Launcher Tracker Pairs/Units with Radar:

    usamap.MIM104_ANMPQ65,
    usamap.MIM104_ANMPQ65A,
    usamap.MIM104_ANMPQ65_HEMTT,
    usamap.MIM104_ANMPQ65A_HEMTT,
    usamap.MIM104_LTAMDS,
    usamap.MIM104_LTAMDS_HEMTT,
    usamap.CH_THAAD_ANTPY2,

Step 8: Add icons for the UI (Optional)

Add icons for the new plane there: https://github.com/dcs-retribution/dcs-retribution/tree/dev/resources/ui/units/aircrafts/icons

And a banner, there: https://github.com/dcs-retribution/dcs-retribution/tree/dev/resources/ui/units/aircrafts/banners

Icons should be 91x24 and banners are 720x360 pixels both in jpeg format.

Step 9: Playtest

Play a few missions with the plane, test as much cases as you can.

Step 10: Release

Step 11: Maintenance

Redo the data export thing (Step 1), every time said mod is updated, if needed create new loadouts and account for new capabilities in db.