ScummVM API documentation
macs2.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 MACS2_MACS2_H
23 #define MACS2_MACS2_H
24 
25 #include "advancedDetector.h"
26 #include "audio/audiostream.h"
27 #include "audio/mixer.h"
28 #include "common/array.h"
29 #include "common/error.h"
30 #include "common/file.h"
31 #include "common/fs.h"
32 #include "common/hashmap.h"
33 #include "common/memstream.h"
34 #include "common/random.h"
35 #include "common/scummsys.h"
36 #include "common/serializer.h"
37 #include "common/system.h"
38 #include "common/text-to-speech.h"
39 #include "common/util.h"
40 #include "engines/engine.h"
41 #include "macs2/events.h"
42 #include "macs2/macs2_constants.h"
43 #include "macs2/scriptexecutor.h"
44 
45 namespace Macs2 {
46 
48 public:
49  Common::Array<byte> _data;
50  int64 _pos;
51 
52  virtual ~MacsAudioStream() {}
53 
70  virtual int readBuffer(int16 *buffer, const int numSamples);
71 
73  virtual bool isStereo() const;
74 
76  virtual int getRate() const;
77 
87  virtual bool endOfData() const;
88 
89  virtual bool seek(const Audio::Timestamp &where);
90 
91  virtual Audio::Timestamp getLength() const;
92 };
93 
94 struct Macs2GameDescription;
95 
96 // enum class CursorMode { Talk = 0, Look = 1, Touch = 2, Walk = 3};
97 class Music;
98 
99 struct Sprite {
100  uint16 _width = 0;
101  uint16 _height = 0;
102  Common::Array<uint8> _data;
103 };
104 
105 struct GlyphData : public Sprite {
106  char _ascii = 0;
107 
108  void readFromeFile(Common::File &file);
109  void readFromMemory(Common::MemoryReadStream *stream);
110 };
111 
112 struct AnimFrame : public Sprite {
113  int16 _offsetX = 0;
114  int16 _offsetY = 0;
115 
116  void readFromeFile(Common::File &file);
117  void readFromStream(Common::MemoryReadStream *stream);
118  bool pixelHit(const Common::Point &point) const;
119  Common::Point getBottomMiddleOffset(uint16 scale = 100) const;
120 };
121 
123  uint16 _x = 0;
124  uint16 _y = 0;
125  Common::Array<AnimFrame> _frames;
126  uint32 _frameIndex = 0;
127 };
128 
130  uint16 _x = 0;
131  uint16 _y = 0;
132  Common::Array<uint8> _blob;
133  uint16 _unknown0C = 0; // +0x50F3: purpose unknown (word, read from file, not used at runtime)
134  uint8 _unknown0E = 0; // +0x50F5: purpose unknown (byte, read from file, not used at runtime)
135  uint8 _unknown0F = 0; // +0x50F6: purpose unknown (byte, read from file, not used at runtime)
136  AnimFrame getCurrentFrame();
137  static uint16 advanceAnimFrame(Common::Array<uint8> &blob, bool bpp6, uint16 bpp8);
138  static uint16 getAnimFrameCount(Common::Array<uint8> &blob);
139  // Mirrors (horizontally flips) all frames in an animation blob in-place.
140  // Matches binary decodeAnimBlob (1010:184d) which calls the row-flip at 1010:1319.
141  static void mirrorAnimBlob(Common::Array<uint8> &blob);
142 };
143 
161 struct AnimBlobView {
162  const Common::Array<uint8> &_blob;
163 
164  explicit AnimBlobView(const Common::Array<uint8> &blob) : _blob(blob) {}
165 
166  bool isValid() const { return _blob.size() >= 14; } // header(12) + at least 2 bytes frame count
167 
168  // Header fields
169  uint16 sequencePosition() const { return READ_LE_UINT16(&_blob[0x02]); }
170  uint16 repeatCounter() const { return READ_LE_UINT16(&_blob[0x04]); }
171  uint16 loopStartPosition() const { return READ_LE_UINT16(&_blob[0x06]); }
172  uint16 delayCounter() const { return READ_LE_UINT16(&_blob[0x08]); }
173  uint16 sequenceLength() const { return READ_LE_UINT16(&_blob[0x0A]) + 1; }
174 
175  // Derived offsets
176  uint32 frameDataOffset() const { return 0x0B + sequenceLength(); }
177  uint16 frameCount() const {
178  uint32 off = frameDataOffset();
179  if (off + 2 > _blob.size())
180  return 0;
181  return READ_LE_UINT16(&_blob[off]);
182  }
183 
184  // Get frame info at index (0-based). Returns false if out of bounds.
185  struct FrameInfo {
186  int16 offsetX;
187  int16 offsetY;
188  uint16 unknown;
189  uint16 width;
190  uint16 height;
191  const byte *pixels; // pointer into blob data
192  };
193 
194  bool getFrameInfo(uint16 index, FrameInfo &out) const {
195  uint32 pos = frameDataOffset() + 2; // skip frame count word
196  for (uint16 i = 0; i <= index; i++) {
197  if (pos + 10 > _blob.size())
198  return false;
199  int16 ox = (int16)READ_LE_UINT16(&_blob[pos]);
200  int16 oy = (int16)READ_LE_UINT16(&_blob[pos + 2]);
201  uint16 unk = READ_LE_UINT16(&_blob[pos + 4]);
202  uint16 w = READ_LE_UINT16(&_blob[pos + 6]);
203  uint16 h = READ_LE_UINT16(&_blob[pos + 8]);
204  pos += 10;
205  if (w == 0 || h == 0 || pos + (uint32)w * h > _blob.size())
206  return false;
207  if (i == index) {
208  out = {ox, oy, unk, w, h, &_blob[pos]};
209  return true;
210  }
211  pos += (uint32)w * h;
212  }
213  return false;
214  }
215 };
216 
218  uint8 _index;
219  Common::Point _position;
220  Common::Array<uint8> _adjacentPoints;
221 };
222 
224  bool _active;
225  uint16 _index;
226  uint16 _overrideValue;
227 };
228 
229 // Area override table at scene+0x4EA8 (indexed by pathfinding value 0xC8..0xEF)
230 // Set by opcode 0x4D, read by getAreaAtPoint (1008:101d)
231 #define AREA_OVERRIDE_MIN 200
232 #define AREA_OVERRIDE_MAX 239
233 #define AREA_OVERRIDE_COUNT (AREA_OVERRIDE_MAX - AREA_OVERRIDE_MIN + 1)
234 
235 class Macs2Engine : public Engine, public Events {
236 private:
237  const ADGameDescription *_gameDescription;
238 
239  Music *_adlib = nullptr;
240 
241 protected:
242  // Engine APIs
243  Common::Error run() override;
244 
248  bool shouldQuit() const override {
249  return Engine::shouldQuit();
250  }
251 
252 public:
253  Graphics::ManagedSurface readRLEImage(int64 offs, Common::MemoryReadStream *stream);
254 
255  void readResourceFile();
256 
257  // We also need some data from the executable, specifically embedded
258  // Adlib data
259  void readExecutable();
260 
261  // Assumes that the stream is at the location of the number of background animations
262  void readBackgroundAnimations(Common::MemoryReadStream *stream);
263 
264  // Assumes that the stream is at the start of the right section
265  void readImageResources(Common::MemoryReadStream *stream);
266 
267 public:
268  Macs2Engine(OSystem *osystem, const ADGameDescription *gameDesc);
269  ~Macs2Engine() override;
270 
271  void changeScene(uint32 newSceneIndex, bool executeScript = true);
272 
273  Script::ScriptExecutor *_scriptExecutor = nullptr;
274  Graphics::ManagedSurface _sceneBackground;
275  Graphics::ManagedSurface _hotspotMap;
276 
277  // File offset to the map mode image for the current scene (scene table entry +8).
278  // When 0, the map mode is unavailable for this scene.
279  uint32 _mapImageFileOffset = 0;
280 
281  // Per-depth sub-scene file offsets for map mode preview (binary: scene+0x5DD7+depth*4).
282  // File position where the sub-scene offset table starts (after map depth map).
283  int64 _mapSubSceneTableFilePos = 0;
284 
285  // This is the depth map
286  Graphics::ManagedSurface _depthMap;
287 
288  // Shadow/shading intensity map (scene+0x301B). Per-pixel values 0-32
289  // control character sprite darkening via the shading table.
290  // Only scenes with shadow regions have non-zero data.
291  Graphics::ManagedSurface _shadowMap;
292 
293  // TODO: use Graphics::Palette for this
294  byte _pal[256 * 3] = {0};
295  byte _palVanilla[256 * 3] = {0};
296 
297  Common::Array<Common::String> _debugOutput;
299 
300  // Note: This is used both for pathfinding as well as for area IDs
301  Graphics::ManagedSurface _pathfindingMap;
302 
303  Common::Array<PathfindingAreaOverride> _pathfindingOverrides;
304  // Area override table at scene+value*5+0x4EA8 (for getAreaAtPoint)
305  uint16 _areaOverrides[AREA_OVERRIDE_COUNT] = {0};
306  uint16 _pathfindingPoints[32];
307  Common::Array<PathfindingPoint> pathfindingPoints;
309 
310  bool getPathfindingOverride(uint16 index, uint16 &result);
311  void setPathfindingOverride(uint16 index, uint16 overrideValue);
312 
313  // Walkability threshold 0xC8 uses signed 16-bit comparison in the binary (JL/JGE).
314  // Values with (int16)value < 0xC8 are walkable heights; e.g. -2 (0xFFFE) is walkable.
315  static inline bool isWalkabilityBlocking(uint16 value) {
316  return (int16)value >= 0xC8;
317  }
318  static inline bool isWalkabilityWalkable(uint16 value) {
319  return (int16)value < 0xC8;
320  }
321 
322  // This one implements the lookup relative to es:[di+4EA8h] vs. the other one at es:[di+4EA5h] and es:[di+4EA6h]
323  uint16 getPathfindingOverride2(uint16 index);
324  void removePathfindingOverride(uint16 index);
325 
326  uint16 getWalkabilityAt(int16 y, int16 x);
327  bool isPathWalkable(int16 y1, int16 x1, int16 y2, int16 x2);
328  void snapToWalkablePosition(int16 *pTargetY, int16 *pTargetX, int16 charY, int16 charX);
329  int getPathfindingNodeCount() const { return (int)_numPathfindingPoints; }
330  int euclideanDistance(const Common::Point &a, const Common::Point &b);
331  int walkableDistance(int nodeA, int nodeB);
332  int computeMinCostToReachable(int nodeIndex, int prevNode, uint16 actorIndex, const bool *reachable, int nodeCount, const Common::Point &finalDest);
333 
334  // This is the override list living at [5BD1]
335  Common::Array<uint16> _hotspotOverrides;
336 
337  Common::Array<Macs2::AnimFrame> _imageResources;
338 
339  GlyphData _glyphs[256];
340  GlyphData _panelGlyphs[256]; // Font 2: clean sans-serif font used by save/load panel
341  GlyphData _overlayGlyphs[256];
342  uint16 numOverlayGlyphs = 0;
343  uint16 maxOverlayGlyphHeight = 0;
344  uint16 numPanelGlyphs = 0;
345  uint16 maxPanelGlyphHeight = 0;
346  bool loadOverlayFont(uint8 resourceIndex, uint16 executingObjectID);
347  // Font glyph count (79 glyphs in the resource file's font data)
348  uint16 numGlyphs = 79;
349  uint16 maxGlyphHeight;
350 
351  AnimFrame _animFrames[6];
352  // 6 flag/decoration animation frames at fixed file offset 0x6A5941, each followed by 6 padding bytes
353 
354  bool findGlyph(char c, GlyphData &out) const;
355 
356  // Character shading remap (loadResourceFile @ 1008:2e8d -> scene+0x53D3).
357  // Indexed as (color - 0xC0) * 0x20 + shadowIntensity (drawSpriteTransparent @ 1010:0ed1).
358  Common::Array<byte> _shadingTable;
359  // Per-scene 256-byte UI pixel remap (changeScene @ 1008:2574, drawAnimFrameScaled @ 1010:1399).
360  Common::Array<byte> _panelRemapTable;
361 
362  // Map scene offsets from resource file (scene+0x5DDB, 256 entries x 4 bytes).
363  // Each entry is a file offset to a scene preview image for map mode.
364  uint32 _mapSceneOffsets[256] = {0};
365 
366  Common::Array<BackgroundAnimation> _backgroundAnimations;
367  Common::Array<BackgroundAnimationBlob> _backgroundAnimationsBlobs;
368 
369  Common::MemoryReadStream *_fileStream;
370 
371  void setCursorMode(Script::MouseMode newMode);
372  void nextCursorMode();
373 
374  Common::Array<uint16> _hotspotColorTable;
375 
376  uint16 _numPathfindingPoints;
377  uint16 _walkDepthThresholdY;
378  uint16 _walkDepthScaleFactor;
379  uint16 _walkBaseSpeedPct;
380 
381  // Scene palette/animation mode at sceneData+0x5203:
382  // 1 = normal (no darkening, no bg animation)
383  // 2 = slow bg animation + palette darkening
384  // 3 = fast bg animation + palette darkening
385  uint16 _scenePaletteMode;
386  // Palette darken percentage (0-100) at sceneData+0x5205.
387  // Formula: displayedPal[i] = sourcePal[i] * (100 - _paletteDarkenPercent) / 100
388  uint16 _paletteDarkenPercent;
389 
390  void applyPaletteDarkening();
391  // Palette quantization for g_wHelpButtonDisabled path (1000:103e).
392  // Histograms scene pixels, keeps 16 rarest colors (0..0xBF) plus UI range
393  // 0xC0..0xFF, remaps background + bg-anim blobs + palette via Manhattan RGB.
394  void applyScenePaletteEffect();
395 
396  // Gradual palette brighten effect for _scenePaletteMode == 2, matching the
397  // binary updateBackgroundAnimations (1008:2c05). Called from the game tick at
398  // the mode-gated rate. Despite the name it does NOT advance animation frames
399  // (that happens in the per-frame render); it decrements the darken percent
400  // toward 60 and reapplies the palette darkening.
401  void updateBackgroundAnimationPalette();
402 
403  // Binary g_bMovementFinishedFlag [1020:0000]: set by walkAlongPath on final arrival
404  // (orientation < 9), checked after all characters processed in drawAllCharacters.
405  bool _movementFinishedFlag = false;
406 
407  Common::Array<uint32> _sceneResourceOffsets;
408 
409  bool loadAnimationFromSceneData(uint16 objectIndex, uint16 slotIndex, uint8 arrayIndex, bool shouldMirror = false, uint16 executingScriptObjectId = 0);
410  bool loadObjectData(GameObject *obj);
411  void clearObjectRuntime(GameObject *obj);
412  // sortObjectsByDepth @ 1008:0d79 - inventory cursor reset + free object runtime blobs.
413  void sortObjectsByDepth(uint16 objectIndex);
414 
415  void loadSongFromSceneData(uint8 dataIndex);
416  Music *getAdlib() const { return _adlib; }
417  // Returns the Music volume (0-63) scaled by the user's music_volume setting
418  uint16 scaledMusicVolume(uint16 gameAttenuation) const;
419  void setCurrentSoundData(const Common::Array<uint8> &data);
420  void clearCurrentSoundData();
421  void playCurrentSound();
422  void stopCurrentSound();
423  bool hasCurrentSound() const { return !_currentSoundData.empty(); }
424  bool isCurrentSoundPlaying() const;
425 
426  // Offset 50D3h - This is used in 0037:10C4 to terminate the loop
427  uint16 _numHotspots;
428 
429  // Reserved/unused scene data fields at scene+0x53C3..+0x53CF (4 dwords).
430  // Zeroed on scene change, saved/loaded, but never read or written with meaningful
431  // values by any game logic. Kept only for save/load format compatibility.
432  uint32 _sceneTimerParams[4] = {0};
433 
434  // Clip rect dirty flag [0xfec] - in the original DOS engine this signaled the VGA
435  // blitter to reset the clip region to full screen before the next partial-region
436  // updates (a dirty-rect optimization). Not used in ScummVM because we redraw the
437  // full backbuffer each frame via ManagedSurface. Kept only for save/load compatibility.
438  bool _clipRectDirty = false;
439 
440  uint16 getHotspotAtPoint(const Common::Point &p);
441 
442  Common::Array<uint16> inventoryIconIndices;
443  Common::Array<uint16> containerInventoryIconIndices;
444 
445  void runScriptExecutor(bool firstRun = false) {
446  _scriptExecutor->run(firstRun);
447  }
448 
449  bool _runScheduled = false;
450  // TODO: Feels like this should be more elegantly solved, also check how the game does this
451  // Is required for example after a scene change
452  bool _scheduledRunIsInitScene = false;
453 
454  // Game speed mode from original binary (g_wGameSpeedMode at 1020:0214).
455  // Cycled by Ctrl+T: 0=normal, 1=fast (no frame wait), 2=slow (wait for tick>=0x12).
456  uint16 _gameSpeedMode = 0;
457  void setGameSpeedMode(uint16 mode);
458 
459  // Input record/playback system from original binary.
460  // Original usage: MCSEXEC filename /rRecFile or /pPlayFile
461  // Record writes per-frame: mouseX(2), mouseY(2), buttons(2)
462  // Playback reads the same and also a leading frame-count word.
463  enum class InputMode { None,
464  Record,
465  Playback };
466  InputMode _inputMode = InputMode::None;
467  Common::WriteStream *_inputRecordStream = nullptr;
468  Common::SeekableReadStream *_inputPlaybackStream = nullptr;
469  uint32 _inputFrameCounter = 0;
470  uint32 _inputPlaybackEndFrame = 0;
471 
472  void startInputRecording(const Common::Path &filename);
473  void startInputPlayback(const Common::Path &filename);
474  void stopInputRecording();
475  void recordInputFrame(uint16 mouseX, uint16 mouseY, uint16 buttons);
476  bool readInputFrame(uint16 &mouseX, uint16 &mouseY, uint16 &buttons);
477 
478  Common::Array<uint8> _currentSoundData;
479  Audio::SoundHandle _currentSoundHandle;
480 
481  // Schedules a run of the script the next time the executor is ticked
482  void scheduleRun(bool initScene = false);
483 
484  uint16 getWalkabilityAt(const Common::Point &p);
485 
486  int measureString(const Common::String &s);
487 
488  int measureStrings(const Common::StringArray &sa);
489  int measureStringsVertically(const Common::StringArray &sa);
490 
491  Common::StringArray decodeStrings(Common::MemoryReadStream *stream, int offset, int numStrings, int sceneId = 0, int objectId = 0);
492 
493  // --- Translation support ---
495  Common::StringArray strings;
496  };
498  Common::HashMap<uint32, TranslationEntry> _objectTranslations;
499  Common::HashMap<Common::String, Common::String> _hotspotLabelTranslations;
500  void loadTranslation();
501  Common::String translateHotspotLabel(const Common::String &cp850Name) const;
502  // Compute the sequential string index at the given byte offset in a string blob
503  int computeStringIndex(Common::MemoryReadStream *stream, int targetOffset);
504 
505  uint32 getFeatures() const;
506 
507  bool isDemo() const { return getFeatures() & ADGF_DEMO; }
508 
512  Common::String getGameId() const;
513 
514  bool hasFeature(EngineFeature f) const override {
515  return (f == kSupportsLoadingDuringRuntime) ||
516  (f == kSupportsSavingDuringRuntime) ||
517  (f == kSupportsReturnToLauncher) ||
518  (f == kSupportsChangingOptionsDuringRuntime);
519  };
520 
521  void syncSoundSettings() override;
522 
523  bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override {
524  return true;
525  }
526  bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override {
527  return true;
528  }
529 
530  Common::Error loadGameState(int slot) override;
531 
537  Common::Error syncGame(Common::Serializer &s);
538 
539  Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave = false) override {
540  Common::Serializer s(nullptr, stream);
541  return syncGame(s);
542  }
544  Common::Serializer s(stream, nullptr);
545  return syncGame(s);
546  }
547 
548  // Write a raw original-DOS-format save file ("SAVEGAME.N", N=0..9) with no
549  // ScummVM wrapper, so it can be loaded by the original game executable.
550  // The byte layout is produced directly by syncGame (binary-compatible with
551  // saveGameToFile at 1008:6859). Mirrors loadGameState(slot 100..109).
552  Common::Error saveOriginalGameState(int dosSlot);
553 
554  bool tick() override;
555 
556  void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT) const;
557 
558  void getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots) override;
559  bool hotspotDirty() const override;
560  void rebuildHotspotSnapshot() const;
561 
564  uint16 index = 0;
565  Common::Point center;
566  };
568  uint16 index = 0;
569  Common::Point position;
570  uint16 orientation = 0;
571  };
572 
573  int currentSceneIndex = -1;
574  uint16 numHotspots = 0;
575  Common::Array<uint16> hotspotColorTable;
576  Common::Array<uint16> hotspotOverrides;
577  Common::Array<SceneHotspotEntry> sceneHotspots;
578  Common::Array<SceneObjectEntry> sceneObjects;
579  bool mapModeActive = false;
580  };
581  mutable HotspotSnapshot _hotspotSnapshot;
582 };
583 
584 extern Macs2Engine *g_engine;
585 #define SHOULD_QUIT ::Macs2::g_engine->shouldQuit()
586 
587 } // End of namespace Macs2
588 
589 #endif // MACS2_MACS2H
Definition: managed_surface.h:51
virtual int readBuffer(int16 *buffer, const int numSamples)
bool canSaveGameStateCurrently(Common::U32String *msg=nullptr) override
Definition: macs2.h:526
Definition: str.h:59
EngineFeature
Definition: engine.h:282
bool hasFeature(EngineFeature f) const override
Definition: macs2.h:514
Definition: stream.h:77
Definition: error.h:81
virtual Audio::Timestamp getLength() const
Definition: gameobjects.h:86
virtual bool isStereo() const
Definition: macs2.h:47
Definition: advancedDetector.h:164
virtual int getRate() const
Definition: macs2.h:563
Definition: path.h:52
Definition: timestamp.h:83
Definition: music.h:48
Definition: stream.h:745
Definition: events.h:131
Engine * g_engine
Definition: scriptexecutor.h:99
Definition: serializer.h:80
Definition: audiostream.h:212
bool shouldQuit() const override
Definition: macs2.h:248
Common::Error loadGameStream(Common::SeekableReadStream *stream) override
Definition: macs2.h:543
Definition: macs2.h:122
Definition: mixer.h:49
Definition: hashmap.h:85
Definition: ustr.h:57
Definition: file.h:47
static bool shouldQuit()
Definition: macs2.h:235
Definition: macs2.h:105
Definition: macs2.h:223
Definition: rect.h:144
Definition: macs2.h:185
virtual bool endOfData() const
size_type size() const
Definition: array.h:316
bool canLoadGameStateCurrently(Common::U32String *msg=nullptr) override
Definition: macs2.h:523
Definition: memstream.h:43
Definition: macs2.h:99
Definition: macs2.h:112
Definition: macs2.h:567
Definition: system.h:166
Definition: debugtools.h:25
virtual bool seek(const Audio::Timestamp &where)
Definition: macs2.h:129
Common::Error saveGameStream(Common::WriteStream *stream, bool isAutosave=false) override
Definition: macs2.h:539
Definition: macs2.h:161
Add "-demo" to gameid.
Definition: advancedDetector.h:157
Definition: macs2.h:494
Definition: engine.h:149
Definition: macs2.h:217