Skip to content

Ghosts

The leaderboard ghosts of the track on screen: the top entries of its Steam leaderboard and your own, each with the path its ball took. Everything here needs host 0.14.0. Loading takes a few seconds (every replay is a download); read State to follow it. Nothing here races: loading ghosts never touches your own run.

Load

bool Ghosts::Load(const string &in leaderboard = "", int count = 25)

Starts loading the top count entries of a leaderboard (0 for every entry: each replay is about 80 KB, so a popular track's whole leaderboard is a long download), plus your own entry. leaderboard "" is the leaderboard of the track on screen (the game's own tracks and workshop tracks alike); a name loads that leaderboard instead (the game's tracks use their level name, "Map_LethTrial_01"). Loading the same leaderboard again keeps the replays already loaded that are still wanted and downloads only the rest; another leaderboard replaces them. False if it can't start.

Example
Ghosts::Load("", 25);    // the top 25 of the track on screen
Ghosts::Load("", 100);   // then 75 more
Ghosts::Load("", 0);     // then everyone

LoadFile

int Ghosts::LoadFile(const string &in file)

Runs from a text file in the plugin's own folder instead of a leaderboard, replacing the runs loaded (anything still downloading is dropped; Leaderboard() reads "local"). Every other Ghosts function works on them. Returns how many runs it read, or -1 if the file can't be read, isn't this format, or isn't the plugin's own (the same rule as Plugins::ReadFile). The format, tab-separated:

RUN1  ox  oy  oz  unit
name  time  skin  t0  dt  samples

The first line gives an origin and a unit (game units, cm); each next line is a run: its name, its time in seconds, a ball skin material path (- for none), the first sample's time and the time between samples, then the samples: base64 of signed 16-bit x, y, z per sample, a position of origin + value x unit. Speed and the camera's view are worked out from the path. Requires host 0.25.0.

Example
int runs = Ghosts::LoadFile("runs/best.txt");

PrefetchFile

void Ghosts::PrefetchFile(const string &in file)

Starts reading and parsing a LoadFile file in the background, so a later LoadFile of it is quick (for stepping through many files). Requires host 0.25.0.

Example
Ghosts::PrefetchFile("runs/" + (shown + 1) + ".txt");

PrefetchReady

bool Ghosts::PrefetchReady(const string &in file)

Whether a PrefetchFile is done. LoadFile works either way; before this is true it reads the file itself. Requires host 0.25.0.

Example
if (Ghosts::PrefetchReady(next))
    Ghosts::LoadFile(next);

CrowdTrailDetail

void Ghosts::CrowdTrailDetail(int points, double budgetMs)

How detailed crowd trails are: points per trail at most (8 to 2000, default 160) and the time spent building them each frame (0.5 to 40 ms, default 3). For every crowd. Requires host 0.25.0.

Example
Ghosts::CrowdTrailDetail(60, 2);          // lighter trails for many runs

CrowdTrailsPending

bool Ghosts::CrowdTrailsPending()

Whether any of your crowds' trails are still waiting to be built. Requires host 0.25.0.

Example
loading.visible = Ghosts::CrowdTrailsPending();

CrowdTrailStyle

bool Ghosts::CrowdTrailStyle(int id, bool glow, float brightness, double fadeSeconds = 0, float fadeMin = 0)

How the crowd's trails look: glowing at a brightness (opaque trails only; set before CrowdTrails), and, for see-through trails built in chunks, each chunk fading with age, from the trails' opacity down to fadeMin over fadeSeconds of the clock given to CrowdTrailsUpTo (0: no fading). False if it isn't your crowd. Requires host 0.25.0.

Example
Ghosts::CrowdTrailStyle(crowd, false, 1, 3.0, 0.05f);     // the last 3 s of each trail, fading out
Ghosts::CrowdTrails(crowd, 4, 0.5f, 0.5);

CrowdBallScale

bool Ghosts::CrowdBallScale(int id, double scale)

The size of the crowd's balls that wear skins (CrowdSkins), 1 the game's ball. Requires host 0.25.0.

Example
Ghosts::CrowdBallScale(crowd, 0.8);

CrowdGroupGlass

bool Ghosts::CrowdGroupGlass(int id, int group, float opacity)

One colour group's balls as tinted glass of its colour at opacity (below 1), for dimming some balls; they then keep their colour when the crowd wears skins. 1 makes them glow again. False if the crowd or group isn't there. Requires host 0.25.0.

Example
Ghosts::CrowdGroupGlass(crowd, 1, 0.3f);     // group 1 dimmed

State

string Ghosts::State()

How the load is going: "idle", "loading ..." (with what it's doing), "ready", or "error: ...".

Example
if (Ghosts::State() == "ready")
    Log::Info(Ghosts::Count() + " ghosts loaded");

Leaderboard

string Ghosts::Leaderboard()

The Steam leaderboard's name, once it's known.

Example
titleText.text = Ghosts::Leaderboard();

Entries

int Ghosts::Entries()

How many entries the leaderboard has in all, once the top entries have arrived (0 until then).

Example
statusText.text = Ghosts::Count() + " of " + Ghosts::Entries();

WithoutReplay

int Ghosts::WithoutReplay()

How many of the entries asked for have no replay file on Steam, so there is nothing to load for them: why loading the top 1000 can give 994 runs.

Example
string missing = Ghosts::WithoutReplay() > 0 ? Ghosts::WithoutReplay() + " have no replay" : "";

Count

int Ghosts::Count()

How many ghosts have arrived, in rank order. The list grows while replays download.

Example
for (int i = 0; i < Ghosts::Count(); i++)
    Log::Info(Ghosts::Rank(i) + ". " + Ghosts::Name(i));

Name

string Ghosts::Name(int)

The player's name, as saved with their replay.

Example
Log::Info(Ghosts::Name(0) + " holds the record");

Rank

int Ghosts::Rank(int)

The entry's global rank on the leaderboard (1 is the record).

Example
int rank = Ghosts::Rank(i);

Time

double Ghosts::Time(int)

The run's time in seconds.

Example
timeText.text = formatFloat(Ghosts::Time(i), "", 0, 3);

IsOwn

bool Ghosts::IsOwn(int)

Whether the entry is yours.

Example
if (Ghosts::IsOwn(i))
    nameText.text += " (you)";

SampleCount

int Ghosts::SampleCount(int)

How many samples the replay has: the ball's position about ten times a second.

Example
int samples = Ghosts::SampleCount(i);

Sample

bool Ghosts::Sample(int, int, double &out, double &out, double &out, double &out)

One sample of a replay: its time from the start and where the ball was. False if there's no such sample.

Example
// the whole path, for Draw::Tube
array<double> path;
double t, x, y, z;
for (int j = 0; j < Ghosts::SampleCount(i); j++)
    if (Ghosts::Sample(i, j, t, x, y, z))
    {
        path.insertLast(x);
        path.insertLast(y);
        path.insertLast(z);
    }

Position

bool Ghosts::Position(int, double, double &out, double &out, double &out)

Where the ball was at a time from the start of the run, between samples along a straight line (before the start or after the end: the first or last sample).

Example
double x, y, z;
if (Ghosts::Position(i, playTime, x, y, z))
    Draw::Move(ball, x, y, z);

Splits

array<double>@ Ghosts::Splits(int)

The times the run took its checkpoints, in the order it took them.

Example
array<double>@ splits = Ghosts::Splits(i);

CheckpointOrder

array<int>@ Ghosts::CheckpointOrder(int)

The numbers of the checkpoints the run took, in order: for each split, the checkpoint nearest to where the ball was then. Two runs with the same order took the same route. 0 for a split no checkpoint matched.

Example
array<int>@ order = Ghosts::CheckpointOrder(i);
string route = "";
for (uint k = 0; k < order.length(); k++)
    route += (k == 0 ? "" : ",") + order[k];

CheckpointCount

int Ghosts::CheckpointCount()

How many checkpoints the track on screen has (the finish is one of them).

Example
Log::Info(Ghosts::CheckpointCount() + " checkpoints");

PlayerBall

int Ghosts::PlayerBall(int ghost, bool realSkin = false)

The player's own ball for a ghost, as the game shows other players' balls: their skin (or special ball), their accessory, see-through like the game's ghosts, with their name over it. With realSkin true, it wears the player's real skin and accessory, fully opaque, instead of the game's rival-ghost look (needs host 0.25.0). Returns a Draw id (show, hide and remove it like a shape), or 0 if it couldn't be made. Move it with PlaceBall.

Example
int ball = Ghosts::PlayerBall(0);
int real = Ghosts::PlayerBall(0, true);     // as the player sees their own ball

PlaceBall

bool Ghosts::PlaceBall(int id, int ghost, double time)

Puts a ball from PlayerBall where that ghost was at time, turning the way their ball turned.

Example
Ghosts::PlaceBall(ball, 0, time);

ShowBallName

bool Ghosts::ShowBallName(int id, bool shown)

Shows or hides the name over a ball from PlayerBall.

Example
Ghosts::ShowBallName(ball, false);

CrowdCreate

int Ghosts::CrowdCreate(double radius, const array<float>@ palette)

A crowd: many ghosts' balls drawn at once, for thousands of them, where a ball each would be too many objects. The balls are glowing spheres of radius, in colour groups (palette: red, green, blue for each group). Returns a Draw id (remove it like a shape), or 0.

Example
array<float> palette = {1, 0.2f, 0.2f,  0.2f, 0.6f, 1};    // two groups: red, blue
int crowd = Ghosts::CrowdCreate(25, palette);

CrowdMembers

bool Ghosts::CrowdMembers(int id, const array<int>@ ghosts, const array<int>@ groups)

Which ghosts are in the crowd, and each one's colour group (by place in the palette).

Example
array<int> who = {30, 31, 32}, groups = {0, 1, 0};
Ghosts::CrowdMembers(crowd, who, groups);

CrowdSkins

bool Ghosts::CrowdSkins(int id)

Puts the crowd's members in their players' own skins: a ball the game's size, one instanced mesh per skin. They don't roll. A member whose skin doesn't load keeps its colour. Call it after CrowdMembers.

Example
Ghosts::CrowdSkins(crowd);

CrowdTrails

bool Ghosts::CrowdTrails(int id, double radius, float opacity, double chunkSeconds = 0)

A see-through trail for each member shown (CrowdTimes), in its colour group's colour (radius in cm, opacity from 0 to 1, as Draw::Tube's). They are built a few each frame, top ranks first, so there's no hitch. Several trails share one mesh, so they can't be hidden one by one: call it again after CrowdTimes changes who's shown, and they're built again for those.

Example
Ghosts::CrowdTrails(crowd, 4, 0.25f);

CrowdTrailsUpTo

bool Ghosts::CrowdTrailsUpTo(int id, double time)

With the trails built in chunks (CrowdTrails' last argument, in seconds), shows only the chunks each run has finished by time on its own clock: the trails so far, for a trail that grows as the balls roll.

Example
Ghosts::CrowdTrails(crowd, 4, 0.25f, 1.0);
Ghosts::CrowdTrailsUpTo(crowd, playTime);       // every frame

CrowdShowTrails

bool Ghosts::CrowdShowTrails(int id, bool shown)

Shows or hides the crowd's trails; ones still being built follow.

Example
Ghosts::CrowdShowTrails(crowd, false);

CrowdTimes

bool Ghosts::CrowdTimes(int id, const array<double>@ offsets, const array<bool>@ shown)

Per ghost (indexed by ghost, for every ghost loaded): its time offset (added to the time CrowdPlace is given), and whether it's shown.

Example
Ghosts::CrowdTimes(crowd, offsets, shown);

CrowdPlace

bool Ghosts::CrowdPlace(int id, double time)

Puts every ball of the crowd where its ghost is at time (plus its offset), in one go. Call it every frame.

Example
Ghosts::CrowdPlace(crowd, time);

View

bool Ghosts::View(int, double, double &out, double &out, double &out, double &out, double &out, double &out)

Where a ghost's player was looking at time t: their camera as the game would have placed it (the ball's camera, turned the way they turned it), for watching the run as they saw it. Gives the camera's position, pitch, yaw and field of view, ready for Camera::Set. The game's camera also pulls in when it would go through the track; this doesn't. False if the replay didn't record where they looked.

Example
double x, y, z, pitch, yaw, fov;
if (Ghosts::View(0, time, x, y, z, pitch, yaw, fov))
    Camera::Set(x, y, z, pitch, yaw, fov);

Checkpoint

bool Ghosts::Checkpoint(int, int &out, double &out, double &out, double &out)

One of the track's checkpoints, by index (0 to CheckpointCount - 1, in order of their number): its number and where it is. Checkpoints a track doesn't number (the game's own tracks, such as Leth Trial, leave them all unnumbered) are numbered 1, 2, 3 ... here, so each still has a number of its own.

Example
int number;
double x, y, z;
if (Ghosts::Checkpoint(0, number, x, y, z))
    Log::Info("checkpoint " + number + " at " + x + ", " + y + ", " + z);

ReplayBall

int Ghosts::ReplayBall(const string &in replay, bool realSkin = false)

A ghost's ball from replay text (the game's ghost JSON, e.g. downloaded from a plugin's own server): the game's player ball dressed as that replay's player, with their name tag, in the game's rival-ghost look (realSkin: their own skin at full opacity). A Draw id (remove it with Draw::Remove), or 0 if the text isn't a replay. Place it with PlaceReplayBall. Requires host 0.26.0.

Example
int ghost = Ghosts::ReplayBall(Net::Body(request));

PlaceReplayBall

bool Ghosts::PlaceReplayBall(int id, double time)

Puts a replay ball where its run was at time t (seconds), turning as it turned: call it every frame with the race's clock (Runs::Elapsed()) to race against it. False once the ball is gone (a new map, or removed). Requires host 0.26.0.

Example
double t = Runs::Elapsed();
Ghosts::PlaceReplayBall(ghost, t < 0 ? 0 : t);

ReplayTime

double Ghosts::ReplayTime(int id)

A replay ball's run time (its bestTime, seconds), -1 for no such ball. Requires host 0.26.0.

Example
Log::Info("ghost time " + Ghosts::ReplayTime(ghost));

ReplayName

string Ghosts::ReplayName(int id)

The player name in a replay ball's replay. Requires host 0.26.0.

Example
Log::Info("racing " + Ghosts::ReplayName(ghost));