trigger-segfault / OpenLRR

An open source re-implementation of LEGO Rock Raiders 🪨⛏
65 stars 5 forks source link

Randomized sound cues can pick multiple sounds at once (Legacy bug) #41

Closed RingtailRaider closed 2 years ago

RingtailRaider commented 2 years ago

Describe the issue Issue #37 fixed the problem with some sounds in a randomized sound list not playing, but now when Rock Raiders slip on a spider, it will sometimes play both slipping sounds at the same time. This does happen with other sound lists, though it seems to vary; it happens for the rock monster's footstep sounds during its normal walking animation, but not for its carrying boulder animation.

Expected behaviour The game should only pick one sound to play, not two or more at once.

Steps to reproduce Enter Driller Night! to hear the slipping sounds. Other sounds are harder to hear, and may require replacing them in the CFG with more distinct sounds.

Additional information

trigger-segfault commented 2 years ago

Code responsible In the Lws module, the following functions are responsible:

Cause of the issue This issue is a vanilla bug, but it's only prominent after #37 was fixed, as it's become much easier to notice different sounds playing at once. What causes this issue are sound triggers that execute on frame 0 (or whatever FirstFrame is set to) in an .lws scene file. Potentially they could also trigger on LastFrame if the scene has LOOPING set to FALSE in the .ae activity file (but this could only realistically happen if a scene's time is set to >= LastFrame. However for example, the highest time observed for Slipup is 174.*, always one unit below LastFrame).

The most easily noticeable instance of this bug is Rock Raiders slipping in Driller Night!. Below are the files responsible for. SND_Slipup can play twice, while SND_groan never plays twice.

Data/Mini-Figures/Pilot/Pilot.ae:

Lego* { SlipUp {
    FILE        VLP_Slipup
    LWSFILE     TRUE
    LOOPING     FALSE
} }

Data/Mini-Figures/Pilot/VLP_Slipup.lws:

FirstFrame 0
LastFrame 175
FrameStep 1
...
AddNullObject SFX,SND_groan,140
...
AddNullObject SFX,SND_Slipup,0

Problem in the code Sound triggers on frame 0 execute twice because when an LWS scene is created, both it's time and lastTime fields are set to 0.0f. This means the first time Key_Passed is called, it will return true. The next time Key_Passed is called, lastTime will still be 0.0f with time being a higher value, meaning the check will still return true.

https://github.com/trigger-segfault/OpenLRR/blob/67e59bb6df8f4ec473fc1bdc47058aa663e9b88e/src/openlrr/engine/gfx/Lws.cpp#L564-L584

Where Lws_KeyPassed is used

https://github.com/trigger-segfault/OpenLRR/blob/67e59bb6df8f4ec473fc1bdc47058aa663e9b88e/src/openlrr/engine/gfx/Lws.cpp#L544-L558

Proposed solution The best choice is to make either the upper or lower bound of these checks exclusive, rather than inclusive. After testing with lower checks as the exclusive bound (like the Gods98 commented out code at the bottom of the function), it became obvious that this won't work. Because minTime will be 0.0f for the two frames that would trigger the sound, neither frame would end up triggering the sound. So going with the upper checks is the only solution. This solution has solved the problem in all noticeable areas, but its unclear if there are scene files that have their sound trigger on LastFrame, if that is the case, then this'll be a problem.

     if ((maxTime - minTime) / totalTime < 0.5f) {
-        if (keyTime <= maxTime && keyTime >= minTime) return true;
+        if (keyTime <= maxTime && keyTime > minTime) return true;
     } else {
-        if (keyTime >= maxTime || keyTime <= minTime) return true;
+        if (keyTime > maxTime || keyTime <= minTime) return true;
     }

More testing will be done before pushing this solution. I'll be scanning all lws files for scenarios of LastFrame being used. Additionally I think it'd be best to create a separate function Lws_KeyPassedExclusive for checking to play a sound. While keeping the inclusive Lws_KeyPassed for checking to stop a sound.