DescentDevelopers / Descent3

Descent 3 by Outrage Entertainment
GNU General Public License v3.0
2.73k stars 231 forks source link

Failing Mercenary 01 counts as completion #468

Open JeodC opened 1 week ago

JeodC commented 1 week ago

Might get some hate for this. In Mercenary 01 you must escape a complex during a countdown. If you fail, the level restarts. Internally, though, the level is counted as completed as you can exit, go to New Game, and choose the second level.

JeodC commented 1 week ago

Wow, my permissions are messed up if I can't even assign a label. Anyway, I know of two levels that can fail: Merc 01 and Retribution 05. These could be tested to see if it's a scripts fault.

gamesequence.cpp#L1275 has a case for when a level fails. It still calls EndLevel(), and passes 0 to it (indicating failure), so let's go to that.

gamesequence.cpp#L1892 where the actual function is, the function CurrentPilotUpdateMissionStatus(); seems to be where highest level completed gets updated. Notably, the if conditional, if (!(Game_mode & GM_MULTI)) {, does not check for the state variable that's passed to EndLevel()--and rightfully so, since pilot data should generally be updated regardless to track other things. Let's go to pilot.cpp#L1532 to see how it works.

We can see that mission_to_use.highest_level gets initialized to 0 when the mission doesn't exist in the pilot file. When a mission is initialized and highest level is zero, we don't see the level select prompt--the mission always starts at the first level. After a mission is initialized (or already exists), we come to an if conditional that determines the number of levels in the mission (so we can set the finished flag later).

The part we're interested in is where it bumps the highest_level variable.

if (Current_mission.cur_level > mission_to_use.highest_level && !IsCheater &&
        Current_mission.cur_level < max_level) { // cheaters don't win
      ASSERT(!(Game_mode & GM_MULTI));
      mission_to_use.highest_level = Current_mission.cur_level;
      mission_to_use.ship_permissions = Players[0].ship_permissions;
    }

If the current level is greater than the highest level, the highest level gets set to the current level. This is likely where the error lies. Since highest_level is initialized to zero and cur_level would be 1, then highest_level < cur_level and thus gets set to 1 even if the player failed the mission. Level 1 still restarts, but if the player quits and goes to New Game, and selects the Mercenary mission, they can now start at Level 2. But how can that be, if highest_level is still just 1?

Over in menu.cpp#L1327, the missions menu gets the highest level achieved by the pilot, and increments it by 1. This normally allows them to select the next level in sequence, but in our case, highest_level got updated to 1 even though we failed the level. That means the level select got incremented to 2.

JeodC commented 1 week ago

I think the ideal solution is to pass the state variable to CurrentPilotUpdateMissionStatus so we can check for it when it tries to bump highest_level by setting it to the current level.