Skip to main content

Data and Handles

Getters return optionals

Every getter in the C++ SDK returns std::optional. An empty optional means the value cannot be answered right now: you are not in a world, the module is unavailable, or the handle you asked through is no longer alive. There is no error code to inspect; emptiness is the whole answer.

Give each read a default with value_or and the "not right now" cases take care of themselves:

const TruckersMP::Player player = g_session->Player().GetLocalPlayer().value_or( {} );
const std::string name = player.GetUsername().value_or( "unknown" );

An invalid handle answers every getter with an empty optional, so chains like this need no checks along the way.

value_or fits wherever a default is harmless: text you display, a value you log, or an empty list. When there is no harmless default, for example, when the value becomes a key in your own data, check the optional instead of working with defaults.

Entity handles

Players, vehicles, and trailers cross the SDK boundary as small handle objects (e.g., TruckersMP::Player, TruckersMP::Vehicle). A handle is an opaque reference, not the entity itself:

  • Copying a handle is trivial; it is a 64-bit value plus a session pointer.
  • IsValid() tells you whether the handle refers to anything at all.
  • Every getter on a stale handle simply returns an empty optional. Nothing crashes; the entity is just gone.

A handle stays meaningful as long as its entity exists for your client: a player handle while that player is streamed in, a vehicle or trailer handle while it is present in the game world.

Do not store handles. Obtain them fresh every time, from events or from the getters of modules and other handles (for example, GetLocalPlayer(), player.GetVehicle()), use them, and let them go. If you track entities across frames, key your own data on a stable ID (player.GetPlayerID()) instead of keeping the handle.

// Good: track by ID, resolve handles per event.
std::unordered_set< int32_t > g_seen;

g_session->Player().OnStreamIn.Register( []( TruckersMP::PlayerStreamInEvent &e )
{
if( const std::optional< TruckersMP::Int32 > id = e.GetPlayer().GetPlayerID() )
{
g_seen.insert( *id );
}
} );

Strings

Strings are UTF-8. The wrapper copies every string the client hands you into a std::string you own, and frees the client allocation for you. There is nothing to manage.

Strings you pass in (LogMessage, ShowNotification) are std::string_view: the client copies what it needs during the call, so any source works, including temporaries.

Lists

List getters return owned vectors, copied out of client memory in one step:

for( const TruckersMP::PackageInfo &pkg : g_session->Gameplay().GetMountedPackages().value_or( {} ) )
{
Log( pkg.name );
}

The vector and its elements are yours; keep them as long as you like.

Value types

Simple types, such as position, rotation, and color, cross as plain structs: Float3, Double3, Quaternion, Placement, etc. They are ordinary value types; copy and store them freely.