It is a clean, minimal, purple-themed graphic social media banner, serving as the official header for the "Metruvia Knowledge Base" (Metruvia and Metruvia Knowledge Base) Transport Fever mod brand on Mod.io. The banner is set against a solid, deep-violet-purple background with a rich saturation. In the upper-center, a white, simplified, and symmetrical game controller icon is positioned. Directly below this icon, in prominent, white, bold, sans-serif text, is the text "Metruvia Knowledge Base". Below that, in smaller, regular white text, is the tagline "Built by Players. Powered by Knowledge." Symmetrically framing the central text and icon, on the far left and far right sides, are two large, detailed, white game controllers, depicted from a top-down aerial perspective with colorful button inputs (yellow, blue, pink, and green) and analog sticks. Scattered across the remaining purple space are small, white line-art icons that reinforce the gaming and puzzle themes: several puzzle piece outlines, small pixel-art heart outlines, simple bullseye targets, and a single solid white circle (a "knowledge" point). The layout is balanced, professional, and easily recognizable as part of a gaming community and documentation resource. The art style is flat vector graphic. A small, stylized white compass needle icon, derived from the Metruvia brand mark, is visible over the left analog stick of the controller on the right, providing a cohesive brand identity. The text is sharp and legible.

Metruvia Content Creator Series: Scenarios and Campaigns

·

·

Welcome to the Metruvia Content Creator Series Scenarios and Campaigns Guide. In Free Play, the player creates their own goals. In a Scenario, you are the director. You dictate the economy, set the challenges, script the dialogue, and control the pacing.

For a PC modder, building a campaign is an exercise in Lua scripting. For a cross-platform creator targeting Mod.io and console ecosystems, it is an exercise in strict state management and hardware-compliant UI design. Because scenarios run custom logic every single frame to check if objectives are complete, a poorly optimized script will cause CPU watchdog timeouts, while an improperly structured save-state will corrupt a player’s progress entirely.

This masterclass will deconstruct the Transport Fever 2 campaign framework. We will explore the directory hierarchy, the mathematics of the mission state machine, event-driven triggers, and the absolute necessity of serializable data for console save-game integrity.

1. The Campaign Directory Architecture

A scenario is not a single file; it is a rigid hierarchy of interconnected directories. If this structure deviates by a single character, the game’s internal parser will fail to register the campaign in the main menu.

Your staging area must follow this exact schema:

res/campaign/my_metruvia_campaign/

campaign.lua (The master index linking all missions together).

image_00.tga (The 16:9 banner image for the campaign menu).

01_first_mission/

mission.lua (The core logic script for mission one).

map.lua (The topological generation script).

image_00.tga (The thumbnail for mission one).

heightmap.png (The 16-bit grayscale terrain map).

strings.lua (The localized text dictionary).

1.1 The campaign.lua Master File

This script is lightweight. Its sole purpose is to declare the sequential order of the missions and define the unlocking logic.

function data()
return {
title = (“The Metruvia Transit Initiative”), description = (“A three-part campaign restoring a fractured network.”),
missions = {
{ id = “01_first_mission”, title = (“Chapter 1: The Foundation”) }, { id = “02_second_mission”, title = (“Chapter 2: Industrial Expansion”) },
{ id = “03_third_mission”, title = _(“Chapter 3: The High-Speed Era”) }
}
}
end

2. The mission.lua State Machine

The brain of your scenario is the mission.lua file. Transport Fever 2 missions operate as Finite State Machines (FSM). The engine constantly evaluates the current “state” of the game and checks if the player has met the conditions to advance to the next state.

2.1 The Three Core Functions

Every mission script must contain three fundamental functions:

init(): Runs exactly once when the mission is first started. This is where you spawn initial towns, place starting industries, and set the starting bank balance.

step(): Runs continuously (multiple times per second). This is your polling loop where you check if a player has delivered the required cargo or built the required tracks.

save() / load(): The serialization hooks. These are the most critical functions for console certification.

2.2 The state Table and Serialization

To track a player’s progress, you store variables in a global state table.

Example: state.steel_delivered = 0

The Console Save-Breaker Trap: When a console player saves the game, the engine serializes (converts to binary data) everything inside the state table and writes it to the SSD. You cannot put functions, userdata, or game engine object references inside the state table.

Fatal Error: state.my_train = api.engine.getEntities()[1] (This stores a dynamic memory pointer. When the game is reloaded, that pointer is dead, and the console will crash).

Compliant: state.my_train_id = 1402 (Store only basic data types: integers, strings, booleans. Retrieve the actual entity using the ID upon reloading).

3. Objective & Task Management

A scenario requires clear objectives. For console compatibility, you must rely entirely on the vanilla UI task tracker.

3.1 The Vanilla Task API

Do not attempt to write a Lua script that draws a custom “Quest Log” window on the screen. Gamepad controllers cannot navigate unmapped UI space, and your mod will be instantly rejected.

You must utilize the built-in game.interface to add tasks to the vanilla left-hand objective panel:

— Adding a task securely using the vanilla API
game.interface.sendScriptEvent(“ui”, “addTask”, {
id = “deliver_steel”,
title = (“Fuel the Forge”), text = (“Deliver 500 units of steel to the Central Plant.”),
type = “PROGRESS”,
progress = { current = state.steel_delivered, max = 500 }
})

3.2 Updating Task Progress

Inside your step() function, you continuously update this vanilla UI element. Once state.steel_delivered >= 500, you send another script event changing the task state to COMPLETED, which triggers the vanilla success chime and green checkmark.

4. Algorithmic Efficiency: Event Listeners vs. Polling

Because the step() function runs constantly, poor algorithmic logic here will decimate a console’s CPU, triggering a watchdog timeout.

4.1 The Polling Danger

If your objective is “Wait for the player to buy a specific locomotive,” the amateur approach is to use step() to scan the entire vehicle registry every single frame to see if the locomotive exists. If the player has 500 vehicles, you are performing 500 checks per frame, 60 times a second. This is $O(n)$ polling, and it will cause massive stuttering.

4.2 Event-Driven Architecture

The professional, certified approach is to use Event Listeners. Instead of asking “Did it happen?” every frame, you tell the engine, “Wake this script up only when a vehicle is purchased.”

In your init() function, register a listener:

api.engine.system.scriptSystem.addScriptEvent(“vehicleAdded”, function(id)
local vehicleData = api.engine.getComponent(id, api.type.ComponentType.MODEL_INSTANCE)
if vehicleData.modelId == “my_target_locomotive.mdl” then
state.loco_purchased = true
end
end)

This reduces the CPU load from continuous polling to near-zero, guaranteeing smooth performance on unified memory architectures.

5. Camera Manipulation and Cutscenes

Transport Fever 2 allows you to hijack the camera to create dramatic cutscenes. This is highly encouraged, provided it respects the player’s control interface.

5.1 The camera.set() Function

You can move the camera to a specific coordinate matrix to highlight a new factory opening or a bridge completing.

game.interface.setCamera({
matrix = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 150, 200, 50, 1 },
fov = 30,
duration = 5.0 — Camera smoothly pans over 5 seconds
})

5.2 The “Lockout” Warning

When running a cutscene, you must temporarily disable user input to prevent the camera math from fighting the player’s thumbstick input. Always ensure you release the input lock immediately after the duration timer expires. If your script errors out during a cutscene and fails to release the lock, the console player is permanently soft-locked and must hard-reset the game.

6. Localization and Translation Dictionaries

A scenario relies heavily on narrative text. Because Mod.io serves a global audience across all console regions, hardcoding English text directly into your mission.lua is a poor practice.

6.1 The strings.lua File

Every piece of text—mission titles, objectives, character dialogue—must be wrapped in the translation function _("Your Text Here").

The engine then looks inside your mission’s strings.lua file to find the regional equivalent based on the console’s language settings.

function data()
return {
en = {
[“Fuel the Forge”] = “Fuel the Forge”,
[“deliver_steel_desc”] = “Deliver 500 units of steel to the Central Plant.”
},
de = {
[“Fuel the Forge”] = “Die Schmiede anfeuern”,
[“deliver_steel_desc”] = “Liefere 500 Einheiten Stahl an das Hauptwerk.”
}
}
end

By utilizing this dictionary structure, you expand your campaign’s reach to international player bases without altering a single line of your core logical code.

7. Summary: The Campaign Certification Checklist

Before you package your campaign for the Mod.io API, run your structure through this final audit:

Directory Integrity: Does your hierarchy strictly follow the campaign/name/01_mission folder structure?

State Serialization: Are there zero functions, components, or memory pointers stored inside your state table?

UI Compliance: Are all tasks and objectives pushed to the vanilla UI instead of relying on custom window generation?

CPU Optimization: Have you utilized Event Listeners for trigger conditions instead of heavy $O(n)$ polling in the step() function?

Localization: Is all dialogue and objective text routed through a strings.lua dictionary?

Creating a scenario transforms you from an asset modeler into a game designer. By adhering to strict state-machine protocols and event-driven architecture, you ensure your narrative vision executes flawlessly across the entire cross-platform ecosystem.



Leave a Reply

Your email address will not be published. Required fields are marked *