Ground of Aces Modding
Loading...
Searching...
No Matches
Missions

A mission is a flight the player sends aircraft on: it appears on the war map, costs time and fuel, and comes back with one of four endings - the flight went as planned or went wrong, and the crew passed or failed its skill check. Each ending counts as a success or a failure.

What it is Best for
The Mission Editor A window inside the game. Fill in fields, press Validate, press Save. A first mission, and anything whose numbers you want to try out quickly.
JSON and Python files A .json with the numbers and an optional .py with the text and logic. Everything else - chains, campaigns, missions that react to what the player did.

They are the same thing underneath. The editor writes exactly the JSON you would have written by hand, so you can start in the editor and keep going in a text editor.

Page Contents
Mission locations every place a mission can happen, with coordinates
How mods work what a mod folder is, where it lives, and how to share it
Changing game data with a CSV mod changing the game's numbers rather than adding missions
Example missions shipped mission files to open or save - a single mission, the four-mission Blitz chain, the default script

Where missions come from

The game reads missions from four places, in this order, and later ones win when two carry the same id:

  1. Shipped missions - inside the game, read-only. You can override one by re-using its id, but you cannot edit it in place.
  2. The Supporter Pack - its missions, when the DLC is installed.
  3. Mods - Mods/<your mod>/Missions/, holding .json and .py files. This is how you hand a mission to somebody else. See How mods work.
  4. Your own saved missions - the folder the in-game Mission Editor writes to:
Platform Folder
Windows USERPROFILE%\AppData\LocalLow\Blindflug Studios\Ground Of Aces\Missions\
macOS ~/Library/Application Support/Blindflug Studios/Ground Of Aces/Missions/
Linux ~/.config/unity3d/Blindflug Studios/Ground Of Aces/Missions/

The id of a mission is its file name without the extension - there is no id field inside the file. That last folder always wins, which makes it a good place to try changes out - and a place to remember to clear when a mission of yours seems to ignore the copy in your mod folder.

Note
Mission files are re-read every time a level is loaded - return to the main menu and load again. Saving in the Mission Editor reloads them at once. A brand-new mod folder needs Refresh in the Mods window (main menu → Mods) first, or a restart. (Developers: reload_scripts / reload_mods in development builds.)

An id that contains test_, debug_, _debug or _test is treated as developer content and is not loaded by the shipped game, so do not name a real mission that way.

The Mission Editor

Open the game and choose Mission Editor in the main menu. That opens the mission list: every shipped mission, every mission from an enabled mod, and every mission you saved yourself.

  • Create starts a fresh mission with the id new_mission. Rename it in the Mission Id field before saving.
  • Edit opens any mission in the list, shipped ones included. A shipped mission cannot be saved under its own id - Validate reports the id as reserved - so give it a new id and it is saved as your copy in the Missions folder above. That is how you copy a shipped mission.
  • Your own saved missions carry a delete button; shipped and mod missions do not.
  • Refresh re-reads the list, and the folder button opens your Missions folder in the file browser.

What you fill in

Identity. The mission id, and the tags that decide which maps the mission can appear on. An id must be lower case letters, digits and underscores only.

Tags

Tags are free-form labels, not map names. A map lists the tags it supports in its Metadata.json, a mission lists the tags it is written for, and the mission can only turn up on that map when the two lists share at least one tag. Several tags on a mission mean "any of these" - one match is enough.

Type them into the mission tags field as one comma-separated list without spaces: naval,atlantic. A tag follows the same rules as an id - lower case letters, digits and underscores - and the field enforces that as you type, lowercasing capitals and dropping anything else. The match against a map is case-insensitive anyway, so a map that spells its tag Campaign_Debden still counts.

Leaving the list empty is the normal case. An empty list on either side counts as the single tag default, and every shipped map lists default, so an untagged mission is available on all of them. Each shipped map also lists its own name (debden, duxford, lichfield, perranporth), so a mission tagged debden and nothing else appears only there. Tag a mission campaign_debden and it appears only on maps that also list campaign_debden - and nowhere else, not even on the shipped maps, unless you add default alongside it. A map made for a campaign usually lists only its own tag, precisely to keep the regular missions out.

Tags are a mission-only concept. A map chooses the quest line it starts with a different field, StartingQuestIds - see Quests and maps.

Note
Validate warns you when a mission carries a tag that no known map declares - that mission can never appear anywhere, which is only what you want if a script hands it out directly.

Timing and size. How many aircraft the player may send, how long the mission waits on the map before it expires, and how long the flight itself takes. Both times are ranges in game minutes - the game rolls between the two numbers. A time-to-start maximum of 0 means the mission never expires, and a historic mission (below) never expires regardless of what is entered.

Difficulty. The chance the mission goes as planned (0–100), a difficulty rating from 1 to 3, and the enemy strength the intel report shows the player.

Text. The mission name and description, the intel threat report, two mid-flight radio messages (one for a flight that is going to plan, one for a flight that has gone wrong), and one closing message for each of the four endings. Type them straight in - a mission written in the editor does not need a script, because every mission without one runs on the game's shared default mission script.

Locations. Where on the war map the mission can appear. Pick from the list and add as many as you like - the same place cannot be added twice; the game chooses one when the mission is created. The full list, with coordinates, is on Mission locations.

Requirements. Conditions that must all hold before the mission shows up. Four kinds exist:

Requirement Value
RunwayTier the tier the base's runway must have reached, as a number
EarliestAppearanceDay the day number, counted from the start of the campaign, from which the mission may appear
LatestAppearanceDay the last day number on which it may appear
CompletedMission <mission id>, or <mission id>,success / ,failure / ,cancelled to require a particular result

Stats. The skills the mission rolls against. You need exactly two: the first is used when the flight goes to plan, the second when it does not.

Outcomes. Exactly four endings, in a fixed order, and what each one gives or costs. Which ending counts as a success is fixed by its position and cannot be edited:

Slot Ending Counts as
1 gone wrong, skill check failed failure
2 gone wrong, skill check passed success
3 as planned, skill check failed failure
4 as planned, skill check passed success

The editor shows them top to bottom from the best ending to the worst. Each ending carries any number of effects: CrewMood, CrewConfidence, CrewEnergy (the crew that flew), BaseMood, BaseConfidence, BaseEnergy (everyone at the base), VictoryPoints, and Damage to the aircraft as a min-max range that is rolled per flight.

Historic day. Tick this to make the mission storyline content. A historic mission is kept out of the random weekly draw and instead appears with certainty once its requirements pass: on the exact day you enter, or - if you leave the date empty - together with the next weekly mission, without using up that week's slot. It never expires off the map, and it is gone for good once it has succeeded; a failed or cancelled attempt brings it back, so the next step of a chain stays reachable.

Validate before you save

Validate lists everything it can find. Save runs the same check and refuses if anything is an error.

Errors block saving:

Error Meaning
Mission id empty, or has characters other than a-z, 0-9 and _
Mission id is reserved a shipped mission already uses it - save under a new id instead
Mission id is in use renaming onto another mission you saved would silently replace it
A tag has invalid characters tags follow the same rules as ids
Not exactly four outcomes the game picks one of four and never checks the count
Not exactly two stats the skill roll reads both
Time to start: minimum above maximum
Time to complete: minimum above maximum, or under 5 minutes
A runway tier or day requirement that is not a whole number
A completed-mission requirement the game cannot read

The reserved-id check only knows the game's own missions. An id that a mod or the Supporter Pack already uses is not reported, and the two silently replace each other by load order - another reason to prefix your ids with your mod's name.

Warnings let you save, and are worth reading anyway:

Warning Meaning
No locations the game places the mission at a random location from the shipped list
Duplicate location names
A tag no known map carries the mission is unavailable everywhere - intended only if a script hands it out
Difficulty rating outside 1–3
Fewer than one aircraft slot nobody can fly it
Time to start never expires the mission sits on the map until flown
Historic day ticked but the date is empty, impossible or before 1900 the mission has no date and behaves like an undated historic mission
A stat whose minimum is above its maximum
An outcome effect the game cannot read that one effect silently never applies
Missing narrative text that slot shows nothing in game

The narrative-text warning does not appear for shipped missions, which carry translated text rather than text typed into the editor.

What the editor cannot do

  • Write the Python side. Anything that has to decide something while the game runs - who flew, what the base looks like, what happened last time - needs a .py next to the .json.
  • Show a chain as a whole. Each mission only lists its own requirements; the links between several files are easier to follow in a text editor.

For either, edit the mission files with a text editor.


JSON and Python

A mission is a .json file holding the numbers, and optionally a .py file of the same name holding the text and any logic. The file name is the mission id. Mission scripts live alongside ordinary scripts, in the Mission folder of the scripts directory, or in your mod's Missions/ folder.

Three things catch people out, and all three are limits of the game rather than of your files:

  • Exactly four outcomes, in order. The game picks one by index and never checks how many there are. Three entries crash the mission when it finishes - long after you have stopped suspecting the JSON.
  • At least two mission stats. The first is used when the mission goes to plan, the second when it does not. One entry crashes the skill roll.
  • Prefix your ids with your mod name. A duplicate id silently replaces the mission it duplicates, and which one wins depends on load order.

The Mission Editor's Validate button catches the first two, and a clash with a shipped id, before you ever launch. A clash with another mod's id it cannot see.

The two modes

How much of the script the game uses depends on whether the mission has a .json beside it.

  • With JSON - the numbers come from the file. The script supplies text and reactions only, and the handlers that report numbers are never called. A .py next to a .json overrides only the handlers it defines; anything it leaves out falls back to the shared default_mission.py.
  • Without JSON - the script supplies everything, including the numbers. Every handler below is called. Two are mandatory: get_mission_id, in a module named exactly like the mission, is how the game finds the script at all, and get_mission_result decides the ending - without it the mission throws when it finishes. Every other getter has a default when it is missing: the name falls back to the mission id, the description to nothing, time to start to 60 minutes, completion time to 120 minutes, the threat level to Low, the enemy count to 0, the number of progress reports to 3 and the fuel to 100.

This is the single most common source of confusion: a handler that works perfectly in a script-only mission is simply ignored once you add a .json.

Mission event handlers

Every mission script needs from Blindflug.FlightMission import MissionEvent at the top - scripts get no automatic imports. The constant names below are the Python names; where the event string differs it is given in brackets.

Handler What it does When it runs
get_mission_id the internal identifier of the mission always - required for a mission without JSON, inherited from the default script otherwise
initialize_mission set the mission up as it is created always
send_mission_event report progress while the flight is in the air always
set_mission_impact apply consequences once the outcome is known always
send_mission_result_event (send_mission_result_text) react when the mission ends always, except when cancelled
send_mission_cancelled_event (send_mission_cancelled_text) react when the player aborts or the mission expires always
get_mission_result_text the text in the result window always
get_mission_name the name in the mission info window script-only missions
get_mission_description_text the description in the info window script-only missions
get_time_to_start how long the player has to start it script-only missions
get_completion_time how long the flight lasts script-only missions
get_threat_level the threat level in the info window script-only missions
get_enemy_count how many enemies are present script-only missions
get_mission_events_count how many progress reports to send script-only missions
get_fuel_level the fuel the mission needs script-only missions
get_mission_result whether the mission succeeded script-only missions - required there
Attention
Do not implement get_mission_icon, calculate_mission_modifiers, calculate_airplane_modifiers or calculate_pilot_modifiers. The constants still exist, but nothing calls them - the modifier system moved into the game itself. Older example scripts implement them at length; that code never runs.

get_mission_fatalities no longer exists. Losses are handled through the Damage outcome effect.
Note
send_mission_event is not fired on a fixed clock. Each game minute the game computes floor(elapsed / completion time × number of reports) and fires once whenever that number grows, so the reports are spread evenly over the flight and the first call already sees a count of 1. For a mission with JSON the number of reports is fixed at 3, so get_mission_events_count is ignored.

Scripting a mission from Python

A mission without JSON fills in its numbers by calling missions_api from initialize_mission. These are the methods that exist for that, with their exact signatures; every one returns False when the mission or a name is unknown.

Method What it sets
SetOutcome(mission_entity_id, outcome_index, is_success) which of the four slots (0–3, see the table above) counts as a success
AddOutcomeEffect(mission_entity_id, outcome_index, effect_type, effect_value) one effect on a slot; effect_type is one of the effect names above, effect_value a number ("-40") or a range ("70-140")
AddMissionRequirement(mission_entity_id, requirement_type, value) one of the four requirements above, by name
SetTags(mission_entity_id, *tags) the map tags
SetMissionStat(mission_entity_id, skill_type, min_value, max_value) one of the two skill rolls
SetAsPlannedChance(mission_entity_id, chance) 0–100; without it the mission always counts as having gone wrong
SetMissionLocation(mission_entity_id, location_name) a name from Mission locations - custom coordinates are not possible from a script
SetMaxAirplaneSlots(mission_entity_id, slots) how many aircraft may fly
SetDifficulty(mission_entity_id, difficulty_rating) the 1–3 star rating
CreateMission(flight_mission_id) puts another mission on the map now, bypassing the weekly draw - how a script hands out a follow-up that JSON requirements cannot express

For reading the state of a running mission see GetMissionId, GetNumberOfEventsSent, MakeAsPlannedRoll, GetAsPlannedRoll, MakeSkillRoll, GetSkillRoll, GetMissionAirplaneFormation, GetEnemyCount and IsMissionSuccessful on missions_api.


Example missions

The files below are the game's own missions, exactly as they ship. Open one to see what a finished mission looks like, or save it into Mods/<your mod>/Missions/ under a new file name and change it from there.

Note
A shipped mission carries its text as localisation keys that live inside the game: missions/<id>/mission_name, mission_description, intel_threat_report, ap_transmission, gw_transmission, ap_success_transmission, ap_failure_transmission, gw_success_transmission and gw_failure_transmission. A copy under a new id therefore shows no text until you give it some. Either fill the _raw* fields at the bottom of the file, or put a .py next to it that registers terms with localization.SetTerm(language, key, text). Keeping the shipped file name instead replaces the shipped mission, text and all.

A single mission

File What it is
calm_supply_drop.json A transport mission with no script of its own: it appears from day 20 on a tier 3 runway, can happen at one of four places, and runs on the shared default script below. Everything a mission needs is in this one file.

The quest example_patrol_chain on the quests page (see Example quests) waits for this mission to succeed, so the two can be tested together.

A chain of missions

The four Blitz missions form the storyline the game plays from November 1940. Each one is historic and dated, so it appears on its exact day once the one before it has succeeded, and each is gated on that success with requirement type 3 (CompletedMission):

german_blitz_1_long_nights 1 Nov 1940, runway tier 1
|
+-- success --> german_blitz_2_aspirin 5 Dec 1940, tier 2 "german_blitz_1_long_nights,success"
|
+-- success --> german_blitz_3_morning_after 5 Jan 1941, tier 2 "german_blitz_2_aspirin,success"
|
+-- success --> german_blitz_4_answering_in_kind 5 Feb 1941, tier 3
File Role
german_blitz_1_long_nights.json the opening mission, tied to a date
german_blitz_2_aspirin.json follows a success of the first
german_blitz_3_morning_after.json follows a success of the second
german_blitz_4_answering_in_kind.json follows a success of the third

Compare the _missionRequirements blocks at the top of the four files: they differ only in the runway tier and the mission each one waits for. Their _tags list names every shipped map and default, so the chain runs everywhere; drop default and keep a single map's tag to restrict a chain of your own to that map.

The shared default script

File What it is
default_mission.py The script every mission without a .py of its own runs on. It answers get_mission_id, rolls once whether the flight goes as planned at the first progress report, rolls the skill check at the second, and picks the transmission and result text keys from the outcome. Copy it as the starting point for a mission that needs its own logic.

See Scripting for how event handlers work, and Mission event handlers above for what each handler in the file is for.