As part of a general need to slow down and stabilise currently-implemented features before taking on new ones, I've decided to stop any and all progress on SuperLemmix until this issue is fixed.
Basically, the Rewind feature just isn't working how I'd originally hoped, and is in serious consideration for being revoked if I can't fix it.
The desired function of it is to be identical to the effect of setting a hotkey to "- 3 Frameskip" and then
pressing and holding that hotkey whilst in-game - this is what I want the Rewind button to do! The game is clearly capable of receiving this input, it's just a question of HOW? The current Rewind mode implementation aims to perform this action, but slightly fails.
The problems with it are these:
1) Occasionally, when Rewinding over long periods, instead of performing a (- 3) skip, as intended, it will "jump" backwards several hundred frames. There seems to be a pattern, it can be replicated thus:
- Load any level with an infinite time limit and no way for the lemmings to die if no action is taken
- Skip/FF ahead into the level until at least the 3-minute mark (5-minute mark is better)
- Press Rewind, and keep an eye on the time display
- At first, it will count down one second at a time: 2:59, 2:58, 2:57, etc
- After a while, it will start to "jump" 7 seconds at a time: 2:43, 2:42, 2:41, 2:40, (jump), 2:33, 2:32, 2:31, 2:30, (jump), 2:23, 2:22, 2:21, etc
- The jumps will then become more pronounced, jumping 27 seconds, and then 57 seconds, until it just skips back to the start of the level
Note: Namida has suggested that this could be because the procedure isn't jumping to the correct frame each time.
2) Sometimes, whilst the Rewind button is in the "pressed" state (with the selected graphic drawn, and the Rewind mode in effect), it won't then respond to subsequent button presses, and so won't cancel out of Rewind mode. This is intermittent - it happens about 20% of the time. It also happens when using the Rewind hotkey, so we can eliminate it being anything to do with the mouse, or the mousedown event handling.
Here is all code relating to the Rewind feature. If anyone has
any programming experience, I could really do with some help with this. If I can't find a solution, the feature will have to be removed
spbRewind:
begin
if Game.IsSuperLemming then Exit;
if fGameWindow.GameSpeed in [gspFF, gspPause, gspSlowMo] then
fGameWindow.GameSpeed := gspNormal;
if Game.TurboPressed then Game.fTurboPressed := False;
if not Game.RewindPressed then
Game.fRewindPressed := True
else if Game.RewindPressed then
Game.fRewindPressed := False;
end;
if Game.RewindPressed then
begin
SkillPanel.DrawButtonSelector(spbRewind, True);
if GameParams.ClassicMode then
Game.CancelReplayAfterSkip := true;
if not RewindTimer.Enabled then
RewindTimer.Enabled := True;
end else if not Game.RewindPressed then
begin
SkillPanel.DrawButtonSelector(spbRewind, False);
if RewindTimer.Enabled then
RewindTimer.Enabled := False;
end;
RewindTimer := TTimer.Create(Self);
RewindTimer.Interval := 59; // hotbookmark
RewindTimer.OnTimer := DoRewind;
//elsewhere
RewindTimer.Free;
procedure TGameWindow.DoRewind(Sender: TObject);
begin
//start-of-level check needs to give a few frames' grace
if Game.CurrentIteration <= 10 then
begin
RewindTimer.Enabled := False;
Game.RewindPressed := False;
Game.fIsBackstepping := False;
end else
GoToSaveState(Game.CurrentIteration - 3);
end;
procedure TGameWindow.GotoSaveState(aTargetIteration: Integer; PauseAfterSkip: Integer = 0; aForceBeforeIteration: Integer = -1);
{-------------------------------------------------------------------------------
Go in hyperspeed from the beginning to aTargetIteration
PauseAfterSkip values:
Negative: Always go to normal speed
Zero: Keep current speed
Positive: Always pause
-------------------------------------------------------------------------------}
var
UseSaveState: Integer;
begin
if aForceBeforeIteration < 0 then
aForceBeforeIteration := aTargetIteration;
CanPlay := False;
if not Game.RewindPressed then
begin
if PauseAfterSkip < 0 then
begin
Game.fIsBackstepping := False;
GameSpeed := gspNormal;
end else if ((aTargetIteration < Game.CurrentIteration) and GameParams.PauseAfterBackwardsSkip)
or (PauseAfterSkip > 0) then
begin
if Game.fIsBackstepping then GameSpeed := gspPause;
end;
end;
if (aTargetIteration <> Game.CurrentIteration) or fRanOneUpdate then
begin
// Find correct save state
if aTargetIteration > 0 then
UseSaveState := fSaveList.FindNearestState(aForceBeforeIteration)
else if fSaveList.Count = 0 then
UseSaveState := -1
else
UseSaveState := 0;
// Load save state or restart the level
if UseSaveState >= 0 then
Game.LoadSavedState(fSaveList[UseSaveState])
else
Game.Start(true);
end;
fSaveList.ClearAfterIteration(Game.CurrentIteration);
if aTargetIteration = Game.CurrentIteration then
begin
SetRedraw(rdRedraw);
if Game.CancelReplayAfterSkip then
begin
Game.RegainControl(true);
Game.CancelReplayAfterSkip := false;
end;
end else begin
// start hyperspeed to the desired interation
fHyperSpeedTarget := aTargetIteration;
end;
CanPlay := True;
end;
lka_Skip: if Game.Playing then
if not (GameParams.HideFrameskipping or Game.IsSuperlemming) then
if func.Modifier < 0 then
begin
if GameParams.NoAutoReplayMode then Game.CancelReplayAfterSkip := true;
if CurrentIteration > (func.Modifier * -1) then
begin
Game.fIsBackstepping := True;
GotoSaveState(CurrentIteration + func.Modifier);
end else begin
Game.fIsBackstepping := False;
GotoSaveState(0);
end;
I'm pretty sure that's all the code relevant to this feature. I have tried testing to see whether the button is setting the flag correctly, and it is. Other buttons do set the RewindPressed flag to false (for example, pressing Pause or FF sets the flag to false in order to cancel the Rewind mode), but even if these lines are commented out, the above problems both still occur.
I have also tried using Message boxes and Debug strings to try and figure out what's happening with the Rewind flags, and to track the CurrentIteration, but so far nothing has helped - this could be because I'm not checking the right things in the right places, though.
I've had these 2 ideas, which between them might help to solve the issue, but I'd rather see if anyone with programming experience has any better ideas before trying these:
1) The first issue might be fixable by setting the CurrentIteration (frame number) at the point that the Rewind button is pressed, and calculating a number of "target steps" from that. So, for example, let's say that the Rewind button is pressed at frame 342, we could:
- Calculate 100 steps from 342, and set each as an "n - y" value, with n being the origin frame and y being - 3, then - 6, then - 9, then - 12, then -15, etc (there must be some algorithm which can achieve this)
- Set the 100 steps as a "Rewind map" of target frames
- Call GoToSaveState for each of the target frames on the "Rewind map", at an interval set by the TTimer
This way, the Rewind procedure isn't just calling a skip to "the current frame - 3", but rather "the current frame -
a set number of values, iterating through each at the rate set by the TTimer." This way, there can be no doubt that the Rewind procedure is hitting the correct frames. When Rewind mode is cancelled, the "Rewind map" is cleared.
2) The second issue might be fixable by having the button be a "whilst-pressed" rather than a "toggle on/off" control. This would work great for the hotkey, since it's actually currently
better to simply set a hotkey to "- 3 Frameskip" and then press and hold it. The Rewind hotkey, then, would simply do this.
In the meantime, the button would only respond whilst the mouse button is being pressed over the Rewind button, and would cancel when the mouse button is un-pressed. I've tested this, and it works with 100% success. The only obvious issue is that it's slightly more effort than toggling the button on/off, and it means it doesn't work the same way as the FF button, which I feel it probably should.
Any and all ideas, suggestions and help are welcome. I really don't want to have to cull this feature, but if we can't get it working between us, then it will have to go.