ScummVM API documentation
secondarymovie.h
1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #ifndef NANCY_ACTION_SECONDARYMOVIE_H
23 #define NANCY_ACTION_SECONDARYMOVIE_H
24 
25 #include "common/ptr.h"
26 
27 #include "engines/nancy/action/actionrecord.h"
28 #include "engines/nancy/action/interactivevideo.h"
29 #include "engines/nancy/movieplayer.h"
30 
31 namespace Nancy {
32 namespace Action {
33 
34 class InteractiveVideo;
35 
36 // Plays an AVF or Bink video. Optionally supports:
37 // - playing a sound;
38 // - reverse playback;
39 // - moving with the scene's background frame;
40 // - hiding of player cursor (and thus, disabling input);
41 // - setting event flags on a specific frame, as well as at the end of the video;
42 // - changing the scene after playback ends
43 // Mostly used for cinematics, with some occasional uses for background animations.
44 //
45 // Construct with `isRandom = true` for Nancy 11's AT_PLAY_RANDOM_MOVIE (AR 45):
46 // the chunk holds a list of sequences and one is picked at readData() time.
48 public:
49  static const byte kMovieSceneChange = 5;
50  static const byte kMovieNoSceneChange = 6;
51 
52  static const byte kPlayerCursorAllowed = 1;
53  static const byte kNoPlayerCursorAllowed = 2;
54 
55  static const byte kPlayMovieForward = 1;
56  static const byte kPlayMovieReverse = 2;
57 
58  struct FlagAtFrame {
59  int16 frameID;
60  FlagDescription flagDesc;
61  };
62 
63  // What makes a next-sequence entry the one picked once the current sequence
64  // finishes. Entries carry a percent weight, unless the chunk tags them with
65  // one of the negative "special flag" values below; a tagged entry is picked
66  // whenever its condition holds, ahead of the weighted roll.
67  enum NextCondition {
68  kNextWeighted = 0, // ordinary percent weight
69  kNextEqualChance, // -1: uniform share among the entries
70  kNextIfHovered, // -2: the mouse is over the movie
71  kNextIfNotHovered, // -3: it isn't
72  kNextIfChannel13Playing, // -4
73  kNextIfChannel12Playing, // -5
74  kNextIfCommanded // -6: a movie was commanded; "commanded_movie" plays it, "random_movie" ignores it
75  };
76 
77  // Name of the next sequence to chain to once the current one finishes,
78  // plus what makes it the one picked.
79  struct NextSequenceRef {
80  Common::Path name;
81  uint16 weight = 0;
82  NextCondition condition = kNextWeighted;
83  };
84 
85  // `name` is both the sequence id and the movie filename.
86  struct RandomSequence {
87  Common::Path name;
88  int16 pauseFlag = -1; // Nancy18: an event flag that is set for as long as the sequence pauses
89  uint16 startFrame = 0;
90  uint16 lastFrame = 0;
91  int32 minPauseMs = 0;
92  int32 maxPauseMs = 0;
93  // Weight assigned to "stay on this sequence" in the weighted random
94  // pick. A roll inside [0, stayWeight) means "don't transition";
95  // instead pause for [minPauseMs, maxPauseMs] before moving on.
96  uint16 stayWeight = 0;
97  Common::Array<NextSequenceRef> nextSequences;
98  // Every weighted entry takes an equal share of the pick.
99  bool equalChanceNext = false;
100  };
101 
102  // Which of the action record types sharing this class is being played.
103  enum MovieType {
104  kSecondaryMovie, // AR 53, up to Nancy13
105  kSecondaryMovieTerse, // AR 41, Nancy13 and up
106  kRandomMovie, // ARs 42, 43, 45
107  kMovieWithVolume, // AR 44
108  kInteractiveMovie, // AR 47
109  kMovieWithRandomPosition // AR 48, Nancy16: AR 44 at a random spot inside its dest rect
110  };
111 
112  PlaySecondaryMovie(MovieType movieType);
113  virtual ~PlaySecondaryMovie();
114 
115  void init() override;
116  void onPause(bool pause) override;
117  void registerGraphics() override;
118  void updateGraphics() override;
119 
120  void readData(Common::SeekableReadStream &stream) override;
121  void execute() override;
122 
123  bool getIsFinished() const { return _isFinished; }
124 
125  // Enhancement: jump a cinematic straight to its end, as if it had finished
126  // playing on its own. Only cinematics are skipped; background animations
127  // the player isn't waiting on keep playing. Returns true when skipped.
128  bool skip();
129 
130  Common::Path _videoName;
131  Common::Path _paletteName;
132  Common::Path _bitmapOverlayName;
133 
134  // Container the record asks for; only meaningful from Nancy7 to Nancy12.
135  byte _videoPlaytype = kVideoPlaytypeAuto;
136 
137  uint16 _videoFormat = kLargeVideoFormat;
138  uint16 _videoSceneChange = kMovieNoSceneChange;
139  byte _playerCursorAllowed = kPlayerCursorAllowed;
140  byte _playDirection = kPlayMovieForward;
141  uint16 _firstFrame = 0;
142  uint16 _lastFrame = 0;
143  // Nancy14-only: when non-zero, hide the movie once it reaches its last frame.
144  uint16 _hideOnFinish = 0;
145  // Nancy15 AR 44: a "play style" selector (1 or 3). Read but currently
146  // unused by playback.
147  uint16 _playStyle = 1;
148 
149  // How many times the movie plays before the record finishes; 0 loops
150  // for as long as the scene lasts.
151  uint16 _numLoops = 1;
152  uint16 _playCount = 0;
153 
154  // Volume of the movie's audio track, as a percentage. Carried by Nancy14+
155  // AR 44/47 only; every other record plays at full volume. SetMovieVolume
156  // (AR 150) can change it later.
157  byte _movieVolume = 100;
158 
159  // How a kMovieWithRandomPosition movie picks its spot
160  enum RandomPosition : byte {
161  kFixedPosition = 0,
162  kRandomPositionOnce = 1,
163  kRandomPositionPerLoop = 2
164  };
165 
166  byte _randomPosition = kFixedPosition;
167  Common::Point _randomOffset;
168 
169  // AR 47 "InteractiveVideo" (a PlaySecondaryMovie subclass): the movie's
170  // clickable areas live in an external .iv file, which lists them per movie
171  // frame and tags each one with a set ID. The record itself carries the name
172  // of that file and the table below, which turns a set ID into the event flag
173  // a click sets and the cursor shown while the mouse is over the area.
174  // From Nancy14 the sets are named instead of numbered; setID is then the
175  // index of that name in the .iv file's own set list, resolved at load.
176  struct InteractiveSet {
177  Common::String name;
178  int32 setID = 0;
179  FlagDescription flagDesc;
180  int16 cursorID = -1;
181  };
182  Common::Path _interactiveName;
183  Common::Array<InteractiveSet> _interactiveSets;
184  InteractiveVideoData _interactiveVideo;
185  Common::Array<FlagAtFrame> _frameFlags;
186  MultiEventFlagDescription _triggerFlags;
187  FlagDescription _videoStartFlag;
188 
189  SoundDescription _sound;
190 
191  SceneChangeDescription _sceneChange;
193 
194  MoviePlayer _decoder;
195 
196  MovieType _movieType;
197 
198  bool isRandom() const { return _movieType == kRandomMovie; }
199 
200  // Whether skip() applies: a movie the player is waiting through, either
201  // because it hides the cursor or because it covers the whole viewport.
202  bool isSkippable();
203 
204  // Random-movie state (only populated for kRandomMovie).
205  // "RandomMovie" picks any sequence; otherwise it names the starting one.
206  Common::String _startingSequenceName;
207  // Nancy16: the name movie commands are left under, e.g. the wolf's commands
208  Common::String _playlistName;
209  uint16 _randomPlayerCursorAllowed = kPlayerCursorAllowed;
211 
212  // Nancy13+ carries one extra "secondary" movie after the sequence list: the
213  // character's recognition animation, played while the mouse hovers it.
214  RandomSequence _secondaryMovie;
215 
216  // Nancy14 replaced that slot with a foreground mask: a still image blitted
217  // over the movie, so scenery standing in front of the character (a table,
218  // a counter) covers the lower part of it.
219  class ForegroundMask : public RenderObject {
220  public:
221  ForegroundMask() : RenderObject(9) {}
222  bool isViewportRelative() const override { return true; }
223  };
224 
225  Common::Path _maskName;
227 
228  // Talkable characters (Nancy13+): the scene to open when the character is
229  // clicked (its conversation). kNoScene means the character isn't clickable.
230  uint16 _talkSceneID = kNoScene;
231  // Hover cursor for the character (a raw cursor id from the chunk), or -1
232  // when the record doesn't name one.
233  int16 _talkCursorType = -1;
234 
235  // Chain state. After a sequence's movie finishes the engine rolls a
236  // weighted pick: "stay" -> enter pause for a random duration and
237  // re-roll; valid next-sequence -> swap to that sequence's movie.
238  enum RandomChainState { kRandomPlaying, kRandomPaused };
239  // What ends the pause: its duration running out, or the mouse entering or
240  // leaving the movie (minPauseMs -2 / -3). A sequence waiting on the mouse
241  // holds its last frame on screen instead of hiding.
242  enum RandomPauseMode { kPauseTimed, kPauseUntilHovered, kPauseUntilNotHovered };
243  int _activeSequenceIndex = -1;
244  Common::Array<Common::Path> _commandedSequences;
245  RandomChainState _randomChainState = kRandomPlaying;
246  RandomPauseMode _randomPauseMode = kPauseTimed;
247  uint32 _randomPauseEndTime = 0;
248  int16 _randomPauseFlag = -1; // the flag of the sequence that is pausing
249  bool _randomStopRequested = false;
250  bool _randomPaused = false;
251  bool _isHiddenByDependencies = false; // Nancy18: taken off screen until the dependencies hold again
252 
253  // Whether the mouse is over the movie (which drives both the hover-based
254  // sequence chain and the click that opens a character's conversation), and
255  // whether the recognition (secondary) movie is currently playing.
256  bool _isHovered = false;
257  bool _playingSecondary = false;
258 
259  // Rewind state for the recognition movie. When the mouse leaves the
260  // character the movie is played backwards to its first frame so the
261  // character turns away again instead of snapping back to its idle pose.
262  // Bink can't be played in reverse by the decoder, so the frames are
263  // stepped through by hand.
264  bool _secondaryRewinding = false;
265  int _rewindFrame = 0;
266  uint32 _rewindLastFrameTime = 0;
267  uint32 _rewindFrameDelay = 66;
268 
269  // Called by PlayRandomMovieControl::execute(). stopRandom() winds the AR
270  // down once the sequence that's playing finishes; stopRandomNow() ends it
271  // on the spot; pauseRandom() freezes the movie with the record still
272  // running.
273  void stopRandom() { _randomStopRequested = true; }
274  void stopRandomNow();
275  void pauseRandom(bool pause);
276 
277  // Pick & start a fresh random sequence. No-op when not a random AR.
278  void playRandomSequence();
279 
280  bool isViewportRelative() const override { return true; }
281 
282  bool survivesSceneChange(bool nextSceneIsNoArt) const override;
283 
284  // Nancy13 talkable characters expose the character's on-screen box as a
285  // clickable hotspot with a talk cursor; clicking opens _talkSceneID, and
286  // hovering plays the recognition ("turn around") movie.
287  void handleInput(NancyInput &input) override;
288  CursorManager::CursorType getHoverCursor() const override;
289  bool cursorSetFromScript() const override { return isRandom() && _talkSceneID != kNoScene && _talkCursorType >= 0; }
290 
291  Common::String getRecordExtraInfo() const override {
292  return Common::String::format("Scene %d, file %s", _sceneChange.sceneID, _videoName.baseName().c_str());
293  }
294 
295 protected:
296  Common::String getRecordTypeName() const override {
297  return isRandom() ? "PlayRandomMovie" : "PlaySecondaryMovie";
298  }
299 
300  // `ser` and `stream` must wrap the same input; `stream` is only
301  // needed for SecondaryVideoDescription::readData.
302  void readRandomMovieData(Common::Serializer &ser, Common::SeekableReadStream &stream);
303  // Nancy14 reworked the random-movie layout (confirmed identical in Nancy15):
304  // a larger header (shared with the non-random AR) and a tail of two
305  // blt-descriptor lists separated by the recognition movie's name, in place
306  // of Nancy13's secondaryMovie record + hotspot list.
307  void readRandomMovieDataNancy14(Common::Serializer &ser, Common::SeekableReadStream &stream);
308  void readRandomSequence(Common::Serializer &ser, RandomSequence &seq);
309  void readSecondaryRandomMovie(Common::Serializer &ser, RandomSequence &seq);
310 
311  // Shared tail of the random-movie readers: pick the starting sequence
312  // (random or by name) and seed the flat playback fields from it.
313  void applyStartingRandomSequence();
314 
315  // Nancy13 compacted the non-random layout: a z-order, an alpha selector,
316  // the cursor flag, a loop count, the frame range and the scene change.
317  // Direction follows from lastFrame preceding firstFrame, and a scene
318  // change is requested through the sceneID sentinel.
319  void readDataNancy13(Common::Serializer &ser, Common::SeekableReadStream &stream);
320 
321  void readDataNancy14(Common::Serializer &ser, Common::SeekableReadStream &stream);
322 
323  // AR 47 appends the name of its .iv file and the set table to the movie data.
324  void readInteractiveData(Common::Serializer &ser);
325  void resolveInteractiveSets();
326 
327  // The set a hotspot belongs to, or nullptr if the record doesn't describe it.
328  const InteractiveSet *getInteractiveSet(int32 setID) const;
329 
330  // Picks the hover cursor from the hotspots the .iv file lists for the frame
331  // currently on screen, and sets their event flag when one is clicked.
332  void handleInteractiveInput(NancyInput &input);
333 
334  // Apply a RandomSequence's playback config to the PSM flat fields
335  // and reload the decoder. Returns true on success.
336  bool activateRandomSequence(int index);
337 
338  // Load & start the recognition (secondary) movie in place of the idle loop.
339  bool activateSecondaryMovie();
340 
341  // Start playing the recognition movie backwards from wherever it is now.
342  void beginSecondaryRewind();
343  // Advance the manual rewind; returns the frame to draw, or nullptr when
344  // the next frame isn't due yet.
345  const Graphics::Surface *updateSecondaryRewind();
346 
347  // A Nancy13 talkable character: has a conversation scene and a recognition
348  // movie to swap to on hover.
349  bool isTalkable() const { return isRandom() && _talkSceneID != kNoScene && !_secondaryMovie.name.empty(); }
350 
351  // Pick the next sequence (or "stay") per the weighted random rules.
352  // Returns -1 if "stay" was picked (and sets up the pause state),
353  // or the chosen sequence index otherwise.
354  int rollNextSequence();
355 
356  // Pick the sequence to chain to, without rolling for "stay" first: the
357  // special-flag entries take priority over the weighted random pick.
358  // Returns the chosen sequence index, or -1 if nothing was picked.
359  int pickNextSequence();
360 
361  // Enter the paused chain state for a random duration in the sequence's
362  // [minPauseMs, maxPauseMs] range, or until the mouse enters or leaves the
363  // movie. Always returns -1.
364  int beginRandomPause(const RandomSequence &seq);
365 
366  // Whether whatever the current pause is waiting for has happened.
367  bool randomPauseElapsed() const;
368 
369  // Find a sequence by name, warning and returning -1 if it isn't present.
370  int lookupSequence(const Common::Path &name) const;
371 
372  // Resolve the -1/-2 "whole movie" sentinels in _firstFrame/_lastFrame
373  // against the loaded decoder's frame count. Random sequences only.
374  void resolveSentinelFrames();
375 
376  // Show the foreground mask blit belonging to the given background frame,
377  // or hide it when the record doesn't describe one for that frame.
378  void updateMask(int viewportFrame);
379 
380  // Place and show the movie for the current background frame, or hide it
381  // when the record doesn't describe one for that frame.
382  void updateViewportFrame();
383 
384  // Crop the last decoded frame with the given video description and move
385  // the movie to its destination. -1 uses the whole frame at its current spot.
386  void applyVideoDesc(int descID);
387  Common::Rect getVideoDescDest(int descID) const;
388  void pickRandomPosition();
389 
390  Graphics::ManagedSurface _fullFrame;
391  Graphics::ManagedSurface _maskImage;
392  ForegroundMask _mask;
393  int _curViewportFrame = -1;
394  bool _isFinished = false;
395 };
396 
397 // Companion AR for the random-movie variant of PlaySecondaryMovie. When
398 // executed it stops the currently-active random PlaySecondaryMovie and
399 // optionally performs a scene change / event-flag set. From Nancy15 on the
400 // record names the movie it controls, so it can also address an already-loaded
401 // movie by filename.
403 public:
405 
406  void readData(Common::SeekableReadStream &stream) override;
407  void execute() override;
408 
409  enum RandomMovieControlMode : byte {
410  kStopNow = 0,
411  kPauseMovie = 1,
412  kResumeMovie = 2,
413  kStopAtEnd = 3,
414  kPauseAtEnd = 4
415  };
416 
417 protected:
418  Common::String getRecordTypeName() const override { return "PlayRandomMovieControl"; }
419 
420  byte _mode = kStopNow;
421  SceneChangeWithFlag _sceneChange;
422  // Nancy13's record is the mode byte alone; earlier games append a scene
423  // change to it.
424  bool _hasSceneChange = true;
425  // Nancy15 prefixes the mode with the name of the movie to control.
426  Common::Path _movieName;
427  bool _isRandomMovie = true;
428 };
429 
430 } // End of namespace Action
431 } // End of namespace Nancy
432 
433 #endif // NANCY_ACTION_SECONDARYMOVIE_H
Definition: managed_surface.h:51
Definition: commontypes.h:235
Definition: str.h:59
Definition: surface.h:67
static String format(MSVC_PRINTF const char *fmt,...) GCC_PRINTF(1
Definition: interactivevideo.h:50
Definition: commontypes.h:186
Definition: movieplayer.h:56
Definition: array.h:52
Definition: rect.h:536
Definition: path.h:52
Definition: stream.h:745
Definition: secondarymovie.h:47
Definition: input.h:41
Definition: serializer.h:80
Definition: commontypes.h:206
Definition: actionrecord.h:169
Definition: renderobject.h:36
bool empty() const
Definition: path.h:353
Definition: secondarymovie.h:58
Definition: actionrecord.h:107
Definition: rect.h:144
String baseName() const
Definition: commontypes.h:306
Definition: secondarymovie.h:402
Definition: commontypes.h:201
Definition: actionmanager.h:32