Make a career mode ​

You'll build mod.decades, a Driver Career from 2008 to 2012. The player sees a Decades tile in the career menu. Each season races in that year's box, with its cars, liveries and rules, and at every season break the grid changes to the next year's real line-up.

The mode builds on five year packs, mod.season-2008 to mod.season-2012, each made like the one in Make a year pack. Each pack registers its year's career season: the box, the line-up and the ratings. Decades takes each one as a variant, so it inherits the pack's data, and adds the rules of its own mode.

The folder ​

text
mod.decades/
├── swofty.json
├── registry.json
├── art/tile.png
└── native/
    ├── build.sh
    ├── decades.dll
    └── src/decades.cpp

1. The mode and its tile ​

A career mode is what the game saves a career as. Decades' mode is a variant of the 2026 Driver Career, so it inherits everything the stock mode does, including changes other addons make to it. It gets its own data group and save container, so Decades careers never mix with stock ones.

The content record is the tile in the career menu. It runs five seasons, and after 2012 the career ends.

mod.decades/native/src/decades.cpp
cpp
#include <swofty/registry.hpp>
#include <swofty/events.hpp>
#include <swofty/systems/career.hpp>

using namespace swofty;

static const events::Selector kDecades = events::content("mod.decades/f1_career_decades");

SWOFTY_REGISTER
{
    auto mode = career::modes.variant("decades", career::modes["game/f1_career_2026"], [](career::ModeRecord &m) {
        m.data_group = "f1_career_decades";
        m.save_container = "driver_career_decades";
        m.title = "lng_decades_title";
    });

    career::contents.add("f1_career_decades", {
        .mode = mode,
        .tile = file("art/tile.png"),
        .model = career::contents["game/f1_career_2026"],
        .after_last = career::AfterLast::end,          // 2012 is the final season
    });

    register_seasons();                                // step 2
}

With no DLL ​

The tile needs no code. A career.content record in a data file makes the same tile as SWOFTY_REGISTER above. The content names its suffix, and its mode gives the data group, save container and title:

text
mod.decades/
├── swofty.json
├── registry/career.json
└── db/career.json
json
// registry/career.json
{
  "career.mode": {
    "decades": { "data_group": "f1_career_decades", "save_container": "driver_career_decades",
                 "title": "lng_decades_title" }
  },
  "career.content": {
    "f1_career_decades": { "mode": "mod.decades/decades", "suffix": "DEC", "description": "lng_decades_description",
                           "after_last": "end" }
  }
}

The 1.6 core has no stock career.mode records to extend: the JSON-only mode is a record of its own. The game data behind the tile comes from database operations in db/career.json:

  • a game_modes.ModeChampionship row whose save_data.name is driver_career_decades;
  • an entrants.entrant_lists row named f1_career_decades, with a stat_id below 256, which the mode's championship names in ComCareerSetup_0.entrant_list;
  • the official entrant list F1CAREER_DEC in a content set, and F2CAREER_DEC and ICONS_DEC for the F2 and Icons tabs of Choose Driver.

Progressive's db/career.json and db/grid.json add these rows for its own tile. swofty.json lists the operations and no DLL:

json
"operations": { "database": [ "db/career.json" ] }
text
[career] content 'f1_career_decades' from mod.decades (career.content record mod.decades/f1_career_decades, no call)
         registered (tile 1 after the stock ones, 2026 model, data group f1_career_decades, suffix DEC, mode
         driver_career_decades): queued for the next mixin commit

Career contents lists every field and its default.

2. Five seasons from five packs ​

Each Decades season is a variant of a pack's season. It inherits the pack's box, calendar, line-up and mid-season changes, and sets the Decades data group so the mode uses it. A pack update that fixes its line-up reaches Decades with no change here.

Ratings are their own records, one per driver per season. Decades makes a variant of each of the pack's ratings that points at its own season.

If two packs offer the same year, the career setup page lets the player pick one. Each season's box is chosen when the career is created.

Above SWOFTY_REGISTER
mod.decades/native/src/decades.cpp
cpp
static void register_seasons()
{
    for (int year = 2008; year <= 2012; ++year) {
        const std::string pack = std::format("mod.season-{}/{}", year, year);   // "mod.season-2008/2008"

        auto season = career::seasons.variant(std::to_string(year), career::seasons[pack],
                                              [](career::Season &s) { s.data_group = "f1_career_decades"; });

        for (const auto &id : career::ratings.all()) {
            auto r = career::ratings.get(id);
            if (r && r->season.id() == pack)
                career::ratings.variant(std::format("{}/{}", year, r->driver.id()), id,
                                        [season](career::DriverRatings &d) { d.season = season; });
        }
    }
}

registry.json requires the five packs' seasons, so the launcher checks them before Play:

json
{
  "requires": [
    { "registry": "career.season", "version": "^1" },
    { "record": "career.season:mod.season-2008/2008", "for": "the 2008 season" },
    { "record": "career.season:mod.season-2009/2009", "for": "the 2009 season" },
    { "record": "career.season:mod.season-2010/2010", "for": "the 2010 season" },
    { "record": "career.season:mod.season-2011/2011", "for": "the 2011 season" },
    { "record": "career.season:mod.season-2012/2012", "for": "the 2012 season" }
  ]
}

3. The grid at every season break ​

The game starts each season with its own driver market. Decades replaces that with the year's line-up when the season starts: apply_lineup signs every seat in the season record, and the report says what changed.

ctx.record<career::Season>() is this career's season as it was frozen when the season started. A career freezes each season's line-up, ratings and rules at that moment, so a running career gets pack fixes for every season it hasn't reached yet.

The handler only runs in Decades careers: .in(kDecades).

Below SWOFTY_REGISTER
mod.decades/native/src/decades.cpp
cpp
SWOFTY_ON_LOAD
{
    events::on<career::events::SeasonStart>().in(kDecades)
        .after([](const career::events::SeasonStart &e, const events::Context &ctx) {
            auto season = ctx.record<career::Season>();
            if (!season)
                return;
            career::ApplyReport report;
            career::apply_lineup(*season, &report);
            swofty::log("[decades] %d: %zu seat(s) set, %zu unchanged, %zu refused",
                        e.year, report.changed, report.unchanged, report.refused);
        });
}

SWOFTY_ADDON()

To freeze every season when the career is created instead, set m.freeze = career::Freeze::at_creation on the mode. Career covers freezing, and Events the other career events: contracts, the market, R&D.

swofty.json ​

json
{
  "schema_version": 1,
  "id": "mod.decades",
  "name": "Decades",
  "version": "1.0.0",
  "author": "you",
  "category": "career",
  "compat": { "game": "f1_25", "launcher": ">=1.6.0" },
  "requires": [
    { "id": "mod.season-2008" }, { "id": "mod.season-2009" }, { "id": "mod.season-2010" },
    { "id": "mod.season-2011" }, { "id": "mod.season-2012" }
  ],
  "operations": { "native": [ { "dll": "native/decades.dll" } ] }
}

Try it ​

sh
native/build.sh
swofty addons install ./mod.decades
for id in mod.season-2008 mod.season-2009 mod.season-2010 mod.season-2011 mod.season-2012 mod.decades; do
    swofty addons enable $id
done
swofty run --apply

Start a Decades career, finish or skip the 2008 season, and check the 2009 grid. swofty/loader.log:

text
[registry] register: mod.decades (decades.dll) 1 career.mode, 1 career.content, 5 career.season, 110 career.driver_ratings
[registry] career season 2008 starts in mod.decades/f1_career_decades: snapshot of career.season 1, career.driver_ratings 22
[decades] 2008: 22 seat(s) set, 0 unchanged, 0 refused
[registry] career season 2009 starts in mod.decades/f1_career_decades: snapshot of career.season 1, career.driver_ratings 22
[decades] 2009: 20 seat(s) set, 2 unchanged, 0 refused

When something's missing ​

  • A pack is off before Play: requires stops Play and names it.
  • A pack goes missing mid-career: the career's dependency record names every pack whose box it uses. The launcher warns before Play, and a guard stops the career loading with a popup naming the pack. Its saves are never changed.
  • A pack removes a driver Decades seats: apply_lineup refuses that seat, keeps the game's driver in it, and the report counts it as refused.
  • A track a season's calendar names isn't installed: that round is left out of the season's calendar.

Publish ​

See Packaging and publishing. List the packs as requirements on the Addon Browser too, so the Browser installs them with Decades.