SCI: rave support (KQ6 hires portrait lip sync)

Thanks to wjp and [md5] for helping
This commit is contained in:
Martin Kiewitz 2013-12-11 08:25:23 +01:00
parent 298f4a5c06
commit 693d5e6625
3 changed files with 252 additions and 24 deletions

View File

@ -40,6 +40,7 @@ Portrait::Portrait(ResourceManager *resMan, EventManager *event, GfxScreen *scre
}
Portrait::~Portrait() {
delete[] _lipSyncDataOffsetTable;
delete[] _bitmaps;
delete[] _fileData;
}
@ -52,7 +53,7 @@ void Portrait::init() {
// 2 bytes main height (should be the same as first bitmap header height)
// 2 bytes animation count
// 2 bytes unknown
// 2 bytes unknown
// 2 bytes lip sync ID count
// 4 bytes paletteSize (base 1)
// -> 17 bytes
// paletteSize bytes paletteData
@ -82,6 +83,8 @@ void Portrait::init() {
_width = READ_LE_UINT16(_fileData + 3);
_height = READ_LE_UINT16(_fileData + 5);
_bitmapCount = READ_LE_UINT16(_fileData + 7);
_lipSyncIDCount = READ_LE_UINT16(_fileData + 11);
_bitmaps = new PortraitBitmap[_bitmapCount];
uint16 portraitPaletteSize = READ_LE_UINT16(_fileData + 13);
@ -129,7 +132,48 @@ void Portrait::init() {
}
data += offsetTableSize;
// raw lip-sync data follows
// raw lip-sync ID table follows
uint32 lipSyncIDTableSize;
lipSyncIDTableSize = READ_LE_UINT32(data);
data += 4;
assert( lipSyncIDTableSize == (_lipSyncIDCount * 4) );
_lipSyncIDTable = data;
data += lipSyncIDTableSize;
// raw lip-sync frame table follows
uint32 lipSyncDataTableSize;
uint32 lipSyncDataTableLastOffset;
byte lipSyncData;
uint16 lipSyncDataNr;
uint16 lipSyncCurOffset;
lipSyncDataTableSize = READ_LE_UINT32(data);
data += 4;
assert( lipSyncDataTableSize == 0x220 ); // always this size, just a safety-check
_lipSyncData = data;
lipSyncDataTableLastOffset = lipSyncDataTableSize - 1;
_lipSyncDataOffsetTable = new uint16[ _lipSyncIDCount ];
lipSyncDataNr = 0;
lipSyncCurOffset = 0;
while ( (lipSyncCurOffset < lipSyncDataTableSize) && (lipSyncDataNr < _lipSyncIDCount) ) {
// We are currently at the start of ID-frame data
_lipSyncDataOffsetTable[lipSyncDataNr] = lipSyncCurOffset;
// Look for end of ID-frame data
lipSyncData = *data++; lipSyncCurOffset++;
while ( (lipSyncData != 0xFF) && (lipSyncCurOffset < lipSyncDataTableLastOffset) ) {
// Either terminator (0xFF) or frame-data (1 byte tick count and 1 byte bitmap ID)
data++;
lipSyncData = *data++;
lipSyncCurOffset += 2;
}
lipSyncDataNr++;
}
_lipSyncDataOffsetTableEnd = data;
// last 4 bytes seem to be garbage
}
void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint16 verb, uint16 cond, uint16 seq) {
@ -137,10 +181,20 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
// Now init audio and sync resource
uint32 audioNumber = ((noun & 0xff) << 24) | ((verb & 0xff) << 16) | ((cond & 0xff) << 8) | (seq & 0xff);
ResourceId syncResourceId = ResourceId(kResourceTypeSync36, resourceId, noun, verb, cond, seq);
Resource *syncResource = _resMan->findResource(syncResourceId, true);
//ResourceId syncResourceId = ResourceId(kResourceTypeSync36, resourceId, noun, verb, cond, seq);
//Resource *syncResource = _resMan->findResource(syncResourceId, true);
ResourceId raveResourceId = ResourceId(kResourceTypeRave, resourceId, noun, verb, cond, seq);
Resource *raveResource = _resMan->findResource(raveResourceId, true);
#if 0
uint syncOffset = 0;
#endif
// TODO: play through the game if this is 100% accurate
// TODO: maybe try to create the missing sync resources for low-res KQ6 out of the rave resources
uint raveOffset = 0;
#if 0
// Dump the sync resources to disk
Common::DumpFile *outFile = new Common::DumpFile();
@ -172,7 +226,92 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
// Start playing audio...
_audio->stopAudio();
_audio->startAudio(resourceId, audioNumber);
if (!raveResource) {
warning("kPortrait: no rave resource %d %X", resourceId, audioNumber);
return;
}
// Do animation depending on rave resource till audio is done playing
int16 raveTicks;
uint16 raveID;
byte *raveLipSyncData;
byte raveLipSyncTicks;
byte raveLipSyncBitmapNr;
int timerPosition = 0;
int timerPositionWithin = 0;
int curPosition;
SciEvent curEvent;
bool userAbort = false;
while ((raveOffset < raveResource->size) && (!userAbort)) {
// rave string starts with tick count, followed by lipSyncID, tick count and so on
raveTicks = raveGetTicks(raveResource, &raveOffset);
if (raveTicks < 0)
break;
// get lipSyncID
raveID = raveGetID(raveResource, &raveOffset);
if (raveID) {
raveLipSyncData = raveGetLipSyncData(raveID);
} else {
raveLipSyncData = NULL;
}
timerPosition += raveTicks;
// Wait till syncTime passed, then show specific animation bitmap
if (timerPosition > 0) {
do {
g_sci->getEngineState()->wait(1);
curEvent = _event->getSciEvent(SCI_EVENT_ANY);
if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
(curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) ||
g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame)
userAbort = true;
curPosition = _audio->getAudioPosition();
} while ((curPosition != -1) && (curPosition < timerPosition) && (!userAbort));
}
if (raveLipSyncData) {
// lip sync data is
// Tick:Byte, Bitmap-Nr:BYTE
// Tick = 0xFF is the terminator for the data
timerPositionWithin = timerPosition;
raveLipSyncTicks = *raveLipSyncData++;
while ( (raveLipSyncData < _lipSyncDataOffsetTableEnd) && (raveLipSyncTicks != 0xFF) ) {
timerPositionWithin += raveLipSyncTicks;
do {
g_sci->getEngineState()->wait(1);
curEvent = _event->getSciEvent(SCI_EVENT_ANY);
if (curEvent.type == SCI_EVENT_MOUSE_PRESS ||
(curEvent.type == SCI_EVENT_KEYBOARD && curEvent.data == SCI_KEY_ESC) ||
g_sci->getEngineState()->abortScriptProcessing == kAbortQuitGame)
userAbort = true;
curPosition = _audio->getAudioPosition();
} while ((curPosition != -1) && (curPosition < timerPositionWithin) && (!userAbort));
raveLipSyncBitmapNr = *raveLipSyncData++;
// bitmap nr within sync data is base 1, we need base 0
raveLipSyncBitmapNr--;
if (raveLipSyncBitmapNr < _bitmapCount) {
drawBitmap(0);
drawBitmap(raveLipSyncBitmapNr);
bitsShow();
} else {
warning("kPortrait: rave lip sync data tried to draw non-existent bitmap %d", raveLipSyncBitmapNr);
}
raveLipSyncTicks = *raveLipSyncData++;
}
}
}
// old sync resource code
#if 0
if (!syncResource) {
// Getting the book in the book shop calls kPortrait where no sync exists
// TODO: find out what to do then
@ -219,6 +358,7 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
}
}
}
#endif
if (userAbort) {
// Reset the portrait bitmap to "closed mouth" state, when skipping dialogs
@ -227,7 +367,81 @@ void Portrait::doit(Common::Point position, uint16 resourceId, uint16 noun, uint
_audio->stopAudio();
}
_resMan->unlockResource(raveResource);
#if 0
_resMan->unlockResource(syncResource);
#endif
}
// returns ASCII ticks from lip sync string as uint16
int16 Portrait::raveGetTicks(Resource *resource, uint *offset) {
uint curOffset = *offset;
byte *curData = resource->data + curOffset;
byte curByte;
uint16 curValue = 0;
if (curOffset >= resource->size)
return -1;
while (curOffset < resource->size) {
curByte = *curData++; curOffset++;
if ( curByte == ' ' )
break;
if ( (curByte >= '0') && (curByte <= '9') ) {
curValue = curValue * 10 + ( curByte - '0' );
} else {
// no number -> assume there is an ID at current offset
return 0;
}
}
*offset = curOffset;
return curValue;
}
// returns ASCII ID from lip sync string as uint16
uint16 Portrait::raveGetID(Resource *resource, uint *offset) {
uint curOffset = *offset;
byte *curData = resource->data + curOffset;
byte curByte = 0;
uint16 curValue = 0;
while (curOffset < resource->size) {
curByte = *curData++; curOffset++;
if ( curByte == ' ' )
break;
if (!curValue) {
curValue = curByte << 8;
} else {
curValue |= curByte;
}
}
*offset = curOffset;
return curValue;
}
// Searches for a specific lip sync ID and returns pointer to lip sync data or NULL in case ID was not found
byte *Portrait::raveGetLipSyncData(uint16 raveID) {
uint lipSyncIDNr = 0;
byte *lipSyncIDPtr = _lipSyncIDTable;
byte lipSyncIDByte1, lipSyncIDByte2;
uint16 lipSyncID;
lipSyncIDPtr++; // skip over first byte
while (lipSyncIDNr < _lipSyncIDCount) {
lipSyncIDByte1 = *lipSyncIDPtr++;
lipSyncIDByte2 = *lipSyncIDPtr++;
lipSyncID = ( lipSyncIDByte1 << 8 ) | lipSyncIDByte2;
if ( lipSyncID == raveID ) {
return _lipSyncData + _lipSyncDataOffsetTable[lipSyncIDNr];
}
lipSyncIDNr++;
lipSyncIDPtr += 2; // ID is every 4 bytes
}
return NULL;
}
void Portrait::drawBitmap(uint16 bitmapNr) {

View File

@ -52,6 +52,10 @@ private:
void drawBitmap(uint16 bitmapNr);
void bitsShow();
int16 raveGetTicks(Resource *resource, uint *offset);
uint16 raveGetID(Resource *resource, uint *offset);
byte *raveGetLipSyncData(uint16 raveID);
ResourceManager *_resMan;
EventManager *_event;
GfxPalette *_palette;
@ -68,6 +72,13 @@ private:
Common::String _resourceName;
byte *_fileData;
uint32 _lipSyncIDCount;
byte *_lipSyncIDTable;
byte *_lipSyncData;
uint16 *_lipSyncDataOffsetTable;
byte *_lipSyncDataOffsetTableEnd;
Common::Point _position;
};

View File

@ -101,32 +101,34 @@ bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) {
}
file->seek(-4, SEEK_CUR);
ResourceType type = _resMan->convertResType(file->readByte());
if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio))
|| ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) {
warning("Resource type mismatch loading %s", _id.toString().c_str());
unalloc();
return false;
}
_headerSize = file->readByte();
if (type == kResourceTypeAudio) {
if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) {
warning("Unsupported audio header");
// Rave-resources (King's Quest 6) don't have any header at all
if (getType() != kResourceTypeRave) {
ResourceType type = _resMan->convertResType(file->readByte());
if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio))
|| ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) {
warning("Resource type mismatch loading %s", _id.toString().c_str());
unalloc();
return false;
}
_headerSize = file->readByte();
if (_headerSize != 7) { // Size is defined already from the map
// Load sample size
file->seek(7, SEEK_CUR);
size = file->readUint32LE();
// Adjust offset to point at the header data again
file->seek(-11, SEEK_CUR);
if (type == kResourceTypeAudio) {
if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) {
warning("Unsupported audio header");
unalloc();
return false;
}
if (_headerSize != 7) { // Size is defined already from the map
// Load sample size
file->seek(7, SEEK_CUR);
size = file->readUint32LE();
// Adjust offset to point at the header data again
file->seek(-11, SEEK_CUR);
}
}
}
return loadPatch(file);
}
@ -863,6 +865,7 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
switch (res->getType()) {
case kResourceTypeSync:
case kResourceTypeSync36:
case kResourceTypeRave:
// we should already have a (valid) size
break;
default: