[Scummvm-git-logs] scummvm master -> ba7d1f4df6d802302b624cc83f847ffaa02d75be
mgerhardy
noreply at scummvm.org
Tue Sep 1 14:40:23 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
71638a8a58 MACS2: renamed methods and cleaned up comments
6d917ecf4c MACS2: fixed text-to-speech playing per frame
ba7d1f4df6 MACS2: support gender and different voices in tts
Commit: 71638a8a5831032663c53598c0d37c390c7ad716
https://github.com/scummvm/scummvm/commit/71638a8a5831032663c53598c0d37c390c7ad716
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-01T16:40:09+02:00
Commit Message:
MACS2: renamed methods and cleaned up comments
Changed paths:
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/saveload.cpp
engines/macs2/scriptexecutor.cpp
engines/macs2/scriptexecutor.h
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index d018cb6e924..7fd592bc3b1 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -1798,7 +1798,7 @@ void Macs2Engine::playDeltaFrameSfx(uint16 displayFrame) {
if (ev.duckMusic)
getMusic()->setSmfDucked(true, _talkVol);
const Common::String base = Script::ScriptExecutor::stripAudioExtension(ev.fileName);
- playDigitalAudioFile(Common::Path("SOUNDFX").join(base), false);
+ playAudioFile(Common::Path("SOUNDFX").join(base), false);
}
}
@@ -3230,7 +3230,7 @@ bool Macs2Engine::isSpeechPlaying() const {
return g_system->getMixer()->isSoundHandleActive(_speechSoundHandle);
}
-void Macs2Engine::playDigitalAudioFile(const Common::Path &basename, bool speechBus) {
+void Macs2Engine::playAudioFile(const Common::Path &basename, bool speech) {
Audio::SeekableAudioStream *stream = Audio::SeekableAudioStream::openStreamFile(basename);
if (stream == nullptr) {
debugC(kDebugScript, "playDigitalAudioFile: no audio for %s",
@@ -3238,7 +3238,7 @@ void Macs2Engine::playDigitalAudioFile(const Common::Path &basename, bool speech
return;
}
- if (speechBus) {
+ if (speech) {
stopSpeech();
g_system->getMixer()->playStream(Audio::Mixer::kSpeechSoundType, &_speechSoundHandle, stream);
} else {
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 80366d91055..924f7c9f5ab 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -662,7 +662,7 @@ public:
* via SeekableAudioStream::openStreamFile (codec #ifdefs live in audio/).
* SPEECH paths use the speech mixer handle; others use the SFX handle.
*/
- void playDigitalAudioFile(const Common::Path &basename, bool speechBus);
+ void playAudioFile(const Common::Path &basename, bool speechBus);
void stopSpeech();
bool isSpeechPlaying() const;
diff --git a/engines/macs2/saveload.cpp b/engines/macs2/saveload.cpp
index 9a97ec488ff..971f872f864 100644
--- a/engines/macs2/saveload.cpp
+++ b/engines/macs2/saveload.cpp
@@ -26,10 +26,6 @@
namespace Macs2 {
-// Binary-compatible save format matching the original DOS game's
-// saveGameToFile (1008:6859) and loadGameFromFile (1008:747e).
-// Save files are interchangeable between ScummVM and the original game.
-
Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
const byte SAVE_MAGIC[12] = {'A', 'H', 'F', 'F', 'M', 'S', 'G', 'M', '0', '1', '0', '0'};
View1 *view1 = (View1 *)findView("View1");
@@ -53,11 +49,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
return Common::kReadingFailed;
}
- // --- 21-byte slot name (Pascal string: [len][chars], zero-padded to 21) ---
- // The original save/load menu (initSaveLoadPanel 1008:6184) reads these 21
- // bytes and renders them with drawText as a length-prefixed string. A zero
- // length byte makes the slot appear empty in the original, so write a real
- // name when producing an original-format save.
byte slotName[21] = {0};
if (s.isSaving()) {
const char defName[] = {'S', 'C', 'U', 'M', 'M', 'V', 'M'};
@@ -66,7 +57,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
s.syncBytes(slotName, 21);
- // --- Core indices: actor (2 bytes) + scene (2 bytes) ---
uint16 actorIndex = (uint16)Scenes::instance()._currentActorIndex;
uint16 sceneIndex = (uint16)Scenes::instance()._currentSceneIndex;
s.syncAsUint16LE(actorIndex);
@@ -90,8 +80,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
// --- Script variables: exactly 0x2000 bytes (0x800 vars x {uint16 a, b}) ---
- // Must always be 0x800 entries to match the binary's fixed-size block; do not
- // key off _variables.size() in case it ever differs.
+ // Must always be 0x800 entries to match the binary's fixed-size block
for (uint i = 0; i < 0x800; i++) {
uint16 a = (i < _scriptExecutor->_variables.size()) ? _scriptExecutor->_variables[i].a : 0;
uint16 b = (i < _scriptExecutor->_variables.size()) ? _scriptExecutor->_variables[i].b : 0;
@@ -103,14 +92,12 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
}
- // --- g_wSoundSystemActive: 2 bytes [0x1f4c] ---
uint16 soundSystemActive = _scriptExecutor->_soundSystemActive ? 1 : 0;
s.syncAsUint16LE(soundSystemActive);
if (s.isLoading())
_scriptExecutor->_soundSystemActive = soundSystemActive != 0;
// --- Script execution state ---
- // g_wScriptIsExecuting [0xf88]: 1 byte â binary: g_wScriptPosition < g_wScriptEndPosition
uint8 scriptIsExecuting = 0;
if (s.isSaving()) {
_scriptExecutor->prepareScriptStateForSave();
@@ -118,15 +105,12 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
s.syncAsByte(scriptIsExecuting);
- // g_wScriptPosition [0xf8a]: 2 bytes
uint16 scriptPosition = (uint16)_scriptExecutor->getScriptPosition();
s.syncAsUint16LE(scriptPosition);
- // g_wScriptEndPosition [0xf90]: 2 bytes
uint16 scriptEndPosition = (uint16)_scriptExecutor->getScriptEndPosition();
s.syncAsUint16LE(scriptEndPosition);
- // g_wExecutingScriptObjectId [0xf92]: 2 bytes (0 = scene script)
uint16 executingObjectId = _scriptExecutor->getExecutingObjectId();
s.syncAsUint16LE(executingObjectId);
@@ -140,41 +124,28 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
pendingScriptEndPosition = scriptEndPosition;
}
- // g_wScriptClickFlag [0xf94]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_scriptClickFlag);
-
- // g_wScriptClickX [0xf96]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_scriptClickX);
-
- // g_wScriptClickY [0xf98]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_scriptClickY);
-
- // g_wScriptClickResult [0xf9a]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_scriptClickResult);
- // --- Game state globals ---
- // g_wRepeatRunFlag [0x1012]: 1 byte
uint8 repeatRunFlag = _scriptExecutor->getRepeatRunFlag() ? 1 : 0;
s.syncAsByte(repeatRunFlag);
if (s.isLoading())
_scriptExecutor->setRepeatRunFlag(repeatRunFlag != 0);
- // g_wFrameWaitCounter [0x100a]: 2 bytes
uint16 frameWaitCounter = _scriptExecutor->getFrameWaitCounter();
s.syncAsUint16LE(frameWaitCounter);
if (s.isLoading())
_scriptExecutor->setFrameWaitCounter(frameWaitCounter);
- // g_wWalkTargetObjectIndex [0x1016]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_walkTargetObjectIndex);
- // g_wPickupInProgress [0x1030]: 2 bytes
uint16 pickupInProgress = _scriptExecutor->_pickupInProgress ? 1 : 0;
s.syncAsUint16LE(pickupInProgress);
if (s.isLoading())
_scriptExecutor->_pickupInProgress = pickupInProgress != 0;
- // g_wActiveInventoryItemId [0xfd0]: 2 bytes
uint16 activeInventoryItemId = 0;
if (s.isSaving() && view1->_activeInventoryItem)
activeInventoryItemId = view1->_activeInventoryItem->_index + 0x400;
@@ -191,22 +162,18 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
}
- // g_wSavedCursorMode [0xfea]: 2 bytes
uint16 savedCursorMode = (uint16)view1->_savedCursorMode;
s.syncAsUint16LE(savedCursorMode);
if (s.isLoading())
view1->_savedCursorMode = (Script::MouseMode)savedCursorMode;
- // g_wClipRectDirty [0xfec]: 1 byte - flags clip region needs full-screen reset
uint8 clipRectDirty = _clipRectDirty ? 1 : 0;
s.syncAsByte(clipRectDirty);
if (s.isLoading())
_clipRectDirty = clipRectDirty != 0;
- // g_wWalkTargetObjectIndex (duplicate) [0x1016]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_walkTargetObjectIndex);
- // PTR_LOOP_1020_1018 [0x1018]: 2 bytes - mouse mode
uint16 mouseMode = (uint16)_scriptExecutor->_cursorMode;
s.syncAsUint16LE(mouseMode);
if (s.isLoading()) {
@@ -230,7 +197,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
uint16 reserved101e = 0;
s.syncAsUint16LE(reserved101e);
- // g_bMovementFinishedFlag [0x1020]: 1 byte
// Per-frame latch: cleared by drawAllCharacters, set by walkAlongPath when a
// character reaches destination, checked at end of frame.
uint8 movementFinishedFlag = _movementFinishedFlag ? 1 : 0;
@@ -238,70 +204,55 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
if (s.isLoading())
_movementFinishedFlag = (movementFinishedFlag != 0);
- // g_wInteractedObjectId [0x1024]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_interactedObjectID);
-
- // g_wInteractedInventoryItemId [0x1026]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_interactedInventoryItemId);
- // g_wScriptSkippable [0x102a]: 1 byte
uint8 scriptSkippable = _scriptExecutor->_scriptSkippable ? 1 : 0;
s.syncAsByte(scriptSkippable);
if (s.isLoading())
_scriptExecutor->_scriptSkippable = scriptSkippable != 0;
- // g_wPickupActorObjectId [0x102c]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_pickupActorObjectID);
-
- // g_wPickupTargetObjectId [0x102e]: 2 bytes
s.syncAsUint16LE(_scriptExecutor->_pickupTargetObjectID);
- // g_wIsRepeatRun [0x1032]: 2 bytes
uint16 isRepeatRun16 = _scriptExecutor->_isRepeatRun ? 1 : 0;
s.syncAsUint16LE(isRepeatRun16);
if (s.isLoading())
_scriptExecutor->_isRepeatRun = isRepeatRun16 != 0;
- // g_wInventoryCheckResult [0x103c]: 1 byte
uint8 inventoryCheckResult = _scriptExecutor->_inventoryCheckResult ? 1 : 0;
s.syncAsByte(inventoryCheckResult);
if (s.isLoading())
_scriptExecutor->_inventoryCheckResult = inventoryCheckResult != 0;
- // g_wAnimBlobRangeTestResult [0x103e]: 1 byte
uint8 animBlobRangeTestResult = _scriptExecutor->_animBlobRangeTestResult ? 1 : 0;
s.syncAsByte(animBlobRangeTestResult);
if (s.isLoading())
_scriptExecutor->_animBlobRangeTestResult = animBlobRangeTestResult != 0;
- // g_wInventoryActionFlag [0x1040]: 1 byte
uint8 inventoryActionFlag = _scriptExecutor->_inventoryActionFlag ? 1 : 0;
s.syncAsByte(inventoryActionFlag);
if (s.isLoading())
_scriptExecutor->_inventoryActionFlag = inventoryActionFlag != 0;
- // g_wInventoryCombineFlag [0x1042]: 1 byte
uint8 inventoryCombineFlag = _scriptExecutor->_inventoryCombineFlag ? 1 : 0;
s.syncAsByte(inventoryCombineFlag);
if (s.isLoading())
_scriptExecutor->_inventoryCombineFlag = inventoryCombineFlag != 0;
- // g_wInventoryObjectCount [0x222a]: 2 bytes - number of items in inventory list
uint16 inventoryObjectCount = (uint16)view1->_inventoryItems.size();
s.syncAsUint16LE(inventoryObjectCount);
- // --- Inventory object list [0x202a]: 0x200 bytes ---
- // Byte array of object indices in the actor's inventory.
- // Original stores as bytes (object index, 1-based).
+ // Inventory object list indices in the actor's inventory
byte inventoryObjectList[512] = {0};
if (s.isSaving()) {
- for (uint16 i = 0; i < inventoryObjectCount && i < 512; i++)
+ for (uint16 i = 0; i < inventoryObjectCount && i < ARRAYSIZE(inventoryObjectList); i++)
inventoryObjectList[i] = (byte)view1->_inventoryItems[i]->_index;
}
- s.syncBytes(inventoryObjectList, 512);
+ s.syncBytes(inventoryObjectList, ARRAYSIZE(inventoryObjectList));
if (s.isLoading()) {
view1->_inventoryItems.clear();
- for (uint16 i = 0; i < inventoryObjectCount && i < 512; i++) {
+ for (uint16 i = 0; i < inventoryObjectCount && i < ARRAYSIZE(inventoryObjectList); i++) {
uint16 idx = inventoryObjectList[i];
if (idx > 0 && idx <= GameObjects::instance()._objects.size() && GameObjects::instance()._objects[idx - 1] != nullptr)
view1->_inventoryItems.push_back(GameObjects::instance()._objects[idx - 1]);
@@ -313,7 +264,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
// indexed by pathfinding value 0xC8..0xEF
if (s.isLoading())
_pathfindingOverrides.clear();
- for (int i = 0; i < 40; i++) {
+ for (int i = 0; i < ARRAYSIZE(_areaOverrides); i++) {
uint8 active = 0;
uint16 overrideValue = 0;
uint16 remap = 0;
@@ -326,7 +277,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
break;
}
}
- // Remap field at +0x4EA8 = _areaOverrides[i]
remap = _areaOverrides[i];
}
s.syncAsByte(active);
@@ -344,13 +294,10 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
}
- // --- Scene data: hotspot overrides [+0x5BD3]: 32 bytes (16 x uint16) ---
- // Binary table is at scene+i*2+0x5BD1, accessed with 1-based index (1..16).
- // Save format is fixed at 16 words (AHFFMSGM0100). Runtime capacity may be
- // larger; expand only after the DOS fields are restored.
+ // --- Scene data: hotspot overrides
if (s.isLoading()) {
_hotspotOverrides.clear();
- _hotspotOverrides.resize(0x11, 0xFFFF);
+ _hotspotOverrides.resize(17, 0xFFFF);
}
for (int i = 0; i < 16; i++) {
uint16 val = 0xFFFF;
@@ -361,8 +308,8 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
_hotspotOverrides[i + 1] = val;
}
}
- if (s.isLoading() && _hotspotOverrides.size() < 0x21) {
- _hotspotOverrides.resize(0x21, 0xFFFF);
+ if (s.isLoading() && _hotspotOverrides.size() < 33) {
+ _hotspotOverrides.resize(33, 0xFFFF);
}
for (int i = 0; i < ARRAYSIZE(_sceneTimerParams); i++) {
@@ -387,7 +334,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
BackgroundAnimationBlob &blob = _backgroundAnimationsBlobs[i];
uint16 seqPos = 0;
if (s.isSaving() && blob._blob.size() >= 4)
- seqPos = READ_LE_UINT16(&blob._blob[2]); // header word at +2 = sequence position
+ seqPos = READ_LE_UINT16(&blob._blob[2]);
s.syncAsUint16LE(seqPos);
if (s.isLoading()) {
// advanceAnimFrame(1, V + 100, blob): jump to sequence position V.
@@ -491,36 +438,30 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
if (s.isLoading())
obj->_hasBoundsAttachment = (hasBoundsWord & 0xFF) != 0;
- // BoundsAttachmentObjectID [+0x232]: 2 bytes
s.syncAsUint16LE(obj->_boundsAttachmentObjectID);
- // BoundsAttachmentValue1 [+0x234]: 2 bytes
s.syncAsUint16LE(obj->_boundsAttachmentValue1);
- // BoundsAttachmentValue2 [+0x236]: 2 bytes
s.syncAsUint16LE(obj->_boundsAttachmentValue2);
- // BoundsAttachmentValue3 [+0x238]: 2 bytes
s.syncAsUint16LE(obj->_boundsAttachmentValue3);
// Runtime walk state [+0x00..+0x0A]: targetX, targetY, deltaX, deltaY, finalX, finalY
// Find the Character for this object (exists after changeScene on load too)
Character *chr = nullptr;
- if (view1) {
- for (uint ci = 0; ci < view1->_characters.size(); ci++) {
- if (view1->_characters[ci] && view1->_characters[ci]->_gameObject == obj) {
- chr = view1->_characters[ci];
- break;
- }
- }
- // On load, create the Character on demand if none exists yet. We are
- // inside the `hasExtendedData` branch (object is in-scene / actor /
- // inventory per its just-read saved scene), which is exactly when the
- // binary allocates the object's runtime struct. Creating it here lets
- // the runtime walk/draw/dirty fields below round-trip losslessly.
- if (chr == nullptr && s.isLoading()) {
- chr = new Character();
- chr->_gameObject = obj;
- view1->_characters.push_back(chr);
+ for (uint ci = 0; ci < view1->_characters.size(); ci++) {
+ if (view1->_characters[ci] && view1->_characters[ci]->_gameObject == obj) {
+ chr = view1->_characters[ci];
+ break;
}
}
+ // On load, create the Character on demand if none exists yet. We are
+ // inside the `hasExtendedData` branch (object is in-scene / actor /
+ // inventory per its just-read saved scene), which is exactly when the
+ // binary allocates the object's runtime struct. Creating it here lets
+ // the runtime walk/draw/dirty fields below round-trip losslessly.
+ if (chr == nullptr && s.isLoading()) {
+ chr = new Character();
+ chr->_gameObject = obj;
+ view1->_characters.push_back(chr);
+ }
uint16 targetX = chr ? (uint16)chr->_targetPosition.x : 0;
uint16 targetY = chr ? (uint16)chr->_targetPosition.y : 0;
uint16 deltaX = chr ? (uint16)chr->_stepDeltaX : 0;
@@ -533,7 +474,7 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
s.syncAsUint16LE(deltaY);
s.syncAsUint16LE(finalX);
s.syncAsUint16LE(finalY);
- if (s.isLoading() && chr) {
+ if (s.isLoading()) {
chr->_targetPosition.x = (int16)targetX;
chr->_targetPosition.y = (int16)targetY;
chr->_stepDeltaX = (int16)deltaX;
@@ -542,22 +483,18 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
chr->_pathFinalDestination.y = (int16)finalY;
}
- // [+0x0C]: 0x20 bytes - raw runtime path block (opaque per-waypoint data
- // in the original). Preserved verbatim for byte-exact DOS save round-trip.
byte pathBlock[32] = {0};
if (s.isSaving() && chr)
memcpy(pathBlock, chr->_pathBlockRaw, 32);
s.syncBytes(pathBlock, 32);
- // [+0x2C]: 2 bytes - path current index
uint16 pathIndex = chr ? (uint16)chr->_currentPathIndex : 0;
s.syncAsUint16LE(pathIndex);
- // [+0x2E]: 2 bytes - path length
uint16 pathLength = chr ? (uint16)chr->_path.size() : 0;
s.syncAsUint16LE(pathLength);
- if (s.isLoading() && chr) {
+ if (s.isLoading()) {
memcpy(chr->_pathBlockRaw, pathBlock, 32);
chr->_path.clear();
for (uint16 pi = 0; pi < pathLength && pi < 32; pi++)
@@ -565,24 +502,19 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
chr->_currentPathIndex = (int)pathIndex;
}
- // [+0x30]: 2 bytes - step accumulator (Bresenham error)
uint16 stepAccum = chr ? (uint16)chr->_stepError : 0;
s.syncAsUint16LE(stepAccum);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_stepError = (int16)stepAccum;
- // [+0x32]: 1 byte - walk step flag (legacy, no binary equivalent)
uint8 walkStepFlag = 0;
s.syncAsByte(walkStepFlag);
- // Not used - binary has no _isLerping flag
- // [+0x33]: 1 byte - direction set flag (_stepDirectionSet)
uint8 directionSet = chr ? (chr->_stepDirectionSet ? 1 : 0) : 0;
s.syncAsByte(directionSet);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_stepDirectionSet = directionSet != 0;
- // [+0x20D..+0x213]: 4 x uint16 - per-object dirty rect (inclusive coords)
uint16 clipLeft = (uint16)obj->_dirtyLeft;
uint16 clipTop = (uint16)obj->_dirtyTop;
uint16 clipRight = (uint16)obj->_dirtyRight;
@@ -598,29 +530,22 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
obj->_dirtyBottom = (int16)clipBottom;
}
- // [+0x21D..+0x22B]: 8 x uint16 - motion vertical offset state + sprite draw bounds
- // +0x21D: motion target vertical offset (scriptSetMotion param 1)
- // +0x21F: vOffset accumulator (Bresenham error, runtime running total)
- // +0x221: vOffset threshold (scriptSetMotion param 3: motionDistance)
- // +0x223: vOffset step delta (scriptSetMotion param 2: verticalOffsetDelta)
- // +0x225..+0x22B: transient sprite draw bounds (recalculated each frame)
uint16 motionTarget = chr ? chr->_motionTargetVerticalOffset : 0;
s.syncAsUint16LE(motionTarget);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_motionTargetVerticalOffset = motionTarget;
uint16 motionAccum = chr ? chr->_motionProgress : 0;
s.syncAsUint16LE(motionAccum);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_motionProgress = motionAccum;
uint16 motionThreshold = chr ? chr->_motionDistanceUnits : 0;
s.syncAsUint16LE(motionThreshold);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_motionDistanceUnits = motionThreshold;
uint16 motionStep = chr ? chr->_motionVerticalOffsetDelta : 0;
s.syncAsUint16LE(motionStep);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_motionVerticalOffsetDelta = motionStep;
- // +0x225..+0x22B: sprite draw bounds from previous frame
uint16 lastDrawX = (uint16)obj->_lastDrawX;
uint16 lastDrawY = (uint16)obj->_lastDrawY;
uint16 lastDrawW = obj->_lastDrawWidth;
@@ -636,67 +561,49 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
obj->_lastDrawHeight = lastDrawH;
}
- // [+0x215]: 2 bytes - pickup frame counter
uint16 pickupFrameCounter = chr ? chr->_pickupFrameCounter : 0;
s.syncAsUint16LE(pickupFrameCounter);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_pickupFrameCounter = pickupFrameCounter;
- // [+0x217]: 2 bytes - _pickupFrameStart
s.syncAsUint16LE(obj->_pickupFrameStart);
- // [+0x219]: 2 bytes - _pickupFrameEnd
s.syncAsUint16LE(obj->_pickupFrameEnd);
- // [+0x21B]: 2 bytes - previous orientation (saved before pickup)
uint16 prevOrientation = chr ? chr->_previousOrientation : 0;
s.syncAsUint16LE(prevOrientation);
- if (s.isLoading() && chr)
+ if (s.isLoading())
chr->_previousOrientation = (uint8)prevOrientation;
- // overloadAnimTriggerDirection [+0x22D]: 2 bytes
s.syncAsUint16LE(obj->_overloadAnimTriggerDirection);
- // _snapToTarget [+0x22F]: 1 byte
uint8 snapToTarget = obj->_snapToTarget ? 1 : 0;
s.syncAsByte(snapToTarget);
if (s.isLoading())
obj->_snapToTarget = snapToTarget != 0;
- // useOverloadAnimation [+0x230]: 1 byte
uint8 useOverload = obj->_useOverloadAnimation ? 1 : 0;
s.syncAsByte(useOverload);
if (s.isLoading())
obj->_useOverloadAnimation = useOverload != 0;
- // [+0x184]: 1 byte - hasInventoryIcon (loaded from re_runtimeSlotValuessource file)
- // Indicates presence of inventory icon blob (slot 0x14 = index 0x13)
uint8 hasInventoryIcon = (obj->_blobs.size() > 0x13 && !obj->_blobs[0x13].empty()) ? 1 : 0;
s.syncAsByte(hasInventoryIcon);
- // [+0x185]: 1 byte - HasShading (per-object rendering flag from file)
uint8 hasShading = obj->_hasShading ? 1 : 0;
s.syncAsByte(hasShading);
if (s.isLoading())
obj->_hasShading = hasShading != 0;
- // [+0x186]: 1 byte - HasScaling (per-object rendering flag from file)
uint8 hasScaling = obj->_hasScaling ? 1 : 0;
s.syncAsByte(hasScaling);
if (s.isLoading())
obj->_hasScaling = hasScaling != 0;
- // NOTE: the binary save record has NO isClickable/isVisible fields here.
- // loadGameFromFile/saveGameToFile go directly from +0x186 (hasScaling) to
- // +0x18B (scriptSize). These flags are not persisted by the original (they
- // are re-established by script execution), so we must not serialize them
- // or the object record length diverges from the original file.
-
- // Script size [+0x18B]: 2 bytes
uint16 scriptSize = (uint16)obj->_script.size();
s.syncAsUint16LE(scriptSize);
// Script resource table [+0x18D]: 0x80 bytes (128 bytes = 32 dword offsets)
// Stored in GameObject::_resourceOffsets, loaded from file during readResourceFile.
const uint maxObjRes = maxObjectResources();
- byte scriptResourceTable[128] = {0}; // DOS save table is always 0x80 bytes
+ byte scriptResourceTable[128] = {0};
if (s.isSaving()) {
for (uint r = 0; r < maxObjRes; r++) {
WRITE_LE_UINT32(&scriptResourceTable[r * 4], obj->_resourceOffsets[r]);
@@ -709,25 +616,17 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
}
- // Script data: scriptSize bytes from [+0x187] pointer
if (s.isLoading())
obj->_script.resize(scriptSize);
if (scriptSize > 0)
s.syncBytes(obj->_script.data(), scriptSize);
- // --- Animation blobs (21 slots, 1-based: 1..0x15) ---
- // Animation blob save format (binary-compatible with original 1008:6859/747e):
- // Per slot (1..0x15): 2-byte active flag, then if active:
- // +0x00: 2 bytes (frame cursor X), +0x02: 2 bytes (frame cursor Y),
- // +0x0C: 2 bytes (source key), +0x0E: 2 bytes (speed),
- // +0x04: 2 bytes (data size), then data_size bytes of pixel data.
const int animSlotCount = (int)maxAnimSlots();
const int overloadBlobIdx = (int)overloadAnimSlot() - 1;
for (int blobIdx = 0; blobIdx < animSlotCount; blobIdx++) {
// Overload slot is stored separately from normal orientation blobs.
bool isOverloadSlot = (blobIdx == overloadBlobIdx);
- // Active flag (2 bytes, but only low byte matters)
uint16 blobActive = 0;
if (s.isSaving()) {
if (isOverloadSlot) {
@@ -754,13 +653,12 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
if (blobActive) {
- // entry+0x00: frame cursor/offset X (not tracked in ScummVM, save 0)
+ // frame cursor/offset X (not tracked in ScummVM, save 0)
uint16 field00 = 0;
s.syncAsUint16LE(field00);
- // entry+0x02: frame cursor/offset Y (not tracked in ScummVM, save 0)
+ // frame cursor/offset Y (not tracked in ScummVM, save 0)
uint16 field02 = 0;
s.syncAsUint16LE(field02);
- // entry+0x0C: source resource key
uint16 blobSourceKey = 0;
if (s.isSaving()) {
if (isOverloadSlot) {
@@ -772,12 +670,10 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
blobSourceKey = obj->_blobSourceKeys[blobIdx];
}
s.syncAsUint16LE(blobSourceKey);
- // entry+0x0E: speed/timing
uint16 blobSpeed = 0;
if (s.isSaving() && !isOverloadSlot && blobIdx < (int)obj->_blobWalkSpeeds.size())
blobSpeed = obj->_blobWalkSpeeds[blobIdx];
s.syncAsUint16LE(blobSpeed);
- // entry+0x04: data size
uint16 blobSize = 0;
if (s.isSaving()) {
if (isOverloadSlot) {
@@ -831,7 +727,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
}
}
- // --- Post-load: rebuild view state ---
if (s.isLoading()) {
if (pendingScriptRestore) {
_scriptExecutor->restoreScriptExecutionAfterLoad(pendingScriptIsExecuting,
@@ -861,8 +756,6 @@ Common::Error Macs2Engine::syncGame(Common::Serializer &s) {
view1->updateCursor();
view1->_paletteDirty = true;
- // Binary loadGameFromFile (1008:82a7): help-disabled path uses
- // applyScenePaletteEffect + partial UI palette; else fade from black.
if (view1->isHelpButtonDisabled()) {
applyScenePaletteEffect();
view1->restoreUiPaletteEntries();
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 67f9c2bd469..412a256d891 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -482,7 +482,7 @@ void ScriptExecutor::clearScriptUiWaitState() {
void ScriptExecutor::recordScriptErrorPosition() {
if (!hasScriptError() || !_stream)
return;
- // Binary LAB_1008_e3bd: save position and scene/object context on halt.
+ // save position and scene/object context on halt.
_errorScriptPosition = (uint32)_stream->pos();
if (_executingScriptObjectId == 0) {
_errorScriptContext = Scenes::instance()._currentSceneIndex;
@@ -618,8 +618,6 @@ void ScriptExecutor::step() {
// Check if the currently executing script is at the end
if (!_stream || _stream->pos() >= effectiveScriptEnd()) {
syncScriptIsExecutingFlag();
- // Binary (runScriptExecutor 1008:e3e7): if script finishes while
- // g_wScriptSkippable is still set, treat as error 0x11 and abort.
if (_stream && _scriptSkippable) {
setScriptError(0x11);
_scriptSkippable = false;
@@ -947,7 +945,6 @@ OpcodeResult Script::ScriptExecutor::scriptNop09() {
}
OpcodeResult Script::ScriptExecutor::scriptPrintStringLeft() {
- // l0037_DDE8:
debugC(kDebugScript, "SCRIPT::printStringLeft()");
scriptPrintString();
// Ends execution (confirmed: jumps to e3bd in disassembly).
@@ -1364,7 +1361,7 @@ OpcodeResult Script::ScriptExecutor::scriptShowDialogue() {
_dialogueSpeakerObjectID = objectID;
currentView->showSpeechAct(objectID, strings, Common::Point(x, y), side);
- tryPlayGeneratedDialogueSpeech((uint16)offset);
+ tryPlayDialogueSpeech((uint16)offset);
_waitingForUiClick = true;
@@ -3310,28 +3307,30 @@ Common::Path ScriptExecutor::resolveMidiFilePath(const Common::String &fileName)
return Common::Path();
}
-void ScriptExecutor::tryPlayGeneratedDialogueSpeech(uint16 stringOffset) {
- const bool enhOn = _engine->enhancementEnabled(kEnhAudioChanges);
- Common::String baseName;
+void ScriptExecutor::tryPlayDialogueSpeech(uint16 stringOffset) {
+ Common::String speechFile;
if (_executingScriptObjectId == 0) {
- baseName = Common::String::format("s%02x_%04x",
+ speechFile = Common::String::format("s%02x_%04x",
Scenes::instance()._currentSceneIndex, stringOffset);
} else {
- baseName = Common::String::format("o%03x_%04x",
+ speechFile = Common::String::format("o%03x_%04x",
_executingScriptObjectId, stringOffset);
}
- const Common::Path basename = Common::Path("SPEECH").join(baseName);
+ const Common::Path &speechPath = Common::Path("SPEECH").join(speechFile);
+ const bool enhOn = _engine->enhancementEnabled(kEnhAudioChanges);
debugC(kDebugScript,
"tryPlayGeneratedDialogueSpeech: looking for '%s'.* (enhAudio=%d soundEnabled=%d "
"scriptObject=%u scene=%u offset=%u)",
- basename.toString().c_str(), enhOn ? 1 : 0, _soundEnabled ? 1 : 0,
+ speechPath.toString().c_str(), enhOn ? 1 : 0, _soundEnabled ? 1 : 0,
_executingScriptObjectId, Scenes::instance()._currentSceneIndex, stringOffset);
+ // TODO: TTS support
+
if (!_soundEnabled || !enhOn)
return;
- _engine->playDigitalAudioFile(basename, true);
+ _engine->playAudioFile(speechPath, true);
}
OpcodeResult ScriptExecutor::scriptNopSkipRemainder() {
@@ -3354,7 +3353,7 @@ OpcodeResult ScriptExecutor::scriptPlaySfx() {
return OpcodeResult::Continue;
}
const bool speechBus = path.toString('/').hasPrefixIgnoreCase("SPEECH/");
- _engine->playDigitalAudioFile(path, speechBus);
+ _engine->playAudioFile(path, speechBus);
return OpcodeResult::Continue;
}
@@ -4029,7 +4028,7 @@ OpcodeResult ScriptExecutor::scriptTalkTo() {
if (_soundEnabled && !voiceFile.empty()) {
const Common::Path path = resolveAudioFilePath(voiceFile, true);
if (!path.empty()) {
- _engine->playDigitalAudioFile(path, true);
+ _engine->playAudioFile(path, true);
playingVoice = _engine->isSpeechPlaying();
} else {
warning("talkTo: missing voice %s (looked in SPEECH/SOUNDFX)", voiceFile.c_str());
diff --git a/engines/macs2/scriptexecutor.h b/engines/macs2/scriptexecutor.h
index 059c663b860..4319255275c 100644
--- a/engines/macs2/scriptexecutor.h
+++ b/engines/macs2/scriptexecutor.h
@@ -231,7 +231,7 @@ public:
* scene -> SPEECH/sSS_OOOO.*, object -> SPEECH/oOOO_OOOO.*.
* Missing files are ignored.
*/
- void tryPlayGeneratedDialogueSpeech(uint16 stringOffset);
+ void tryPlayDialogueSpeech(uint16 stringOffset);
// Dialect v2: extended opcodes 0x4F..0x6D (stubs until backends exist).
OpcodeResult scriptSetMainActor();
Commit: 6d917ecf4ce6edc0ebf4febc2c5740d7e5946b75
https://github.com/scummvm/scummvm/commit/6d917ecf4ce6edc0ebf4febc2c5740d7e5946b75
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-01T16:40:09+02:00
Commit Message:
MACS2: fixed text-to-speech playing per frame
Changed paths:
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/scriptexecutor.cpp
engines/macs2/scriptexecutor.h
engines/macs2/view1.cpp
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 7fd592bc3b1..37621514322 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -855,13 +855,25 @@ Macs2Engine::~Macs2Engine() {
void Macs2Engine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
#ifdef USE_TTS
+ if (text.empty())
+ return;
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
- if (ttsMan && ConfMan.getBool("tts_enabled")) {
- ttsMan->say(text, action);
+ if (ttsMan && ConfMan.getBool("tts_enabled") && _ttsPreviousSaid != text) {
+ ttsMan->say(text, action, Common::kDos850);
+ _ttsPreviousSaid = text;
}
#endif
}
+void Macs2Engine::stopTextToSpeech() const {
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking())
+ ttsMan->stop();
+ _ttsPreviousSaid.clear();
+#endif
+}
+
void Macs2Engine::syncSoundSettings() {
Engine::syncSoundSettings();
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 924f7c9f5ab..6a136a0919f 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -555,6 +555,9 @@ public:
Common::Array<byte> _amigaPendingSceneScript;
Common::Array<byte> _amigaPendingSceneStrings;
+ /** Last string submitted to TTS; skips per-frame INTERRUPT restarts. */
+ mutable Common::String _ttsPreviousSaid;
+
/** Bottom HUD visible (showActionBar/hideActionBar); default shown. */
bool _bottomHudVisible = true;
@@ -913,6 +916,7 @@ public:
bool tick() override;
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT) const;
+ void stopTextToSpeech() const;
void getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots) override;
bool hotspotDirty() const override;
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 412a256d891..657d8a6ec0d 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -52,6 +52,16 @@ static Common::String joinDebugStrings(const Common::StringArray &strings) {
return result;
}
+static Common::String joinTtsLines(const Common::Array<Common::String> &lines) {
+ Common::String text;
+ for (const Common::String &line : lines) {
+ if (!text.empty())
+ text += " ";
+ text += line;
+ }
+ return text;
+}
+
#define ScriptNoEntry debugC(kDebugScript, "Unhandled case in script handling.");
#define STR_HELPER(x) #x
@@ -429,6 +439,7 @@ void ScriptExecutor::scriptPrintString(bool alignRight) {
currentView->_isShowingTextBox = true;
currentView->currentSpeechActData.speaker = nullptr;
currentView->_continueScriptAfterUI = true;
+ _engine->sayText(joinTtsLines(strings), Common::TextToSpeechManager::INTERRUPT);
currentView->redraw();
}
@@ -1361,7 +1372,7 @@ OpcodeResult Script::ScriptExecutor::scriptShowDialogue() {
_dialogueSpeakerObjectID = objectID;
currentView->showSpeechAct(objectID, strings, Common::Point(x, y), side);
- tryPlayDialogueSpeech((uint16)offset);
+ tryPlayDialogueSpeech((uint16)offset, strings);
_waitingForUiClick = true;
@@ -3307,7 +3318,7 @@ Common::Path ScriptExecutor::resolveMidiFilePath(const Common::String &fileName)
return Common::Path();
}
-void ScriptExecutor::tryPlayDialogueSpeech(uint16 stringOffset) {
+void ScriptExecutor::tryPlayDialogueSpeech(uint16 stringOffset, const Common::Array<Common::String> &lines) {
Common::String speechFile;
if (_executingScriptObjectId == 0) {
speechFile = Common::String::format("s%02x_%04x",
@@ -3325,12 +3336,15 @@ void ScriptExecutor::tryPlayDialogueSpeech(uint16 stringOffset) {
speechPath.toString().c_str(), enhOn ? 1 : 0, _soundEnabled ? 1 : 0,
_executingScriptObjectId, Scenes::instance()._currentSceneIndex, stringOffset);
- // TODO: TTS support
+ if (_soundEnabled && enhOn)
+ _engine->playAudioFile(speechPath, true);
- if (!_soundEnabled || !enhOn)
+ if (_engine->isSpeechPlaying()) {
+ _engine->stopTextToSpeech();
return;
+ }
- _engine->playAudioFile(speechPath, true);
+ _engine->sayText(joinTtsLines(lines), Common::TextToSpeechManager::INTERRUPT);
}
OpcodeResult ScriptExecutor::scriptNopSkipRemainder() {
@@ -4040,12 +4054,16 @@ OpcodeResult ScriptExecutor::scriptTalkTo() {
enterBlockingWaitCursor();
if (playingVoice) {
+ _engine->stopTextToSpeech();
_engine->getMusic()->setSmfDucked(true, _engine->_talkVol);
_waitForPcmSound = true;
endFrameWait();
return OpcodeResult::WaitForCallback;
}
+ if (_textEnabled)
+ _engine->sayText(joinTtsLines(strings), Common::TextToSpeechManager::INTERRUPT);
+
uint16 waitFrames = talkTime;
if (waitFrames == 0) {
waitFrames = 0x12;
diff --git a/engines/macs2/scriptexecutor.h b/engines/macs2/scriptexecutor.h
index 4319255275c..f1e76318248 100644
--- a/engines/macs2/scriptexecutor.h
+++ b/engines/macs2/scriptexecutor.h
@@ -229,9 +229,9 @@ public:
/**
* Optional generated dialogue audio (kEnhAudioChanges):
* scene -> SPEECH/sSS_OOOO.*, object -> SPEECH/oOOO_OOOO.*.
- * Missing files are ignored.
+ * Missing files are ignored. If no voice plays, speak `lines` via TTS.
*/
- void tryPlayDialogueSpeech(uint16 stringOffset);
+ void tryPlayDialogueSpeech(uint16 stringOffset, const Common::Array<Common::String> &lines);
// Dialect v2: extended opcodes 0x4F..0x6D (stubs until backends exist).
OpcodeResult scriptSetMainActor();
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index 64e8f09be54..bbe04eb4ee9 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -3364,15 +3364,6 @@ void View1::showSpeechAct(uint16 characterIndex, const Common::Array<Common::Str
_dialogueChoiceCount = 0;
_continueScriptAfterUI = true;
- // TTS: speak the dialogue text
- Common::String ttsText;
- for (const Common::String &line : strings) {
- if (!ttsText.empty())
- ttsText += " ";
- ttsText += line;
- }
- g_engine->sayText(ttsText, Common::TextToSpeechManager::INTERRUPT);
-
currentSpeechActData.speaker = getCharacterByIndex(characterIndex);
currentSpeechActData.strings = strings;
currentSpeechActData.position = position;
Commit: ba7d1f4df6d802302b624cc83f847ffaa02d75be
https://github.com/scummvm/scummvm/commit/ba7d1f4df6d802302b624cc83f847ffaa02d75be
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-09-01T16:40:09+02:00
Commit Message:
MACS2: support gender and different voices in tts
Changed paths:
engines/macs2/macs2.cpp
engines/macs2/macs2.h
engines/macs2/scriptexecutor.cpp
engines/macs2/view1.cpp
engines/macs2/view1.h
diff --git a/engines/macs2/macs2.cpp b/engines/macs2/macs2.cpp
index 37621514322..c56e13840c4 100644
--- a/engines/macs2/macs2.cpp
+++ b/engines/macs2/macs2.cpp
@@ -853,12 +853,114 @@ Macs2Engine::~Macs2Engine() {
Scenes::instance()._currentSceneStrings = nullptr;
}
-void Macs2Engine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
+#ifdef USE_TTS
+// Object indices and names match GameObjects::init / isNpcIndex.
+// voiceId is reused for the same person (Tramp, Winnetou).
+struct TTSSpeakerVoice {
+ uint16 objectIndex;
+ bool male;
+ uint8 voiceId;
+};
+
+static const TTSSpeakerVoice kTTSSpeakerVoices[] = {
+ { 0x01, true, 1 }, // Old Firehand
+ { 0x02, true, 2 }, // Kapitän
+ { 0x04, true, 3 }, // Bootsjunge
+ { 0x06, true, 4 }, // Tramp
+ { 0x07, false, 5 }, // Mädchen
+ { 0x09, true, 6 }, // Panther
+ { 0x0C, true, 7 }, // Droll (nickname Tante Droll)
+ { 0x0D, true, 8 }, // Patterson
+ { 0x0F, true, 9 }, // Cornel
+ { 0x12, true, 10 }, // Wachposten
+ { 0x13, true, 4 }, // Tramp
+ { 0x16, false, 11 }, // Passagierin
+ { 0x21, true, 12 }, // Matrose
+ { 0x27, true, 13 }, // Branshky
+ { 0x35, true, 14 }, // Dieb
+ { 0x45, true, 15 }, // Rafter
+ { 0x4D, true, 4 }, // Tramp
+ { 0x69, true, 16 }, // Wirt
+ { 0x6E, false, 17 }, // Mrs. Butler
+ { 0x90, true, 18 }, // Bandit
+ { 0x91, true, 19 }, // Bandit
+ { 0x92, true, 20 }, // Bandit
+ { 0x93, true, 21 }, // Winnetou
+ { 0x95, true, 22 }, // Grosser Bär
+ { 0xA7, true, 21 }, // Winnetou
+ { 0xA8, true, 23 }, // Kleiner Bär
+ { 0xB5, true, 21 } // Winnetou
+};
+
+static void lookupTtsSpeakerVoice(uint16 objectIndex, bool &male, uint8 &voiceId) {
+ if (objectIndex == 0) {
+ male = true;
+ voiceId = 0;
+ return;
+ }
+ for (uint i = 0; i < ARRAYSIZE(kTTSSpeakerVoices); i++) {
+ if (kTTSSpeakerVoices[i].objectIndex == objectIndex) {
+ male = kTTSSpeakerVoices[i].male;
+ voiceId = kTTSSpeakerVoices[i].voiceId;
+ return;
+ }
+ }
+ male = true;
+ voiceId = (uint8)(objectIndex & 0x7F);
+}
+#endif
+
+void Macs2Engine::setTTSVoice(uint16 speakerObjectId) const {
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan && ConfMan.getBool("tts_enabled")) {
+ Common::Array<int> voices;
+ int pitch = 0;
+ Common::TTSVoice::Gender gender;
+
+ bool male = true;
+ uint8 voiceId = 0;
+ lookupTtsSpeakerVoice(speakerObjectId, male, voiceId);
+
+ if (male) {
+ voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
+ gender = Common::TTSVoice::MALE;
+ } else {
+ voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+ gender = Common::TTSVoice::FEMALE;
+ }
+
+ // If no voice is available for the necessary gender, set the voice to default
+ if (voices.empty()) {
+ ttsMan->setVoice(0);
+ } else {
+ int voiceIndex = voiceId % voices.size();
+ ttsMan->setVoice(voices[voiceIndex]);
+ }
+
+ // If no voices are available for this gender, alter the pitch to mimic a voice
+ // of the other gender
+ if (ttsMan->getVoice().getGender() != gender) {
+ if (gender == Common::TTSVoice::MALE) {
+ pitch -= 50;
+ } else {
+ pitch += 50;
+ }
+ }
+
+ ttsMan->setPitch(pitch);
+ }
+#endif
+}
+
+void Macs2Engine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action,
+ uint16 speakerObjectId) const {
#ifdef USE_TTS
if (text.empty())
return;
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ConfMan.getBool("tts_enabled") && _ttsPreviousSaid != text) {
+ setTTSVoice(speakerObjectId);
ttsMan->say(text, action, Common::kDos850);
_ttsPreviousSaid = text;
}
diff --git a/engines/macs2/macs2.h b/engines/macs2/macs2.h
index 6a136a0919f..5b1144c4f16 100644
--- a/engines/macs2/macs2.h
+++ b/engines/macs2/macs2.h
@@ -915,7 +915,10 @@ public:
bool tick() override;
- void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT) const;
+ void sayText(const Common::String &text,
+ Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT_NO_REPEAT,
+ uint16 speakerObjectId = 0) const;
+ void setTTSVoice(uint16 speakerObjectId) const;
void stopTextToSpeech() const;
void getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots) override;
diff --git a/engines/macs2/scriptexecutor.cpp b/engines/macs2/scriptexecutor.cpp
index 657d8a6ec0d..466b025c709 100644
--- a/engines/macs2/scriptexecutor.cpp
+++ b/engines/macs2/scriptexecutor.cpp
@@ -3344,7 +3344,7 @@ void ScriptExecutor::tryPlayDialogueSpeech(uint16 stringOffset, const Common::Ar
return;
}
- _engine->sayText(joinTtsLines(lines), Common::TextToSpeechManager::INTERRUPT);
+ _engine->sayText(joinTtsLines(lines), Common::TextToSpeechManager::INTERRUPT, _dialogueSpeakerObjectID);
}
OpcodeResult ScriptExecutor::scriptNopSkipRemainder() {
@@ -4062,7 +4062,7 @@ OpcodeResult ScriptExecutor::scriptTalkTo() {
}
if (_textEnabled)
- _engine->sayText(joinTtsLines(strings), Common::TextToSpeechManager::INTERRUPT);
+ _engine->sayText(joinTtsLines(strings), Common::TextToSpeechManager::INTERRUPT, (uint16)actorIndex);
uint16 waitFrames = talkTime;
if (waitFrames == 0) {
diff --git a/engines/macs2/view1.cpp b/engines/macs2/view1.cpp
index bbe04eb4ee9..9a6d276484b 100644
--- a/engines/macs2/view1.cpp
+++ b/engines/macs2/view1.cpp
@@ -3578,7 +3578,7 @@ void View1::showDialogueChoice(uint16 speakerObjectID, const Common::Array<Commo
ttsText += line + " ";
}
}
- g_engine->sayText(ttsText, Common::TextToSpeechManager::INTERRUPT);
+ g_engine->sayText(ttsText, Common::TextToSpeechManager::INTERRUPT, speakerObjectID);
showSpeechAct(speakerObjectID, joinedLines, position, onRightSide);
_isDialogueChoiceInputActive = true;
diff --git a/engines/macs2/view1.h b/engines/macs2/view1.h
index f86ac6f35d0..953fd0ffb48 100644
--- a/engines/macs2/view1.h
+++ b/engines/macs2/view1.h
@@ -523,7 +523,7 @@ public:
void closeScriptActionBar(Script::MouseMode &outSavedCursorMode);
void enterMapMode();
- // Binary openActionBarAtPosition (1008:3fba): stores button hit rects at panel+4+col*(btnW+4).
+ // Binary openActionBarAtPosition
void layoutActionBarButtons();
void drawMainMenu(Graphics::ManagedSurface &s);
void drawSceneUpdate();
@@ -559,9 +559,7 @@ public:
Macs2::AnimFrame *getUISprite(uint32 offset);
- // fn0037_3737 proc
void drawHorizontalBorderHighlight(const Common::Point &pos, int16 width, uint32 spriteAddress, Graphics::ManagedSurface &s);
- // 0037h:3876h
void drawVerticalBorderHighlight(const Common::Point &pos, int16 height, uint32 spriteAddress, Graphics::ManagedSurface &s);
void drawImageResources(Graphics::ManagedSurface &s);
More information about the Scummvm-git-logs
mailing list