This document explains how the MXBMRP3 plugin works, from the ground up. It’s designed to help new contributors understand the codebase quickly.
MXBMRP3 is a HUD (Heads-Up Display) plugin for Piboso racing simulators (MX Bikes, GP Bikes, WRS, KRP). The plugin displays real-time racing information on screen: lap times, standings, speedometer, track map, and more.
The plugin is a Windows DLL (with .dlo extension) that each game loads at startup. The game calls our exported functions to send us data and request rendering instructions. A multi-game translation layer allows the same core code to work across all supported games.
mxbmrp3/
├── mxbmrp3/ # Main plugin source code
│ ├── vendor/piboso/ # Game API definitions and exports
│ │ ├── mxb_api.h/.cpp # MX Bikes API header and DLL exports
│ │ ├── gpb_api.h/.cpp # GP Bikes API header and DLL exports
│ │ ├── wrs_api.h # WRS API header (stubbed)
│ │ └── krp_api.h # KRP API header (stubbed)
│ ├── game/ # Multi-game abstraction layer
│ │ ├── unified_types.h # Game-agnostic data structures
│ │ ├── game_config.h # Compile-time game selection
│ │ └── adapters/ # Per-game type converters
│ │ ├── mxbikes_adapter.h
│ │ ├── gpbikes_adapter.h
│ │ └── ...
│ ├── core/ # Core infrastructure
│ │ ├── plugin_manager.* # Main coordinator, routes API callbacks
│ │ ├── plugin_data.* # Central game state cache
│ │ ├── hud_manager.* # Owns and updates all HUDs
│ │ ├── input_manager.* # Keyboard and mouse input
│ │ ├── xinput_reader.* # XInput controller state and rumble
│ │ ├── rumble_profile_manager.* # Per-bike rumble profiles (JSON)
│ │ ├── settings_manager.* # Save/load configuration (INI file)
│ │ ├── asset_manager.* # Dynamic asset discovery (fonts, textures, icons)
│ │ ├── font_config.* # User-configurable font categories
│ │ ├── color_config.* # User-configurable color palette
│ │ ├── fmx_manager.* # FMX trick detection and scoring
│ │ ├── fmx_types.h # FMX data structures and enums
│ │ ├── plugin_constants.h # All named constants
│ │ └── plugin_utils.* # Shared helper functions
│ ├── handlers/ # Event processors (one per API callback type)
│ │ ├── draw_handler.* # Frame rendering and FPS tracking
│ │ ├── event_handler.* # Event lifecycle (init/deinit)
│ │ ├── run_*_handler.* # Player-only events
│ │ └── race_*_handler.* # Multiplayer race events
│ ├── hud/ # Display components
│ │ ├── base_hud.* # Abstract base class for all HUDs
│ │ ├── *_hud.* # Full HUDs (complex, configurable)
│ │ ├── *_widget.* # Simple widgets (focused display)
│ │ └── settings/ # Settings UI components
│ │ ├── settings_hud.* # Main settings menu
│ │ ├── settings_layout.* # Layout helper context
│ │ └── settings_tab_*.cpp # Individual tab renderers
│ └── diagnostics/ # Debugging tools
│ ├── logger.* # Debug logging to file
│ └── timer.h # Performance measurement
├── mxbmrp3_data/ # Runtime assets (discovered dynamically)
│ ├── fonts/ # .fnt files (bitmap fonts)
│ ├── textures/ # .tga files (HUD backgrounds with variants)
│ ├── icons/ # .tga files (rider icons for map/radar)
│ └── tooltips.json # UI tooltip definitions
├── docs/ # Documentation
├── replay_tool/ # Separate tool for replay analysis
└── mxbmrp3.sln # Visual Studio solution
Here’s how data flows through the plugin:
┌─────────────────────────────────────────────────────────────────────────┐
│ GAME ENGINE (MX Bikes / GP Bikes / etc.) │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ mxb_api.cpp / gpb_api.cpp │
│ (Per-Game DLL Exports) │
│ │
│ Startup(), Draw(), RunLap(), │
│ RaceEvent(), etc. │
└───────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ Game Adapters │
│ (mxbikes_adapter.h, etc.) │
│ │
│ Convert game structs to │
│ Unified:: types │
└───────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ PluginManager │
│ (Main Coordinator) │
│ │
│ Receives Unified:: types, │
│ routes to handlers │
└───────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Handlers │ │ DrawHandler │ │ InputManager │
│ │ │ │ │ │
│ Process events, │ │ Triggers HUD │ │ Tracks mouse, │
│ update data │ │ render cycle │ │ keyboard state │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ │
┌─────────────────┐ │
│ PluginData │◄───────────┘
│ (State Cache) │
│ │
│ Stores all game │
│ state, notifies │
│ on changes │
└─────────────────┘
│
│ notifies
▼
┌─────────────────┐
│ HudManager │
│ │
│ Owns all HUDs, │
│ marks dirty, │
│ collects output │
└─────────────────┘
│
▼
┌─────────────────┐
│ HUDs │
│ │
│ Build quads & │
│ strings for │
│ rendering │
└─────────────────┘
│
│ returns render data
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ GAME ENGINE (MX Bikes / GP Bikes / etc.) │
│ (Renders our output) │
└─────────────────────────────────────────────────────────────────────────┘
vendor/piboso/*_api.*)Each Piboso game defines a C API that plugins must implement. The APIs are nearly identical, with game-specific struct variations. Each game has its own API file:
mxb_api.h/.cpp - MX Bikesgpb_api.h/.cpp - GP Bikeswrs_api.h / krp_api.h - WRS and KRP (headers only, stubs)Key exported functions (same across all games):
| Function | When Called | Purpose |
|---|---|---|
Startup() |
Game starts | Initialize plugin, return telemetry rate |
Shutdown() |
Game closes | Clean up resources |
EventInit() |
Track loaded | Receive track/vehicle info |
RunInit() |
Player goes on track | Session begins |
RunTelemetry() |
Every physics tick | Receive vehicle telemetry (100Hz) |
RunLap() |
Lap completed | Receive lap time |
Draw() |
Every frame | Return quads/strings to render |
RaceEvent() |
Online race starts | Receive race info |
RaceClassification() |
Continuously | Receive standings updates |
The API uses C structs to pass data. Each game’s structs have different field names and contents:
SPluginsBikeData_t, SPluginsBikeEvent_tSPluginsGPBBikeData_t, SPluginsGPBBikeEvent_tThe adapter layer (game/adapters/*.h) converts these game-specific structs to unified types (Unified::TelemetryData, Unified::VehicleEventData, etc.) that the core plugin uses.
core/plugin_manager.*)The central coordinator. It:
Note: PluginManager is game-agnostic - it never sees raw game API structs, only Unified::* types.
// Example: mxb_api.cpp converts and forwards to PluginManager:
// In mxb_api.cpp:
void RunLap(void* _pData, int _iDataSize) {
auto* gameData = static_cast<SPluginsBikeLap_t*>(_pData);
auto unified = Adapter::toPlayerLap(gameData); // Convert to unified type
PluginManager::getInstance().handleRunLap(&unified);
}
// PluginManager receives unified type:
void PluginManager::handleRunLap(Unified::PlayerLapData* psLapData) {
RunLapHandler::getInstance().handleRunLap(psLapData);
}
core/plugin_data.*)The single source of truth for all game state. This singleton:
Key data structures:
SessionData - Track name, session type, weather, etc.RaceEntryData - Rider name, bike, race numberStandingsData - Position, gap, best lap for each riderBikeTelemetryData - Speed, RPM, gear, fuelIdealLapData - Best sector/lap times per rider// Example: Handler stores data, HUD reads it
// In handler:
PluginData::getInstance().updateSpeedometer(speed, gear, rpm, fuel);
// In HUD:
const BikeTelemetryData& data = PluginData::getInstance().getBikeTelemetry();
int speedMph = data.speedometer * MS_TO_MPH;
core/hud_manager.*)Owns and orchestrates all HUD instances. It:
update() on each HUD every framehandlers/*)Each handler processes a specific category of game events. They’re all singletons.
Run Handlers (player-only, single-player or your own bike):
EventHandler - Track loaded/unloadedRunHandler - Session start/stopRunLapHandler - Player crossed finish lineRunSplitHandler - Player crossed split timing pointRunTelemetryHandler - Physics tick (100Hz telemetry)Race Handlers (all riders in online races):
RaceEventHandler - Online race initializedRaceEntryHandler - Rider joined/leftRaceSessionHandler - Session state changesRaceLapHandler - Any rider completed a lapRaceSplitHandler - Any rider crossed split timing pointRaceClassificationHandler - Standings updateRaceTrackPositionHandler - Real-time positions of all ridersRaceCommunicationHandler - Penalties, warnings, state changesRaceVehicleDataHandler - Telemetry for all riders (during replays)Other Handlers:
DrawHandler - Frame rendering, FPS calculationTrackCenterlineHandler - Track geometry for map displaySpectateHandler - Camera/vehicle selection in spectator modecore/profile_manager.*)Manages HUD layout profiles for different game contexts:
Features:
core/rumble_profile_manager.*)Manages per-bike rumble profiles stored in a JSON file:
RumbleConfig (effect strengths, input ranges){save_path}/mxbmrp3/rumble_profiles.jsonFeatures:
core/odometer_manager.*)Manages per-bike odometer data stored in a JSON file:
{save_path}/mxbmrp3/odometer.jsonFeatures:
double precision to maintain accuracy at high distances (100k+ km)core/fmx_manager.*)Manages FMX (Freestyle Motocross) trick detection and scoring:
IDLE → ACTIVE → GRACE → CHAIN → COMPLETED/FAILEDData types are defined in fmx_types.h:
TrickType enum (27 trick types across ground, air, and combination categories)TrickInstance - Active or completed trick with rotation, timing, and scoring dataRotationTracker - Angular velocity integration for reliable rotation accumulationGroundContactState - Wheel contact, speed, and slip detectionFmxConfig - Adjustable detection/scoring thresholdsDisplay settings are split between global and per-profile:
hud/base_hud.*)Abstract base class that all HUDs inherit from. Provides:
Rendering Infrastructure:
m_quads - Vector of rectangles to draw (backgrounds, indicators)m_strings - Vector of text strings to displayaddString(), addBackgroundQuad(), addLineSegment()Dirty Flag System (for performance):
m_bDataDirty - True when underlying data changed, needs full rebuildm_bLayoutDirty - True when position changed, needs position update onlyrebuildRenderData() - Expensive: regenerate all quads/stringsrebuildLayout() - Cheap: just update positionsPositioning & Scaling:
m_fOffsetX, m_fOffsetY - Position offset (draggable)m_fScale - Size multipliervalidatePosition() - Keep HUD within screen boundsVisibility & Interaction:
m_bVisible - Show/hide togglem_bDraggable - Can user drag this HUD?handleMouseInput() - Process drag operationsFull HUDs (complex, highly configurable):
StandingsHud - Race standings table with columnsLapLogHud - History of lap times with sector breakdownLapConsistencyHud - Lap time consistency analysis with bars and trend linesIdealLapHud - Ideal (purple) sector times with gap comparisonMapHud - 2D track map with rider positions and zoom/range modeTelemetryHud - Throttle/brake/suspension graphsPerformanceHud - FPS, CPU usage graphsRadarHud - Proximity radar with nearby rider alertsPitboardHud - Pitboard-style lap/split informationRecordsHud - Track records from online databases (CBR or MXB-Ranked providers)TimingHud - Split time comparison popup (center display)GapBarHud - Live gap visualization bar with ghost position markerSettingsHud - Interactive settings menu UIFmxHud - FMX trick detection display with rotation arcs, chain stack, and scoringSessionHud - Session info (type, format, track, server, players, password)Widgets (simple, focused):
SpeedWidget - Speed and gear displayPositionWidget - Current race position (P1, P2…)LapWidget - Current lap numberTimeWidget - Session time remainingSpeedoWidget - Analog speedometer dialTachoWidget - Analog tachometer dialBarsWidget - Visual telemetry bars (throttle, brake, etc.)LeanWidget - Bike lean/roll angle display with arc gauge and steering barFuelWidget - Fuel calculator with consumption trackingNoticesWidget - Race status notices (wrong way, blue flag, last lap, finished)GamepadWidget - Controller visualization with button/stick/trigger displayVersionWidget - Plugin version displaySettingsButtonWidget - Settings menu toggle buttoninitialize()update() called -> if dirty, calls rebuildRenderData()getQuads() and getStrings() return render dataHere’s the pattern for adding a new HUD:
// 1. Create header: hud/my_hud.h
class MyHud : public BaseHud {
public:
MyHud();
void update() override;
bool handlesDataType(DataChangeType type) const override;
private:
void rebuildRenderData() override;
void rebuildLayout() override;
};
// 2. Implement: hud/my_hud.cpp
MyHud::MyHud() {
setDraggable(true);
setPosition(0.1f, 0.1f); // Top-left area
m_quads.reserve(1); // Background
m_strings.reserve(5); // Text lines
rebuildRenderData();
}
bool MyHud::handlesDataType(DataChangeType type) const {
return type == DataChangeType::SessionData; // What triggers updates?
}
void MyHud::update() {
if (isDataDirty()) {
rebuildRenderData();
clearDataDirty();
} else if (isLayoutDirty()) {
rebuildLayout();
clearLayoutDirty();
}
}
void MyHud::rebuildRenderData() {
m_quads.clear();
m_strings.clear();
auto dim = getScaledDimensions();
// Add background
addBackgroundQuad(START_X, START_Y, width, height);
// Add text
addString("Hello", x, y, Justify::LEFT, Fonts::ROBOTO_MONO,
ColorConfig::getInstance().getPrimary(), dim.fontSize);
setBounds(START_X, START_Y, START_X + width, START_Y + height);
}
// 3. Register in HudManager::initialize()
auto myHudPtr = std::make_unique<MyHud>();
m_pMyHud = myHudPtr.get();
registerHud(std::move(myHudPtr));
// 4. Add settings tab in SettingsHud (optional)
// 5. Add save/load in SettingsManager (optional)
The game engine handles actual rendering. We just provide instructions.
SPluginQuad_t)Rectangles with 4 corners. Used for:
struct SPluginQuad_t {
float m_aafPos[4][2]; // 4 corners, each with (x, y)
int m_iSprite; // 0 = solid color, 1+ = sprite index
unsigned long m_ulColor; // ABGR format
};
SPluginString_t)Text to render:
struct SPluginString_t {
char m_szString[100]; // Text content
float m_afPos[2]; // Position (x, y)
int m_iFont; // Font index (1-based)
float m_fSize; // Font size
int m_iJustify; // 0=left, 1=center, 2=right
unsigned long m_ulColor; // ABGR format
};
(0, 0) = top-left, (1, 1) = bottom-rightColors use ABGR (Alpha-Blue-Green-Red) format:
// Helper in plugin_utils.h
constexpr unsigned long makeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
return (static_cast<unsigned long>(a) << 24) |
(static_cast<unsigned long>(b) << 16) |
(static_cast<unsigned long>(g) << 8) |
static_cast<unsigned long>(r);
}
core/settings_manager.*)Saves/loads HUD configuration to INI file format:
[StandingsHud]
visible=1
showTitle=1
backgroundOpacity=0.8
scale=1.0
offsetX=0.05
offsetY=0.1
displayRowCount=20
[SpeedWidget]
visible=1
scale=1.0
offsetX=0.4125
offsetY=0.6882
Settings are saved:
{game_save_path}/mxbmrp3/mxbmrp3_settings.inihud/settings_hud.*)In-game settings menu (toggle with ~ key). Allows users to:
The settings UI uses a helper class (SettingsLayoutContext) for consistent layout across all tabs:
mxbmrp3/hud/settings/
├── settings_hud.h/.cpp # Main SettingsHud class
├── settings_layout.h/.cpp # SettingsLayoutContext helper
├── settings_tab_general.cpp # General preferences & profiles
├── settings_tab_appearance.cpp # Fonts & colors
├── settings_tab_standings.cpp # Standings HUD options
├── settings_tab_map.cpp # Track map options
├── settings_tab_radar.cpp # Radar options
├── settings_tab_*.cpp # Other tab implementations
└── ...
SettingsLayoutContext provides standardized control rendering:
| Method | Purpose |
|---|---|
addSectionHeader(title) |
Section divider with label |
addToggleControl(label, value, ...) |
On/Off toggle with < value > arrows |
addCycleControl(label, value, ...) |
Multi-value cycle control |
addStandardHudControls(hud) |
Common controls (Visible, Title, Texture, Opacity, Scale) |
addWidgetRow(name, hud, ...) |
Table row for Widgets tab |
addSpacing(factor) |
Vertical spacing |
Control Width Standardization: All controls use VALUE_WIDTH = 10 to ensure vertical alignment - users can toggle settings by moving the mouse vertically without horizontal adjustment.
Tooltips provide contextual help when hovering over controls:
mxbmrp3_data/
└── tooltips.json # Tooltip definitions
tooltips.json structure:
{
"version": 1,
"tabs": {
"standings": {
"title": "Standings",
"tooltip": "Live race standings showing position, gaps..."
}
},
"controls": {
"common.visible": "Show or hide this element during gameplay.",
"standings.rows": "Maximum number of rider rows to display.",
"map.range": "Zoom level. Full shows entire track..."
}
}
TooltipManager (core/tooltip_manager.h) is a header-only singleton that:
tooltips.json at startupgetTabTooltip(tabId) and getControlTooltip(controlId) methodsTooltips are rendered when hovering over:
The row-wide tooltip regions are created by passing a tooltipId parameter to control helpers like addToggleControl() and addCycleControl().
The plugin uses a dynamic asset discovery system that scans subdirectories at startup.
core/asset_manager.*)Discovers and registers assets from plugins/mxbmrp3_data/ subdirectories:
| Directory | File Type | Purpose |
|---|---|---|
fonts/ |
.fnt |
Bitmap fonts (game engine format) |
textures/ |
.tga |
HUD background textures with variants (e.g., standings_hud_1.tga) |
icons/ |
.tga |
Rider icons for map/radar display |
Texture Variants: Textures can have numbered variants (e.g., standings_hud_1.tga, standings_hud_2.tga). Users can cycle through variants in settings.
Icon Discovery: Icons are discovered alphabetically. Use AssetManager::getIconSpriteIndex(filename) to get the sprite index for a specific icon by filename. Settings store icon filenames for persistence.
User Asset Overrides: Users can override bundled assets by placing custom files in the save directory:
{save_path}/mxbmrp3/{fonts,textures,icons}/core/font_config.*)Maps semantic font categories to user-selected fonts:
| Category | Default Font | Usage |
|---|---|---|
TITLE |
EnterSansman-Italic | HUD titles |
NORMAL |
RobotoMono-Regular | Standard text |
STRONG |
RobotoMono-Bold | Emphasized text |
MARKER |
FuzzyBubbles-Regular | Handwritten style |
SMALL |
Tiny5-Regular | Map/radar labels |
Access via PluginConstants::Fonts::getTitle(), getNormal(), etc.
core/color_config.*)User-configurable color palette with semantic slots:
PRIMARY, SECONDARY - Main UI colorsPOSITIVE, NEGATIVE, WARNING, NEUTRAL - Status indicatorsACCENT - Highlightscore/input_manager.*)Polls Windows for input state each frame:
HUDs can be dragged with right-click:
handleMouseInput() detects click within boundsvalidatePosition() keeps HUD on screenThe plugin includes an optional auto-update system that checks for new versions on GitHub.
core/update_checker.*)Checks GitHub releases API for newer versions:
core/update_downloader.*)Downloads and installs plugin updates:
Update Flow:
UPDATE_AVAILABLEREADY → Restart requiredVendor Dependency: Uses vendor/miniz/ for ZIP extraction (public domain, single-file library).
Most core components are singletons:
class PluginData {
public:
static PluginData& getInstance() {
static PluginData instance;
return instance;
}
private:
PluginData() = default;
};
Why? The plugin API gives us one entry point. The game calls our exported functions - we don’t create multiple instances.
Instead of rebuilding every frame:
This is crucial for performance since Draw() is called every frame.
Use processDirtyFlags() for HUDs that rely on DataChangeType notifications:
void MyHud::update() {
processDirtyFlags(); // Handles isDataDirty/isLayoutDirty automatically
}
Some widgets display values that don’t trigger DataChangeType notifications (e.g., session time updates continuously but doesn’t fire SessionData). These widgets must poll PluginData and detect changes themselves:
void TimeWidget::update() {
// 1. Poll fresh data
int currentTime = pluginData.getSessionTime();
// 2. Compare to cached "last rendered" value
if (currentSeconds != m_cachedSeconds) {
setDataDirty(); // Self-mark dirty
}
// 3. Process dirty flags
if (isDataDirty()) {
rebuildRenderData();
m_cachedSeconds = currentSeconds; // Update cache AFTER rebuild
clearDataDirty();
clearLayoutDirty();
}
else if (isLayoutDirty()) {
rebuildLayout();
clearLayoutDirty();
}
}
Why can’t these use processDirtyFlags()? The cache update must happen after rebuildRenderData() using local variables calculated before the dirty check. The onAfterDataRebuild() hook exists for simpler cases, but these widgets use values computed at the top of update().
Some HUDs do change detection but don’t need post-rebuild caching:
void NoticesWidget::update() {
// Change detection - updates member state and marks dirty
if (wrongWay != m_bIsWrongWay) {
m_bIsWrongWay = wrongWay; // State updated BEFORE dirty check
setDataDirty();
}
processDirtyFlags(); // Can use standard helper
}
| Pattern | Use When | Examples |
|---|---|---|
processDirtyFlags() |
HUD relies on DataChangeType notifications | StandingsHud, IdealLapHud, MapHud |
| Hybrid | Polls data but caches state BEFORE dirty check | NoticesWidget, GapBarHud |
| Self-Detection | Needs to cache “last rendered value” AFTER rebuild | TimeWidget, PositionWidget, LapWidget |
All handlers use this pattern:
// In header
class MyHandler {
public:
static MyHandler& getInstance();
void handleSomething(Data* data);
};
// In .cpp
DEFINE_HANDLER_SINGLETON(MyHandler)
void MyHandler::handleSomething(Data* data) {
HANDLER_NULL_CHECK(data);
// Process data...
}
// PluginData notifies HudManager directly (no observer pattern overhead)
void PluginData::notifyHudManager(DataChangeType changeType) {
HudManager::getInstance().onDataChanged(changeType);
}
// HudManager marks relevant HUDs as dirty
void HudManager::onDataChanged(DataChangeType changeType) {
for (auto& hud : m_huds) {
if (hud->handlesDataType(changeType)) {
hud->setDataDirty();
}
}
}
All magic numbers live in plugin_constants.h:
namespace PluginConstants {
namespace FontSizes {
constexpr float NORMAL = 0.0200f;
constexpr float LARGE = 0.0300f;
}
// Colors are configurable via ColorConfig singleton
// ColorConfig::getInstance().getPrimary(), getSecondary(), etc.
namespace Session {
constexpr int RACE_1 = 6;
constexpr int RACE_2 = 7;
}
}
DEBUG_INFO("Plugin initialized");
DEBUG_INFO_F("Received %d riders", count);
DEBUG_WARN("Something unexpected");
Logs go to {save_path}/mxbmrp3/mxbmrp3.log
SCOPED_TIMER_THRESHOLD("MyFunction", 100); // Logs if > 100us
Don’t cache game data in HUDs for rendering - Always read fresh from PluginData when building render data. HUDs only cache formatted render data (m_quads, m_strings). Exception: Widgets that poll continuously-changing values (like session time) may cache “last rendered value” for change detection - see “Self-Detection Pattern” in Dirty Flag Pattern section.
0-based vs 1-based indexing - API uses 0-based lap numbers, UI shows 1-based. Check the API header comments.
No exceptions in callbacks - The game engine doesn’t handle C++ exceptions. Use defensive checks.
Thread safety - The plugin runs single-threaded (all callbacks on main thread). No synchronization needed.
Sprite indices are 1-based - Index 0 means “solid color fill”, not “first sprite”.
Font indices are 1-based - Font index 0 is invalid.
Icon ordering is alphabetical - Icons in mxbmrp3_data/icons/ are discovered alphabetically. Use filename-based lookups via AssetManager for persistence; icon additions/removals won’t break saved settings.
The plugin supports multiple Piboso racing games from a single codebase using compile-time game selection.
| Game | Mod ID | Vehicle Type | Splits | Unique Features |
|---|---|---|---|---|
| MX Bikes | mxbikes |
Bike (2 wheels) | 2 | Holeshot timing, Straight Rhythm |
| GP Bikes | gpbikes |
Bike (2 wheels) | 3 | ECU/TC/AW, Tread temps |
| WRS | wrs |
Car (4-6 wheels) | 2 | Rolling start, Turbo, Handbrake |
| KRP | krp |
Kart (4 wheels) | 2 | Session series, Qualify heats |
Each game produces its own DLL:
| Configuration | Output | Install Location |
|---|---|---|
| MXB-Release | mxbmrp3.dlo |
MX Bikes plugins/ |
| GPB-Release | gpbmrp3.dlo |
GP Bikes plugins/ |
| (future) | wrsmrp3.dlo |
WRS plugins/ |
| (future) | krpmrp3.dlo |
KRP plugins/ |
The Visual Studio project uses conditional compilation to include only the relevant API file:
<!-- MX Bikes API - excluded from GP Bikes builds -->
<ClCompile Include="vendor\piboso\mxb_api.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='GPB-Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='GPB-Release|x64'">true</ExcludedFromBuild>
</ClCompile>
Compile-Time (game/game_config.h):
#if GAME_HAS_HOLESHOT
void handleRaceHoleshot(const Unified::RaceHoleshotData* data);
#endif
Runtime (adapter constants):
if constexpr (Game::Adapter::HAS_RACE_SPEED) {
// Show speed trap data
}
Key feature flags:
GAME_HAS_HOLESHOT - MX Bikes onlyGAME_HAS_RACE_SPEED - All except MX BikesGAME_HAS_ECU - GP Bikes onlyGAME_HAS_TRACK_TEMP - All except MX BikesGAME_HAS_CRASH_STATE - MX Bikes, GP BikesGames have different numbers of timing splits. Unified types use a dynamic count:
struct RaceLapData {
int splits[MAX_SPLITS]; // MAX_SPLITS = 3
int splitCount; // Actual count (2 for MXB, 3 for GPB)
};
When Piboso releases a new API version:
mxb_api.h, gpb_api.h, etc.)The adapter layer isolates changes - core HUDs don’t need modification for most API updates.
Identical across all games:
SPluginQuad_t, SPluginString_t)Per-game variations:
| What | Where |
|---|---|
| API entry points (MX Bikes) | vendor/piboso/mxb_api.cpp |
| API entry points (GP Bikes) | vendor/piboso/gpb_api.cpp |
| Game adapters | game/adapters/*_adapter.h |
| Unified types | game/unified_types.h |
| Game config | game/game_config.h |
| Central state | core/plugin_data.cpp |
| HUD base class | hud/base_hud.cpp |
| All constants | core/plugin_constants.h |
| Asset manager | core/asset_manager.cpp |
| Font configuration | core/font_config.cpp |
| Color configuration | core/color_config.cpp |
| Update checker | core/update_checker.cpp |
| Update downloader | core/update_downloader.cpp |
| XInput / Rumble | core/xinput_reader.cpp |
| Rumble profiles manager | core/rumble_profile_manager.cpp |
| FMX trick detection | core/fmx_manager.cpp |
| FMX types | core/fmx_types.h |
| Settings UI | hud/settings/settings_hud.cpp |
| Settings layout helpers | hud/settings/settings_layout.cpp |
| Settings tabs | hud/settings/settings_tab_*.cpp |
| Tooltip definitions | mxbmrp3_data/tooltips.json |
| Tooltip manager | core/tooltip_manager.h |
| Settings file | {save_path}/mxbmrp3/mxbmrp3_settings.ini |
| Rumble profiles file | {save_path}/mxbmrp3/rumble_profiles.json |
| Log file | {save_path}/mxbmrp3/mxbmrp3.log |
| Build output (MX Bikes) | build/MXB-Release/mxbmrp3.dlo |
| Build output (GP Bikes) | build/GPB-Release/gpbmrp3.dlo |
| Runtime assets | {game_path}/plugins/mxbmrp3_data/{fonts,textures,icons}/ |
| User asset overrides | {save_path}/mxbmrp3/{fonts,textures,icons}/ |
| Task | Steps |
|---|---|
| Add new HUD | Create class, inherit BaseHud, register in HudManager |
| Add new data type | Add struct to PluginData, add DataChangeType enum |
| Add new setting | Add field to HUD, save/load in SettingsManager |
| Add settings tab | Create settings_tab_*.cpp, add tab enum, register in SettingsHud |
| Add tooltip | Add entry to tooltips.json, pass tooltipId to control helper |
| Add keyboard shortcut | Handle in HudManager::processKeyboardInput() |
| Add new handler | Create handler class, route from PluginManager |
| Add new font | Place .fnt file in mxbmrp3_data/fonts/ (auto-discovered) |
| Add new texture | Place .tga file in mxbmrp3_data/textures/ (auto-discovered) |
| Add new icon | Place .tga file in mxbmrp3_data/icons/ (auto-discovered, alphabetical order) |
| Add game-specific feature | Add to unified_types.h, update adapters, add feature flag to game_config.h |
| Support new game | Create adapter in game/adapters/, add API file in vendor/piboso/, update game_config.h |