TWINE: added lba2 intro support

This commit is contained in:
Martin Gerhardy 2021-09-10 19:18:28 +02:00
parent 1fb91752e1
commit 4b512d64b8
13 changed files with 193 additions and 59 deletions

View File

@ -34,7 +34,9 @@
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/grid.h"
#include "twine/shared.h"
#include "twine/twine.h"
#include "video/smk_decoder.h"
namespace TwinE {
@ -342,6 +344,7 @@ void FlaMovies::playGIFMovie(const char *flaName) {
}
void FlaMovies::playFlaMovie(const char *flaName) {
assert(_engine->isLBA1());
_engine->_sound->stopSamples();
Common::String fileNamePath = Common::String::format("%s", flaName);
@ -435,4 +438,46 @@ void FlaMovies::playFlaMovie(const char *flaName) {
_engine->_sound->stopSamples();
}
void FlaMovies::playSmkMovie(int index) {
assert(_engine->isLBA2());
Video::SmackerDecoder decoder;
Common::SeekableReadStream *stream = HQR::makeReadStream(TwineResource(Resources::HQR_VIDEO_FILE, index));
if (stream == nullptr) {
warning("Failed to find smacker video %i", index);
return;
}
if (!decoder.loadStream(stream)) {
warning("Failed to load smacker video %i", index);
return;
}
decoder.start();
for (;;) {
if (decoder.endOfVideo()) {
break;
}
FrameMarker frame(_engine);
_engine->_input->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
if (decoder.needsUpdate()) {
const Graphics::Surface *frameSurf = decoder.decodeNextFrame();
if (!frameSurf) {
continue;
}
if (decoder.hasDirtyPalette()) {
_engine->setPalette(0, 256, decoder.getPalette());
}
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
const Common::Rect frameBounds(0, 0, frameSurf->w, frameSurf->h);
target.transBlitFrom(*frameSurf, frameBounds, target.getBounds(), 0, false, 0, 0xff, nullptr, true);
}
}
decoder.close();
}
} // namespace TwinE

View File

@ -103,6 +103,8 @@ public:
* @param flaName FLA movie name
*/
void playFlaMovie(const char *flaName);
void playSmkMovie(int index);
};
} // namespace TwinE

View File

@ -121,7 +121,8 @@ void Holomap::clearHolomapPosition(int32 locationIdx) {
}
void Holomap::loadHolomapGFX() {
_engine->_screens->loadCustomPalette(RESSHQR_HOLOPAL);
constexpr TwineResource resource(Resources::HQR_RESS_FILE, RESSHQR_HOLOPAL);
_engine->_screens->loadCustomPalette(resource);
int32 j = 576;
for (int32 i = 0; i < 96; i += 3, j += 3) {

View File

@ -53,7 +53,7 @@ void MenuOptions::newGame() {
_engine->_cfgfile.FlagDisplayText = true;
// intro screen 1 - twinsun
_engine->_screens->loadImage(RESSHQR_INTROSCREEN1IMG, RESSHQR_INTROSCREEN1PAL);
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 15, 16));
_engine->_text->_drawTextBoxBackground = false;
_engine->_text->_renderTextTriangle = true;
@ -66,11 +66,11 @@ void MenuOptions::newGame() {
// intro screen 2
if (!aborted) {
_engine->_screens->loadImage(RESSHQR_INTROSCREEN2IMG, RESSHQR_INTROSCREEN2PAL);
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 17, 18));
aborted |= _engine->_text->drawTextProgressive(TextId::kIntroText2);
if (!aborted) {
_engine->_screens->loadImage(RESSHQR_INTROSCREEN3IMG, RESSHQR_INTROSCREEN3PAL);
_engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 19, 20));
aborted |= _engine->_text->drawTextProgressive(TextId::kIntroText3);
}
}

View File

@ -57,13 +57,20 @@ bool TextData::loadFromHQR(const char *name, TextBankId textBankId, int language
for (int entry = 0; entry < numIdxEntries; ++entry) {
const TextId textIdx = (TextId)indexStream->readUint16LE();
const uint16 start = offsetStream->readUint16LE();
uint16 start = offsetStream->readUint16LE();
const int32 offsetPos = offsetStream->pos();
const uint16 end = offsetStream->readUint16LE();
if (!lba1) {
++start;
}
offsetStream->seek(start);
Common::String result;
for (int16 i = start; i < end - 1; ++i) {
const char c = (char)offsetStream->readByte();
if (c == '\0') {
break;
}
result += c;
}
add(textBankId, TextEntry{result, entry, textIdx});

View File

@ -34,17 +34,17 @@ namespace TwinE {
bool Screens::adelineLogo() {
_engine->_music->playMidiMusic(31);
return loadImageDelay(RESSHQR_ADELINEIMG, RESSHQR_ADELINEPAL, 7);
return loadImageDelay(_engine->_resources->adelineLogo(), 7);
}
void Screens::loadMenuImage(bool fadeIn) {
loadImage(RESSHQR_MENUIMG, -1, fadeIn);
loadImage(_engine->_resources->menuBackground(), fadeIn);
_engine->_workVideoBuffer.blitFrom(_engine->_frontVideoBuffer);
}
void Screens::loadCustomPalette(int32 index) {
if (HQR::getEntry(_palette, Resources::HQR_RESS_FILE, index) == 0) {
warning("Failed to load custom palette %i", index);
void Screens::loadCustomPalette(const TwineResource &resource) {
if (HQR::getEntry(_palette, resource.hqr, resource.index) == 0) {
warning("Failed to load custom palette %i", resource.index);
return;
}
convertPalToRGBA(_palette, _paletteRGBACustom);
@ -62,18 +62,18 @@ void Screens::convertPalToRGBA(const uint8 *in, uint32 *out) {
}
}
void Screens::loadImage(int32 index, int32 paletteIndex, bool fadeIn) {
void Screens::loadImage(TwineImage image, bool fadeIn) {
Graphics::ManagedSurface& src = _engine->_imageBuffer;
if (HQR::getEntry((uint8 *)src.getPixels(), Resources::HQR_RESS_FILE, index) == 0) {
warning("Failed to load image with index %i", index);
if (HQR::getEntry((uint8 *)src.getPixels(), image.image) == 0) {
warning("Failed to load image with index %i", image.image.index);
return;
}
debug(0, "Load image: %i", index);
debug(0, "Load image: %i", image.image.index);
Graphics::ManagedSurface& target = _engine->_frontVideoBuffer;
target.transBlitFrom(src, src.getBounds(), target.getBounds(), 0, false, 0, 0xff, nullptr, true);
const uint32 *pal = _paletteRGBA;
if (paletteIndex != -1) {
loadCustomPalette(paletteIndex);
if (image.palette.index != -1) {
loadCustomPalette(image.palette);
pal = _paletteRGBACustom;
}
if (fadeIn) {
@ -83,8 +83,8 @@ void Screens::loadImage(int32 index, int32 paletteIndex, bool fadeIn) {
}
}
bool Screens::loadImageDelay(int32 index, int32 paletteIndex, int32 seconds) {
loadImage(index, paletteIndex);
bool Screens::loadImageDelay(TwineImage image, int32 seconds) {
loadImage(image);
if (_engine->delaySkip(1000 * seconds)) {
adjustPalette(0, 0, 0, _paletteRGBACustom, 100);
return true;

View File

@ -78,7 +78,7 @@ public:
* Load a custom palette
* @param index \a RESS.HQR entry index (starting from 0)
*/
void loadCustomPalette(int32 index);
void loadCustomPalette(const TwineResource &resource);
/** Load and display Main Menu image */
void loadMenuImage(bool fadeIn = true);
@ -89,7 +89,7 @@ public:
* @param paletteIndex \a RESS.HQR entry index of the palette for the given image. This is often the @c index + 1
* @param fadeIn if we fade in before using the palette
*/
void loadImage(int32 index, int32 paletteIndex, bool fadeIn = true);
void loadImage(TwineImage image, bool fadeIn = true);
/**
* Load and display a particulary image on \a RESS.HQR file with cross fade effect and delay
@ -97,7 +97,7 @@ public:
* @param paletteIndex \a RESS.HQR entry index of the palette for the given image. This is often the @c index + 1
* @param seconds number of seconds to delay
*/
bool loadImageDelay(int32 index, int32 paletteIndex, int32 seconds);
bool loadImageDelay(TwineImage image, int32 seconds);
/**
* Fade image in

View File

@ -25,6 +25,7 @@
#include "common/scummsys.h"
#include "common/stream.h"
#include "twine/shared.h"
namespace TwinE {
@ -37,6 +38,7 @@ class TwinEEngine;
*/
namespace HQR {
/**
* Get a HQR entry pointer
* @param ptr pointer to save the entry
@ -45,6 +47,9 @@ namespace HQR {
* @return entry real size
*/
int32 getEntry(uint8 *ptr, const char *filename, int32 index);
inline int32 getEntry(uint8 *ptr, TwineResource resource) {
return getEntry(ptr, resource.hqr, resource.index);
}
/**
* Get a HQR entry pointer
@ -53,6 +58,9 @@ int32 getEntry(uint8 *ptr, const char *filename, int32 index);
* @return entry real size
*/
int32 entrySize(const char *filename, int32 index);
inline int32 entrySize(TwineResource resource) {
return entrySize(resource.hqr, resource.index);
}
/**
* Get a HQR total number of entries
@ -70,11 +78,17 @@ int32 numEntries(const char *filename);
* @return entry real size
*/
int32 getAllocEntry(uint8 **ptr, const char *filename, int32 index);
inline int32 getAllocEntry(uint8 **ptr, TwineResource resource) {
return getAllocEntry(ptr, resource.hqr, resource.index);
}
/**
* @brief Helper method to dump the content of the given hqr index to a file
*/
bool dumpEntry(const char *filename, int32 index, const char *targetFileName);
inline bool dumpEntry(TwineResource resource, const char *targetFileName) {
return dumpEntry(resource.hqr, resource.index, targetFileName);
}
/**
* Get a HQR entry pointer
@ -95,7 +109,9 @@ int32 getVoxEntry(uint8 *ptr, const char *filename, int32 index, int32 hiddenInd
int32 getAllocVoxEntry(uint8 **ptr, const char *filename, int32 index, int32 hiddenIndex);
Common::SeekableReadStream *makeReadStream(const char *filename, int index);
inline Common::SeekableReadStream *makeReadStream(TwineResource resource) {
return makeReadStream(resource.hqr, resource.index);
}
} // namespace HQR
} // namespace TwinE

View File

@ -48,33 +48,17 @@ namespace TwinE {
#define RESSHQR_HOLOTWINMDL 9
#define RESSHQR_HOLOARROWMDL 10
#define RESSHQR_HOLOTWINARROWMDL 11
#define RESSHQR_RELLENTIMG 12
#define RESSHQR_RELLENTPAL 13
#define RESSHQR_MENUIMG 14
#define RESSHQR_INTROSCREEN1IMG 15
#define RESSHQR_INTROSCREEN1PAL 16
#define RESSHQR_INTROSCREEN2IMG 17
#define RESSHQR_INTROSCREEN2PAL 18
#define RESSHQR_INTROSCREEN3IMG 19
#define RESSHQR_INTROSCREEN3PAL 20
#define RESSHQR_GAMEOVERMDL 21
#define RESSHQR_ALARMREDPAL 22
#define RESSHQR_FLAINFO 23
#define RESSHQR_DARKPAL 24
#define RESSHQR_TWINSEN_ZOE_SENDELLIMG 25
#define RESSHQR_TWINSEN_ZOE_SENDELLPAL 26
#define RESSHQR_ADELINEIMG 27
#define RESSHQR_ADELINEPAL 28
#define RESSHQR_HOLOPOINTMDL 29
#define RESSHQR_HOLOPOINTANIM 30
#define RESSHQR_LBAIMG 49
#define RESSHQR_LBAPAL 50
#define RESSHQR_PLASMAEFFECT 51
#define RESSHQR_EAIMG 52
#define RESSHQR_EAPAL 53
#define FLA_DRAGON3 "dragon3"
#define FLA_INTROD "introd"
@ -229,6 +213,8 @@ public:
static constexpr const char *HQR_LBA_BRK_FILE = "lba_brk.hqr";
// scenes (active area content (actors, scripts, etc.))
static constexpr const char *HQR_SCENE_FILE = "scene.hqr";
// full screen images (lba2)
static constexpr const char *HQR_SCREEN_FILE = "screen.hqr";
// sprites
static constexpr const char *HQR_SPRITES_FILE = "sprites.hqr";
/**
@ -250,6 +236,51 @@ public:
static constexpr const char *HQR_FLASAMP_FILE = "flasamp.hqr";
static constexpr const char *HQR_MIDI_MI_DOS_FILE = "midi_mi.hqr";
static constexpr const char *HQR_MIDI_MI_WIN_FILE = "midi_mi_win.hqr";
static constexpr const char *HQR_VIDEO_FILE = "video.hqr"; // lba2 - smk files
TwineImage adelineLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 27, 28);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 0, 1);
}
TwineImage lbaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 49, 50);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 60, 61);
}
TwineImage eaLogo() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 52, 53);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 74, 75);
}
TwineImage activisionLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 72, 73);
}
TwineImage virginLogo() const {
assert(_engine->isLBA2());
return TwineImage(Resources::HQR_SCREEN_FILE, 76, 77);
}
TwineImage relentLogo() const {
assert(_engine->isLBA1());
return TwineImage(Resources::HQR_RESS_FILE, 12, 13);
}
TwineImage menuBackground() const {
if (_engine->isLBA1()) {
return TwineImage(Resources::HQR_RESS_FILE, 14, -1);
}
return TwineImage(Resources::HQR_SCREEN_FILE, 4, 5);
}
};
} // namespace TwinE

View File

@ -1578,7 +1578,7 @@ static int32 lSET_NORMAL_PAL(TwinEEngine *engine, LifeScriptContext &ctx) {
static int32 lMESSAGE_SENDELL(TwinEEngine *engine, LifeScriptContext &ctx) {
ScopedEngineFreeze scoped(engine);
engine->_screens->fadeToBlack(engine->_screens->_paletteRGBA);
engine->_screens->loadImage(RESSHQR_TWINSEN_ZOE_SENDELLIMG, RESSHQR_TWINSEN_ZOE_SENDELLPAL);
engine->_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 25, 26));
engine->_text->textClipFull();
engine->_text->setFontCrossColor(COLOR_WHITE);
engine->_text->_drawTextBoxBackground = false;

View File

@ -568,6 +568,22 @@ enum InventoryItems {
MaxInventoryItems = 28
};
struct TwineResource {
const char *hqr;
const int32 index;
constexpr TwineResource(const char *_hqr, int32 _index) : hqr(_hqr), index(_index) {
}
};
struct TwineImage {
TwineResource image;
TwineResource palette;
constexpr TwineImage(const char *hqr, int32 index, int32 paletteIndex = -1) : image(hqr, index), palette(hqr, paletteIndex) {
}
};
// lba2 does from 0 to 0x1000
// lba1 angles
// TODO: wrap in a class to be able to handle lba1 and lba2

View File

@ -129,6 +129,9 @@ TwinEEngine::TwinEEngine(OSystem *system, Common::Language language, uint32 flag
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "fla");
SearchMan.addSubDirectoryMatching(gameDataDir, "vox");
if (isLBA2()) {
SearchMan.addSubDirectoryMatching(gameDataDir, "video");
}
if (flags & TF_DOTEMU_ENHANCED) {
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/hqr");
SearchMan.addSubDirectoryMatching(gameDataDir, "resources/lba_files/fla");
@ -483,30 +486,42 @@ void TwinEEngine::initEngine() {
_input->enableKeyMap(cutsceneKeyMapId);
// Display company logo
bool abort = false;
if (isLBA2()) {
//abort |= _screens->loadImageDelay(_resources->activisionLogo(), 7);
abort |= _screens->loadImageDelay(_resources->eaLogo(), 7);
}
abort |= _screens->adelineLogo();
// verify game version screens
if (!abort && _cfgfile.Version == EUROPE_VERSION) {
// Little Big Adventure screen
abort |= _screens->loadImageDelay(RESSHQR_LBAIMG, RESSHQR_LBAPAL, 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(RESSHQR_EAIMG, RESSHQR_EAPAL, 2);
if (isLBA1()) {
// verify game version screens
if (!abort && _cfgfile.Version == EUROPE_VERSION) {
// Little Big Adventure screen
abort |= _screens->loadImageDelay(_resources->lbaLogo(), 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(_resources->eaLogo(), 2);
}
} else if (!abort && _cfgfile.Version == USA_VERSION) {
// Relentless screen
abort |= _screens->loadImageDelay(_resources->relentLogo(), 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(_resources->eaLogo(), 2);
}
} else if (!abort && _cfgfile.Version == MODIFICATION_VERSION) {
// Modification screen
abort |= _screens->loadImageDelay(_resources->relentLogo(), 2);
}
} else if (!abort && _cfgfile.Version == USA_VERSION) {
// Relentless screen
abort |= _screens->loadImageDelay(RESSHQR_RELLENTIMG, RESSHQR_RELLENTPAL, 3);
if (!abort) {
// Electronic Arts Logo
abort |= _screens->loadImageDelay(RESSHQR_EAIMG, RESSHQR_EAPAL, 2);
}
} else if (!abort && _cfgfile.Version == MODIFICATION_VERSION) {
// Modification screen
abort |= _screens->loadImageDelay(RESSHQR_RELLENTIMG, RESSHQR_RELLENTPAL, 2);
}
if (!abort) {
_flaMovies->playFlaMovie(FLA_DRAGON3);
if (isLBA1()) {
_flaMovies->playFlaMovie(FLA_DRAGON3);
} else {
_flaMovies->playSmkMovie(16);
}
}
_input->enableKeyMap(uiKeyMapId);
@ -566,7 +581,7 @@ void TwinEEngine::processActorSamplePosition(int32 actorIdx) {
void TwinEEngine::processBookOfBu() {
_screens->fadeToBlack(_screens->_paletteRGBA);
_screens->loadImage(RESSHQR_INTROSCREEN1IMG, RESSHQR_INTROSCREEN1PAL);
_screens->loadImage(TwineImage(Resources::HQR_RESS_FILE, 15, 16));
_text->initTextBank(TextBankId::Inventory_Intro_and_Holomap);
_text->_drawTextBoxBackground = false;
_text->textClipFull();

View File

@ -59,6 +59,7 @@ class BigHuffmanTree;
* - sword2
* - toon
* - trecision
* - twine
*/
class SmackerDecoder : public VideoDecoder {
public: