The problem was in my use of a TMap to store gameplay update times. The GetAuthGameMode call (the only one remaining in my code base that hadn’t been replaced by GetGameMode!) was in the game’s custom character class. After the character is loaded into the level it performs some initialization actions and, when finished, informs the active GameMode it is ready to play. When the GameMode recieves the update from the character it triggers some final actions before beginning actual gameplay (removes loading screen, plays quick animation etc.). At this point it also logs a time duration within a TMap.
Here’s where my problem happened: if a duration for the current level already exists, it pulls the existing duration and adds the current duration to the existing value and stores the new total; if the duration doesn’t exist for the current level, it stores the current duration to the TMap under a new label.
//fixed and correct code
if (Durations.Contains(DurationLabel)) {
CurrentTimespan += *Durations.Find(DurationLabel);
}
Durations.Emplace(DurationLabel, CurrentTimespan);
There was a typo in the code, though. Rather than adding a new label if the level duration didn’t exist within the TMap, the code tried to pull the value and add it to the current duration. When you pull a value from a TMap with Find() you don’t get the actual value, you get a pointer to the value, but I was ensuring the label didn’t exist before I called it! Thus, I got a null pointer and caused my crash every time the game tried to load the first level.
//the conditional should run if Contains() returns TRUE
//but here it's set to run if it returns FALSE. OOPS!
if (!Durations.Contains(DurationLabel)) {
CurrentTimespan += *Durations.Find(DurationLabel);
}
Durations.Emplace(DurationLabel, CurrentTimespan);
I lost three days to an errant exclamation point!
Originally, the code here was set up as an if->else, with the FALSE condition first. When the code was restructured to its current form the conditional wasn’t modified, resulting in the logical error.
//the original code before restructured/streamlined;
//this code is logically correct, as is the fixed code above
if (!Durations.Contains(DurationLabel)) {
Durations.Add(DurationLabel, CurrentTimespan);
} else {
CurrentTimespan += *Durations.Find(DurationLabel);
Durations.Emplace(DurationLabel, CurrentTimespan);
}
As a side note, avoiding problems like this is probably one of the best parts of Blueprints. Thanks, Epic, for the platform and all the support.
Special thanks to Jamie Dale for seeing what I missed, getting me pointed in the right direction, and presenting alternatives to jumping out a window.