diff options
Diffstat (limited to 'engines')
314 files changed, 40770 insertions, 4521 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 81aec3e351..9d88dd73ef 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -62,9 +62,7 @@ void AgiEngine::processEvents() { while (_eventMan->pollEvent(event)) { switch (event.type) { case Common::EVENT_QUIT: - _gfx->deinitVideo(); - _gfx->deinitMachine(); - _system->quit(); + _game.quitProgNow = true; break; case Common::EVENT_PREDICTIVE_DIALOG: if (_predictiveDialogRunning) @@ -766,12 +764,15 @@ AgiEngine::~AgiEngine() { } agiDeinit(); + delete _loader; _sound->deinitSound(); delete _sound; _gfx->deinitVideo(); delete _sprites; + delete _picture; free(_game.sbufOrig); _gfx->deinitMachine(); + delete _gfx; delete _rnd; delete _console; diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 9b22240f83..a9fd204d73 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -37,6 +37,7 @@ #include "sound/mididrv.h" #include "sound/mods/protracker.h" +#include "sound/audiocd.h" using Common::File; @@ -96,6 +97,8 @@ AGOSEngine::AGOSEngine(OSystem *syst) _vc_get_out_of_code = 0; _gameOffsetsPtr = 0; + _quit = false; + _debugger = 0; _gameFile = 0; @@ -556,14 +559,17 @@ int AGOSEngine::init() { // Setup midi driver int midiDriver = MidiDriver::detectMusicDriver(MDT_ADLIB | MDT_MIDI); _nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); - MidiDriver *driver = MidiDriver::createMidi(midiDriver); + + _driver = MidiDriver::createMidi(midiDriver); + if (_nativeMT32) { - driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); } _midi.mapMT32toGM (getGameType() != GType_SIMON2 && !_nativeMT32); - _midi.setDriver(driver); + _midi.setDriver(_driver); + int ret = _midi.open(); if (ret) warning("MIDI Player init failed: \"%s\"", _midi.getErrorName (ret)); @@ -572,6 +578,8 @@ int AGOSEngine::init() { _midiEnabled = true; + } else { + _driver = NULL; } // allocate buffers @@ -875,6 +883,10 @@ AGOSEngine::~AGOSEngine() { delete _gameFile; _midi.close(); + if (_driver) + delete _driver; + + AudioCD.destroy(); for (uint i = 0; i < _itemHeap.size(); i++) { delete[] _itemHeap[i]; @@ -883,6 +895,8 @@ AGOSEngine::~AGOSEngine() { free(_tablesHeapPtr - _tablesHeapCurPos); + free(_mouseData); + free(_gameOffsetsPtr); free(_iconFilePtr); free(_itemArrayPtr); @@ -894,6 +908,7 @@ AGOSEngine::~AGOSEngine() { free(_backGroundBuf); free(_backBuf); free(_scaleBuf); + free(_zoneBuffers); free(_window4BackScn); free(_window6BackScn); @@ -937,7 +952,7 @@ void AGOSEngine::pauseEngineIntern(bool pauseIt) { void AGOSEngine::pause() { pauseEngine(true); - while (_pause) { + while (_pause && !_quit) { delay(1); if (_keyPressed.keycode == Common::KEYCODE_p) pauseEngine(false); @@ -974,7 +989,7 @@ int AGOSEngine::go() { (getFeatures() & GF_DEMO)) { int i; - while (1) { + while (!_quit) { for (i = 0; i < 4; i++) { setWindowImage(3, 9902 + i); debug(0, "Displaying image %d", 9902 + i); @@ -1003,7 +1018,7 @@ int AGOSEngine::go() { runSubroutine101(); permitInput(); - while (1) { + while (!_quit) { waitForInput(); handleVerbClicked(_verbHitArea); delay(100); @@ -1012,6 +1027,9 @@ int AGOSEngine::go() { return 0; } + +/* I do not think that this will be used + * void AGOSEngine::shutdown() { // Sync with AGOSEngine::~AGOSEngine() // In Simon 2, this gets deleted along with _sound further down @@ -1019,6 +1037,7 @@ void AGOSEngine::shutdown() { delete _gameFile; _midi.close(); + delete _driver; for (uint i = 0; i < _itemHeap.size(); i++) { delete[] _itemHeap[i]; @@ -1058,6 +1077,7 @@ void AGOSEngine::shutdown() { _system->quit(); } +*/ uint32 AGOSEngine::getTime() const { // FIXME: calling time() is not portable, use OSystem::getMillis instead diff --git a/engines/agos/agos.h b/engines/agos/agos.h index 448d26a9d0..8ad5487b35 100644 --- a/engines/agos/agos.h +++ b/engines/agos/agos.h @@ -269,6 +269,7 @@ protected: uint16 _marks; + bool _quit; bool _scriptVar2; bool _runScriptReturn1; bool _runScriptCondition[40]; @@ -523,6 +524,7 @@ protected: byte _lettersToPrintBuf[80]; MidiPlayer _midi; + MidiDriver *_driver; bool _midiEnabled; bool _nativeMT32; @@ -1073,6 +1075,8 @@ protected: virtual void drawImage(VC10_state *state); void drawBackGroundImage(VC10_state *state); void drawVertImage(VC10_state *state); + void drawVertImageCompressed(VC10_state *state); + void drawVertImageUncompressed(VC10_state *state); void setMoveRect(uint16 x, uint16 y, uint16 width, uint16 height); diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp index fd78c65002..c92f834a3b 100644 --- a/engines/agos/animation.cpp +++ b/engines/agos/animation.cpp @@ -280,7 +280,7 @@ void MoviePlayer::handleNextFrame() { _rightButtonDown = false; break; case Common::EVENT_QUIT: - _vm->_system->quit(); + _vm->_quit = true; break; default: break; diff --git a/engines/agos/debug.cpp b/engines/agos/debug.cpp index 76a0b8e76f..2cf285d56a 100644 --- a/engines/agos/debug.cpp +++ b/engines/agos/debug.cpp @@ -393,11 +393,11 @@ static const byte bmp_hdr[] = { }; void dumpBMP(const char *filename, int w, int h, const byte *bytes, const uint32 *palette) { - Common::File out; + Common::DumpFile out; byte my_hdr[sizeof(bmp_hdr)]; int i; - out.open(filename, Common::File::kFileWriteMode); + out.open(filename); if (!out.isOpen()) return; diff --git a/engines/agos/draw.cpp b/engines/agos/draw.cpp index 737f5317af..d38a5ad33b 100644 --- a/engines/agos/draw.cpp +++ b/engines/agos/draw.cpp @@ -473,7 +473,7 @@ void AGOSEngine::restoreBackGround() { animTable = animTableTmp = _screenAnim1; while (animTable->srcPtr) { if (!(animTable->windowNum & 0x8000)) { - memcpy(animTableTmp, animTable, sizeof(AnimTable)); + memmove(animTableTmp, animTable, sizeof(AnimTable)); animTableTmp++; } animTable++; diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp index 250ff2fcfd..010b331cf8 100644 --- a/engines/agos/event.cpp +++ b/engines/agos/event.cpp @@ -142,7 +142,7 @@ bool AGOSEngine::kickoffTimeEvents() { cur_time = getTime() - _gameStoppedClock; - while ((te = _firstTimeStruct) != NULL && te->time <= cur_time) { + while ((te = _firstTimeStruct) != NULL && te->time <= cur_time && !_quit) { result = true; _pendingDeleteTimeEvent = te; invokeTimeEvent(te); @@ -521,7 +521,7 @@ void AGOSEngine::delay(uint amount) { _rightButtonDown++; break; case Common::EVENT_QUIT: - shutdown(); + _quit = true; return; default: break; @@ -544,7 +544,7 @@ void AGOSEngine::delay(uint amount) { _system->delayMillis(this_delay); cur = _system->getMillis(); - } while (cur < start + amount); + } while (cur < start + amount && !_quit); } void AGOSEngine::timer_callback() { diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp index 193b7347d6..9a3962ea21 100644 --- a/engines/agos/gfx.cpp +++ b/engines/agos/gfx.cpp @@ -744,10 +744,6 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) { } void AGOSEngine::drawBackGroundImage(VC10_state *state) { - const byte *src; - byte *dst; - uint h, i; - state->width = _screenWidth; if (_window3Flag == 1) { state->width = 0; @@ -755,15 +751,19 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) { state->y_skip = 0; } - src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8); - dst = state->surf_addr; + const byte* src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8); + byte* dst = state->surf_addr; state->draw_width *= 2; - h = state->draw_height; + uint h = state->draw_height; + const uint w = state->draw_width; + const byte paletteMod = state->paletteMod; do { - for (i = 0; i != state->draw_width; i++) - dst[i] = src[i] + state->paletteMod; + for (uint i = 0; i != w; i+=2) { + dst[i] = src[i] + paletteMod; + dst[i+1] = src[i+1] + paletteMod; + } dst += state->surf_pitch; src += state->width; } while (--h); @@ -771,63 +771,86 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) { void AGOSEngine::drawVertImage(VC10_state *state) { if (state->flags & kDFCompressed) { - uint w, h; - byte *src, *dst, *dstPtr; + drawVertImageCompressed(state); + } else { + drawVertImageUncompressed(state); + } +} - state->x_skip *= 4; /* reached */ +void AGOSEngine::drawVertImageUncompressed(VC10_state *state) { + assert ((state->flags & kDFCompressed) == 0) ; - state->dl = state->width; - state->dh = state->height; + const byte *src; + byte *dst; + uint count; - vc10_skip_cols(state); + src = state->srcPtr + (state->width * state->y_skip) * 8; + dst = state->surf_addr; + state->x_skip *= 4; - dstPtr = state->surf_addr; - if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */ - dstPtr += vcReadVar(252); - } - w = 0; - do { + do { + for (count = 0; count != state->draw_width; count++) { byte color; + color = (src[count + state->x_skip] / 16) + state->paletteMod; + if ((state->flags & kDFNonTrans) || color) + dst[count * 2] = color | state->palette; + color = (src[count + state->x_skip] & 15) + state->paletteMod; + if ((state->flags & kDFNonTrans) || color) + dst[count * 2 + 1] = color | state->palette; + } + dst += state->surf_pitch; + src += state->width * 8; + } while (--state->draw_height); +} - src = vc10_depackColumn(state); - dst = dstPtr; +void AGOSEngine::drawVertImageCompressed(VC10_state *state) { + assert (state->flags & kDFCompressed) ; + uint w, h; + + state->x_skip *= 4; /* reached */ - h = 0; + state->dl = state->width; + state->dh = state->height; + + vc10_skip_cols(state); + + byte *dstPtr = state->surf_addr; + if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */ + dstPtr += vcReadVar(252); + } + w = 0; + do { + byte color; + + const byte *src = vc10_depackColumn(state); + byte *dst = dstPtr; + + h = 0; + if (state->flags & kDFNonTrans) { + do { + byte colors = *src; + color = (colors / 16); + dst[0] = color | state->palette; + color = (colors & 15); + dst[1] = color | state->palette; + dst += state->surf_pitch; + src++; + } while (++h != state->draw_height); + } else { do { - color = (*src / 16); - if ((state->flags & kDFNonTrans) || color != 0) + byte colors = *src; + color = (colors / 16); + if (color != 0) dst[0] = color | state->palette; - color = (*src & 15); - if ((state->flags & kDFNonTrans) || color != 0) + color = (colors & 15); + if (color != 0) dst[1] = color | state->palette; dst += state->surf_pitch; src++; } while (++h != state->draw_height); - dstPtr += 2; - } while (++w != state->draw_width); - } else { - const byte *src; - byte *dst; - uint count; - - src = state->srcPtr + (state->width * state->y_skip) * 8; - dst = state->surf_addr; - state->x_skip *= 4; - - do { - for (count = 0; count != state->draw_width; count++) { - byte color; - color = (src[count + state->x_skip] / 16) + state->paletteMod; - if ((state->flags & kDFNonTrans) || color) - dst[count * 2] = color | state->palette; - color = (src[count + state->x_skip] & 15) + state->paletteMod; - if ((state->flags & kDFNonTrans) || color) - dst[count * 2 + 1] = color | state->palette; - } - dst += state->surf_pitch; - src += state->width * 8; - } while (--state->draw_height); - } + } + dstPtr += 2; + } while (++w != state->draw_width); } void AGOSEngine::drawImage(VC10_state *state) { @@ -1263,7 +1286,7 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vga_res) { if (getGameType() == GType_WW && (mode == 6 || mode == 8 || mode == 9)) { setWindowImage(mode, vga_res); } else { - while (_copyScnFlag) + while (_copyScnFlag && !_quit) delay(1); setWindowImage(mode, vga_res); diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp index add7eb96f0..d36549f187 100644 --- a/engines/agos/input.cpp +++ b/engines/agos/input.cpp @@ -189,12 +189,12 @@ void AGOSEngine::waitForInput() { resetVerbs(); } - for (;;) { + while (!_quit) { _lastHitArea = NULL; _lastHitArea3 = NULL; _dragAccept = 1; - for (;;) { + while (!_quit) { if ((getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) && _keyPressed.keycode == Common::KEYCODE_F10) displayBoxStars(); diff --git a/engines/agos/intern.h b/engines/agos/intern.h index 54cf4dba16..4479e2851e 100644 --- a/engines/agos/intern.h +++ b/engines/agos/intern.h @@ -161,6 +161,7 @@ struct WindowBlock { uint8 fill_color, text_color; IconBlock *iconPtr; WindowBlock() { memset(this, 0, sizeof(*this)); } + ~WindowBlock() { free (iconPtr); } }; // note on text offset: // the actual x-coordinate is: textColumn * 8 + textColumnOffset diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp index 34e5f2cfeb..4a5c43e706 100644 --- a/engines/agos/saveload.cpp +++ b/engines/agos/saveload.cpp @@ -75,7 +75,7 @@ int AGOSEngine::countSaveGames() { } char *AGOSEngine::genSaveName(int slot) { - static char buf[15]; + static char buf[20]; if (getGameId() == GID_DIMP) { sprintf(buf, "dimp.sav"); @@ -111,7 +111,7 @@ void AGOSEngine::quickLoadOrSave() { } bool success; - char buf[50]; + char buf[60]; char *filename = genSaveName(_saveLoadSlot); if (_saveLoadType == 2) { @@ -978,7 +978,7 @@ bool AGOSEngine::loadGame(const char *filename, bool restartMode) { if (restartMode) { // Load restart state Common::File *file = new Common::File(); - file->open(filename, Common::File::kFileReadMode); + file->open(filename); f = file; } else { f = _saveFileMan->openForLoading(filename); @@ -1154,7 +1154,7 @@ bool AGOSEngine_Elvira2::loadGame(const char *filename, bool restartMode) { if (restartMode) { // Load restart state Common::File *file = new Common::File(); - file->open(filename, Common::File::kFileReadMode); + file->open(filename); f = file; } else { f = _saveFileMan->openForLoading(filename); diff --git a/engines/agos/script.cpp b/engines/agos/script.cpp index 44fbb4e9e0..6758aec511 100644 --- a/engines/agos/script.cpp +++ b/engines/agos/script.cpp @@ -410,7 +410,7 @@ void AGOSEngine::o_msg() { void AGOSEngine::o_end() { // 68: exit interpreter - shutdown(); + _quit = true; } void AGOSEngine::o_done() { @@ -965,6 +965,9 @@ void AGOSEngine::writeVariable(uint16 variable, uint16 contents) { int AGOSEngine::runScript() { bool flag; + if (_quit) + return 1; + do { if (_continousMainScript) dumpOpcode(_codePtr); @@ -1007,7 +1010,7 @@ int AGOSEngine::runScript() { error("Invalid opcode '%d' encountered", _opcode); executeOpcode(_opcode); - } while (getScriptCondition() != flag && !getScriptReturn()); + } while (getScriptCondition() != flag && !getScriptReturn() && !_quit); return getScriptReturn(); } @@ -1063,7 +1066,7 @@ void AGOSEngine::waitForSync(uint a) { _exitCutscene = false; _rightButtonDown = false; - while (_vgaWaitFor != 0) { + while (_vgaWaitFor != 0 && !_quit) { if (_rightButtonDown) { if (_vgaWaitFor == 200 && (getGameType() == GType_FF || !getBitFlag(14))) { skipSpeech(); diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp index 94df21979c..c7e1d6736e 100644 --- a/engines/agos/script_e1.cpp +++ b/engines/agos/script_e1.cpp @@ -565,7 +565,7 @@ void AGOSEngine_Elvira1::oe1_look() { lobjFunc(l, "You can see "); /* Show objects */ } if (r && (r->flags & 4) && levelOf(i) < 10000) { - shutdown(); + _quit = true; } } @@ -944,7 +944,7 @@ restart: windowPutChar(window, *message2); if (confirmYesOrNo(120, 62) == 0x7FFF) { - shutdown(); + _quit = true; } else { goto restart; } diff --git a/engines/agos/script_s1.cpp b/engines/agos/script_s1.cpp index a1308b951d..51918b9515 100644 --- a/engines/agos/script_s1.cpp +++ b/engines/agos/script_s1.cpp @@ -345,14 +345,14 @@ void AGOSEngine_Simon1::os1_pauseGame() { if (isSmartphone()) { if (_keyPressed.keycode) { if (_keyPressed.keycode == Common::KEYCODE_RETURN) - shutdown(); + _quit = true; else break; } } #endif if (_keyPressed.keycode == keyYes) - shutdown(); + _quit = true; else if (_keyPressed.keycode == keyNo) break; } diff --git a/engines/agos/subroutine.cpp b/engines/agos/subroutine.cpp index 44ada82585..cb71ed7efa 100644 --- a/engines/agos/subroutine.cpp +++ b/engines/agos/subroutine.cpp @@ -554,6 +554,10 @@ int AGOSEngine::startSubroutine(Subroutine *sub) { _currentTable = sub; restart: + + if (_quit) + return result; + while ((byte *)sl != (byte *)sub) { _currentLine = sl; if (checkIfToRunSubroutineLine(sl, sub)) { diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp index 055eb733c3..8dbccebedf 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -769,19 +769,18 @@ void loadAbs(const char *resourceName, uint16 idx) { /*! \brief Load animDataTable from save * \param fHandle Savefile open for reading - * \param broken Broken/correct file format switch + * \param saveGameFormat The used savegame format * \todo Add Operation Stealth savefile support * * Unlike the old code, this one actually rebuilds the table one frame * at a time. */ -void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) { int16 currentAnim, foundFileIdx; int8 isMask = 0, isSpl = 0; byte *dataPtr, *ptr; char *animName, part[256]; byte transparentColor = 0; - AnimData *currentPtr; AnimHeaderStruct animHeader; uint16 width, height, bpp, var1; @@ -791,30 +790,46 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { strcpy(part, currentPartName); - for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim++) { - currentPtr = &animDataTable[currentAnim]; + // We only support these variations of the savegame format at the moment. + assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT); + const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30); + const int fileStartPos = fHandle.pos(); + for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) { + // Initialize the number of frames variable to a sane number. + // This is needed when using continue later in this function. + animHeader.numFrames = 1; + + // Seek to the start of the current animation's entry + fHandle.seek(fileStartPos + currentAnim * entrySize); + // Read in the current animation entry width = fHandle.readUint16BE(); var1 = fHandle.readUint16BE(); bpp = fHandle.readUint16BE(); height = fHandle.readUint16BE(); - if (!broken) { - if (!fHandle.readUint32BE()) { - fHandle.skip(18); - continue; - } - fHandle.readUint32BE(); + bool validPtr = false; + // Handle variables only present in animation entries of size 30 + if (entrySize == 30) { + validPtr = (fHandle.readUint32BE() != 0); // Read data pointer + fHandle.readUint32BE(); // Discard mask pointer } foundFileIdx = fHandle.readSint16BE(); frame = fHandle.readSint16BE(); fHandle.read(name, 10); - if (foundFileIdx < 0 || (broken && !fHandle.readByte())) { + // Handle variables only present in animation entries of size 23 + if (entrySize == 23) { + validPtr = (fHandle.readByte() != 0); + } + + // Don't try to load invalid entries. + if (foundFileIdx < 0 || !validPtr) { continue; } + // Alright, the animation entry looks to be valid so let's start handling it... if (strcmp(currentPartName, name)) { closePart(); loadPart(name); @@ -823,13 +838,14 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { animName = partBuffer[foundFileIdx].partName; ptr = dataPtr = readBundleFile(foundFileIdx); + // isSpl and isMask are mutually exclusive cases isSpl = (strstr(animName, ".SPL")) ? 1 : 0; isMask = (strstr(animName, ".MSK")) ? 1 : 0; if (isSpl) { width = (uint16) partBuffer[foundFileIdx].unpackedSize; height = 1; - frame = 0; + animHeader.numFrames = 1; type = ANIM_RAW; } else { Common::MemoryReadStream readS(ptr, 0x16); @@ -843,25 +859,35 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) { type = ANIM_MASK; } else { type = ANIM_MASKSPRITE; + } + } - loadRelatedPalette(animName); - transparentColor = getAnimTransparentColor(animName); - - // special case transparency handling - if (!strcmp(animName, "L2202.ANI")) { - transparentColor = (frame < 2) ? 0 : 7; - } else if (!strcmp(animName, "L4601.ANI")) { - transparentColor = (frame < 1) ? 0xE : 0; - } + loadRelatedPalette(animName); + transparentColor = getAnimTransparentColor(animName); + // Make sure we load at least one frame and also that we + // don't overflow the animDataTable by writing beyond its end. + animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim); + + // Load the frames + for (frame = 0; frame < animHeader.numFrames; frame++) { + // special case transparency handling + if (!strcmp(animName, "L2202.ANI")) { + transparentColor = (frame < 2) ? 0 : 7; + } else if (!strcmp(animName, "L4601.ANI")) { + transparentColor = (frame < 1) ? 0xE : 0; } + + // Load a single frame + animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor); } - ptr += frame * width * height; - currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor); free(dataPtr); } loadPart(part); + + // Make sure we jump over all the animation entries + fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize); } } // End of namespace Cine diff --git a/engines/cine/anim.h b/engines/cine/anim.h index d63033e670..b0ce55f7ee 100644 --- a/engines/cine/anim.h +++ b/engines/cine/anim.h @@ -26,8 +26,63 @@ #ifndef CINE_ANIM_H #define CINE_ANIM_H +#include "common/endian.h" + namespace Cine { +/** + * Cine engine's save game formats. + * Enumeration entries (Excluding the one used as an error) + * are sorted according to age (i.e. top one is oldest, last one newest etc). + * + * ANIMSIZE_UNKNOWN: + * - Animation data entry size is unknown (Used as an error). + * + * ANIMSIZE_23: + * - Animation data entry size is 23 bytes. + * - Used at least by 0.11.0 and 0.11.1 releases of ScummVM. + * - Introduced in revision 21772, stopped using in revision 31444. + * + * ANIMSIZE_30_PTRS_BROKEN: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are always NULL. + * - Introduced in revision 31453, stopped using in revision 32073. + * + * ANIMSIZE_30_PTRS_INTACT: + * - Animation data entry size is 30 bytes. + * - Data and mask pointers in the saved structs are intact, + * so you can test them for equality or inequality with NULL + * but don't try using them for anything else, it won't work. + * - Introduced in revision 31444, got broken in revision 31453, + * got fixed in revision 32073 and used after that. + * + * TEMP_OS_FORMAT: + * - Temporary Operation Stealth savegame format. + * - NOT backward compatible and NOT to be supported in the future. + * This format should ONLY be used during development and abandoned + * later in favor of a better format! + */ +enum CineSaveGameFormat { + ANIMSIZE_UNKNOWN, + ANIMSIZE_23, + ANIMSIZE_30_PTRS_BROKEN, + ANIMSIZE_30_PTRS_INTACT, + TEMP_OS_FORMAT +}; + +/** Identifier for the temporary Operation Stealth savegame format. */ +static const uint32 TEMP_OS_FORMAT_ID = MKID_BE('TEMP'); + +/** The current version number of Operation Stealth's savegame format. */ +static const uint32 CURRENT_OS_SAVE_VER = 0; + +/** Chunk header used by the temporary Operation Stealth savegame format. */ +struct ChunkHeader { + uint32 id; ///< Identifier (e.g. MKID_BE('TEMP')) + uint32 version; ///< Version number + uint32 size; ///< Size of the chunk after this header in bytes +}; + struct AnimHeaderStruct { byte field_0; byte field_1; @@ -101,7 +156,7 @@ void freeAnimDataTable(void); void freeAnimDataRange(byte startIdx, byte numIdx); void loadResource(const char *resourceName); void loadAbs(const char *resourceName, uint16 idx); -void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken); +void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat); void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency); } // End of namespace Cine diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp index c5b7fb4e3d..2a4e7f0ab1 100644 --- a/engines/cine/bg.cpp +++ b/engines/cine/bg.cpp @@ -35,7 +35,7 @@ namespace Cine { uint16 bgVar0; byte *additionalBgTable[9]; -byte currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0; +int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0; byte loadCtFW(const char *ctName) { uint16 header[32]; diff --git a/engines/cine/bg.h b/engines/cine/bg.h index 5fa8209131..9f97bc467d 100644 --- a/engines/cine/bg.h +++ b/engines/cine/bg.h @@ -35,6 +35,9 @@ void addBackground(const char *bgName, uint16 bgIdx); extern uint16 bgVar0; +extern int16 currentAdditionalBgIdx; +extern int16 currentAdditionalBgIdx2; + } // End of namespace Cine #endif diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp index cf25f1d355..fddca078e5 100644 --- a/engines/cine/bg_list.cpp +++ b/engines/cine/bg_list.cpp @@ -63,6 +63,7 @@ void addSpriteFilledToBGList(int16 objIdx) { void createBgIncrustListElement(int16 objIdx, int16 param) { BGIncrust tmp; + tmp.unkPtr = 0; tmp.objIdx = objIdx; tmp.param = param; tmp.x = objectTable[objIdx].x; @@ -82,7 +83,7 @@ void resetBgIncrustList(void) { /*! \brief Restore incrust list from savefile * \param fHandle Savefile open for reading */ -void loadBgIncrustFromSave(Common::InSaveFile &fHandle) { +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) { BGIncrust tmp; int size = fHandle.readSint16BE(); @@ -90,6 +91,7 @@ void loadBgIncrustFromSave(Common::InSaveFile &fHandle) { fHandle.readUint32BE(); fHandle.readUint32BE(); + tmp.unkPtr = 0; tmp.objIdx = fHandle.readUint16BE(); tmp.param = fHandle.readUint16BE(); tmp.x = fHandle.readUint16BE(); diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h index 1849d6ec3d..9a402baee8 100644 --- a/engines/cine/bg_list.h +++ b/engines/cine/bg_list.h @@ -51,7 +51,7 @@ void addSpriteFilledToBGList(int16 idx); void createBgIncrustListElement(int16 objIdx, int16 param); void resetBgIncrustList(void); -void loadBgIncrustFromSave(Common::InSaveFile &fHandle); +void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle); } // End of namespace Cine diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp index efc33fadaf..f6778b6457 100644 --- a/engines/cine/cine.cpp +++ b/engines/cine/cine.cpp @@ -42,7 +42,6 @@ #include "cine/sound.h" #include "cine/various.h" - namespace Cine { Sound *g_sound; @@ -70,6 +69,10 @@ CineEngine::~CineEngine() { freeErrmessDat(); } Common::clearAllSpecialDebugLevels(); + + free(palPtr); + free(partBuffer); + free(textDataPtr); } int CineEngine::init() { diff --git a/engines/cine/cine.h b/engines/cine/cine.h index 710840c17e..eaae555812 100644 --- a/engines/cine/cine.h +++ b/engines/cine/cine.h @@ -94,10 +94,17 @@ public: Common::StringList _volumeResourceFiles; StringPtrHashMap _volumeEntriesMap; + TextHandler _textHandler; private: void initialize(void); + void resetEngine(); + bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat); + bool loadTempSaveOS(Common::SeekableReadStream &in); bool makeLoad(char *saveName); + void makeSaveFW(Common::OutSaveFile &out); + void makeSaveOS(Common::OutSaveFile &out); + void makeSave(char *saveFileName); void mainLoop(int bootScriptIdx); void readVolCnf(); @@ -107,6 +114,7 @@ private: extern CineEngine *g_cine; #define BOOT_PRC_NAME "AUTO00.PRC" +#define COPY_PROT_FAIL_PRC_NAME "L201.ANI" enum { VAR_MOUSE_X_MODE = 253, diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp index 47446f2410..cbddf0fc59 100644 --- a/engines/cine/gfx.cpp +++ b/engines/cine/gfx.cpp @@ -337,7 +337,7 @@ int FWRenderer::drawChar(char character, int x, int y) { x += 5; } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) { idx = fontParamTable[(unsigned char)character].characterIdx; - drawSpriteRaw(textTable[idx][0], textTable[idx][1], 16, 8, _backBuffer, x, y); + drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y); x += width + 1; } @@ -601,20 +601,26 @@ void FWRenderer::setScroll(unsigned int shift) { error("Future Wars renderer doesn't support multiple backgrounds"); } +/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero. + */ +uint FWRenderer::getScroll() const { + return 0; +} + /*! \brief Placeholder for Operation Stealth implementation */ void FWRenderer::removeBg(unsigned int idx) { error("Future Wars renderer doesn't support multiple backgrounds"); } -void FWRenderer::saveBg(Common::OutSaveFile &fHandle) { +void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) { fHandle.write(_bgName, 13); } /*! \brief Restore active and backup palette from save * \param fHandle Savefile open for reading */ -void FWRenderer::restorePalette(Common::InSaveFile &fHandle) { +void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) { int i; if (!_palette) { @@ -655,6 +661,49 @@ void FWRenderer::savePalette(Common::OutSaveFile &fHandle) { } } +/*! \brief Write active and backup palette to save + * \param fHandle Savefile open for writing + */ +void OSRenderer::savePalette(Common::OutSaveFile &fHandle) { + int i; + + assert(_activeHiPal); + + // Write the active 256 color palette. + for (i = 0; i < _hiPalSize; i++) { + fHandle.writeByte(_activeHiPal[i]); + } + + // Write the active 256 color palette a second time. + // FIXME: The backup 256 color palette should be saved here instead of the active one. + for (i = 0; i < _hiPalSize; i++) { + fHandle.writeByte(_activeHiPal[i]); + } +} + +/*! \brief Restore active and backup palette from save + * \param fHandle Savefile open for reading + */ +void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) { + int i; + + if (!_activeHiPal) { + _activeHiPal = new byte[_hiPalSize]; + } + + assert(_activeHiPal); + + for (i = 0; i < _hiPalSize; i++) { + _activeHiPal[i] = fHandle.readByte(); + } + + // Jump over the backup 256 color palette. + // FIXME: Load the backup 256 color palette and use it properly. + fHandle.seek(_hiPalSize, SEEK_CUR); + + _changePal = 1; +} + /*! \brief Rotate active palette * \param a First color to rotate * \param b Last color to rotate @@ -938,7 +987,7 @@ int OSRenderer::drawChar(char character, int x, int y) { x += 5; } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) { idx = fontParamTable[(unsigned char)character].characterIdx; - drawSpriteRaw2(textTable[idx][0], 0, 16, 8, _backBuffer, x, y); + drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y); x += width + 1; } @@ -969,6 +1018,7 @@ void OSRenderer::drawBackground() { /*! \brief Draw one overlay * \param it Overlay info + * \todo Add handling of type 22 overlays */ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { int len; @@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { switch (it->type) { // color sprite case 0: + if (objectTable[it->objIdx].frame < 0) { + break; + } sprite = animDataTable + objectTable[it->objIdx].frame; len = sprite->_realWidth * sprite->_height; mask = new byte[len]; @@ -988,6 +1041,13 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) { delete[] mask; break; + // bitmap + case 4: + if (objectTable[it->objIdx].frame >= 0) { + FWRenderer::renderOverlay(it); + } + break; + // masked background case 20: assert(it->objIdx < NUM_MAX_OBJECT); @@ -1236,6 +1296,13 @@ void OSRenderer::setScroll(unsigned int shift) { _bgShift = shift; } +/*! \brief Get background scroll + * \return Background scroll in pixels + */ +uint OSRenderer::getScroll() const { + return _bgShift; +} + /*! \brief Unload background from renderer * \param idx Background to unload */ @@ -1259,6 +1326,12 @@ void OSRenderer::removeBg(unsigned int idx) { memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name)); } +void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) { + for (int i = 0; i < 8; i++) { + fHandle.write(_bgTable[i].name, 13); + } +} + /*! \brief Fade to black * \bug Operation Stealth sometimes seems to fade to black using * transformPalette resulting in double fadeout diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h index c63c79ac82..6a3aa1ef89 100644 --- a/engines/cine/gfx.h +++ b/engines/cine/gfx.h @@ -108,13 +108,14 @@ public: virtual void selectBg(unsigned int idx); virtual void selectScrollBg(unsigned int idx); virtual void setScroll(unsigned int shift); + virtual uint getScroll() const; virtual void removeBg(unsigned int idx); - void saveBg(Common::OutSaveFile &fHandle); + virtual void saveBgNames(Common::OutSaveFile &fHandle); virtual void refreshPalette(); virtual void reloadPalette(); - void restorePalette(Common::InSaveFile &fHandle); - void savePalette(Common::OutSaveFile &fHandle); + virtual void restorePalette(Common::SeekableReadStream &fHandle); + virtual void savePalette(Common::OutSaveFile &fHandle); virtual void rotatePalette(int a, int b, int c); virtual void transformPalette(int first, int last, int r, int g, int b); @@ -128,6 +129,7 @@ public: */ class OSRenderer : public FWRenderer { private: + // FIXME: Background table's size is probably 8 instead of 9. Check to make sure and correct if necessary. palBg _bgTable[9]; ///< Table of backgrounds loaded into renderer byte *_activeHiPal; ///< Active 256 color palette unsigned int _currentBg; ///< Current background @@ -163,10 +165,14 @@ public: void selectBg(unsigned int idx); void selectScrollBg(unsigned int idx); void setScroll(unsigned int shift); + uint getScroll() const; void removeBg(unsigned int idx); + void saveBgNames(Common::OutSaveFile &fHandle); void refreshPalette(); void reloadPalette(); + void restorePalette(Common::SeekableReadStream &fHandle); + void savePalette(Common::OutSaveFile &fHandle); void rotatePalette(int a, int b, int c); void transformPalette(int first, int last, int r, int g, int b); diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp index cfb828cf3c..e5e670c973 100644 --- a/engines/cine/main_loop.cpp +++ b/engines/cine/main_loop.cpp @@ -179,6 +179,20 @@ int getKeyData() { return k; } +/** Removes elements from seqList that have their member variable var4 set to value -1. */ +void purgeSeqList() { + Common::List<SeqListElement>::iterator it = seqList.begin(); + while (it != seqList.end()) { + if (it->var4 == -1) { + // Erase the element and jump to the next element + it = seqList.erase(it); + } else { + // Let the element be and jump to the next element + it++; + } + } +} + void CineEngine::mainLoop(int bootScriptIdx) { bool playerAction; uint16 quitFlag; @@ -186,6 +200,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { uint16 mouseButton; quitFlag = 0; + exitEngine = 0; if (_preLoad == false) { resetBgIncrustList(); @@ -194,7 +209,7 @@ void CineEngine::mainLoop(int bootScriptIdx) { errorVar = 0; - addScriptToList0(bootScriptIdx); + addScriptToGlobalScripts(bootScriptIdx); menuVar = 0; @@ -234,13 +249,25 @@ void CineEngine::mainLoop(int bootScriptIdx) { do { stopMusicAfterFadeOut(); di = executePlayerInput(); + + // Clear the zoneQuery table (Operation Stealth specific) + if (g_cine->getGameType() == Cine::GType_OS) { + for (uint i = 0; i < NUM_MAX_ZONE; i++) { + zoneQuery[i] = 0; + } + } - processSeqList(); - executeList1(); - executeList0(); + if (g_cine->getGameType() == Cine::GType_OS) { + processSeqList(); + } + executeObjectScripts(); + executeGlobalScripts(); - purgeList1(); - purgeList0(); + purgeObjectScripts(); + purgeGlobalScripts(); + if (g_cine->getGameType() == Cine::GType_OS) { + purgeSeqList(); + } if (playerCommand == -1) { setMouseCursor(MOUSE_CURSOR_NORMAL); diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp index 7666f05352..c02e01c8ce 100644 --- a/engines/cine/object.cpp +++ b/engines/cine/object.cpp @@ -99,21 +99,36 @@ int removeOverlay(uint16 objIdx, uint16 param) { /*! \brief Add new overlay sprite to the list * \param objIdx Associate the overlay with this object - * \param param Type of new overlay + * \param type Type of new overlay * \todo Why are x, y, width and color left uninitialized? */ -void addOverlay(uint16 objIdx, uint16 param) { +void addOverlay(uint16 objIdx, uint16 type) { Common::List<overlay>::iterator it; overlay tmp; for (it = overlayList.begin(); it != overlayList.end(); ++it) { + // This is done for both Future Wars and Operation Stealth if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask) { break; } + + // There are additional checks in Operation Stealth's implementation + if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) { + break; + } + } + + // In Operation Stealth's implementation we might bail out early + if (g_cine->getGameType() == Cine::GType_OS && it != overlayList.end() && it->objIdx == objIdx && it->type == type) { + return; } tmp.objIdx = objIdx; - tmp.type = param; + tmp.type = type; + tmp.x = 0; + tmp.y = 0; + tmp.width = 0; + tmp.color = 0; overlayList.insert(it, tmp); } @@ -122,24 +137,22 @@ void addOverlay(uint16 objIdx, uint16 param) { * \param objIdx Associate the overlay with this object * \param param source background index */ -void addGfxElementA0(int16 objIdx, int16 param) { +void addGfxElement(int16 objIdx, int16 param, int16 type) { Common::List<overlay>::iterator it; overlay tmp; for (it = overlayList.begin(); it != overlayList.end(); ++it) { - // wtf?! - if (objectTable[it->objIdx].mask == objectTable[objIdx].mask && - (it->type == 2 || it->type == 3)) { + if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask || it->type == 2 || it->type == 3) { break; } } - if (it != overlayList.end() && it->objIdx == objIdx && it->type == 20 && it->x == param) { + if (it != overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) { return; } tmp.objIdx = objIdx; - tmp.type = 20; + tmp.type = type; tmp.x = param; tmp.y = 0; tmp.width = 0; @@ -153,11 +166,11 @@ void addGfxElementA0(int16 objIdx, int16 param) { * \param param Remove overlay using this background * \todo Check that it works */ -void removeGfxElementA0(int16 objIdx, int16 param) { +void removeGfxElement(int16 objIdx, int16 param, int16 type) { Common::List<overlay>::iterator it; for (it = overlayList.begin(); it != overlayList.end(); ++it) { - if (it->objIdx == objIdx && it->type == 20 && it->x == param) { + if (it->objIdx == objIdx && it->type == type && it->x == param) { overlayList.erase(it); return; } @@ -170,8 +183,12 @@ void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint1 objectTable[objIdx].mask = param3; objectTable[objIdx].frame = param4; - if (removeOverlay(objIdx, 0)) { - addOverlay(objIdx, 0); + if (g_cine->getGameType() == Cine::GType_OS) { + resetGfxEntityEntry(objIdx); + } else { // Future Wars + if (removeOverlay(objIdx, 0)) { + addOverlay(objIdx, 0); + } } } @@ -199,9 +216,12 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) { case 3: objectTable[objIdx].mask = newValue; - // TODO: Check this part against disassembly - if (removeOverlay(objIdx, 0)) { - addOverlay(objIdx, 0); + if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific + resetGfxEntityEntry(objIdx); + } else { // Future Wars specific + if (removeOverlay(objIdx, 0)) { + addOverlay(objIdx, 0); + } } break; case 4: @@ -221,6 +241,29 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) { } } +/** + * Check if at least one of the range B's endpoints is inside range A, + * not counting the starting and ending points of range A. + * Used at least by Operation Stealth's opcode 0x8D i.e. 141. + */ +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) { + return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd); +} + +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) { + assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT); + const objectStruct &obj1 = objectTable[objIdx1]; + const objectStruct &obj2 = objectTable[objIdx2]; + + if (compareRanges(obj1.x, obj1.x + xAdd1, obj2.x, obj2.x + xAdd2) && + compareRanges(obj1.y, obj1.y + yAdd1, obj2.y, obj2.y + yAdd2) && + compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) { + return kCmpEQ; + } else { + return 0; + } +} + uint16 compareObjectParam(byte objIdx, byte type, int16 value) { uint16 compareResult = 0; int16 objectParam = getObjectParam(objIdx, type); diff --git a/engines/cine/object.h b/engines/cine/object.h index e7de39649d..7ad65eb75f 100644 --- a/engines/cine/object.h +++ b/engines/cine/object.h @@ -50,7 +50,7 @@ struct overlay { }; #define NUM_MAX_OBJECT 255 -#define NUM_MAX_VAR 256 +#define NUM_MAX_VAR 255 extern objectStruct objectTable[NUM_MAX_OBJECT]; @@ -60,15 +60,17 @@ void loadObject(char *pObjectName); void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4); void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue); -void addOverlay(uint16 objIdx, uint16 param); +void addOverlay(uint16 objIdx, uint16 type); int removeOverlay(uint16 objIdx, uint16 param); -void addGfxElementA0(int16 objIdx, int16 param); -void removeGfxElementA0(int16 objIdx, int16 param); +void addGfxElement(int16 objIdx, int16 param, int16 type); +void removeGfxElement(int16 objIdx, int16 param, int16 type); int16 getObjectParam(uint16 objIdx, uint16 paramIdx); void addObjectParam(byte objIdx, byte paramIdx, int16 newValue); void subObjectParam(byte objIdx, byte paramIdx, int16 newValue); +bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd); +uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2); uint16 compareObjectParam(byte objIdx, byte param1, int16 param2); } // End of namespace Cine diff --git a/engines/cine/pal.h b/engines/cine/pal.h index 70fcc0d98a..768cf0d27d 100644 --- a/engines/cine/pal.h +++ b/engines/cine/pal.h @@ -34,6 +34,8 @@ struct PalEntry { byte pal2[16]; }; +extern PalEntry *palPtr; + void loadPal(const char *fileName); void loadRelatedPalette(const char *fileName); diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp index b39f1eff7d..88f2dcef52 100644 --- a/engines/cine/part.cpp +++ b/engines/cine/part.cpp @@ -289,8 +289,8 @@ void dumpBundle(const char *fileName) { debug(0, "%s", partBuffer[i].partName); - Common::File out; - if (out.open(Common::String("dumps/") + partBuffer[i].partName, Common::File::kFileWriteMode)) { + Common::DumpFile out; + if (out.open(Common::String("dumps/") + partBuffer[i].partName)) { out.write(data, partBuffer[i].unpackedSize); out.close(); } diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp index 402c97b1a6..27b1044620 100644 --- a/engines/cine/prc.cpp +++ b/engines/cine/prc.cpp @@ -40,8 +40,9 @@ ScriptList objectScripts; /*! \todo Is script size of 0 valid? * \todo Fix script dump code + * @return Was the loading successful? */ -void loadPrc(const char *pPrcName) { +bool loadPrc(const char *pPrcName) { byte i; uint16 numScripts; byte *scriptPtr, *dataPtr; @@ -52,9 +53,9 @@ void loadPrc(const char *pPrcName) { scriptTable.clear(); // This is copy protection. Used to hang the machine - if (!scumm_stricmp(pPrcName, "L201.ANI")) { + if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) { exitEngine = 1; - return; + return false; } checkDataDisk(-1); @@ -107,6 +108,8 @@ void loadPrc(const char *pPrcName) { } } #endif + + return true; } } // End of namespace Cine diff --git a/engines/cine/prc.h b/engines/cine/prc.h index f5129d28b1..05bb240372 100644 --- a/engines/cine/prc.h +++ b/engines/cine/prc.h @@ -31,7 +31,7 @@ namespace Cine { extern ScriptList globalScripts; extern ScriptList objectScripts; -void loadPrc(const char *pPrcName); +bool loadPrc(const char *pPrcName); } // End of namespace Cine diff --git a/engines/cine/script.h b/engines/cine/script.h index eeac0e8809..19576e4c1a 100644 --- a/engines/cine/script.h +++ b/engines/cine/script.h @@ -61,7 +61,7 @@ private: public: // Explicit to prevent var=0 instead of var[i]=0 typos. explicit ScriptVars(unsigned int len = 50); - ScriptVars(Common::InSaveFile &fHandle, unsigned int len = 50); + ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len = 50); ScriptVars(const ScriptVars &src); ~ScriptVars(void); @@ -71,8 +71,8 @@ public: void save(Common::OutSaveFile &fHandle) const; void save(Common::OutSaveFile &fHandle, unsigned int len) const; - void load(Common::InSaveFile &fHandle); - void load(Common::InSaveFile &fHandle, unsigned int len); + void load(Common::SeekableReadStream &fHandle); + void load(Common::SeekableReadStream &fHandle, unsigned int len); void reset(void); }; @@ -198,7 +198,7 @@ protected: int o1_blitAndFade(); int o1_fadeToBlack(); int o1_transformPaletteRange(); - int o1_setDefaultMenuColor2(); + int o1_setDefaultMenuBgColor(); int o1_palRotate(); int o1_break(); int o1_endScript(); @@ -213,7 +213,7 @@ protected: int o1_initializeZoneData(); int o1_setZoneDataEntry(); int o1_getZoneDataEntry(); - int o1_setDefaultMenuColor(); + int o1_setPlayerCommandPosY(); int o1_allowPlayerInput(); int o1_disallowPlayerInput(); int o1_changeDataDisk(); @@ -237,7 +237,7 @@ protected: int o2_playSample(); int o2_playSampleAlt(); int o2_op81(); - int o2_op82(); + int o2_modifySeqListElement(); int o2_isSeqRunning(); int o2_gotoIfSupNearest(); int o2_gotoIfSupEquNearest(); @@ -258,10 +258,10 @@ protected: int o2_useBgScroll(); int o2_setAdditionalBgVScroll(); int o2_op9F(); - int o2_addGfxElementA0(); - int o2_removeGfxElementA0(); - int o2_opA2(); - int o2_opA3(); + int o2_addGfxElementType20(); + int o2_removeGfxElementType20(); + int o2_addGfxElementType21(); + int o2_removeGfxElementType21(); int o2_loadMask22(); int o2_unloadMask22(); @@ -371,16 +371,16 @@ void dumpScript(char *dumpName); #define OP_requestCheckPendingDataLoad 0x42 #define OP_endScript 0x50 -void addScriptToList0(uint16 idx); +void addScriptToGlobalScripts(uint16 idx); int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx); void runObjectScript(int16 entryIdx); -void executeList1(void); -void executeList0(void); +void executeObjectScripts(void); +void executeGlobalScripts(void); -void purgeList1(void); -void purgeList0(void); +void purgeObjectScripts(void); +void purgeGlobalScripts(void); } // End of namespace Cine diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp index 845120c99e..e761a0c8e4 100644 --- a/engines/cine/script_fw.cpp +++ b/engines/cine/script_fw.cpp @@ -38,7 +38,13 @@ namespace Cine { -ScriptVars globalVars(NUM_MAX_VAR); +/** + * Global variables. + * 255 of these are saved, but there's one more that's used for bypassing the copy protection. + * In CineEngine::mainLoop(int bootScriptIdx) there's this code: globalVars[VAR_BYPASS_PROTECTION] = 0; + * And as VAR_BYPASS_PROTECTION is 255 that's why we're allocating one more than we otherwise would. + */ +ScriptVars globalVars(NUM_MAX_VAR + 1); uint16 compareVars(int16 a, int16 b); @@ -135,7 +141,7 @@ const Opcode FWScript::_opcodeTable[] = { { &FWScript::o1_transformPaletteRange, "bbwww" }, /* 48 */ { 0, 0 }, - { &FWScript::o1_setDefaultMenuColor2, "b" }, + { &FWScript::o1_setDefaultMenuBgColor, "b" }, { &FWScript::o1_palRotate, "bbb" }, { 0, 0 }, /* 4C */ @@ -174,7 +180,7 @@ const Opcode FWScript::_opcodeTable[] = { { &FWScript::o1_setZoneDataEntry, "bw" }, { &FWScript::o1_getZoneDataEntry, "bb" }, /* 68 */ - { &FWScript::o1_setDefaultMenuColor, "b" }, + { &FWScript::o1_setPlayerCommandPosY, "b" }, { &FWScript::o1_allowPlayerInput, "" }, { &FWScript::o1_disallowPlayerInput, "" }, { &FWScript::o1_changeDataDisk, "b" }, @@ -230,7 +236,7 @@ ScriptVars::ScriptVars(unsigned int len) : _size(len), _vars(new int16[len]) { * \param fHandle Savefile open for reading * \param len Size of array */ -ScriptVars::ScriptVars(Common::InSaveFile &fHandle, unsigned int len) +ScriptVars::ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len) : _size(len), _vars(new int16[len]) { assert(_vars); @@ -306,7 +312,7 @@ void ScriptVars::save(Common::OutSaveFile &fHandle, unsigned int len) const { /*! \brief Restore array from savefile * \param fHandle Savefile open for reading */ -void ScriptVars::load(Common::InSaveFile &fHandle) { +void ScriptVars::load(Common::SeekableReadStream &fHandle) { load(fHandle, _size); } @@ -314,7 +320,7 @@ void ScriptVars::load(Common::InSaveFile &fHandle) { * \param fHandle Savefile open for reading * \param len Length of data to be read */ -void ScriptVars::load(Common::InSaveFile &fHandle, unsigned int len) { +void ScriptVars::load(Common::SeekableReadStream &fHandle, unsigned int len) { debug(6, "assert(%d <= %d)", len, _size); assert(len <= _size); for (unsigned int i = 0; i < len; i++) { @@ -1019,6 +1025,20 @@ int FWScript::o1_divVar() { } int FWScript::o1_compareVar() { + // WORKAROUND: A workaround for a script bug in script file CODE2.PRC + // in at least some of the Amiga and Atari ST versions of Future Wars. + // Fixes bug #2016647 (FW: crash with italian amiga version). A local + // variable 251 is compared against value 0 although it's quite apparent + // from the context in the script that instead global variable 251 should + // be compared against value 0. So looks like someone made a typo when + // making the scripts. Therefore we change that particular comparison + // from using the local variable 251 to using the global variable 251. + if (g_cine->getGameType() == Cine::GType_FW && scumm_stricmp(currentPrcName, "CODE2.PRC") == 0 && + (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) && + _script.getByte(_pos) == 251 && _script.getByte(_pos + 1) == 0 && _script.getWord(_pos + 2) == 0) { + return o1_compareGlobalVar(); + } + byte varIdx = getNextByte(); byte varType = getNextByte(); @@ -1259,7 +1279,7 @@ int FWScript::o1_startGlobalScript() { assert(param < NUM_MAX_SCRIPT); debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _line, param); - addScriptToList0(param); + addScriptToGlobalScripts(param); return 0; } @@ -1385,10 +1405,11 @@ int FWScript::o1_transformPaletteRange() { return 0; } -int FWScript::o1_setDefaultMenuColor2() { +/** Set the default background color used for message boxes. */ +int FWScript::o1_setDefaultMenuBgColor() { byte param = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _line, param); + debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuBgColor(%d)", _line, param); renderer->_messageBg = param; return 0; @@ -1554,10 +1575,11 @@ int FWScript::o1_getZoneDataEntry() { return 0; } -int FWScript::o1_setDefaultMenuColor() { +/** Set the player command string's vertical position on-screen. */ +int FWScript::o1_setPlayerCommandPosY() { byte param = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _line, param); + debugC(5, kCineDebugScript, "Line: %d: setPlayerCommandPosY(%d)", _line, param); renderer->_cmdY = param; return 0; @@ -1732,7 +1754,7 @@ int FWScript::o1_unloadMask5() { //----------------------------------------------------------------------- -void addScriptToList0(uint16 idx) { +void addScriptToGlobalScripts(uint16 idx) { ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx)); assert(tmp); globalScripts.push_back(tmp); @@ -1764,18 +1786,32 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI int16 lx = objectTable[objIdx].x + x; int16 ly = objectTable[objIdx].y + y; int16 idx; + int16 result = 0; for (int16 i = 0; i < numZones; i++) { idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320); - assert(idx >= 0 && idx <= NUM_MAX_ZONE); + assert(idx >= 0 && idx < NUM_MAX_ZONE); + + // The zoneQuery table is updated here only in Operation Stealth + if (g_cine->getGameType() == Cine::GType_OS) { + if (zoneData[idx] < NUM_MAX_ZONE) { + zoneQuery[zoneData[idx]]++; + } + } if (zoneData[idx] == zoneIdx) { - return 1; + result = 1; + // Future Wars breaks out early on the first match, but + // Operation Stealth doesn't because it needs to update + // the zoneQuery table for the whole loop's period. + if (g_cine->getGameType() == Cine::GType_FW) { + break; + } } } - return 0; + return result; } uint16 compareVars(int16 a, int16 b) { @@ -1792,7 +1828,7 @@ uint16 compareVars(int16 a, int16 b) { return flag; } -void executeList1(void) { +void executeObjectScripts(void) { ScriptList::iterator it = objectScripts.begin(); for (; it != objectScripts.end();) { if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1803,7 +1839,7 @@ void executeList1(void) { } } -void executeList0(void) { +void executeGlobalScripts(void) { ScriptList::iterator it = globalScripts.begin(); for (; it != globalScripts.end();) { if ((*it)->_index < 0 || (*it)->execute() < 0) { @@ -1814,12 +1850,16 @@ void executeList0(void) { } } -/*! \todo objectScripts.clear()? +/*! \todo Remove object scripts with script index of -1 (Not script position, but script index!). + * This would seem to be valid for both Future Wars and Operation Stealth. */ -void purgeList1(void) { +void purgeObjectScripts(void) { } -void purgeList0(void) { +/*! \todo Remove global scripts with script index of -1 (Not script position, but script index!). + * This would seem to be valid for both Future Wars and Operation Stealth. + */ +void purgeGlobalScripts(void) { } //////////////////////////////////// @@ -2352,7 +2392,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) param = *(localScriptPtr + position); position++; - sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param); + sprintf(lineBuffer, "setDefaultMenuBgColor(%d)\n", param); break; } @@ -2502,7 +2542,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) param = *(localScriptPtr + position); position++; - sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param); + sprintf(lineBuffer, "setPlayerCommandPosY(%d)\n", param); break; } @@ -2917,10 +2957,10 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx) } void dumpScript(char *dumpName) { - Common::File fHandle; + Common::DumpFile fHandle; uint16 i; - fHandle.open(dumpName, Common::File::kFileWriteMode); + fHandle.open(dumpName); for (i = 0; i < decompileBufferPosition; i++) { fHandle.writeString(Common::String(decompileBuffer[i])); diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp index 319fca5d3c..a764281758 100644 --- a/engines/cine/script_os.cpp +++ b/engines/cine/script_os.cpp @@ -131,7 +131,7 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o1_transformPaletteRange, "bbwww" }, /* 48 */ { 0, 0 }, - { &FWScript::o1_setDefaultMenuColor2, "b" }, + { &FWScript::o1_setDefaultMenuBgColor, "b" }, { &FWScript::o1_palRotate, "bbb" }, { 0, 0 }, /* 4C */ @@ -170,7 +170,7 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o1_setZoneDataEntry, "bw" }, { &FWScript::o1_getZoneDataEntry, "bb" }, /* 68 */ - { &FWScript::o1_setDefaultMenuColor, "b" }, + { &FWScript::o1_setPlayerCommandPosY, "b" }, { &FWScript::o1_allowPlayerInput, "" }, { &FWScript::o1_disallowPlayerInput, "" }, { &FWScript::o1_changeDataDisk, "b" }, /* Same as opcodes 0x95 and 0xA9. */ @@ -202,7 +202,7 @@ const Opcode OSScript::_opcodeTable[] = { /* 80 */ { &FWScript::o2_removeSeq, "bb" }, { &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_op82, "bbwwb" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_modifySeqListElement, "bbwwb" }, { &FWScript::o2_isSeqRunning, "bb" }, /* 84 */ { &FWScript::o2_gotoIfSupNearest, "b" }, @@ -240,10 +240,10 @@ const Opcode OSScript::_opcodeTable[] = { { &FWScript::o2_setAdditionalBgVScroll, "c" }, { &FWScript::o2_op9F, "ww" }, /* TODO: Name this opcode properly. */ /* A0 */ - { &FWScript::o2_addGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_removeGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_opA2, "ww" }, /* TODO: Name this opcode properly. */ - { &FWScript::o2_opA3, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_addGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_removeGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_addGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */ + { &FWScript::o2_removeGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */ /* A4 */ { &FWScript::o2_loadMask22, "b" }, /* TODO: Name this opcode properly. */ { &FWScript::o2_unloadMask22, "b" }, /* TODO: Name this opcode properly. */ @@ -442,6 +442,7 @@ int FWScript::o2_removeSeq() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op81() { warning("STUB: o2_op81()"); @@ -449,23 +450,25 @@ int FWScript::o2_op81() { return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_op82() { +int FWScript::o2_modifySeqListElement() { byte a = getNextByte(); byte b = getNextByte(); uint16 c = getNextWord(); uint16 d = getNextWord(); byte e = getNextByte(); - warning("STUB: o2_op82(%x, %x, %x, %x, %x)", a, b, c, d, e); + debugC(5, kCineDebugScript, "Line: %d: o2_modifySeqListElement(%d,%d,%d,%d,%d)", _line, a, b, c, d, e); + + modifySeqListElement(a, 0, b, c, d, e); return 0; } +/*! \todo Check whether this opcode's name is backwards (i.e. should it be o2_isSeqNotRunning?) + */ int FWScript::o2_isSeqRunning() { byte a = getNextByte(); byte b = getNextByte(); - debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _line, a, b); + debugC(5, kCineDebugScript, "Line: %d: o2_isSeqRunning(%d,%d)", _line, a, b); if (isSeqRunning(a, 0, b)) { _compare = 1; @@ -593,19 +596,18 @@ int FWScript::o2_stopObjectScript() { return 0; } -/*! \todo Implement this instruction - */ int FWScript::o2_op8D() { - uint16 a = getNextWord(); - uint16 b = getNextWord(); - uint16 c = getNextWord(); - uint16 d = getNextWord(); - uint16 e = getNextWord(); - uint16 f = getNextWord(); - uint16 g = getNextWord(); - uint16 h = getNextWord(); - warning("STUB: o2_op8D(%x, %x, %x, %x, %x, %x, %x, %x)", a, b, c, d, e, f, g, h); - // _currentScriptElement->compareResult = ... + uint16 objIdx1 = getNextWord(); + uint16 xAdd1 = getNextWord(); + uint16 yAdd1 = getNextWord(); + uint16 maskAdd1 = getNextWord(); + uint16 objIdx2 = getNextWord(); + uint16 xAdd2 = getNextWord(); + uint16 yAdd2 = getNextWord(); + uint16 maskAdd2 = getNextWord(); + debugC(5, kCineDebugScript, "Line: %d: o2_op8D(%d, %d, %d, %d, %d, %d, %d, %d)", _line, objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2); + + _compare = compareObjectParamRanges(objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2); return 0; } @@ -649,16 +651,15 @@ int FWScript::o2_loadBg() { return 0; } -/*! \todo Check the current implementation for correctness - */ int FWScript::o2_wasZoneChecked() { byte param = getNextByte(); - _compare = (param < 16 && zoneData[param]); + _compare = (param < NUM_MAX_ZONE && zoneQuery[param]) ? 1 : 0; debugC(5, kCineDebugScript, "Line: %d: o2_wasZoneChecked(%d)", _line, param); return 0; } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9B() { uint16 a = getNextWord(); @@ -674,6 +675,7 @@ int FWScript::o2_op9B() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9C() { uint16 a = getNextWord(); @@ -713,6 +715,7 @@ int FWScript::o2_setAdditionalBgVScroll() { } /*! \todo Implement this instruction + * \note According to the scripts' opcode usage comparison this opcode isn't used at all. */ int FWScript::o2_op9F() { warning("o2_op9F()"); @@ -721,42 +724,36 @@ int FWScript::o2_op9F() { return 0; } -int FWScript::o2_addGfxElementA0() { +int FWScript::o2_addGfxElementType20() { uint16 param1 = getNextWord(); uint16 param2 = getNextWord(); - debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _line, param1, param2); - addGfxElementA0(param1, param2); + debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType20(%d,%d)", _line, param1, param2); + addGfxElement(param1, param2, 20); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_removeGfxElementA0() { +int FWScript::o2_removeGfxElementType20() { uint16 idx = getNextWord(); uint16 param = getNextWord(); - warning("STUB? o2_removeGfxElementA0(%x, %x)", idx, param); - removeGfxElementA0(idx, param); + debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType20(%d,%d)", _line, idx, param); + removeGfxElement(idx, param, 20); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA2() { +int FWScript::o2_addGfxElementType21() { uint16 a = getNextWord(); uint16 b = getNextWord(); - warning("STUB: o2_opA2(%x, %x)", a, b); - // addGfxElementA2(); + debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType21(%d,%d)", _line, a, b); + addGfxElement(a, b, 21); return 0; } -/*! \todo Implement this instruction - */ -int FWScript::o2_opA3() { +int FWScript::o2_removeGfxElementType21() { uint16 a = getNextWord(); uint16 b = getNextWord(); - warning("STUB: o2_opA3(%x, %x)", a, b); - // removeGfxElementA2(); + debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType21(%d,%d)", _line, a, b); + removeGfxElement(a, b, 21); return 0; } diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp index e808de6922..f26032fe98 100644 --- a/engines/cine/sound.cpp +++ b/engines/cine/sound.cpp @@ -249,6 +249,7 @@ AdlibSoundDriver::AdlibSoundDriver(Audio::Mixer *mixer) AdlibSoundDriver::~AdlibSoundDriver() { _mixer->stopHandle(_soundHandle); + OPLDestroy(_opl); } void AdlibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) { diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp index 9b4b83f420..e4fd334926 100644 --- a/engines/cine/texte.cpp +++ b/engines/cine/texte.cpp @@ -31,8 +31,6 @@ namespace Cine { byte *textDataPtr; -byte textTable[256][2][16 * 8]; - const char **failureMessages; const CommandeType *defaultActionCommand; const CommandeType *systemMenu; @@ -77,14 +75,14 @@ void loadTextData(const char *pFileName, byte *pDestinationBuffer) { loadRelatedPalette(pFileName); for (i = 0; i < numCharacters; i++) { - gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 16, 8); - generateMask(textTable[i][0], textTable[i][1], 16 * 8, 0); + gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 16, 8); + generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 16 * 8, 0); tempBuffer += dataSize; } } else { for (i = 0; i < 90; i++) { - gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 8, 8); - generateMask(textTable[i][0], textTable[i][1], 8 * 8, 0); + gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 8, 8); + generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 8 * 8, 0); tempBuffer += 0x40; } } diff --git a/engines/cine/texte.h b/engines/cine/texte.h index ae82832aea..f471c3c49e 100644 --- a/engines/cine/texte.h +++ b/engines/cine/texte.h @@ -34,7 +34,10 @@ namespace Cine { typedef char CommandeType[20]; extern byte *textDataPtr; -extern byte textTable[256][2][16 * 8]; + +struct TextHandler { + byte textTable[256][2][16 * 8]; +}; extern const char **failureMessages; extern const CommandeType *defaultActionCommand; diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp index dcd3181242..5d85ff6cab 100644 --- a/engines/cine/unpack.cpp +++ b/engines/cine/unpack.cpp @@ -111,7 +111,7 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen) while (_dst >= _dstBegin && !_error) { /* Bits => Action: - 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..9) + 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8) 1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264) 0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2) 1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3) diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp index 9b98ddb253..2fcb015fcd 100644 --- a/engines/cine/various.cpp +++ b/engines/cine/various.cpp @@ -95,6 +95,9 @@ int16 saveVar2; byte isInPause = 0; +// TODO: Implement inputVar0's changes in the program +// Currently inputVar0 isn't updated anywhere even though it's used at least in processSeqListElement. +uint16 inputVar0 = 0; byte inputVar1 = 0; uint16 inputVar2 = 0, inputVar3 = 0; @@ -112,6 +115,7 @@ int16 objListTab[20]; uint16 exitEngine; uint16 zoneData[NUM_MAX_ZONE]; +uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth void stopMusicAfterFadeOut(void) { @@ -132,6 +136,7 @@ void runObjectScript(int16 entryIdx) { */ void addPlayerCommandMessage(int16 cmd) { overlay tmp; + memset(&tmp, 0, sizeof(tmp)); tmp.objIdx = cmd; tmp.type = 3; @@ -224,6 +229,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) { return -1; } +bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) { + out.writeUint32BE(header.id); + out.writeUint32BE(header.version); + out.writeUint32BE(header.size); + return !out.ioFailed(); +} + +bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) { + header.id = in.readUint32BE(); + header.version = in.readUint32BE(); + header.size = in.readUint32BE(); + return !in.ioFailed(); +} + +void saveObjectTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_OBJECT); // Entry count + out.writeUint16BE(0x20); // Entry size + + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + out.writeUint16BE(objectTable[i].x); + out.writeUint16BE(objectTable[i].y); + out.writeUint16BE(objectTable[i].mask); + out.writeUint16BE(objectTable[i].frame); + out.writeUint16BE(objectTable[i].costume); + out.write(objectTable[i].name, 20); + out.writeUint16BE(objectTable[i].part); + } +} + +void saveZoneData(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneData[i]); + } +} + +void saveCommandVariables(Common::OutSaveFile &out) { + for (int i = 0; i < 4; i++) { + out.writeUint16BE(commandVar3[i]); + } +} + +void saveAnimDataTable(Common::OutSaveFile &out) { + out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count + out.writeUint16BE(0x1E); // Entry size + + for (int i = 0; i < NUM_MAX_ANIMDATA; i++) { + animDataTable[i].save(out); + } +} + +void saveScreenParams(Common::OutSaveFile &out) { + // Screen parameters, unhandled + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); + out.writeUint16BE(0); +} + +void saveGlobalScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(globalScripts.size()); + for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveObjectScripts(Common::OutSaveFile &out) { + ScriptList::const_iterator it; + out.writeUint16BE(objectScripts.size()); + for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { + (*it)->save(out); + } +} + +void saveOverlayList(Common::OutSaveFile &out) { + Common::List<overlay>::const_iterator it; + + out.writeUint16BE(overlayList.size()); + + for (it = overlayList.begin(); it != overlayList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->type); + out.writeSint16BE(it->x); + out.writeSint16BE(it->y); + out.writeSint16BE(it->width); + out.writeSint16BE(it->color); + } +} + +void saveBgIncrustList(Common::OutSaveFile &out) { + Common::List<BGIncrust>::const_iterator it; + out.writeUint16BE(bgIncrustList.size()); + + for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { + out.writeUint32BE(0); // next + out.writeUint32BE(0); // previous? + out.writeUint16BE(it->objIdx); + out.writeUint16BE(it->param); + out.writeUint16BE(it->x); + out.writeUint16BE(it->y); + out.writeUint16BE(it->frame); + out.writeUint16BE(it->part); + } +} + +void saveZoneQuery(Common::OutSaveFile &out) { + for (int i = 0; i < 16; i++) { + out.writeUint16BE(zoneQuery[i]); + } +} + +void saveSeqList(Common::OutSaveFile &out) { + Common::List<SeqListElement>::const_iterator it; + out.writeUint16BE(seqList.size()); + + for (it = seqList.begin(); it != seqList.end(); ++it) { + out.writeSint16BE(it->var4); + out.writeUint16BE(it->objIdx); + out.writeSint16BE(it->var8); + out.writeSint16BE(it->frame); + out.writeSint16BE(it->varC); + out.writeSint16BE(it->varE); + out.writeSint16BE(it->var10); + out.writeSint16BE(it->var12); + out.writeSint16BE(it->var14); + out.writeSint16BE(it->var16); + out.writeSint16BE(it->var18); + out.writeSint16BE(it->var1A); + out.writeSint16BE(it->var1C); + out.writeSint16BE(it->var1E); + } +} + bool CineEngine::loadSaveDirectory(void) { Common::InSaveFile *fHandle; char tmp[80]; @@ -241,21 +383,143 @@ bool CineEngine::loadSaveDirectory(void) { return true; } +/*! \brief Savegame format detector + * \param fHandle Savefile to check + * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure + * + * This function seeks through the savefile and tries to determine the + * savegame format it uses. There's a miniscule chance that the detection + * algorithm could get confused and think that the file uses both the older + * and the newer format but that is such a remote possibility that I wouldn't + * worry about it at all. + * + * Also detects the temporary Operation Stealth savegame format now. + */ +enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) { + const uint32 prevStreamPos = fHandle.pos(); + + // First check for the temporary Operation Stealth savegame format. + fHandle.seek(0); + ChunkHeader hdr; + loadChunkHeader(fHandle, hdr); + fHandle.seek(prevStreamPos); + if (hdr.id == TEMP_OS_FORMAT_ID) { + return TEMP_OS_FORMAT; + } + + // Ok, so the savegame isn't using the temporary Operation Stealth savegame format. + // Let's check for the plain Future Wars savegame format and its different versions then. + // The animDataTable begins at savefile position 0x2315. + // Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443) + // and 30 bytes in the save format after that (Revision 31444 and onwards). + // There are 255 entries in the animDataTable in both of the savefile formats. + static const uint animDataTableStart = 0x2315; + static const uint animEntriesCount = 255; + static const uint oldAnimEntrySize = 23; + static const uint newAnimEntrySize = 30; + static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize}; + Common::Array<uint> animEntrySizeMatches; + + // Try to walk through the savefile using different animDataTable entry sizes + // and make a list of all the successful entry sizes. + for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) { + // 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries) + // 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries) + static const uint sizeofScreenParams = 2 * 6; + static const uint globalScriptEntrySize = 206; + static const uint objectScriptEntrySize = 206; + static const uint overlayEntrySize = 20; + static const uint bgIncrustEntrySize = 20; + static const uint chainEntrySizes[] = { + globalScriptEntrySize, + objectScriptEntrySize, + overlayEntrySize, + bgIncrustEntrySize + }; + + uint animEntrySize = animEntrySizeChoices[i]; + // Jump over the animDataTable entries and the screen parameters + uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams; + // Check that there's data left after the point we're going to jump to + if (newPos >= fHandle.size()) { + continue; + } + fHandle.seek(newPos); + + // Jump over the remaining items in the savegame file + // (i.e. the global scripts, object scripts, overlays and background incrusts). + bool chainWalkSuccess = true; + for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) { + // Read entry count and jump over the entries + int entryCount = fHandle.readSint16BE(); + newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount; + // Check that we didn't go past the end of file. + // Note that getting exactly to the end of file is acceptable. + if (newPos > fHandle.size()) { + chainWalkSuccess = false; + break; + } + fHandle.seek(newPos); + } + + // If we could walk the chain successfully and + // got exactly to the end of file then we've got a match. + if (chainWalkSuccess && fHandle.pos() == fHandle.size()) { + // We found a match, let's save it + animEntrySizeMatches.push_back(animEntrySize); + } + } + + // Check that we got only one entry size match. + // If we didn't, then return an error. + enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN; + if (animEntrySizeMatches.size() == 1) { + const uint animEntrySize = animEntrySizeMatches[0]; + assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize); + if (animEntrySize == oldAnimEntrySize) { + result = ANIMSIZE_23; + } else { // animEntrySize == newAnimEntrySize + // Check data and mask pointers in all of the animDataTable entries + // to see whether we've got the version with the broken data and mask pointers or not. + // In the broken format all data and mask pointers were always zero. + static const uint relativeDataPos = 2 * 4; + bool pointersIntact = false; + for (uint i = 0; i < animEntriesCount; i++) { + fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos); + uint32 data = fHandle.readUint32BE(); + uint32 mask = fHandle.readUint32BE(); + if ((data != 0) || (mask != 0)) { + pointersIntact = true; + break; + } + } + result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN); + } + } else if (animEntrySizeMatches.size() > 1) { + warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format"); + } else { // animEtrySizeMatches.size() == 0 + debug(3, "Savegame format detector was unable to detect savegame's format"); + } + + fHandle.seek(prevStreamPos); + return result; +} + /*! \brief Restore script list item from savefile - * \param fHandle Savefile handlem open for reading + * \param fHandle Savefile handle open for reading * \param isGlobal Restore object or global script? */ -void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { +void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) { ScriptVars localVars, labels; uint16 compare, pos; int16 idx; - labels.load(*fHandle); - localVars.load(*fHandle); + labels.load(fHandle); + localVars.load(fHandle); - compare = fHandle->readUint16BE(); - pos = fHandle->readUint16BE(); - idx = fHandle->readUint16BE(); + compare = fHandle.readUint16BE(); + pos = fHandle.readUint16BE(); + idx = fHandle.readUint16BE(); // no way to reinitialize these if (idx < 0) { @@ -278,7 +542,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) { /*! \brief Restore overlay sprites from savefile * \param fHandle Savefile open for reading */ -void loadOverlayFromSave(Common::InSaveFile &fHandle) { +void loadOverlayFromSave(Common::SeekableReadStream &fHandle) { overlay tmp; fHandle.readUint32BE(); @@ -294,127 +558,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) { overlayList.push_back(tmp); } -/*! \brief Savefile format tester - * \param fHandle Savefile to check - * - * This function seeks through savefile and tries to guess if it's the original - * savegame format or broken format from ScummVM 0.10/0.11 - * The test is incomplete but this should cover 99.99% of cases. - * If anyone makes a savefile which could confuse this test, assert will - * report it - */ -bool brokenSave(Common::InSaveFile &fHandle) { - // Backward seeking not supported in compressed savefiles - // if you really want it, finish it yourself - return false; - - // fixed size part: 14093 bytes (12308 bytes in broken save) - // animDataTable begins at byte 6431 - - int filesize = fHandle.size(); - int startpos = fHandle.pos(); - int pos, tmp; - bool correct = false, broken = false; - - // check for correct format - while (filesize > 14093) { - pos = 14093; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) correct = true; - break; - } - debug(5, "brokenSave: correct format check %s: size=%d, pos=%d", - correct ? "passed" : "failed", filesize, pos); - - // check for broken format - while (filesize > 12308) { - pos = 12308; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 206; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - if (pos >= filesize) break; - - fHandle.seek(pos); - tmp = fHandle.readUint16BE(); - pos += 2 + tmp * 20; - - if (pos == filesize) broken = true; - break; - } - debug(5, "brokenSave: broken format check %s: size=%d, pos=%d", - broken ? "passed" : "failed", filesize, pos); - - // there's a very small chance that both cases will match - // if anyone runs into it, you'll have to walk through - // the animDataTable and try to open part file for each entry - if (!correct && !broken) { - error("brokenSave: file format check failed"); - } else if (correct && broken) { - error("brokenSave: both file formats seem to apply"); - } - - fHandle.seek(startpos); - debug(5, "brokenSave: detected %s file format", - correct ? "correct" : "broken"); - - return broken; -} - -/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only - */ -bool CineEngine::makeLoad(char *saveName) { - int16 i; - int16 size; - bool broken; - Common::InSaveFile *fHandle; - char bgName[13]; - - fHandle = g_saveFileMan->openForLoading(saveName); - - if (!fHandle) { - drawString(otherMessages[0], 0); - waitPlayerInput(); - // restoreScreen(); - checkDataDisk(-1); - return false; - } - +void CineEngine::resetEngine() { g_sound->stopMusic(); freeAnimDataTable(); overlayList.clear(); - // if (g_cine->getGameType() == Cine::GType_OS) { - // freeUnkList(); - // } bgIncrustList.clear(); closePart(); @@ -424,7 +571,9 @@ bool CineEngine::makeLoad(char *saveName) { scriptTable.clear(); messageTable.clear(); - for (i = 0; i < NUM_MAX_OBJECT; i++) { + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = 0; + objectTable[i].y = 0; objectTable[i].part = 0; objectTable[i].name[0] = 0; objectTable[i].frame = 0; @@ -458,124 +607,381 @@ bool CineEngine::makeLoad(char *saveName) { checkForPendingDataLoadSwitch = 0; - broken = brokenSave(*fHandle); + if (g_cine->getGameType() == Cine::GType_OS) { + seqList.clear(); + currentAdditionalBgIdx = 0; + currentAdditionalBgIdx2 = 0; + // TODO: Add resetting of the following variables + // adBgVar1 = 0; + // adBgVar0 = 0; + // gfxFadeOutCompleted = 0; + } +} - currentDisk = fHandle->readUint16BE(); +bool loadObjectTable(Common::SeekableReadStream &in) { + in.readUint16BE(); // Entry count + in.readUint16BE(); // Entry size - fHandle->read(currentPartName, 13); - fHandle->read(currentDatName, 13); + for (int i = 0; i < NUM_MAX_OBJECT; i++) { + objectTable[i].x = in.readSint16BE(); + objectTable[i].y = in.readSint16BE(); + objectTable[i].mask = in.readUint16BE(); + objectTable[i].frame = in.readSint16BE(); + objectTable[i].costume = in.readSint16BE(); + in.read(objectTable[i].name, 20); + objectTable[i].part = in.readUint16BE(); + } + return !in.ioFailed(); +} - saveVar2 = fHandle->readSint16BE(); +bool loadZoneData(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneData[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} - fHandle->read(currentPrcName, 13); - fHandle->read(currentRelName, 13); - fHandle->read(currentMsgName, 13); - fHandle->read(bgName, 13); - fHandle->read(currentCtName, 13); +bool loadCommandVariables(Common::SeekableReadStream &in) { + for (int i = 0; i < 4; i++) { + commandVar3[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} - checkDataDisk(currentDisk); +bool loadScreenParams(Common::SeekableReadStream &in) { + // TODO: handle screen params (really required ?) + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + return !in.ioFailed(); +} - if (strlen(currentPartName)) { - loadPart(currentPartName); +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); } + return !in.ioFailed(); +} - if (strlen(currentPrcName)) { - loadPrc(currentPrcName); +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); } + return !in.ioFailed(); +} - if (strlen(currentRelName)) { - loadRel(currentRelName); +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); } + return !in.ioFailed(); +} - if (strlen(bgName)) { - loadBg(bgName); - } +bool loadSeqList(Common::SeekableReadStream &in) { + uint size = in.readUint16BE(); + SeqListElement tmp; + for (uint i = 0; i < size; i++) { + tmp.var4 = in.readSint16BE(); + tmp.objIdx = in.readUint16BE(); + tmp.var8 = in.readSint16BE(); + tmp.frame = in.readSint16BE(); + tmp.varC = in.readSint16BE(); + tmp.varE = in.readSint16BE(); + tmp.var10 = in.readSint16BE(); + tmp.var12 = in.readSint16BE(); + tmp.var14 = in.readSint16BE(); + tmp.var16 = in.readSint16BE(); + tmp.var18 = in.readSint16BE(); + tmp.var1A = in.readSint16BE(); + tmp.var1C = in.readSint16BE(); + tmp.var1E = in.readSint16BE(); + seqList.push_back(tmp); + } + return !in.ioFailed(); +} - if (strlen(currentCtName)) { - loadCtFW(currentCtName); +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); } + return !in.ioFailed(); +} - fHandle->readUint16BE(); - fHandle->readUint16BE(); +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; - for (i = 0; i < 255; i++) { - objectTable[i].x = fHandle->readSint16BE(); - objectTable[i].y = fHandle->readSint16BE(); - objectTable[i].mask = fHandle->readUint16BE(); - objectTable[i].frame = fHandle->readSint16BE(); - objectTable[i].costume = fHandle->readSint16BE(); - fHandle->read(objectTable[i].name, 20); - objectTable[i].part = fHandle->readUint16BE(); + // First check the temporary Operation Stealth savegame format header. + ChunkHeader hdr; + loadChunkHeader(in, hdr); + if (hdr.id != TEMP_OS_FORMAT_ID) { + warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); + return false; + } else if (hdr.version > CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); + return false; + } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); + } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER + debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); + } + + // There shouldn't be any data in the header's chunk currently so it's an error if there is. + if (hdr.size > 0) { + warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); + return false; } - renderer->restorePalette(*fHandle); + // Ok, so we've got a correct header for a temporary Operation Stealth savegame. + // Let's start loading the plain savegame data then. + currentDisk = in.readUint16BE(); + in.read(currentPartName, 13); + in.read(currentPrcName, 13); + in.read(currentRelName, 13); + in.read(currentMsgName, 13); + + // Load the 8 background names. + for (uint i = 0; i < 8; i++) { + in.read(bgNames[i], 13); + } + + in.read(currentCtName, 13); + + // Moved the loading of current procedure, relation, + // backgrounds and Ct here because if they were at the + // end of this function then the global scripts loading + // made an array out of bounds access. In the original + // game's disassembly these aren't here but at the end. + // The difference is probably in how we handle loading + // the global scripts and some other things (i.e. the + // loading routines aren't exactly the same and subtle + // semantic differences result in having to do things + // in a different order). + { + // Not sure if this is needed with Operation Stealth... + checkDataDisk(currentDisk); + + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); + } + + if (strlen(currentRelName)) { + loadRel(currentRelName); + } + + // Load first background (Uses loadBg) + if (strlen(bgNames[0])) { + loadBg(bgNames[0]); + } - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } - for (i = 0; i < 16; i++) { - zoneData[i] = fHandle->readUint16BE(); + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } } - for (i = 0; i < 4; i++) { - commandVar3[i] = fHandle->readUint16BE(); + loadObjectTable(in); + renderer->restorePalette(in); + globalVars.load(in, NUM_MAX_VAR); + loadZoneData(in); + loadCommandVariables(in); + in.read(commandBuffer, 0x50); + loadZoneQuery(in); + + // TODO: Use the loaded string (Current music name (String, 13 bytes)). + in.read(musicName, 13); + + // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). + in.readUint16BE(); + + // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). + in.readUint16BE(); + + renderer->_cmdY = in.readUint16BE(); + in.readUint16BE(); // Some unknown variable that seems to always be zero + allowPlayerInput = in.readUint16BE(); + playerCommand = in.readUint16BE(); + commandVar1 = in.readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); + var5 = in.readUint16BE(); + var4 = in.readUint16BE(); + var3 = in.readUint16BE(); + var2 = in.readUint16BE(); + commandVar2 = in.readUint16BE(); + renderer->_messageBg = in.readUint16BE(); + + // TODO: Use the loaded value (adBgVar1 (Uint16BE)). + in.readUint16BE(); + + currentAdditionalBgIdx = in.readSint16BE(); + currentAdditionalBgIdx2 = in.readSint16BE(); + + // TODO: Check whether the scroll value really gets used correctly after this. + // Note that the backgrounds are loaded only later than this value is set. + renderer->setScroll(in.readUint16BE()); + + // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). + in.readUint16BE(); + + disableSystemMenu = in.readUint16BE(); + + // TODO: adBgVar1 = 1 here + + // Load the animDataTable entries + in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). + in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). + loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); + + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadSeqList(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); + + // Left this here instead of moving it earlier in this function with + // the other current value loadings (e.g. loading of current procedure, + // current backgrounds etc). Mostly emulating the way we've handled + // Future Wars savegames and hoping that things work out. + if (strlen(currentMsgName)) { + loadMsg(currentMsgName); } - fHandle->read(commandBuffer, 0x50); - renderer->setCommand(commandBuffer); + // TODO: Add current music loading and playing here + // TODO: Palette handling? - renderer->_cmdY = fHandle->readUint16BE(); + if (in.pos() == in.size()) { + debug(3, "loadTempSaveOS: Loaded the whole savefile."); + } else { + warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); + } - bgVar0 = fHandle->readUint16BE(); - allowPlayerInput = fHandle->readUint16BE(); - playerCommand = fHandle->readSint16BE(); - commandVar1 = fHandle->readSint16BE(); - isDrawCommandEnabled = fHandle->readUint16BE(); - var5 = fHandle->readUint16BE(); - var4 = fHandle->readUint16BE(); - var3 = fHandle->readUint16BE(); - var2 = fHandle->readUint16BE(); - commandVar2 = fHandle->readSint16BE(); + return !in.ioFailed(); +} - renderer->_messageBg = fHandle->readUint16BE(); +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At savefile position 0x0000: + currentDisk = in.readUint16BE(); - loadResourcesFromSave(*fHandle, broken); + // At 0x0002: + in.read(currentPartName, 13); + // At 0x000F: + in.read(currentDatName, 13); - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At 0x001C: + saveVar2 = in.readSint16BE(); - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); + // At 0x001E: + in.read(currentPrcName, 13); + // At 0x002B: + in.read(currentRelName, 13); + // At 0x0038: + in.read(currentMsgName, 13); + // At 0x0045: + in.read(bgName, 13); + // At 0x0052: + in.read(currentCtName, 13); + + checkDataDisk(currentDisk); + + if (strlen(currentPartName)) { + loadPart(currentPartName); } - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); } - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); + if (strlen(currentRelName)) { + loadRel(currentRelName); } - loadBgIncrustFromSave(*fHandle); + if (strlen(bgName)) { + loadBg(bgName); + } - delete fHandle; + if (strlen(currentCtName)) { + loadCtFW(currentCtName); + } + + // At 0x005F: + loadObjectTable(in); + + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); + + // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): + globalVars.load(in, NUM_MAX_VAR); + + // At 0x2281 (i.e. 0x2083 + 255 * 2): + loadZoneData(in); + + // At 0x22A1 (i.e. 0x2281 + 16 * 2): + loadCommandVariables(in); + + // At 0x22A9 (i.e. 0x22A1 + 4 * 2): + in.read(commandBuffer, 0x50); + renderer->setCommand(commandBuffer); + + // At 0x22F9 (i.e. 0x22A9 + 0x50): + renderer->_cmdY = in.readUint16BE(); + + // At 0x22FB: + bgVar0 = in.readUint16BE(); + // At 0x22FD: + allowPlayerInput = in.readUint16BE(); + // At 0x22FF: + playerCommand = in.readSint16BE(); + // At 0x2301: + commandVar1 = in.readSint16BE(); + // At 0x2303: + isDrawCommandEnabled = in.readUint16BE(); + // At 0x2305: + var5 = in.readUint16BE(); + // At 0x2307: + var4 = in.readUint16BE(); + // At 0x2309: + var3 = in.readUint16BE(); + // At 0x230B: + var2 = in.readUint16BE(); + // At 0x230D: + commandVar2 = in.readSint16BE(); + + // At 0x230F: + renderer->_messageBg = in.readUint16BE(); + + // At 0x2311: + in.readUint16BE(); + // At 0x2313: + in.readUint16BE(); + + // At 0x2315: + loadResourcesFromSave(in, saveGameFormat); + + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); if (strlen(currentMsgName)) { loadMsg(currentMsgName); } - setMouseCursor(MOUSE_CURSOR_NORMAL); - if (strlen(currentDatName)) { /* i = saveVar2; saveVar2 = 0; @@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -void makeSave(char *saveFileName) { - int16 i; - Common::OutSaveFile *fHandle; - - fHandle = g_saveFileMan->openForSaving(saveFileName); +bool CineEngine::makeLoad(char *saveName) { + Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName)); - if (!fHandle) { - drawString(otherMessages[1], 0); + if (!saveFile) { + drawString(otherMessages[0], 0); waitPlayerInput(); // restoreScreen(); checkDataDisk(-1); - return; - } - - fHandle->writeUint16BE(currentDisk); - fHandle->write(currentPartName, 13); - fHandle->write(currentDatName, 13); - fHandle->writeUint16BE(saveVar2); - fHandle->write(currentPrcName, 13); - fHandle->write(currentRelName, 13); - fHandle->write(currentMsgName, 13); - renderer->saveBg(*fHandle); - fHandle->write(currentCtName, 13); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x20); - - for (i = 0; i < 255; i++) { - fHandle->writeUint16BE(objectTable[i].x); - fHandle->writeUint16BE(objectTable[i].y); - fHandle->writeUint16BE(objectTable[i].mask); - fHandle->writeUint16BE(objectTable[i].frame); - fHandle->writeUint16BE(objectTable[i].costume); - fHandle->write(objectTable[i].name, 20); - fHandle->writeUint16BE(objectTable[i].part); - } - - renderer->savePalette(*fHandle); - - globalVars.save(*fHandle, NUM_MAX_VAR - 1); - - for (i = 0; i < 16; i++) { - fHandle->writeUint16BE(zoneData[i]); + return false; } - for (i = 0; i < 4; i++) { - fHandle->writeUint16BE(commandVar3[i]); + setMouseCursor(MOUSE_CURSOR_DISK); + + uint32 saveSize = saveFile->size(); + // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format. + if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it + // Can't get information about the savefile's size so let's try + // reading as much as we can from the file up to a predefined upper limit. + // + // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each): + // With 256 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB + // With 512 global scripts, object scripts, overlays and background incrusts: + // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB + // + // I think it extremely unlikely that there would be over 512 global scripts, object scripts, + // overlays and background incrusts so 256kB seems like quite a safe upper limit. + // NOTE: If the savegame format is changed then this value might have to be re-evaluated! + // Hopefully devices with more limited memory can also cope with this memory allocation. + saveSize = 256 * 1024; + } + Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize)); + + // Try to detect the used savegame format + enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in); + + // Handle problematic savegame formats + bool load = true; // Should we try to load the savegame? + bool result = false; + if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) { + // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but + // that's not implemented here because it was never used in a stable + // release of ScummVM but only during development (From revision 31453, + // which introduced the problem, until revision 32073, which fixed it). + // Therefore be bail out if we detect this particular savegame format. + warning("Detected a known broken savegame format, not loading savegame"); + load = false; // Don't load the savegame + } else if (saveGameFormat == ANIMSIZE_UNKNOWN) { + // If we can't detect the savegame format + // then let's try the default format and hope for the best. + warning("Couldn't detect the used savegame format, trying default savegame format. Things may break"); + saveGameFormat = ANIMSIZE_30_PTRS_INTACT; + } + + if (load) { + // Reset the engine's state + resetEngine(); + + if (saveGameFormat == TEMP_OS_FORMAT) { + // Load the temporary Operation Stealth savegame format + result = loadTempSaveOS(*in); + } else { + // Load the plain Future Wars savegame format + result = loadPlainSaveFW(*in, saveGameFormat); + } } - fHandle->write(commandBuffer, 0x50); - - fHandle->writeUint16BE(renderer->_cmdY); - - fHandle->writeUint16BE(bgVar0); - fHandle->writeUint16BE(allowPlayerInput); - fHandle->writeUint16BE(playerCommand); - fHandle->writeUint16BE(commandVar1); - fHandle->writeUint16BE(isDrawCommandEnabled); - fHandle->writeUint16BE(var5); - fHandle->writeUint16BE(var4); - fHandle->writeUint16BE(var3); - fHandle->writeUint16BE(var2); - fHandle->writeUint16BE(commandVar2); - - fHandle->writeUint16BE(renderer->_messageBg); - - fHandle->writeUint16BE(0xFF); - fHandle->writeUint16BE(0x1E); + setMouseCursor(MOUSE_CURSOR_NORMAL); - for (i = 0; i < NUM_MAX_ANIMDATA; i++) { - animDataTable[i].save(*fHandle); - } + return result; +} - fHandle->writeUint16BE(0); // Screen params, unhandled - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); +void CineEngine::makeSaveFW(Common::OutSaveFile &out) { + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentDatName, 13); + out.writeUint16BE(saveVar2); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(bgVar0); + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + saveAnimDataTable(out); + saveScreenParams(out); + + saveGlobalScripts(out); + saveObjectScripts(out); + saveOverlayList(out); + saveBgIncrustList(out); +} - { - ScriptList::iterator it; - fHandle->writeUint16BE(globalScripts.size()); - for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { - (*it)->save(*fHandle); - } +void CineEngine::makeSave(char *saveFileName) { + Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName)); - fHandle->writeUint16BE(objectScripts.size()); - for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { - (*it)->save(*fHandle); - } - } + setMouseCursor(MOUSE_CURSOR_DISK); - { - Common::List<overlay>::iterator it; - - fHandle->writeUint16BE(overlayList.size()); - - for (it = overlayList.begin(); it != overlayList.end(); ++it) { - fHandle->writeUint32BE(0); - fHandle->writeUint32BE(0); - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->type); - fHandle->writeSint16BE(it->x); - fHandle->writeSint16BE(it->y); - fHandle->writeSint16BE(it->width); - fHandle->writeSint16BE(it->color); + if (!fHandle) { + drawString(otherMessages[1], 0); + waitPlayerInput(); + // restoreScreen(); + checkDataDisk(-1); + } else { + if (g_cine->getGameType() == GType_FW) { + makeSaveFW(*fHandle); + } else { + makeSaveOS(*fHandle); } } - Common::List<BGIncrust>::iterator it; - fHandle->writeUint16BE(bgIncrustList.size()); - - for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) { - fHandle->writeUint32BE(0); // next - fHandle->writeUint32BE(0); // unkPtr - fHandle->writeUint16BE(it->objIdx); - fHandle->writeUint16BE(it->param); - fHandle->writeUint16BE(it->x); - fHandle->writeUint16BE(it->y); - fHandle->writeUint16BE(it->frame); - fHandle->writeUint16BE(it->part); - } - - delete fHandle; - setMouseCursor(MOUSE_CURSOR_NORMAL); } @@ -854,6 +1264,89 @@ void CineEngine::makeSystemMenu(void) { } } +/** + * Save an Operation Stealth type savegame. WIP! + * + * NOTE: This is going to be very much a work in progress so the Operation Stealth's + * savegame formats that are going to be tried are extremely probably not going + * to be supported at all after Operation Stealth becomes officially supported. + * This means that the savegame format will hopefully change to something nicer + * when official support for Operation Stealth begins. + */ +void CineEngine::makeSaveOS(Common::OutSaveFile &out) { + int i; + + // Make a temporary Operation Stealth savegame format chunk header and save it. + ChunkHeader header; + header.id = TEMP_OS_FORMAT_ID; + header.version = CURRENT_OS_SAVE_VER; + header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it. + writeChunkHeader(out, header); + + // Start outputting the plain savegame data right after the chunk header. + out.writeUint16BE(currentDisk); + out.write(currentPartName, 13); + out.write(currentPrcName, 13); + out.write(currentRelName, 13); + out.write(currentMsgName, 13); + renderer->saveBgNames(out); + out.write(currentCtName, 13); + + saveObjectTable(out); + renderer->savePalette(out); + globalVars.save(out, NUM_MAX_VAR); + saveZoneData(out); + saveCommandVariables(out); + out.write(commandBuffer, 0x50); + saveZoneQuery(out); + + // FIXME: Save a proper name here, saving an empty string currently. + // 0x2925: Current music name (String, 13 bytes). + for (i = 0; i < 13; i++) { + out.writeByte(0); + } + // FIXME: Save proper value for this variable, currently writing zero + // 0x2932: Is music loaded? (Uint16BE, Boolean). + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero + // 0x2934: Is music playing? (Uint16BE, Boolean). + out.writeUint16BE(0); + + out.writeUint16BE(renderer->_cmdY); + out.writeUint16BE(0); // Some unknown variable that seems to always be zero + out.writeUint16BE(allowPlayerInput); + out.writeUint16BE(playerCommand); + out.writeUint16BE(commandVar1); + out.writeUint16BE(isDrawCommandEnabled); + out.writeUint16BE(var5); + out.writeUint16BE(var4); + out.writeUint16BE(var3); + out.writeUint16BE(var2); + out.writeUint16BE(commandVar2); + out.writeUint16BE(renderer->_messageBg); + + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x295E: adBgVar1 (Uint16BE). + out.writeUint16BE(0); + out.writeSint16BE(currentAdditionalBgIdx); + out.writeSint16BE(currentAdditionalBgIdx2); + // FIXME: Save proper value for this variable, currently writing zero. + // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift. + out.writeUint16BE(0); + // FIXME: Save proper value for this variable, currently writing zero. + // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0? + out.writeUint16BE(0); + out.writeUint16BE(disableSystemMenu); + + saveAnimDataTable(out); + saveScreenParams(out); + saveGlobalScripts(out); + saveObjectScripts(out); + saveSeqList(out); + saveOverlayList(out); + saveBgIncrustList(out); +} + void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) { gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom @@ -1510,12 +2003,22 @@ void mainLoopSub6(void) { void checkForPendingDataLoad(void) { if (newPrcName[0] != 0) { - loadPrc(newPrcName); + bool loadPrcOk = loadPrc(newPrcName); strcpy(currentPrcName, newPrcName); strcpy(newPrcName, ""); - addScriptToList0(1); + // Check that the loading of the script file was successful before + // trying to add script 1 from it to the global scripts list. This + // fixes a crash when failing copy protection in Amiga or Atari ST + // versions of Future Wars. + if (loadPrcOk) { + addScriptToGlobalScripts(1); + } else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) { + // We only show an error here for other files than the file that + // is loaded if copy protection fails (i.e. L201.ANI). + warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName); + } } if (newRelName[0] != 0) { @@ -1582,16 +2085,19 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) { } } -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) { Common::List<SeqListElement>::iterator it; for (it = seqList.begin(); it != seqList.end(); ++it) { if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) { - return 1; + // Just to be on the safe side there's a restriction of the + // addition's result to 16-bit arithmetic here like in the + // original. It's possible that it's not strictly needed. + return ((it->var14 + it->var16) & 0xFFFF) == 0; } } - return 0; + return true; } void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) { @@ -1618,6 +2124,19 @@ void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, i seqList.insert(it, tmp); } +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) { + // Find a suitable list element and modify it + for (Common::List<SeqListElement>::iterator it = seqList.begin(); it != seqList.end(); ++it) { + if (it->objIdx == objIdx && it->var4 == var4Test) { + it->varC = param1; + it->var18 = param2; + it->var1A = param3; + it->var10 = it->var12 = param4; + break; + } + } +} + void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1, int16 param2, int16 x2, int16 y2) { element.var16 = 0; @@ -1662,105 +2181,51 @@ uint16 computeMove2(SeqListElement &element) { return returnVar; } -// sort all the gfx stuff... - -void resetGfxEntityEntry(uint16 objIdx) { -#if 0 - overlayHeadElement* tempHead = &overlayHead; - byte* var_16 = NULL; - uint16 var_10 = 0; - uint16 var_12 = 0; - overlayHeadElement* currentHead = tempHead->next; - byte* var_1A = NULL; - overlayHeadElement* var1E = &overlayHead; - - while (currentHead) { - tempHead2 = currentHead->next; - - if (currentHead->objIdx == objIdx && currentHead->type!=2 && currentHead->type!=3 && currentHead->type!=0x14) { - tempHead->next = tempHead2; - - if (tempHead2) { - tempHead2->previous = currentHead->previous; - } else { - seqVar0 = currentHead->previous; - } - - var_22 = var_16; - - if (!var_22) { - // todo: goto? - } - - var_22->previous = currentHead; - } else { - } - - if (currentHead->type == 0x14) { - } else { - } - - if (currentHead->type == 0x2 || currentHead->type == 0x3) { - si = 10000; - } else { - si = objectTable[currentHead->objIdx]; - } - - if (objectTable[objIdx]>si) { - var1E = currentHead; - } - - tempHead = tempHead->next; - - } - - if (var_1A) { - currentHead = var_16; - var_22 = var_1E->next; - var_1E->next = currentHead; - var_1A->next = var_22; - - if (var_1E != &gfxEntityHead) { - currentHead->previous = var_1E; - } - - if (!var_22) { - seqVar0 = var_1A; - } else { - var_22->previous = var_1A; - } - - } -#endif -} - -uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &element, uint16 param3, int16 *param4) { - const byte *currentPtr = ptr; - const byte *ptrData; - const byte *ptr2; +uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) { + const int8 *ptrData; + const int8 *ptr2; int16 di; - assert(ptr); - assert(param4); + debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", + param1, objIdx, ptr, element.var8, element.var14, param3); - dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8); + // In the original an error string is set and 0 is returned if the following doesn't hold + assert(ptr); + // We probably could just use a local variable here instead of the dummyU16 but + // haven't checked if this has any side-effects so keeping it this way still. + dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8); ptrData = ptr + dummyU16; + // In the original an error string is set and 0 is returned if the following doesn't hold assert(*ptrData); di = (objectTable[objIdx].costume + 1) % (*ptrData); - ptr2 = (ptrData + (di * 8)) + 1; - + ++ptrData; // Jump over the just read byte + // Here ptr2 seems to be indexing a table of structs (8 bytes per struct): + // struct { + // int8 x; // 0 (Used with checkCollision) + // int8 y; // 1 (Used with checkCollision) + // int8 numZones; // 2 (Used with checkCollision) + // int8 var3; // 3 (Not used in this function) + // int8 xAdd; // 4 (Used with an object) + // int8 yAdd; // 5 (Used with an object) + // int8 maskAdd; // 6 (Used with an object) + // int8 frameAdd; // 7 (Used with an object) + // }; + ptr2 = ptrData + di * 8; + + // We might probably safely discard the AND by 1 here because + // at least in the original checkCollision returns always 0 or 1. if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) { return 0; } - objectTable[objIdx].x += (int8)ptr2[4]; - objectTable[objIdx].y += (int8)ptr2[5]; - objectTable[objIdx].mask += (int8)ptr2[6]; + objectTable[objIdx].x += ptr2[4]; + objectTable[objIdx].y += ptr2[5]; + objectTable[objIdx].mask += ptr2[6]; - if (objectTable[objIdx].frame) { + if (ptr2[6]) { resetGfxEntityEntry(objIdx); } @@ -1769,19 +2234,79 @@ uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &ele if (param3 || !element.var14) { objectTable[objIdx].costume = di; } else { + assert(param4); *param4 = di; } return 1; } +/*! + * Permutates the overlay list into a different order according to some logic. + * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors) + */ +void resetGfxEntityEntry(uint16 objIdx) { + Common::List<overlay>::iterator it, bObjsCutPoint; + Common::List<overlay> aReverseObjs, bObjs; + bool foundCutPoint = false; + + // Go through the overlay list and partition the whole list into two categories (Type A and type B objects) + for (it = overlayList.begin(); it != overlayList.end(); ++it) { + if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object + aReverseObjs.push_front(*it); + } else { // Type B object + bObjs.push_back(*it); + uint16 objectMask; + if (it->type == 2 || it->type == 3) { + objectMask = 10000; + } else { + objectMask = objectTable[it->objIdx].mask; + } + + if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point + bObjsCutPoint = bObjs.reverse_begin(); + foundCutPoint = true; + } + } + } + + // Recreate the overlay list in a different order. + overlayList.clear(); + if (foundCutPoint) { + // If a cut point was found the order is: + // B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point. + ++bObjsCutPoint; // Include the cut point in the first list insertion + overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint); + overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); + overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end()); + } else { + // If no cut point was found the order is: + // A objects in reverse order, B objects. + overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end()); + overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end()); + } +} + void processSeqListElement(SeqListElement &element) { int16 x = objectTable[element.objIdx].x; int16 y = objectTable[element.objIdx].y; - const byte *ptr1 = animDataTable[element.frame].data(); + const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data(); int16 var_10; int16 var_4; int16 var_2; + + // Initial interpretations for variables addressed through ptr1 (8-bit addressing): + // These may be inaccurate! + // 0: ? + // 1: xRadius + // 2: yRadius + // 3: ? + // 4: xAdd + // 5: yAdd + // 6: ? + // 7: ? + // After this come (At least at positions 0, 1 and 3 in 16-bit addressing) + // 16-bit big-endian values used for addressing through ptr1. if (element.var12 < element.var10) { element.var12++; @@ -1791,22 +2316,44 @@ void processSeqListElement(SeqListElement &element) { element.var12 = 0; if (ptr1) { - uint16 param1 = ptr1[1]; - uint16 param2 = ptr1[2]; + int16 param1 = ptr1[1]; + int16 param2 = ptr1[2]; if (element.varC != 255) { - // FIXME: Why is this here? Fingolfin gets lots of these - // in his copy of Operation Stealth (value 0 or 236) under - // Mac OS X. Maybe it's a endian issue? At least the graphics - // in the copy protection screen are partially messed up. - warning("processSeqListElement: varC = %d", element.varC); - } - - if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { - computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); + int16 x2 = element.var18; + int16 y2 = element.var1A; + if (element.varC) { + x2 += objectTable[element.varC].x; + y2 += objectTable[element.varC].y; + } + computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2); } else { - element.var16 = 0; - element.var14 = 0; + if (inputVar0 && allowPlayerInput) { + int16 adder = param1 + 1; + if (inputVar0 != 1) { + adder = -adder; + } + // FIXME: In Operation Stealth's disassembly global variable 251 is used here + // but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a + // left over from Future Wars's reverse engineering? + globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder; + } + + if (inputVar1 && allowPlayerInput) { + int16 adder = param2 + 1; + if (inputVar1 != 1) { + adder = -adder; + } + // TODO: Name currently unnamed global variable 252 + globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder; + } + + if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) { + computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]); + } else { + element.var16 = 0; + element.var14 = 0; + } } var_10 = computeMove2(element); @@ -1847,14 +2394,14 @@ void processSeqListElement(SeqListElement &element) { } } - if (element.var16 + element.var14) { + if (element.var16 + element.var14 == 0) { if (element.var1C) { if (element.var1E) { objectTable[element.objIdx].costume = 0; element.var1E = 0; } - addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, (int16 *) & var2); + addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2); } } diff --git a/engines/cine/various.h b/engines/cine/various.h index 91662c16ff..d87679ca08 100644 --- a/engines/cine/various.h +++ b/engines/cine/various.h @@ -44,7 +44,7 @@ extern bool inMenu; struct SeqListElement { int16 var4; - uint16 objIdx; + uint16 objIdx; ///< Is this really unsigned? int16 var8; int16 frame; int16 varC; @@ -130,16 +130,20 @@ struct SelectedObjStruct { #define NUM_MAX_ZONE 16 extern uint16 zoneData[NUM_MAX_ZONE]; +extern uint16 zoneQuery[NUM_MAX_ZONE]; void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5); void removeMessages(); void removeSeq(uint16 param1, uint16 param2, uint16 param3); -uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3); +bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3); void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8); +void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4); void processSeqList(void); +void resetGfxEntityEntry(uint16 objIdx); + bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y); } // End of namespace Cine diff --git a/engines/cruise/cruise_main.h b/engines/cruise/cruise_main.h index 60afe5fa4c..c9c27ada49 100644 --- a/engines/cruise/cruise_main.h +++ b/engines/cruise/cruise_main.h @@ -28,7 +28,7 @@ #include <string.h> #include <stdlib.h> -#include <assert.h> +#include <assert.h> // FIXME: WINCE: this is not needed/not portable (probably applies to all above includes) #include "common/scummsys.h" diff --git a/engines/cruise/volume.cpp b/engines/cruise/volume.cpp index e4a3dde78f..b2ff2631c0 100644 --- a/engines/cruise/volume.cpp +++ b/engines/cruise/volume.cpp @@ -456,8 +456,8 @@ int16 readVolCnf(void) { sprintf(nameBuffer, "%s", buffer[j].name); if (buffer[j].size == buffer[j].extSize) { - Common::File fout; - fout.open(nameBuffer, Common::File::kFileWriteMode); + Common::DumpFile fout; + fout.open(nameBuffer); if(fout.isOpen()) fout.write(bufferLocal, buffer[j].size); } else { diff --git a/engines/drascula/animation.cpp b/engines/drascula/animation.cpp index feb6cb93ca..06868494b5 100644 --- a/engines/drascula/animation.cpp +++ b/engines/drascula/animation.cpp @@ -372,7 +372,11 @@ void DrasculaEngine::animation_1_1() { break; clearRoom(); - playMusic(2); + if (_lang == kSpanish) + playMusic(31); + else + playMusic(2); + pause(5); playFLI("intro.bin", 12); term_int = 1; @@ -1669,7 +1673,7 @@ void DrasculaEngine::animation_12_5() { const int frusky_x[] = {100, 139, 178, 217, 100, 178, 217, 139, 100, 139}; const int elfrusky_x[] = {1, 68, 135, 1, 68, 135, 1, 68, 135, 68, 1, 135, 68, 135, 68}; int color, component; - char fade; + signed char fade; playMusic(26); updateRoom(); diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h index ce67cc2c0e..8bb73d8dd1 100644 --- a/engines/drascula/drascula.h +++ b/engines/drascula/drascula.h @@ -245,7 +245,7 @@ public: void loadPic(const char *NamePcc, byte *targetSurface, int colorCount = 1); - typedef char DacPalette256[256][3]; + typedef signed char DacPalette256[256][3]; void setRGB(byte *pal, int plt); void assignDefaultPalette(); @@ -328,7 +328,7 @@ public: int curHeight, curWidth, feetHeight; int talkHeight, talkWidth; int floorX1, floorY1, floorX2, floorY2; - int near, far; + int lowerLimit, upperLimit; int trackFinal, walkToObject; int objExit; int timeDiff, startTime; @@ -397,7 +397,7 @@ public: void playFLI(const char *filefli, int vel); void fadeFromBlack(int fadeSpeed); void fadeToBlack(int fadeSpeed); - char adjustToVGA(char value); + signed char adjustToVGA(signed char value); void color_abc(int cl); void centerText(const char *,int,int); void playSound(int soundNum); diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp index 64591a856e..67993bfb6c 100644 --- a/engines/drascula/graphics.cpp +++ b/engines/drascula/graphics.cpp @@ -89,31 +89,21 @@ void DrasculaEngine::setCursorTable() { } void DrasculaEngine::loadPic(const char *NamePcc, byte *targetSurface, int colorCount) { - unsigned int con, x = 0; - unsigned int fExit = 0; - byte ch, rep; + uint dataSize = 0; + byte *pcxData; _arj.open(NamePcc); if (!_arj.isOpen()) error("missing game data %s %c", NamePcc, 7); - _arj.seek(128); - while (!fExit) { - ch = _arj.readByte(); - rep = 1; - if ((ch & 192) == 192) { - rep = (ch & 63); - ch = _arj.readByte(); - } - for (con = 0; con < rep; con++) { - x++; - if (x > 64000) { - fExit = 1; - break; - } - *targetSurface++ = ch; - } - } + dataSize = _arj.size() - 128 - (256 * 3); + pcxData = (byte *)malloc(dataSize); + + _arj.seek(128, SEEK_SET); + _arj.read(pcxData, dataSize); + + decodeRLE(pcxData, targetSurface); + free(pcxData); for (int i = 0; i < 256; i++) { cPal[i * 3 + 0] = _arj.readByte(); @@ -141,7 +131,6 @@ void DrasculaEngine::showFrame(bool firstFrame) { memcpy(prevFrame, VGA, 64000); decodeRLE(pcxData, VGA); - free(pcxData); if (!firstFrame) diff --git a/engines/drascula/palette.cpp b/engines/drascula/palette.cpp index ad57bce618..6a93f21e55 100644 --- a/engines/drascula/palette.cpp +++ b/engines/drascula/palette.cpp @@ -87,12 +87,12 @@ void DrasculaEngine::color_abc(int cl) { setPalette((byte *)&gamePalette); } -char DrasculaEngine::adjustToVGA(char value) { +signed char DrasculaEngine::adjustToVGA(signed char value) { return (value & 0x3F) * (value > 0); } void DrasculaEngine::fadeToBlack(int fadeSpeed) { - char fade; + signed char fade; unsigned int color, component; DacPalette256 palFade; @@ -110,7 +110,7 @@ void DrasculaEngine::fadeToBlack(int fadeSpeed) { } void DrasculaEngine::fadeFromBlack(int fadeSpeed) { - char fade; + signed char fade; unsigned int color, component; DacPalette256 palFade; @@ -186,7 +186,7 @@ void DrasculaEngine::setDarkPalette() { } void DrasculaEngine::setPaletteBase(int darkness) { - char fade; + signed char fade; unsigned int color, component; for (fade = darkness; fade >= 0; fade--) { diff --git a/engines/drascula/rooms.cpp b/engines/drascula/rooms.cpp index 6fe28bdbdc..37dddf4b7e 100644 --- a/engines/drascula/rooms.cpp +++ b/engines/drascula/rooms.cpp @@ -1672,8 +1672,8 @@ void DrasculaEngine::enterRoom(int roomIndex) { getIntFromLine(buffer, size, &floorY2); if (currentChapter != 2) { - getIntFromLine(buffer, size, &far); - getIntFromLine(buffer, size, &near); + getIntFromLine(buffer, size, &upperLimit); + getIntFromLine(buffer, size, &lowerLimit); } _arj.close(); @@ -1732,27 +1732,27 @@ void DrasculaEngine::enterRoom(int roomIndex) { if (currentChapter != 2) { for (l = 0; l <= floorY1; l++) - factor_red[l] = far; + factor_red[l] = upperLimit; for (l = floorY1; l <= 201; l++) - factor_red[l] = near; + factor_red[l] = lowerLimit; - chiquez = (float)(near - far) / (float)(floorY2 - floorY1); + chiquez = (float)(lowerLimit - upperLimit) / (float)(floorY2 - floorY1); for (l = floorY1; l <= floorY2; l++) { - factor_red[l] = (int)(far + pequegnez); + factor_red[l] = (int)(upperLimit + pequegnez); pequegnez = pequegnez + chiquez; } } if (roomNumber == 24) { for (l = floorY1 - 1; l > 74; l--) { - factor_red[l] = (int)(far - pequegnez); + factor_red[l] = (int)(upperLimit - pequegnez); pequegnez = pequegnez + chiquez; } } if (currentChapter == 5 && roomNumber == 54) { for (l = floorY1 - 1; l > 84; l--) { - factor_red[l] = (int)(far - pequegnez); + factor_red[l] = (int)(upperLimit - pequegnez); pequegnez = pequegnez + chiquez; } } diff --git a/engines/engines.mk b/engines/engines.mk index cfb8e69f3e..4dba913173 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2) MODULES += engines/sword2 endif +ifdef ENABLE_TINSEL +DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL) +MODULES += engines/tinsel +endif + ifdef ENABLE_TOUCHE DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE) MODULES += engines/touche diff --git a/engines/gob/dataio.cpp b/engines/gob/dataio.cpp index 8ae11b8755..bcf566d134 100644 --- a/engines/gob/dataio.cpp +++ b/engines/gob/dataio.cpp @@ -202,7 +202,7 @@ const Common::File *DataIO::file_getHandle(int16 handle) const { return &_filesHandles[handle]; } -int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) { +int16 DataIO::file_open(const char *path) { int16 i; for (i = 0; i < MAX_FILES; i++) { @@ -212,7 +212,7 @@ int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) { if (i == MAX_FILES) return -1; - file_getHandle(i)->open(path, mode); + file_getHandle(i)->open(path); if (file_getHandle(i)->isOpen()) return i; @@ -467,17 +467,14 @@ void DataIO::closeData(int16 handle) { file_getHandle(handle)->close(); } -int16 DataIO::openData(const char *path, Common::File::AccessMode mode) { +int16 DataIO::openData(const char *path) { int16 handle; - if (mode != Common::File::kFileReadMode) - return file_open(path, mode); - handle = getChunk(path); if (handle >= 0) return handle; - return file_open(path, mode); + return file_open(path); } DataStream *DataIO::openAsStream(int16 handle, bool dispose) { diff --git a/engines/gob/dataio.h b/engines/gob/dataio.h index a990dbeda5..4b4c79d1eb 100644 --- a/engines/gob/dataio.h +++ b/engines/gob/dataio.h @@ -79,8 +79,7 @@ public: void closeDataFile(bool itk = 0); byte *getUnpackedData(const char *name); void closeData(int16 handle); - int16 openData(const char *path, - Common::File::AccessMode mode = Common::File::kFileReadMode); + int16 openData(const char *path); DataStream *openAsStream(int16 handle, bool dispose = false); int32 getDataSize(const char *name); @@ -104,8 +103,7 @@ protected: class GobEngine *_vm; - int16 file_open(const char *path, - Common::File::AccessMode mode = Common::File::kFileReadMode); + int16 file_open(const char *path); Common::File *file_getHandle(int16 handle); const Common::File *file_getHandle(int16 handle) const; diff --git a/engines/gob/detection.cpp b/engines/gob/detection.cpp index 8351f2ecfb..63a0f8f45b 100644 --- a/engines/gob/detection.cpp +++ b/engines/gob/detection.cpp @@ -277,6 +277,19 @@ static const GOBGameDescription gameDescriptions[] = { kFeaturesNone, "intro" }, + { // Supplied by raina in the forums + { + "gob1", + "", + AD_ENTRY1s("intro.stk", "6d837c6380d8f4d984c9f6cc0026df4f", 192712), + EN_ANY, + kPlatformMacintosh, + Common::ADGF_NO_FLAGS + }, + kGameTypeGob1, + kFeaturesNone, + "intro" + }, { // Supplied by paul66 in bug report #1652352 { "gob1", diff --git a/engines/gob/driver_vga.cpp b/engines/gob/driver_vga.cpp index f68ce47783..2d3a10b570 100644 --- a/engines/gob/driver_vga.cpp +++ b/engines/gob/driver_vga.cpp @@ -112,7 +112,7 @@ void VGAVideoDriver::drawSprite(SurfaceDesc *source, SurfaceDesc *dest, if ((width < 1) || (height < 1)) return; - byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left; + const byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left; byte *destPos = dest->getVidMem() + (y * dest->getWidth()) + x; uint32 size = width * height; diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp index a3fe0ebbe2..34443251d8 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -147,6 +147,15 @@ void GobEngine::validateVideoMode(int16 videoMode) { error("Video mode 0x%X is not supported!", videoMode); } +Endianness GobEngine::getEndianness() const { + if ((_vm->getPlatform() == Common::kPlatformAmiga) || + (_vm->getPlatform() == Common::kPlatformMacintosh) || + (_vm->getPlatform() == Common::kPlatformAtariST)) + return kEndiannessBE; + + return kEndiannessLE; +} + Common::Platform GobEngine::getPlatform() const { return _platform; } diff --git a/engines/gob/gob.h b/engines/gob/gob.h index ae2b53bc31..041658baea 100644 --- a/engines/gob/gob.h +++ b/engines/gob/gob.h @@ -79,6 +79,11 @@ class SaveLoad; #define VAR(var) READ_VAR_UINT32(var) +enum Endianness { + kEndiannessLE, + kEndiannessBE +}; + enum GameType { kGameTypeNone = 0, kGameTypeGob1, @@ -230,6 +235,7 @@ public: void validateLanguage(); void validateVideoMode(int16 videoMode); + Endianness getEndianness() const; Common::Platform getPlatform() const; GameType getGameType() const; bool isCD() const; diff --git a/engines/gob/goblin.cpp b/engines/gob/goblin.cpp index e7aed0790e..5add0b9cea 100644 --- a/engines/gob/goblin.cpp +++ b/engines/gob/goblin.cpp @@ -78,58 +78,6 @@ Goblin::Goblin(GobEngine *vm) : _vm(vm) { _pressedMapY = 0; _pathExistence = 0; - _some0ValPtr = 0; - - _gobRetVarPtr = 0; - _curGobVarPtr = 0; - _curGobXPosVarPtr = 0; - _curGobYPosVarPtr = 0; - _itemInPocketVarPtr = 0; - - _curGobStateVarPtr = 0; - _curGobFrameVarPtr = 0; - _curGobMultStateVarPtr = 0; - _curGobNextStateVarPtr = 0; - _curGobScrXVarPtr = 0; - _curGobScrYVarPtr = 0; - _curGobLeftVarPtr = 0; - _curGobTopVarPtr = 0; - _curGobRightVarPtr = 0; - _curGobBottomVarPtr = 0; - _curGobDoAnimVarPtr = 0; - _curGobOrderVarPtr = 0; - _curGobNoTickVarPtr = 0; - _curGobTypeVarPtr = 0; - _curGobMaxTickVarPtr = 0; - _curGobTickVarPtr = 0; - _curGobActStartStateVarPtr = 0; - _curGobLookDirVarPtr = 0; - _curGobPickableVarPtr = 0; - _curGobRelaxVarPtr = 0; - _curGobMaxFrameVarPtr = 0; - - _destItemStateVarPtr = 0; - _destItemFrameVarPtr = 0; - _destItemMultStateVarPtr = 0; - _destItemNextStateVarPtr = 0; - _destItemScrXVarPtr = 0; - _destItemScrYVarPtr = 0; - _destItemLeftVarPtr = 0; - _destItemTopVarPtr = 0; - _destItemRightVarPtr = 0; - _destItemBottomVarPtr = 0; - _destItemDoAnimVarPtr = 0; - _destItemOrderVarPtr = 0; - _destItemNoTickVarPtr = 0; - _destItemTypeVarPtr = 0; - _destItemMaxTickVarPtr = 0; - _destItemTickVarPtr = 0; - _destItemActStartStVarPtr = 0; - _destItemLookDirVarPtr = 0; - _destItemPickableVarPtr = 0; - _destItemRelaxVarPtr = 0; - _destItemMaxFrameVarPtr = 0; - _destItemType = 0; _destItemState = 0; for (int i = 0; i < 20; i++) { @@ -690,7 +638,7 @@ void Goblin::switchGoblin(int16 index) { _gobDestY = tmp; _vm->_map->_curGoblinY = tmp; - *_curGobVarPtr = _currentGoblin; + _curGobVarPtr = (uint32) _currentGoblin; _pathExistence = 0; _readyToAct = 0; } @@ -1250,172 +1198,172 @@ void Goblin::loadObjects(const char *source) { void Goblin::saveGobDataToVars(int16 xPos, int16 yPos, int16 someVal) { Gob_Object *obj; - *_some0ValPtr = someVal; - *_curGobXPosVarPtr = xPos; - *_curGobYPosVarPtr = yPos; - *_itemInPocketVarPtr = _itemIndInPocket; + _some0ValPtr = (uint32) someVal; + _curGobXPosVarPtr = (uint32) xPos; + _curGobYPosVarPtr = (uint32) yPos; + _itemInPocketVarPtr = (uint32) _itemIndInPocket; obj = _goblins[_currentGoblin]; - *_curGobStateVarPtr = obj->state; - *_curGobFrameVarPtr = obj->curFrame; - *_curGobMultStateVarPtr = obj->multState; - *_curGobNextStateVarPtr = obj->nextState; - *_curGobScrXVarPtr = obj->xPos; - *_curGobScrYVarPtr = obj->yPos; - *_curGobLeftVarPtr = obj->left; - *_curGobTopVarPtr = obj->top; - *_curGobRightVarPtr = obj->right; - *_curGobBottomVarPtr = obj->bottom; - *_curGobDoAnimVarPtr = obj->doAnim; - *_curGobOrderVarPtr = obj->order; - *_curGobNoTickVarPtr = obj->noTick; - *_curGobTypeVarPtr = obj->type; - *_curGobMaxTickVarPtr = obj->maxTick; - *_curGobTickVarPtr = obj->tick; - *_curGobActStartStateVarPtr = obj->actionStartState; - *_curGobLookDirVarPtr = obj->curLookDir; - *_curGobPickableVarPtr = obj->pickable; - *_curGobRelaxVarPtr = obj->relaxTime; - *_curGobMaxFrameVarPtr = getObjMaxFrame(obj); + _curGobStateVarPtr = (uint32) obj->state; + _curGobFrameVarPtr = (uint32) obj->curFrame; + _curGobMultStateVarPtr = (uint32) obj->multState; + _curGobNextStateVarPtr = (uint32) obj->nextState; + _curGobScrXVarPtr = (uint32) obj->xPos; + _curGobScrYVarPtr = (uint32) obj->yPos; + _curGobLeftVarPtr = (uint32) obj->left; + _curGobTopVarPtr = (uint32) obj->top; + _curGobRightVarPtr = (uint32) obj->right; + _curGobBottomVarPtr = (uint32) obj->bottom; + _curGobDoAnimVarPtr = (uint32) obj->doAnim; + _curGobOrderVarPtr = (uint32) obj->order; + _curGobNoTickVarPtr = (uint32) obj->noTick; + _curGobTypeVarPtr = (uint32) obj->type; + _curGobMaxTickVarPtr = (uint32) obj->maxTick; + _curGobTickVarPtr = (uint32) obj->tick; + _curGobActStartStateVarPtr = (uint32) obj->actionStartState; + _curGobLookDirVarPtr = (uint32) obj->curLookDir; + _curGobPickableVarPtr = (uint32) obj->pickable; + _curGobRelaxVarPtr = (uint32) obj->relaxTime; + _curGobMaxFrameVarPtr = (uint32) getObjMaxFrame(obj); if (_actDestItemDesc == 0) return; obj = _actDestItemDesc; - *_destItemStateVarPtr = obj->state; - *_destItemFrameVarPtr = obj->curFrame; - *_destItemMultStateVarPtr = obj->multState; - *_destItemNextStateVarPtr = obj->nextState; - *_destItemScrXVarPtr = obj->xPos; - *_destItemScrYVarPtr = obj->yPos; - *_destItemLeftVarPtr = obj->left; - *_destItemTopVarPtr = obj->top; - *_destItemRightVarPtr = obj->right; - *_destItemBottomVarPtr = obj->bottom; - *_destItemDoAnimVarPtr = obj->doAnim; - *_destItemOrderVarPtr = obj->order; - *_destItemNoTickVarPtr = obj->noTick; - *_destItemTypeVarPtr = obj->type; - *_destItemMaxTickVarPtr = obj->maxTick; - *_destItemTickVarPtr = obj->tick; - *_destItemActStartStVarPtr = obj->actionStartState; - *_destItemLookDirVarPtr = obj->curLookDir; - *_destItemPickableVarPtr = obj->pickable; - *_destItemRelaxVarPtr = obj->relaxTime; - *_destItemMaxFrameVarPtr = getObjMaxFrame(obj); + _destItemStateVarPtr = (uint32) obj->state; + _destItemFrameVarPtr = (uint32) obj->curFrame; + _destItemMultStateVarPtr = (uint32) obj->multState; + _destItemNextStateVarPtr = (uint32) obj->nextState; + _destItemScrXVarPtr = (uint32) obj->xPos; + _destItemScrYVarPtr = (uint32) obj->yPos; + _destItemLeftVarPtr = (uint32) obj->left; + _destItemTopVarPtr = (uint32) obj->top; + _destItemRightVarPtr = (uint32) obj->right; + _destItemBottomVarPtr = (uint32) obj->bottom; + _destItemDoAnimVarPtr = (uint32) obj->doAnim; + _destItemOrderVarPtr = (uint32) obj->order; + _destItemNoTickVarPtr = (uint32) obj->noTick; + _destItemTypeVarPtr = (uint32) obj->type; + _destItemMaxTickVarPtr = (uint32) obj->maxTick; + _destItemTickVarPtr = (uint32) obj->tick; + _destItemActStartStVarPtr = (uint32) obj->actionStartState; + _destItemLookDirVarPtr = (uint32) obj->curLookDir; + _destItemPickableVarPtr = (uint32) obj->pickable; + _destItemRelaxVarPtr = (uint32) obj->relaxTime; + _destItemMaxFrameVarPtr = (uint32) getObjMaxFrame(obj); _destItemState = obj->state; _destItemType = obj->type; } void Goblin::initVarPointers(void) { - _gobRetVarPtr = (int32 *)VAR_ADDRESS(59); - _curGobStateVarPtr = (int32 *)VAR_ADDRESS(60); - _curGobFrameVarPtr = (int32 *)VAR_ADDRESS(61); - _curGobMultStateVarPtr = (int32 *)VAR_ADDRESS(62); - _curGobNextStateVarPtr = (int32 *)VAR_ADDRESS(63); - _curGobScrXVarPtr = (int32 *)VAR_ADDRESS(64); - _curGobScrYVarPtr = (int32 *)VAR_ADDRESS(65); - _curGobLeftVarPtr = (int32 *)VAR_ADDRESS(66); - _curGobTopVarPtr = (int32 *)VAR_ADDRESS(67); - _curGobRightVarPtr = (int32 *)VAR_ADDRESS(68); - _curGobBottomVarPtr = (int32 *)VAR_ADDRESS(69); - _curGobDoAnimVarPtr = (int32 *)VAR_ADDRESS(70); - _curGobOrderVarPtr = (int32 *)VAR_ADDRESS(71); - _curGobNoTickVarPtr = (int32 *)VAR_ADDRESS(72); - _curGobTypeVarPtr = (int32 *)VAR_ADDRESS(73); - _curGobMaxTickVarPtr = (int32 *)VAR_ADDRESS(74); - _curGobTickVarPtr = (int32 *)VAR_ADDRESS(75); - _curGobActStartStateVarPtr = (int32 *)VAR_ADDRESS(76); - _curGobLookDirVarPtr = (int32 *)VAR_ADDRESS(77); - _curGobPickableVarPtr = (int32 *)VAR_ADDRESS(80); - _curGobRelaxVarPtr = (int32 *)VAR_ADDRESS(81); - _destItemStateVarPtr = (int32 *)VAR_ADDRESS(82); - _destItemFrameVarPtr = (int32 *)VAR_ADDRESS(83); - _destItemMultStateVarPtr = (int32 *)VAR_ADDRESS(84); - _destItemNextStateVarPtr = (int32 *)VAR_ADDRESS(85); - _destItemScrXVarPtr = (int32 *)VAR_ADDRESS(86); - _destItemScrYVarPtr = (int32 *)VAR_ADDRESS(87); - _destItemLeftVarPtr = (int32 *)VAR_ADDRESS(88); - _destItemTopVarPtr = (int32 *)VAR_ADDRESS(89); - _destItemRightVarPtr = (int32 *)VAR_ADDRESS(90); - _destItemBottomVarPtr = (int32 *)VAR_ADDRESS(91); - _destItemDoAnimVarPtr = (int32 *)VAR_ADDRESS(92); - _destItemOrderVarPtr = (int32 *)VAR_ADDRESS(93); - _destItemNoTickVarPtr = (int32 *)VAR_ADDRESS(94); - _destItemTypeVarPtr = (int32 *)VAR_ADDRESS(95); - _destItemMaxTickVarPtr = (int32 *)VAR_ADDRESS(96); - _destItemTickVarPtr = (int32 *)VAR_ADDRESS(97); - _destItemActStartStVarPtr = (int32 *)VAR_ADDRESS(98); - _destItemLookDirVarPtr = (int32 *)VAR_ADDRESS(99); - _destItemPickableVarPtr = (int32 *)VAR_ADDRESS(102); - _destItemRelaxVarPtr = (int32 *)VAR_ADDRESS(103); - _destItemMaxFrameVarPtr = (int32 *)VAR_ADDRESS(105); - _curGobVarPtr = (int32 *)VAR_ADDRESS(106); - _some0ValPtr = (int32 *)VAR_ADDRESS(107); - _curGobXPosVarPtr = (int32 *)VAR_ADDRESS(108); - _curGobYPosVarPtr = (int32 *)VAR_ADDRESS(109); - _curGobMaxFrameVarPtr = (int32 *)VAR_ADDRESS(110); - - _itemInPocketVarPtr = (int32 *)VAR_ADDRESS(114); - - *_itemInPocketVarPtr = -2; + _gobRetVarPtr.set(*_vm->_inter->_variables, 236); + _curGobStateVarPtr.set(*_vm->_inter->_variables, 240); + _curGobFrameVarPtr.set(*_vm->_inter->_variables, 244); + _curGobMultStateVarPtr.set(*_vm->_inter->_variables, 248); + _curGobNextStateVarPtr.set(*_vm->_inter->_variables, 252); + _curGobScrXVarPtr.set(*_vm->_inter->_variables, 256); + _curGobScrYVarPtr.set(*_vm->_inter->_variables, 260); + _curGobLeftVarPtr.set(*_vm->_inter->_variables, 264); + _curGobTopVarPtr.set(*_vm->_inter->_variables, 268); + _curGobRightVarPtr.set(*_vm->_inter->_variables, 272); + _curGobBottomVarPtr.set(*_vm->_inter->_variables, 276); + _curGobDoAnimVarPtr.set(*_vm->_inter->_variables, 280); + _curGobOrderVarPtr.set(*_vm->_inter->_variables, 284); + _curGobNoTickVarPtr.set(*_vm->_inter->_variables, 288); + _curGobTypeVarPtr.set(*_vm->_inter->_variables, 292); + _curGobMaxTickVarPtr.set(*_vm->_inter->_variables, 296); + _curGobTickVarPtr.set(*_vm->_inter->_variables, 300); + _curGobActStartStateVarPtr.set(*_vm->_inter->_variables, 304); + _curGobLookDirVarPtr.set(*_vm->_inter->_variables, 308); + _curGobPickableVarPtr.set(*_vm->_inter->_variables, 320); + _curGobRelaxVarPtr.set(*_vm->_inter->_variables, 324); + _destItemStateVarPtr.set(*_vm->_inter->_variables, 328); + _destItemFrameVarPtr.set(*_vm->_inter->_variables, 332); + _destItemMultStateVarPtr.set(*_vm->_inter->_variables, 336); + _destItemNextStateVarPtr.set(*_vm->_inter->_variables, 340); + _destItemScrXVarPtr.set(*_vm->_inter->_variables, 344); + _destItemScrYVarPtr.set(*_vm->_inter->_variables, 348); + _destItemLeftVarPtr.set(*_vm->_inter->_variables, 352); + _destItemTopVarPtr.set(*_vm->_inter->_variables, 356); + _destItemRightVarPtr.set(*_vm->_inter->_variables, 360); + _destItemBottomVarPtr.set(*_vm->_inter->_variables, 364); + _destItemDoAnimVarPtr.set(*_vm->_inter->_variables, 368); + _destItemOrderVarPtr.set(*_vm->_inter->_variables, 372); + _destItemNoTickVarPtr.set(*_vm->_inter->_variables, 376); + _destItemTypeVarPtr.set(*_vm->_inter->_variables, 380); + _destItemMaxTickVarPtr.set(*_vm->_inter->_variables, 384); + _destItemTickVarPtr.set(*_vm->_inter->_variables, 388); + _destItemActStartStVarPtr.set(*_vm->_inter->_variables, 392); + _destItemLookDirVarPtr.set(*_vm->_inter->_variables, 396); + _destItemPickableVarPtr.set(*_vm->_inter->_variables, 408); + _destItemRelaxVarPtr.set(*_vm->_inter->_variables, 412); + _destItemMaxFrameVarPtr.set(*_vm->_inter->_variables, 420); + _curGobVarPtr.set(*_vm->_inter->_variables, 424); + _some0ValPtr.set(*_vm->_inter->_variables, 428); + _curGobXPosVarPtr.set(*_vm->_inter->_variables, 432); + _curGobYPosVarPtr.set(*_vm->_inter->_variables, 436); + _curGobMaxFrameVarPtr.set(*_vm->_inter->_variables, 440); + + _itemInPocketVarPtr.set(*_vm->_inter->_variables, 456); + + _itemInPocketVarPtr = (uint32) -2; } void Goblin::loadGobDataFromVars(void) { Gob_Object *obj; - _itemIndInPocket = *_itemInPocketVarPtr; + _itemIndInPocket = (int32) _itemInPocketVarPtr; obj = _goblins[_currentGoblin]; - obj->state = *_curGobStateVarPtr; - obj->curFrame = *_curGobFrameVarPtr; - obj->multState = *_curGobMultStateVarPtr; - obj->nextState = *_curGobNextStateVarPtr; - obj->xPos = *_curGobScrXVarPtr; - obj->yPos = *_curGobScrYVarPtr; - obj->left = *_curGobLeftVarPtr; - obj->top = *_curGobTopVarPtr; - obj->right = *_curGobRightVarPtr; - obj->bottom = *_curGobBottomVarPtr; - obj->doAnim = *_curGobDoAnimVarPtr; - obj->order = *_curGobOrderVarPtr; - obj->noTick = *_curGobNoTickVarPtr; - obj->type = *_curGobTypeVarPtr; - obj->maxTick = *_curGobMaxTickVarPtr; - obj->tick = *_curGobTickVarPtr; - obj->actionStartState = *_curGobActStartStateVarPtr; - obj->curLookDir = *_curGobLookDirVarPtr; - obj->pickable = *_curGobPickableVarPtr; - obj->relaxTime = *_curGobRelaxVarPtr; + obj->state = (int32) _curGobStateVarPtr; + obj->curFrame = (int32) _curGobFrameVarPtr; + obj->multState = (int32) _curGobMultStateVarPtr; + obj->nextState = (int32) _curGobNextStateVarPtr; + obj->xPos = (int32) _curGobScrXVarPtr; + obj->yPos = (int32) _curGobScrYVarPtr; + obj->left = (int32) _curGobLeftVarPtr; + obj->top = (int32) _curGobTopVarPtr; + obj->right = (int32) _curGobRightVarPtr; + obj->bottom = (int32) _curGobBottomVarPtr; + obj->doAnim = (int32) _curGobDoAnimVarPtr; + obj->order = (int32) _curGobOrderVarPtr; + obj->noTick = (int32) _curGobNoTickVarPtr; + obj->type = (int32) _curGobTypeVarPtr; + obj->maxTick = (int32) _curGobMaxTickVarPtr; + obj->tick = (int32) _curGobTickVarPtr; + obj->actionStartState = (int32) _curGobActStartStateVarPtr; + obj->curLookDir = (int32) _curGobLookDirVarPtr; + obj->pickable = (int32) _curGobPickableVarPtr; + obj->relaxTime = (int32) _curGobRelaxVarPtr; if (_actDestItemDesc == 0) return; obj = _actDestItemDesc; - obj->state = *_destItemStateVarPtr; - obj->curFrame = *_destItemFrameVarPtr; - obj->multState = *_destItemMultStateVarPtr; - obj->nextState = *_destItemNextStateVarPtr; - obj->xPos = *_destItemScrXVarPtr; - obj->yPos = *_destItemScrYVarPtr; - obj->left = *_destItemLeftVarPtr; - obj->top = *_destItemTopVarPtr; - obj->right = *_destItemRightVarPtr; - obj->bottom = *_destItemBottomVarPtr; - obj->doAnim = *_destItemDoAnimVarPtr; - obj->order = *_destItemOrderVarPtr; - obj->noTick = *_destItemNoTickVarPtr; - obj->type = *_destItemTypeVarPtr; - obj->maxTick = *_destItemMaxTickVarPtr; - obj->tick = *_destItemTickVarPtr; - obj->actionStartState = *_destItemActStartStVarPtr; - obj->curLookDir = *_destItemLookDirVarPtr; - obj->pickable = *_destItemPickableVarPtr; - obj->relaxTime = *_destItemRelaxVarPtr; + obj->state = (int32) _destItemStateVarPtr; + obj->curFrame = (int32) _destItemFrameVarPtr; + obj->multState = (int32) _destItemMultStateVarPtr; + obj->nextState = (int32) _destItemNextStateVarPtr; + obj->xPos = (int32) _destItemScrXVarPtr; + obj->yPos = (int32) _destItemScrYVarPtr; + obj->left = (int32) _destItemLeftVarPtr; + obj->top = (int32) _destItemTopVarPtr; + obj->right = (int32) _destItemRightVarPtr; + obj->bottom = (int32) _destItemBottomVarPtr; + obj->doAnim = (int32) _destItemDoAnimVarPtr; + obj->order = (int32) _destItemOrderVarPtr; + obj->noTick = (int32) _destItemNoTickVarPtr; + obj->type = (int32) _destItemTypeVarPtr; + obj->maxTick = (int32) _destItemMaxTickVarPtr; + obj->tick = (int32) _destItemTickVarPtr; + obj->actionStartState = (int32) _destItemActStartStVarPtr; + obj->curLookDir = (int32) _destItemLookDirVarPtr; + obj->pickable = (int32) _destItemPickableVarPtr; + obj->relaxTime = (int32) _destItemRelaxVarPtr; if (obj->type != _destItemType) obj->toRedraw = 1; diff --git a/engines/gob/goblin.h b/engines/gob/goblin.h index 3fd8a9f93b..2100bcbdac 100644 --- a/engines/gob/goblin.h +++ b/engines/gob/goblin.h @@ -28,6 +28,7 @@ #include "gob/util.h" #include "gob/mult.h" +#include "gob/variables.h" #include "gob/sound/sounddesc.h" namespace Gob { @@ -115,57 +116,57 @@ public: char _pathExistence; // Pointers to interpreter variables - int32 *_some0ValPtr; - - int32 *_gobRetVarPtr; - int32 *_curGobVarPtr; - int32 *_curGobXPosVarPtr; - int32 *_curGobYPosVarPtr; - int32 *_itemInPocketVarPtr; - - int32 *_curGobStateVarPtr; - int32 *_curGobFrameVarPtr; - int32 *_curGobMultStateVarPtr; - int32 *_curGobNextStateVarPtr; - int32 *_curGobScrXVarPtr; - int32 *_curGobScrYVarPtr; - int32 *_curGobLeftVarPtr; - int32 *_curGobTopVarPtr; - int32 *_curGobRightVarPtr; - int32 *_curGobBottomVarPtr; - int32 *_curGobDoAnimVarPtr; - int32 *_curGobOrderVarPtr; - int32 *_curGobNoTickVarPtr; - int32 *_curGobTypeVarPtr; - int32 *_curGobMaxTickVarPtr; - int32 *_curGobTickVarPtr; - int32 *_curGobActStartStateVarPtr; - int32 *_curGobLookDirVarPtr; - int32 *_curGobPickableVarPtr; - int32 *_curGobRelaxVarPtr; - int32 *_curGobMaxFrameVarPtr; - - int32 *_destItemStateVarPtr; - int32 *_destItemFrameVarPtr; - int32 *_destItemMultStateVarPtr; - int32 *_destItemNextStateVarPtr; - int32 *_destItemScrXVarPtr; - int32 *_destItemScrYVarPtr; - int32 *_destItemLeftVarPtr; - int32 *_destItemTopVarPtr; - int32 *_destItemRightVarPtr; - int32 *_destItemBottomVarPtr; - int32 *_destItemDoAnimVarPtr; - int32 *_destItemOrderVarPtr; - int32 *_destItemNoTickVarPtr; - int32 *_destItemTypeVarPtr; - int32 *_destItemMaxTickVarPtr; - int32 *_destItemTickVarPtr; - int32 *_destItemActStartStVarPtr; - int32 *_destItemLookDirVarPtr; - int32 *_destItemPickableVarPtr; - int32 *_destItemRelaxVarPtr; - int32 *_destItemMaxFrameVarPtr; + VariableReference _some0ValPtr; + + VariableReference _gobRetVarPtr; + VariableReference _curGobVarPtr; + VariableReference _curGobXPosVarPtr; + VariableReference _curGobYPosVarPtr; + VariableReference _itemInPocketVarPtr; + + VariableReference _curGobStateVarPtr; + VariableReference _curGobFrameVarPtr; + VariableReference _curGobMultStateVarPtr; + VariableReference _curGobNextStateVarPtr; + VariableReference _curGobScrXVarPtr; + VariableReference _curGobScrYVarPtr; + VariableReference _curGobLeftVarPtr; + VariableReference _curGobTopVarPtr; + VariableReference _curGobRightVarPtr; + VariableReference _curGobBottomVarPtr; + VariableReference _curGobDoAnimVarPtr; + VariableReference _curGobOrderVarPtr; + VariableReference _curGobNoTickVarPtr; + VariableReference _curGobTypeVarPtr; + VariableReference _curGobMaxTickVarPtr; + VariableReference _curGobTickVarPtr; + VariableReference _curGobActStartStateVarPtr; + VariableReference _curGobLookDirVarPtr; + VariableReference _curGobPickableVarPtr; + VariableReference _curGobRelaxVarPtr; + VariableReference _curGobMaxFrameVarPtr; + + VariableReference _destItemStateVarPtr; + VariableReference _destItemFrameVarPtr; + VariableReference _destItemMultStateVarPtr; + VariableReference _destItemNextStateVarPtr; + VariableReference _destItemScrXVarPtr; + VariableReference _destItemScrYVarPtr; + VariableReference _destItemLeftVarPtr; + VariableReference _destItemTopVarPtr; + VariableReference _destItemRightVarPtr; + VariableReference _destItemBottomVarPtr; + VariableReference _destItemDoAnimVarPtr; + VariableReference _destItemOrderVarPtr; + VariableReference _destItemNoTickVarPtr; + VariableReference _destItemTypeVarPtr; + VariableReference _destItemMaxTickVarPtr; + VariableReference _destItemTickVarPtr; + VariableReference _destItemActStartStVarPtr; + VariableReference _destItemLookDirVarPtr; + VariableReference _destItemPickableVarPtr; + VariableReference _destItemRelaxVarPtr; + VariableReference _destItemMaxFrameVarPtr; int16 _destItemType; int16 _destItemState; diff --git a/engines/gob/goblin_v2.cpp b/engines/gob/goblin_v2.cpp index 9144e35070..d763aeb01c 100644 --- a/engines/gob/goblin_v2.cpp +++ b/engines/gob/goblin_v2.cpp @@ -88,7 +88,7 @@ void Goblin_v2::placeObject(Gob_Object *objDesc, char animated, (_vm->_scenery->_animBottom - _vm->_scenery->_animTop) - (y + 1) / 2; *obj->pPosX = x * _vm->_map->_tilesWidth; } else { - if (obj->goblinStates[state] != 0) { + if ((obj->goblinStates != 0) && (obj->goblinStates[state] != 0)) { layer = obj->goblinStates[state][0].layer; animation = obj->goblinStates[state][0].animation; objAnim->state = state; diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp index 9c39653a1d..02e7f99cbd 100644 --- a/engines/gob/inter.cpp +++ b/engines/gob/inter.cpp @@ -212,25 +212,35 @@ void Inter::funcBlock(int16 retFlag) { break; // WORKAROUND: - // The EGA version of gob1 doesn't add a delay after showing + // The EGA and Mac versions of gob1 doesn't add a delay after showing // images between levels. We manually add it here. - if ((_vm->getGameType() == kGameTypeGob1) && _vm->isEGA()) { + if ((_vm->getGameType() == kGameTypeGob1) && + (_vm->isEGA() || (_vm->getPlatform() == Common::kPlatformMacintosh))) { + int addr = _vm->_global->_inter_execPtr-_vm->_game->_totFileData; - if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie + + if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie, EGA + !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) || + (startaddr == 0x188D && addr == 0x1A58 && // Zombie, Mac !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) || (startaddr == 0x1299 && addr == 0x139A && // Dungeon !strncmp(_vm->_game->_curTotFile, "avt006.tot", 10)) || - (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron + (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron, EGA + !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) || + (startaddr == 0x11C8 && addr == 0x1341 && // Cauldron, Mac !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) || (startaddr == 0x09F2 && addr == 0x0AF3 && // Statue !strncmp(_vm->_game->_curTotFile, "avt016.tot", 10)) || (startaddr == 0x0B92 && addr == 0x0C93 && // Castle !strncmp(_vm->_game->_curTotFile, "avt019.tot", 10)) || - (startaddr == 0x17D9 && addr == 0x18DA && // Finale + (startaddr == 0x17D9 && addr == 0x18DA && // Finale, EGA + !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10)) || + (startaddr == 0x17E9 && addr == 0x19A8 && // Finale, Mac !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10))) { _vm->_util->longDelay(5000); } + } // End of workaround cmd = *_vm->_global->_inter_execPtr; @@ -286,9 +296,7 @@ void Inter::callSub(int16 retFlag) { } void Inter::allocateVars(uint32 count) { - if ((_vm->getPlatform() == Common::kPlatformAmiga) || - (_vm->getPlatform() == Common::kPlatformMacintosh) || - (_vm->getPlatform() == Common::kPlatformAtariST)) + if (_vm->getEndianness() == kEndiannessBE) _variables = new VariablesBE(count * 4); else _variables = new VariablesLE(count * 4); diff --git a/engines/gob/inter.h b/engines/gob/inter.h index 60b3974d6d..b684be6c07 100644 --- a/engines/gob/inter.h +++ b/engines/gob/inter.h @@ -79,7 +79,7 @@ protected: }; struct OpGobParams { int16 extraData; - int32 *retVarPtr; + VariableReference retVarPtr; Goblin::Gob_Object *objDesc; }; diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp index e2b8d65112..865d188a2e 100644 --- a/engines/gob/inter_v1.cpp +++ b/engines/gob/inter_v1.cpp @@ -912,12 +912,21 @@ void Inter_v1::o1_initMult() { animDataVar = _vm->_parse->parseVarIndex(); if (_vm->_mult->_objects && (oldObjCount != _vm->_mult->_objCount)) { + warning("Initializing new objects without having " "cleaned up the old ones at first"); + + for (int i = 0; i < _vm->_mult->_objCount; i++) { + delete _vm->_mult->_objects[i].pPosX; + delete _vm->_mult->_objects[i].pPosY; + } + delete[] _vm->_mult->_objects; delete[] _vm->_mult->_renderData; + _vm->_mult->_objects = 0; _vm->_mult->_renderObjs = 0; + } if (_vm->_mult->_objects == 0) { @@ -933,8 +942,8 @@ void Inter_v1::o1_initMult() { uint32 offPosY = i * 4 + (posYVar / 4) * 4; uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize; - _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX); - _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY); + _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX); + _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY); _vm->_mult->_objects[i].pAnimData = (Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim, @@ -1774,7 +1783,7 @@ bool Inter_v1::o1_goblinFunc(OpFuncParams ¶ms) { gobParams.extraData = 0; gobParams.objDesc = 0; - gobParams.retVarPtr = (int32 *) VAR_ADDRESS(59); + gobParams.retVarPtr.set(*_vm->_inter->_variables, 236); cmd = load16(); _vm->_global->_inter_execPtr += 2; @@ -2268,49 +2277,49 @@ bool Inter_v1::o1_manageDataFile(OpFuncParams ¶ms) { void Inter_v1::o1_setState(OpGobParams ¶ms) { params.objDesc->state = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemStateVarPtr = params.extraData; + _vm->_goblin->_destItemStateVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setCurFrame(OpGobParams ¶ms) { params.objDesc->curFrame = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemFrameVarPtr = params.extraData; + _vm->_goblin->_destItemFrameVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setNextState(OpGobParams ¶ms) { params.objDesc->nextState = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemNextStateVarPtr = params.extraData; + _vm->_goblin->_destItemNextStateVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setMultState(OpGobParams ¶ms) { params.objDesc->multState = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemMultStateVarPtr = params.extraData; + _vm->_goblin->_destItemMultStateVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setOrder(OpGobParams ¶ms) { params.objDesc->order = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemOrderVarPtr = params.extraData; + _vm->_goblin->_destItemOrderVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setActionStartState(OpGobParams ¶ms) { params.objDesc->actionStartState = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemActStartStVarPtr = params.extraData; + _vm->_goblin->_destItemActStartStVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setCurLookDir(OpGobParams ¶ms) { params.objDesc->curLookDir = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemLookDirVarPtr = params.extraData; + _vm->_goblin->_destItemLookDirVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setType(OpGobParams ¶ms) { params.objDesc->type = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemTypeVarPtr = params.extraData; + _vm->_goblin->_destItemTypeVarPtr = (uint32) params.extraData; if (params.extraData == 0) params.objDesc->toRedraw = 1; @@ -2319,107 +2328,107 @@ void Inter_v1::o1_setType(OpGobParams ¶ms) { void Inter_v1::o1_setNoTick(OpGobParams ¶ms) { params.objDesc->noTick = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemNoTickVarPtr = params.extraData; + _vm->_goblin->_destItemNoTickVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setPickable(OpGobParams ¶ms) { params.objDesc->pickable = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemPickableVarPtr = params.extraData; + _vm->_goblin->_destItemPickableVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setXPos(OpGobParams ¶ms) { params.objDesc->xPos = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemScrXVarPtr = params.extraData; + _vm->_goblin->_destItemScrXVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setYPos(OpGobParams ¶ms) { params.objDesc->yPos = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemScrYVarPtr = params.extraData; + _vm->_goblin->_destItemScrYVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setDoAnim(OpGobParams ¶ms) { params.objDesc->doAnim = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemDoAnimVarPtr = params.extraData; + _vm->_goblin->_destItemDoAnimVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setRelaxTime(OpGobParams ¶ms) { params.objDesc->relaxTime = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemRelaxVarPtr = params.extraData; + _vm->_goblin->_destItemRelaxVarPtr = (uint32) params.extraData; } void Inter_v1::o1_setMaxTick(OpGobParams ¶ms) { params.objDesc->maxTick = params.extraData; if (params.objDesc == _vm->_goblin->_actDestItemDesc) - *_vm->_goblin->_destItemMaxTickVarPtr = params.extraData; + _vm->_goblin->_destItemMaxTickVarPtr = (uint32) params.extraData; } void Inter_v1::o1_getState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->state; + params.retVarPtr = (uint32) params.objDesc->state; } void Inter_v1::o1_getCurFrame(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->curFrame; + params.retVarPtr = (uint32) params.objDesc->curFrame; } void Inter_v1::o1_getNextState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->nextState; + params.retVarPtr = (uint32) params.objDesc->nextState; } void Inter_v1::o1_getMultState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->multState; + params.retVarPtr = (uint32) params.objDesc->multState; } void Inter_v1::o1_getOrder(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->order; + params.retVarPtr = (uint32) params.objDesc->order; } void Inter_v1::o1_getActionStartState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->actionStartState; + params.retVarPtr = (uint32) params.objDesc->actionStartState; } void Inter_v1::o1_getCurLookDir(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->curLookDir; + params.retVarPtr = (uint32) params.objDesc->curLookDir; } void Inter_v1::o1_getType(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->type; + params.retVarPtr = (uint32) params.objDesc->type; } void Inter_v1::o1_getNoTick(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->noTick; + params.retVarPtr = (uint32) params.objDesc->noTick; } void Inter_v1::o1_getPickable(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->pickable; + params.retVarPtr = (uint32) params.objDesc->pickable; } void Inter_v1::o1_getObjMaxFrame(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->getObjMaxFrame(params.objDesc); + params.retVarPtr = (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc); } void Inter_v1::o1_getXPos(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->xPos; + params.retVarPtr = (uint32) params.objDesc->xPos; } void Inter_v1::o1_getYPos(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->yPos; + params.retVarPtr = (uint32) params.objDesc->yPos; } void Inter_v1::o1_getDoAnim(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->doAnim; + params.retVarPtr = (uint32) params.objDesc->doAnim; } void Inter_v1::o1_getRelaxTime(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->relaxTime; + params.retVarPtr = (uint32) params.objDesc->relaxTime; } void Inter_v1::o1_getMaxTick(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->maxTick; + params.retVarPtr = (uint32) params.objDesc->maxTick; } void Inter_v1::o1_manipulateMap(OpGobParams ¶ms) { @@ -2435,9 +2444,9 @@ void Inter_v1::o1_getItem(OpGobParams ¶ms) { int16 yPos = load16(); if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0) - *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8; + params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8); else - *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos]; + params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos]; } void Inter_v1::o1_manipulateMapIndirect(OpGobParams ¶ms) { @@ -2460,9 +2469,9 @@ void Inter_v1::o1_getItemIndirect(OpGobParams ¶ms) { yPos = VAR(yPos); if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0) - *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8; + params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8); else - *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos]; + params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos]; } void Inter_v1::o1_setPassMap(OpGobParams ¶ms) { @@ -2500,11 +2509,11 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams ¶ms) { params.objDesc->curFrame = 0; params.objDesc->state = 21; if (_vm->_goblin->_currentGoblin == item) { - *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; + _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; - *_vm->_goblin->_curGobFrameVarPtr = 0; - *_vm->_goblin->_curGobStateVarPtr = 18; + _vm->_goblin->_curGobFrameVarPtr = 0; + _vm->_goblin->_curGobStateVarPtr = 18; _vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x; _vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y; } @@ -2512,12 +2521,12 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams ¶ms) { void Inter_v1::o1_getGoblinPosXH(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].x >> 1; + params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].x >> 1); } void Inter_v1::o1_getGoblinPosYH(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].y >> 1; + params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].y >> 1); } void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) { @@ -2539,14 +2548,14 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) { params.objDesc->xPos = animLayer->posX; params.objDesc->yPos = animLayer->posY; - *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; - *_vm->_goblin->_curGobFrameVarPtr = 0; - *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state; - *_vm->_goblin->_curGobNextStateVarPtr = params.objDesc->nextState; - *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState; - *_vm->_goblin->_curGobMaxFrameVarPtr = - _vm->_goblin->getObjMaxFrame(params.objDesc); + _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; + _vm->_goblin->_curGobFrameVarPtr = 0; + _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state; + _vm->_goblin->_curGobNextStateVarPtr = (uint32) params.objDesc->nextState; + _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState; + _vm->_goblin->_curGobMaxFrameVarPtr = + (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc); _vm->_goblin->_noPick = 1; return; } @@ -2573,12 +2582,12 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) { _vm->_goblin->_pressedMapY = yPos; _vm->_map->_curGoblinY = yPos; - *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; - *_vm->_goblin->_curGobFrameVarPtr = 0; - *_vm->_goblin->_curGobStateVarPtr = 21; - *_vm->_goblin->_curGobNextStateVarPtr = 21; - *_vm->_goblin->_curGobMultStateVarPtr = -1; + _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; + _vm->_goblin->_curGobFrameVarPtr = 0; + _vm->_goblin->_curGobStateVarPtr = 21; + _vm->_goblin->_curGobNextStateVarPtr = 21; + _vm->_goblin->_curGobMultStateVarPtr = (uint32) -1; _vm->_goblin->_noPick = 0; } @@ -2598,11 +2607,11 @@ void Inter_v1::o1_setItemIndInPocket(OpGobParams ¶ms) { } void Inter_v1::o1_getItemIdInPocket(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->_itemIdInPocket; + params.retVarPtr = (uint32) _vm->_goblin->_itemIdInPocket; } void Inter_v1::o1_getItemIndInPocket(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->_itemIndInPocket; + params.retVarPtr = (uint32) _vm->_goblin->_itemIndInPocket; } void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) { @@ -2632,10 +2641,10 @@ void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) { params.objDesc->state = 21; if (_vm->_goblin->_currentGoblin == item) { - *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; - *_vm->_goblin->_curGobFrameVarPtr = 0; - *_vm->_goblin->_curGobStateVarPtr = 18; + _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; + _vm->_goblin->_curGobFrameVarPtr = 0; + _vm->_goblin->_curGobStateVarPtr = 18; _vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x; _vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y; @@ -2659,11 +2668,11 @@ void Inter_v1::o1_setGoblinState(OpGobParams ¶ms) { params.objDesc->yPos = animLayer->posY; if (item == _vm->_goblin->_currentGoblin) { - *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos; - *_vm->_goblin->_curGobFrameVarPtr = 0; - *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state; - *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState; + _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos; + _vm->_goblin->_curGobFrameVarPtr = 0; + _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state; + _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState; } } @@ -2686,13 +2695,13 @@ void Inter_v1::o1_setGoblinStateRedraw(OpGobParams ¶ms) { params.objDesc->toRedraw = 1; params.objDesc->type = 0; if (params.objDesc == _vm->_goblin->_actDestItemDesc) { - *_vm->_goblin->_destItemScrXVarPtr = params.objDesc->xPos; - *_vm->_goblin->_destItemScrYVarPtr = params.objDesc->yPos; + _vm->_goblin->_destItemScrXVarPtr = (uint32) params.objDesc->xPos; + _vm->_goblin->_destItemScrYVarPtr = (uint32) params.objDesc->yPos; - *_vm->_goblin->_destItemStateVarPtr = params.objDesc->state; - *_vm->_goblin->_destItemNextStateVarPtr = -1; - *_vm->_goblin->_destItemMultStateVarPtr = -1; - *_vm->_goblin->_destItemFrameVarPtr = 0; + _vm->_goblin->_destItemStateVarPtr = (uint32) params.objDesc->state; + _vm->_goblin->_destItemNextStateVarPtr = (uint32) -1; + _vm->_goblin->_destItemMultStateVarPtr = (uint32) -1; + _vm->_goblin->_destItemFrameVarPtr = 0; } } @@ -2712,12 +2721,12 @@ void Inter_v1::o1_decRelaxTime(OpGobParams ¶ms) { void Inter_v1::o1_getGoblinPosX(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].x; + params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].x; } void Inter_v1::o1_getGoblinPosY(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].y; + params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].y; } void Inter_v1::o1_clearPathExistence(OpGobParams ¶ms) { @@ -2741,9 +2750,9 @@ void Inter_v1::o1_getObjectIntersect(OpGobParams ¶ms) { params.objDesc = _vm->_goblin->_objects[params.extraData]; if (_vm->_goblin->objIntersected(params.objDesc, _vm->_goblin->_goblins[item])) - *params.retVarPtr = 1; + params.retVarPtr = 1; else - *params.retVarPtr = 0; + params.retVarPtr = 0; } void Inter_v1::o1_getGoblinIntersect(OpGobParams ¶ms) { @@ -2753,9 +2762,9 @@ void Inter_v1::o1_getGoblinIntersect(OpGobParams ¶ms) { params.objDesc = _vm->_goblin->_goblins[params.extraData]; if (_vm->_goblin->objIntersected(params.objDesc, _vm->_goblin->_goblins[item])) - *params.retVarPtr = 1; + params.retVarPtr = 1; else - *params.retVarPtr = 0; + params.retVarPtr = 0; } void Inter_v1::o1_setItemPos(OpGobParams ¶ms) { @@ -2886,7 +2895,7 @@ void Inter_v1::o1_initGoblin(OpGobParams ¶ms) { _vm->_map->_destY = _vm->_goblin->_gobPositions[0].y; _vm->_goblin->_gobDestY = _vm->_goblin->_gobPositions[0].y; - *_vm->_goblin->_curGobVarPtr = 0; + _vm->_goblin->_curGobVarPtr = 0; _vm->_goblin->_pathExistence = 0; _vm->_goblin->_readyToAct = 0; } diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp index d8c33fcce6..2f1d2ec0be 100644 --- a/engines/gob/inter_v2.cpp +++ b/engines/gob/inter_v2.cpp @@ -880,9 +880,15 @@ void Inter_v2::o2_initMult() { _vm->_mult->clearObjectVideos(); + for (int i = 0; i < _vm->_mult->_objCount; i++) { + delete _vm->_mult->_objects[i].pPosX; + delete _vm->_mult->_objects[i].pPosY; + } + delete[] _vm->_mult->_objects; delete[] _vm->_mult->_renderObjs; delete[] _vm->_mult->_orderArray; + _vm->_mult->_objects = 0; _vm->_mult->_renderObjs = 0; _vm->_mult->_orderArray = 0; @@ -907,8 +913,8 @@ void Inter_v2::o2_initMult() { uint32 offPosY = i * 4 + (posYVar / 4) * 4; uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize; - _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX); - _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY); + _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX); + _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY); _vm->_mult->_objects[i].pAnimData = (Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim, @@ -1046,7 +1052,7 @@ void Inter_v2::o2_loadMultObject() { } else if ((objAnim.animType != 100) && (objAnim.animType != 101)) { - if ((*(obj.pPosX) == -1234) && (*(obj.pPosY) == -4321)) { + if ((((int32) *(obj.pPosX)) == -1234) && (((int32) *(obj.pPosY)) == -4321)) { if (obj.videoSlot > 0) _vm->_vidPlayer->slotClose(obj.videoSlot - 1); diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp index 3d6a7942f9..b9373d48b3 100644 --- a/engines/gob/mult.cpp +++ b/engines/gob/mult.cpp @@ -93,12 +93,18 @@ Mult::Mult(GobEngine *vm) : _vm(vm) { } Mult::~Mult() { + if (_objects) + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + delete[] _objects; delete[] _orderArray; delete[] _renderData; delete[] _renderObjs; - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; delete _multData; } @@ -123,6 +129,12 @@ void Mult::freeAll(void) { void Mult::freeMult() { clearObjectVideos(); + if (_objects) + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + delete[] _objects; delete[] _renderData; delete[] _renderObjs; @@ -203,11 +215,17 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape, if (_animDataAllocated) { clearObjectVideos(); + if (_objects) + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + delete[] _objects; delete[] _renderData; delete[] _renderObjs; - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; delete[] _orderArray; diff --git a/engines/gob/mult.h b/engines/gob/mult.h index aaf2e2826c..3bb3af17b3 100644 --- a/engines/gob/mult.h +++ b/engines/gob/mult.h @@ -27,6 +27,7 @@ #define GOB_MULT_H #include "gob/video.h" +#include "gob/variables.h" namespace Gob { @@ -77,8 +78,8 @@ public: } PACKED_STRUCT; struct Mult_Object { - int32 *pPosX; - int32 *pPosY; + VariableReference *pPosX; + VariableReference *pPosY; Mult_AnimData *pAnimData; int16 tick; int16 lastLeft; @@ -267,8 +268,8 @@ protected: bool _doPalSubst; - int32 *_animArrayX; - int32 *_animArrayY; + Variables *_animArrayX; + Variables *_animArrayY; Mult_AnimData *_animArrayData; int16 _palKeyIndex; diff --git a/engines/gob/mult_v1.cpp b/engines/gob/mult_v1.cpp index 22683437e7..a369e7d297 100644 --- a/engines/gob/mult_v1.cpp +++ b/engines/gob/mult_v1.cpp @@ -216,10 +216,16 @@ void Mult_v1::freeMultKeys() { if (_animDataAllocated) { clearObjectVideos(); + if (_objects) + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + delete[] _objects; delete[] _renderData; - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; _objects = 0; @@ -263,6 +269,14 @@ void Mult_v1::playMultInit() { _oldPalette = _vm->_global->_pPaletteDesc->vgaPal; if (!_animSurf) { + if (_objects) + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + + delete[] _objects; + _vm->_util->setFrameRate(_multData->frameRate); _animTop = 0; _animLeft = 0; @@ -270,30 +284,27 @@ void Mult_v1::playMultInit() { _animHeight = 200; _objCount = 4; - delete[] _objects; delete[] _renderData; - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; _objects = new Mult_Object[_objCount]; _renderData = new int16[9 * _objCount]; - _animArrayX = new int32[_objCount]; - _animArrayY = new int32[_objCount]; + _animArrayX = new VariablesLE(_objCount * 4); + _animArrayY = new VariablesLE(_objCount * 4); _animArrayData = new Mult_AnimData[_objCount]; memset(_objects, 0, _objCount * sizeof(Mult_Object)); memset(_renderData, 0, _objCount * 9 * sizeof(int16)); - memset(_animArrayX, 0, _objCount * sizeof(int32)); - memset(_animArrayY, 0, _objCount * sizeof(int32)); memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData)); for (_counter = 0; _counter < _objCount; _counter++) { Mult_Object &multObj = _objects[_counter]; Mult_AnimData &animData = _animArrayData[_counter]; - multObj.pPosX = (int32 *) &_animArrayX[_counter]; - multObj.pPosY = (int32 *) &_animArrayY[_counter]; + multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4); + multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4); multObj.pAnimData = &animData; animData.isStatic = 1; diff --git a/engines/gob/mult_v2.cpp b/engines/gob/mult_v2.cpp index 3a83ac1867..20a81174e5 100644 --- a/engines/gob/mult_v2.cpp +++ b/engines/gob/mult_v2.cpp @@ -329,8 +329,8 @@ void Mult_v2::freeMultKeys() { if (_animDataAllocated) { freeMult(); - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; _animArrayX = 0; @@ -510,6 +510,13 @@ void Mult_v2::playMultInit() { if (!_animSurf) { int16 width, height; + for (int i = 0; i < _objCount; i++) { + delete _objects[i].pPosX; + delete _objects[i].pPosY; + } + + delete[] _objects; + _vm->_util->setFrameRate(_multData->frameRate); _animTop = 0; _animLeft = 0; @@ -517,33 +524,30 @@ void Mult_v2::playMultInit() { _animHeight = _vm->_video->_surfHeight; _objCount = 4; - delete[] _objects; delete[] _orderArray; delete[] _renderObjs; - delete[] _animArrayX; - delete[] _animArrayY; + delete _animArrayX; + delete _animArrayY; delete[] _animArrayData; _objects = new Mult_Object[_objCount]; _orderArray = new int8[_objCount]; _renderObjs = new Mult_Object*[_objCount]; - _animArrayX = new int32[_objCount]; - _animArrayY = new int32[_objCount]; + _animArrayX = new VariablesLE(_objCount * 4); + _animArrayY = new VariablesLE(_objCount * 4); _animArrayData = new Mult_AnimData[_objCount]; memset(_objects, 0, _objCount * sizeof(Mult_Object)); memset(_orderArray, 0, _objCount * sizeof(int8)); memset(_renderObjs, 0, _objCount * sizeof(Mult_Object *)); - memset(_animArrayX, 0, _objCount * sizeof(int32)); - memset(_animArrayY, 0, _objCount * sizeof(int32)); memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData)); for (_counter = 0; _counter < _objCount; _counter++) { Mult_Object &multObj = _objects[_counter]; Mult_AnimData &animData = _animArrayData[_counter]; - multObj.pPosX = (int32 *) &_animArrayX[_counter]; - multObj.pPosY = (int32 *) &_animArrayY[_counter]; + multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4); + multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4); multObj.pAnimData = &animData; animData.isStatic = 1; diff --git a/engines/gob/saveload.cpp b/engines/gob/saveload.cpp index 2788716858..fa9f8ea7a9 100644 --- a/engines/gob/saveload.cpp +++ b/engines/gob/saveload.cpp @@ -153,7 +153,7 @@ bool TempSprite::fromBuffer(const byte *buffer, int32 size, bool palette) { } -PlainSave::PlainSave() { +PlainSave::PlainSave(Endianness endianness) : _endianness(endianness) { } PlainSave::~PlainSave() { @@ -230,7 +230,8 @@ bool PlainSave::save(int16 dataVar, int32 size, int32 offset, const char *name, } bool retVal; - retVal = SaveLoad::saveDataEndian(*out, dataVar, size, variables, variableSizes); + retVal = SaveLoad::saveDataEndian(*out, dataVar, size, + variables, variableSizes, _endianness); out->finalize(); if (out->ioFailed()) { @@ -258,13 +259,14 @@ bool PlainSave::load(int16 dataVar, int32 size, int32 offset, const char *name, return false; } - bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, variables, variableSizes); + bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, + variables, variableSizes, _endianness); delete in; return retVal; } -StagedSave::StagedSave() { +StagedSave::StagedSave(Endianness endianness) : _endianness(endianness) { _mode = kModeNone; _name = 0; _loaded = false; @@ -487,7 +489,7 @@ bool StagedSave::write() const { } else result = SaveLoad::saveDataEndian(*out, 0, _stages[i].size, - _stages[i].bufVar, _stages[i].bufVarSizes); + _stages[i].bufVar, _stages[i].bufVarSizes, _endianness); } if (result) { @@ -533,7 +535,7 @@ bool StagedSave::read() { _stages[i].bufVarSizes = new byte[_stages[i].size]; result = SaveLoad::loadDataEndian(*in, 0, _stages[i].size, - _stages[i].bufVar, _stages[i].bufVarSizes); + _stages[i].bufVar, _stages[i].bufVarSizes, _endianness); } } @@ -734,12 +736,14 @@ void SaveLoad::buildIndex(byte *buffer, char *name, int n, int32 size, int32 off } } -bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) { +bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) { + bool LE = (endianness == kEndiannessLE); + while (count-- > 0) { if (*sizes == 3) - *((uint32 *) buf) = READ_LE_UINT32(buf); + *((uint32 *) buf) = LE ? READ_LE_UINT32(buf) : READ_BE_UINT32(buf); else if (*sizes == 1) - *((uint16 *) buf) = READ_LE_UINT16(buf); + *((uint16 *) buf) = LE ? READ_LE_UINT16(buf) : READ_BE_UINT16(buf); else if (*sizes != 0) { warning("SaveLoad::fromEndian(): Corrupted variables sizes"); return false; @@ -753,12 +757,19 @@ bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) { return true; } -bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count) { +bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) { while (count-- > 0) { - if (*sizes == 3) - WRITE_LE_UINT32(buf, *((uint32 *) buf)); - else if (*sizes == 1) - WRITE_LE_UINT16(buf, *((uint16 *) buf)); + if (*sizes == 3) { + if (endianness == kEndiannessLE) + WRITE_LE_UINT32(buf, *((uint32 *) buf)); + else + WRITE_BE_UINT32(buf, *((uint32 *) buf)); + } else if (*sizes == 1) { + if (endianness == kEndiannessLE) + WRITE_LE_UINT16(buf, *((uint16 *) buf)); + else + WRITE_BE_UINT16(buf, *((uint16 *) buf)); + } else if (*sizes != 0) { warning("SaveLoad::toEndian(): Corrupted variables sizes"); return false; @@ -811,7 +822,8 @@ uint32 SaveLoad::write(Common::WriteStream &out, } bool SaveLoad::loadDataEndian(Common::ReadStream &in, - int16 dataVar, uint32 size, byte *variables, byte *variableSizes) { + int16 dataVar, uint32 size, + byte *variables, byte *variableSizes, Endianness endianness) { bool retVal = false; @@ -821,7 +833,7 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in, assert(varBuf && sizeBuf); if (read(in, varBuf, sizeBuf, size) == size) { - if (fromEndian(varBuf, sizeBuf, size)) { + if (fromEndian(varBuf, sizeBuf, size, endianness)) { memcpy(variables + dataVar, varBuf, size); memcpy(variableSizes + dataVar, sizeBuf, size); retVal = true; @@ -835,7 +847,8 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in, } bool SaveLoad::saveDataEndian(Common::WriteStream &out, - int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes) { + int16 dataVar, uint32 size, + const byte *variables, const byte *variableSizes, Endianness endianness) { bool retVal = false; @@ -847,7 +860,7 @@ bool SaveLoad::saveDataEndian(Common::WriteStream &out, memcpy(varBuf, variables + dataVar, size); memcpy(sizeBuf, variableSizes + dataVar, size); - if (toEndian(varBuf, sizeBuf, size)) + if (toEndian(varBuf, sizeBuf, size, endianness)) if (write(out, varBuf, sizeBuf, size) == size) retVal = true; diff --git a/engines/gob/saveload.h b/engines/gob/saveload.h index 29f7ee2594..52c3a9b260 100644 --- a/engines/gob/saveload.h +++ b/engines/gob/saveload.h @@ -65,7 +65,7 @@ private: class PlainSave { public: - PlainSave(); + PlainSave(Endianness endianness); ~PlainSave(); bool save(int16 dataVar, int32 size, int32 offset, const char *name, @@ -77,11 +77,14 @@ public: const byte *variables, const byte *variableSizes) const; bool load(int16 dataVar, int32 size, int32 offset, const char *name, byte *variables, byte *variableSizes) const; + +private: + Endianness _endianness; }; class StagedSave { public: - StagedSave(); + StagedSave(Endianness endianness); ~StagedSave(); void addStage(int32 size, bool endianed = true); @@ -114,6 +117,8 @@ private: kModeLoad }; + Endianness _endianness; + Common::Array<Stage> _stages; enum Mode _mode; char *_name; @@ -178,17 +183,19 @@ public: static const char *stripPath(const char *fileName); - static bool fromEndian(byte *buf, const byte *sizes, uint32 count); - static bool toEndian(byte *buf, const byte *sizes, uint32 count); + static bool fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness); + static bool toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness); static uint32 read(Common::ReadStream &in, byte *buf, byte *sizes, uint32 count); static uint32 write(Common::WriteStream &out, const byte *buf, const byte *sizes, uint32 count); static bool loadDataEndian(Common::ReadStream &in, - int16 dataVar, uint32 size, byte *variables, byte *variableSizes); + int16 dataVar, uint32 size, + byte *variables, byte *variableSizes, Endianness endianness); static bool saveDataEndian(Common::WriteStream &out, - int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes); + int16 dataVar, uint32 size, + const byte *variables, const byte *variableSizes, Endianness endianness); protected: GobEngine *_vm; @@ -228,8 +235,8 @@ protected: int32 _varSize; TempSprite _tmpSprite; - PlainSave _notes; - StagedSave _save; + PlainSave *_notes; + StagedSave *_save; byte _indexBuffer[600]; bool _hasIndex; @@ -306,8 +313,8 @@ protected: TempSprite _screenshot; TempSprite _tmpSprite; - PlainSave _notes; - StagedSave _save; + PlainSave *_notes; + StagedSave *_save; byte _propBuffer[1000]; byte _indexBuffer[1200]; @@ -370,7 +377,7 @@ protected: int32 _varSize; - StagedSave _save; + StagedSave *_save; byte _propBuffer[1000]; byte _indexBuffer[1200]; diff --git a/engines/gob/saveload_v2.cpp b/engines/gob/saveload_v2.cpp index a92fe8cf01..fc11950368 100644 --- a/engines/gob/saveload_v2.cpp +++ b/engines/gob/saveload_v2.cpp @@ -45,6 +45,9 @@ SaveLoad_v2::SaveFile SaveLoad_v2::_saveFiles[] = { SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) : SaveLoad(vm, targetName) { + _notes = new PlainSave(_vm->getEndianness()); + _save = new StagedSave(_vm->getEndianness()); + _saveFiles[0].destName = new char[strlen(targetName) + 5]; _saveFiles[1].destName = _saveFiles[0].destName; _saveFiles[2].destName = 0; @@ -58,6 +61,9 @@ SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) : } SaveLoad_v2::~SaveLoad_v2() { + delete _notes; + delete _save; + delete[] _saveFiles[0].destName; delete[] _saveFiles[3].destName; } @@ -227,7 +233,7 @@ bool SaveLoad_v2::loadGame(SaveFile &saveFile, return false; } - if (!_save.load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) + if (!_save->load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -268,7 +274,7 @@ bool SaveLoad_v2::loadNotes(SaveFile &saveFile, debugC(2, kDebugSaveLoad, "Loading the notes"); - return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); + return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); } bool SaveLoad_v2::saveGame(SaveFile &saveFile, @@ -313,10 +319,10 @@ bool SaveLoad_v2::saveGame(SaveFile &saveFile, byte sizes[40]; memset(sizes, 0, 40); - if(!_save.save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes)) + if(!_save->save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes)) return false; - if (!_save.save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) + if (!_save->save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -350,7 +356,7 @@ bool SaveLoad_v2::saveNotes(SaveFile &saveFile, debugC(2, kDebugSaveLoad, "Saving the notes"); - return _notes.save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); + return _notes->save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); return false; } @@ -360,8 +366,8 @@ void SaveLoad_v2::assertInited() { _varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; - _save.addStage(40); - _save.addStage(_varSize); + _save->addStage(40); + _save->addStage(_varSize); } } // End of namespace Gob diff --git a/engines/gob/saveload_v3.cpp b/engines/gob/saveload_v3.cpp index 67879db3d1..dab5fd9385 100644 --- a/engines/gob/saveload_v3.cpp +++ b/engines/gob/saveload_v3.cpp @@ -48,6 +48,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName, uint32 screenshotSize, int32 indexOffset, int32 screenshotOffset) : SaveLoad(vm, targetName) { + _notes = new PlainSave(_vm->getEndianness()); + _save = new StagedSave(_vm->getEndianness()); + _screenshotSize = screenshotSize; _indexOffset = indexOffset; _screenshotOffset = screenshotOffset; @@ -71,6 +74,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName, } SaveLoad_v3::~SaveLoad_v3() { + delete _notes; + delete _save; + delete[] _saveFiles[0].destName; delete[] _saveFiles[3].destName; } @@ -243,7 +249,7 @@ int32 SaveLoad_v3::getSizeNotes(SaveFile &saveFile) { int32 SaveLoad_v3::getSizeScreenshot(SaveFile &saveFile) { if (!_useScreenshots) { _useScreenshots = true; - _save.addStage(_screenshotSize, false); + _save->addStage(_screenshotSize, false); } Common::SaveFileManager *saveMan = g_system->getSavefileManager(); @@ -312,7 +318,7 @@ bool SaveLoad_v3::loadGame(SaveFile &saveFile, return false; } - if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) + if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -353,7 +359,7 @@ bool SaveLoad_v3::loadNotes(SaveFile &saveFile, debugC(2, kDebugSaveLoad, "Loading the notes"); - return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); + return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables); } bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile, @@ -363,7 +369,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile, if (!_useScreenshots) { _useScreenshots = true; - _save.addStage(_screenshotSize, false); + _save->addStage(_screenshotSize, false); } if (offset == _indexOffset) { @@ -395,7 +401,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile, byte *buffer = new byte[_screenshotSize]; - if (!_save.load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { + if (!_save->load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { delete[] buffer; return false; } @@ -483,13 +489,13 @@ bool SaveLoad_v3::saveGame(SaveFile &saveFile, _hasIndex = false; - if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) + if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) return false; - if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0)) + if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0)) return false; - if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) + if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -523,7 +529,7 @@ bool SaveLoad_v3::saveNotes(SaveFile &saveFile, debugC(2, kDebugSaveLoad, "Saving the notes"); - return _notes.save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables); + return _notes->save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables); return false; } @@ -534,7 +540,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile, if (!_useScreenshots) { _useScreenshots = true; - _save.addStage(_screenshotSize, false); + _save->addStage(_screenshotSize, false); } if (offset >= _screenshotOffset) { @@ -571,7 +577,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile, return false; } - if (!_save.save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { + if (!_save->save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) { delete[] buffer; return false; } @@ -588,9 +594,9 @@ void SaveLoad_v3::assertInited() { _varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; - _save.addStage(500); - _save.addStage(40, false); - _save.addStage(_varSize); + _save->addStage(500); + _save->addStage(40, false); + _save->addStage(_varSize); } void SaveLoad_v3::buildScreenshotIndex(byte *buffer, char *name, int n) { diff --git a/engines/gob/saveload_v4.cpp b/engines/gob/saveload_v4.cpp index a6548dd82d..0bd3dc03e6 100644 --- a/engines/gob/saveload_v4.cpp +++ b/engines/gob/saveload_v4.cpp @@ -50,6 +50,8 @@ SaveLoad_v4::SaveFile SaveLoad_v4::_saveFiles[] = { SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) : SaveLoad(vm, targetName) { + _save = new StagedSave(_vm->getEndianness()); + _firstSizeGame = true; _saveFiles[0].destName = 0; @@ -76,6 +78,8 @@ SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) : } SaveLoad_v4::~SaveLoad_v4() { + delete _save; + delete[] _screenProps; delete[] _saveFiles[1].destName; } @@ -297,7 +301,7 @@ bool SaveLoad_v4::loadGame(SaveFile &saveFile, return false; } - if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) + if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -314,7 +318,7 @@ bool SaveLoad_v4::loadGameScreenProps(SaveFile &saveFile, setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0'); - if (!_save.load(0, 256000, _varSize + 540, saveFile.destName, + if (!_save->load(0, 256000, _varSize + 540, saveFile.destName, _screenProps, _screenProps + 256000)) return false; @@ -393,13 +397,13 @@ bool SaveLoad_v4::saveGame(SaveFile &saveFile, _hasIndex = false; - if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) + if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500)) return false; - if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0)) + if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0)) return false; - if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) + if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables)) return false; } @@ -417,7 +421,7 @@ bool SaveLoad_v4::saveGameScreenProps(SaveFile &saveFile, setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0'); - if (!_save.save(0, 256000, _varSize + 540, saveFile.destName, + if (!_save->save(0, 256000, _varSize + 540, saveFile.destName, _screenProps, _screenProps + 256000)) return false; @@ -430,10 +434,10 @@ void SaveLoad_v4::assertInited() { _varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4; - _save.addStage(500); - _save.addStage(40, false); - _save.addStage(_varSize); - _save.addStage(256000); + _save->addStage(500); + _save->addStage(40, false); + _save->addStage(_varSize); + _save->addStage(256000); } } // End of namespace Gob diff --git a/engines/gob/scenery.cpp b/engines/gob/scenery.cpp index 6b52cdbbd6..33e540ace4 100644 --- a/engines/gob/scenery.cpp +++ b/engines/gob/scenery.cpp @@ -595,7 +595,7 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags, int16 destX; int16 destY; - if (animation < 0) { + if ((_vm->getGameType() == kGameTypeWoodruff) && (animation < 0)) { // Object video if (flags & 1) { // Do capture @@ -736,6 +736,8 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags, return; } + if ((animation < 0) || (animation >= 10)) + return; if ((_animPictCount[animation] == 0) || (layer < 0)) return; if (layer >= _animations[animation].layersCount) diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h index b59510e4bb..07b5a737db 100644 --- a/engines/gob/sound/sound.h +++ b/engines/gob/sound/sound.h @@ -144,4 +144,4 @@ private: } // End of namespace Gob -#endif // GOB_SOUND_H +#endif // GOB_SOUND_SOUND_H diff --git a/engines/gob/sound/soundmixer.h b/engines/gob/sound/soundmixer.h index 5789885a99..3e8e6b5c1b 100644 --- a/engines/gob/sound/soundmixer.h +++ b/engines/gob/sound/soundmixer.h @@ -37,7 +37,7 @@ namespace Gob { class SoundMixer : public Audio::AudioStream { public: - SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType); + SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type); ~SoundMixer(); virtual void play(SoundDesc &sndDesc, int16 repCount, diff --git a/engines/gob/variables.cpp b/engines/gob/variables.cpp index 0eea2f6547..805aaeb839 100644 --- a/engines/gob/variables.cpp +++ b/engines/gob/variables.cpp @@ -308,4 +308,62 @@ uint32 VariablesBE::read32(const byte *buf) const { return READ_BE_UINT32(buf); } +VariableReference::VariableReference() { + _vars = 0; + _offset = 0; +} + +VariableReference::VariableReference(Variables &vars, uint32 offset, Variables::Type type) { + set(vars, offset, type); +} + +VariableReference::~VariableReference() { +} + +void VariableReference::set(Variables &vars, uint32 offset, Variables::Type type) { + _vars = &vars; + _offset = offset; + _type = type; +} + +VariableReference &VariableReference::operator=(uint32 value) { + if (_vars) { + switch (_type) { + case Variables::kVariableType8: + _vars->writeOff8(_offset, (uint8) value); + break; + case Variables::kVariableType16: + _vars->writeOff16(_offset, (uint16) value); + break; + case Variables::kVariableType32: + _vars->writeOff32(_offset, value); + break; + } + } + return *this; +} + +VariableReference::operator uint32() { + if (_vars) { + switch (_type) { + case Variables::kVariableType8: + return (uint32) _vars->readOff8(_offset); + case Variables::kVariableType16: + return (uint32) _vars->readOff16(_offset); + case Variables::kVariableType32: + return _vars->readOff32(_offset); + } + } + + return 0; +} + +VariableReference &VariableReference::operator+=(uint32 value) { + return (*this = (*this + value)); +} + +VariableReference &VariableReference::operator*=(uint32 value) { + return (*this = (*this * value)); +} + } // End of namespace Gob diff --git a/engines/gob/variables.h b/engines/gob/variables.h index 5989ed38ee..32f160a6bd 100644 --- a/engines/gob/variables.h +++ b/engines/gob/variables.h @@ -30,6 +30,12 @@ namespace Gob { class Variables { public: + enum Type { + kVariableType8, + kVariableType16, + kVariableType32 + }; + Variables(uint32 size); virtual ~Variables(); @@ -142,6 +148,26 @@ protected: uint32 read32(const byte *buf) const; }; +class VariableReference { + public: + VariableReference(); + VariableReference(Variables &vars, uint32 offset, + Variables::Type type = Variables::kVariableType32); + ~VariableReference(); + + void set(Variables &vars, uint32 offset, Variables::Type type = Variables::kVariableType32); + + VariableReference &operator=(uint32 value); + VariableReference &operator+=(uint32 value); + VariableReference &operator*=(uint32 value); + operator uint32(); + + private: + Variables *_vars; + uint32 _offset; + Variables::Type _type; +}; + } // End of namespace Gob #endif // GOB_VARIABLES_H diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp index 909d39a63b..aa47e6cf84 100644 --- a/engines/gob/videoplayer.cpp +++ b/engines/gob/videoplayer.cpp @@ -588,12 +588,14 @@ bool VideoPlayer::doPlay(int16 frame, int16 breakKey, } void VideoPlayer::copyPalette(CoktelVideo &video, int16 palStart, int16 palEnd) { - if ((palStart != -1) && (palEnd != -1)) - memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3, - video.getPalette() + palStart * 3, - (palEnd - palStart + 1) * 3); - else - memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal, video.getPalette(), 768); + if (palStart < 0) + palStart = 0; + if (palEnd < 0) + palEnd = 255; + + memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3, + video.getPalette() + palStart * 3, + (palEnd - palStart + 1) * 3); } void VideoPlayer::writeVideoInfo(const char *videoFile, int16 varX, int16 varY, diff --git a/engines/igor/igor.cpp b/engines/igor/igor.cpp index 018709f34f..4d4fb97762 100644 --- a/engines/igor/igor.cpp +++ b/engines/igor/igor.cpp @@ -404,7 +404,7 @@ void IgorEngine::playSound(int num, int type) { debugC(9, kDebugEngine, "playSound() %d", num); --num; int soundOffset = -1; - Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType; + Audio::Mixer::SoundType soundType; Audio::SoundHandle *soundHandle = 0; if (type == 1) { if (_mixer->isSoundHandleActive(_sfxHandle)) { diff --git a/engines/kyra/animator_lok.cpp b/engines/kyra/animator_lok.cpp index 1baa78b203..75d5537e4a 100644 --- a/engines/kyra/animator_lok.cpp +++ b/engines/kyra/animator_lok.cpp @@ -665,7 +665,7 @@ void Animator_LoK::animRefreshNPC(int character) { void Animator_LoK::setCharacterDefaultFrame(int character) { debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharacterDefaultFrame()"); - static uint16 initFrameTable[] = { + static const uint16 initFrameTable[] = { 7, 41, 77, 0, 0 }; assert(character < ARRAYSIZE(initFrameTable)); @@ -678,7 +678,7 @@ void Animator_LoK::setCharacterDefaultFrame(int character) { void Animator_LoK::setCharactersHeight() { debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharactersHeight()"); - static int8 initHeightTable[] = { + static const int8 initHeightTable[] = { 48, 40, 48, 47, 56, 44, 42, 47, 38, 35, 40 diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index 344121b503..2d592069d2 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -26,6 +26,7 @@ #include "kyra/kyra_lok.h" #include "kyra/kyra_hof.h" #include "kyra/kyra_mr.h" +#include "kyra/lol.h" #include "common/config-manager.h" #include "common/advancedDetector.h" @@ -41,9 +42,11 @@ struct KYRAGameDescription { namespace { -#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id } +#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::UNK_LANG, Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id } +#define FLAGS_FAN(fanLang, repLang, x, y, z, a, b, c, id) { Common::UNK_LANG, fanLang, repLang, Common::kPlatformUnknown, x, y, z, a, b, c, id } #define KYRA1_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1) +#define KYRA1_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA1) #define KYRA1_AMIGA_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1) #define KYRA1_TOWNS_FLAGS FLAGS(false, true, false, false, false, false, Kyra::GI_KYRA1) #define KYRA1_TOWNS_SJIS_FLAGS FLAGS(false, true, false, true, false, false, Kyra::GI_KYRA1) @@ -59,13 +62,38 @@ namespace { #define KYRA2_TOWNS_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_KYRA2) #define KYRA3_CD_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3) -#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3) +#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, false, Kyra::GI_KYRA3) +#define KYRA3_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, true, false, Kyra::GI_KYRA3) + +#define LOL_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_LOL) const KYRAGameDescription adGameDescs[] = { { { "kyra1", 0, + AD_ENTRY1("DISK1.EXE", "c8641d0414d6c966d0a3dad79db07bf4"), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + KYRA1_FLOPPY_CMP_FLAGS + }, + { + { + "kyra1", + 0, + AD_ENTRY1("DISK1.EXE", "5d5cee4c3d0b68d586788b74243d254a"), + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + KYRA1_FLOPPY_CMP_FLAGS + }, + { + { + "kyra1", + "Extracted", AD_ENTRY1("GEMCUT.EMC", "3c244298395520bb62b5edfe41688879"), Common::EN_ANY, Common::kPlatformPC, @@ -76,7 +104,7 @@ const KYRAGameDescription adGameDescs[] = { { { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "796e44863dd22fa635b042df1bf16673"), Common::EN_ANY, Common::kPlatformPC, @@ -87,7 +115,7 @@ const KYRAGameDescription adGameDescs[] = { { { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "abf8eb360e79a6c2a837751fbd4d3d24"), Common::FR_FRA, Common::kPlatformPC, @@ -98,7 +126,7 @@ const KYRAGameDescription adGameDescs[] = { { { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "6018e1dfeaca7fe83f8d0b00eb0dd049"), Common::DE_DEU, Common::kPlatformPC, @@ -109,7 +137,7 @@ const KYRAGameDescription adGameDescs[] = { { // from Arne.F { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "f0b276781f47c130f423ec9679fe9ed9"), Common::DE_DEU, Common::kPlatformPC, @@ -120,7 +148,7 @@ const KYRAGameDescription adGameDescs[] = { { // from VooD { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "8909b41596913b3f5deaf3c9f1017b01"), Common::ES_ESP, Common::kPlatformPC, @@ -131,7 +159,7 @@ const KYRAGameDescription adGameDescs[] = { { // floppy 1.8 from clemmy { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "747861d2a9c643c59fdab570df5b9093"), Common::ES_ESP, Common::kPlatformPC, @@ -142,7 +170,7 @@ const KYRAGameDescription adGameDescs[] = { { // from gourry { "kyra1", - 0, + "Extracted", AD_ENTRY1("GEMCUT.EMC", "ef08c8c237ee1473fd52578303fc36df"), Common::IT_ITA, Common::kPlatformPC, @@ -323,7 +351,7 @@ const KYRAGameDescription adGameDescs[] = { KYRA2_FLOPPY_CMP_FLAGS }, - { // // Floppy version extracted + { // Floppy version extracted { "kyra2", "Extracted", @@ -463,6 +491,28 @@ const KYRAGameDescription adGameDescs[] = { }, KYRA2_TOWNS_SJIS_FLAGS }, + { // PC-9821 + { + "kyra2", + 0, + AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), + Common::EN_ANY, + Common::kPlatformPC98, + Common::ADGF_NO_FLAGS + }, + KYRA2_TOWNS_FLAGS + }, + { + { + "kyra2", + 0, + AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"), + Common::JA_JPN, + Common::kPlatformPC98, + Common::ADGF_NO_FLAGS + }, + KYRA2_TOWNS_SJIS_FLAGS + }, // Kyra3 @@ -480,7 +530,7 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_INS_FLAGS + KYRA3_CD_FLAGS }, { { @@ -495,7 +545,7 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_INS_FLAGS + KYRA3_CD_FLAGS }, { { @@ -510,7 +560,7 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_INS_FLAGS + KYRA3_CD_FLAGS }, // installed version @@ -527,7 +577,7 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_FLAGS + KYRA3_CD_INS_FLAGS }, { { @@ -542,7 +592,7 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_FLAGS + KYRA3_CD_INS_FLAGS }, { { @@ -557,9 +607,152 @@ const KYRAGameDescription adGameDescs[] = { Common::kPlatformPC, Common::ADGF_DROPLANGUAGE }, - KYRA3_CD_FLAGS + KYRA3_CD_INS_FLAGS + }, + + // Spanish fan translation, see fr#1994040 "KYRA3: Add support for Spanish fan translation" + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::ES_ESP, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY) + }, + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY) + }, + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::FR_FRA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY) }, + // Itlian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3" + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) + }, + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) + }, + { + { + "kyra3", + 0, + { + { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 }, + { "AUD.PAK", 0, 0, -1 }, + { 0, 0, 0, 0 } + }, + Common::IT_ITA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE + }, + KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA) + }, + + // Lands of Lore CD + { + { + "lol", + "CD", + { + { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, + { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, + { 0, 0, 0, 0 } + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + LOL_CD_FLAGS + }, + + { + { + "lol", + "CD", + { + { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, + { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, + { 0, 0, 0, 0 } + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + LOL_CD_FLAGS + }, + + { + { + "lol", + "CD", + { + { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 }, + { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 }, + { 0, 0, 0, 0 } + }, + Common::FR_FRA, + Common::kPlatformPC, + Common::ADGF_DROPLANGUAGE | Common::ADGF_CD + }, + LOL_CD_FLAGS + }, + { AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) } }; @@ -567,6 +760,7 @@ const PlainGameDescriptor gameList[] = { { "kyra1", "The Legend of Kyrandia" }, { "kyra2", "The Legend of Kyrandia: The Hand of Fate" }, { "kyra3", "The Legend of Kyrandia: Malcolm's Revenge" }, + { "lol", "Lands of Lore: The Throne of Chaos" }, { 0, 0 } }; @@ -639,6 +833,9 @@ bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common case Kyra::GI_KYRA3: *engine = new Kyra::KyraEngine_MR(syst, flags); break; + case Kyra::GI_LOL: + *engine = new Kyra::LoLEngine(syst, flags); + break; default: res = false; warning("Kyra engine: unknown gameID"); diff --git a/engines/kyra/gui_hof.cpp b/engines/kyra/gui_hof.cpp index 555934cb7f..7d56743af5 100644 --- a/engines/kyra/gui_hof.cpp +++ b/engines/kyra/gui_hof.cpp @@ -454,14 +454,26 @@ void KyraEngine_HoF::loadBookBkgd() { void KyraEngine_HoF::showBookPage() { char filename[16]; - sprintf(filename, "PAGE%.01X.", _bookCurPage); - strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); + sprintf(filename, "PAGE%.01X.%s", _bookCurPage, _languageExtension[_lang]); uint8 *leftPage = _res->fileData(filename, 0); + if (!leftPage) { + // some floppy version use a TXT extension + sprintf(filename, "PAGE%.01X.TXT", _bookCurPage); + leftPage = _res->fileData(filename, 0); + } + int leftPageY = _bookPageYOffset[_bookCurPage]; - sprintf(filename, "PAGE%.01X.", _bookCurPage+1); - strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); - uint8 *rightPage = (_bookCurPage != _bookMaxPage) ? _res->fileData(filename, 0) : 0; + sprintf(filename, "PAGE%.01X.%s", _bookCurPage+1, _languageExtension[_lang]); + uint8 *rightPage = 0; + if (_bookCurPage != _bookMaxPage) { + rightPage = _res->fileData(filename, 0); + if (!rightPage) { + sprintf(filename, "PAGE%.01X.TXT", _bookCurPage); + rightPage = _res->fileData(filename, 0); + } + } + int rightPageY = _bookPageYOffset[_bookCurPage+1]; _screen->hideMouse(); diff --git a/engines/kyra/gui_lok.cpp b/engines/kyra/gui_lok.cpp index 6fa30c9e9a..35b343fc25 100644 --- a/engines/kyra/gui_lok.cpp +++ b/engines/kyra/gui_lok.cpp @@ -188,6 +188,7 @@ int KyraEngine_LoK::buttonAmuletCallback(Button *caller) { #pragma mark - GUI_LoK::GUI_LoK(KyraEngine_LoK *vm, Screen_LoK *screen) : GUI(vm), _vm(vm), _screen(screen) { + _lastScreenUpdate = 0; _menu = 0; initStaticResource(); _scrollUpFunctor = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::scrollUp); @@ -479,7 +480,6 @@ int GUI_LoK::buttonMenuCallback(Button *caller) { void GUI_LoK::getInput() { Common::Event event; - static uint32 lastScreenUpdate = 0; uint32 now = _vm->_system->getMillis(); _mouseWheel = 0; @@ -496,7 +496,7 @@ void GUI_LoK::getInput() { break; case Common::EVENT_MOUSEMOVE: _vm->_system->updateScreen(); - lastScreenUpdate = now; + _lastScreenUpdate = now; break; case Common::EVENT_WHEELUP: _mouseWheel = -1; @@ -512,9 +512,9 @@ void GUI_LoK::getInput() { } } - if (now - lastScreenUpdate > 50) { + if (now - _lastScreenUpdate > 50) { _vm->_system->updateScreen(); - lastScreenUpdate = now; + _lastScreenUpdate = now; } _vm->_system->delayMillis(3); @@ -1056,7 +1056,7 @@ void GUI_LoK::fadePalette() { if (_vm->gameFlags().platform == Common::kPlatformAmiga) return; - static int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1}; + static const int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1}; int index = 0; memcpy(_screen->getPalette(2), _screen->_currentPalette, 768); diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h index 607ef0b605..49081c7ae2 100644 --- a/engines/kyra/gui_lok.h +++ b/engines/kyra/gui_lok.h @@ -157,6 +157,8 @@ private: KyraEngine_LoK *_vm; Screen_LoK *_screen; + uint32 _lastScreenUpdate; + bool _menuRestoreScreen; uint8 _toplevelMenu; int _savegameOffset; diff --git a/engines/kyra/items_lok.cpp b/engines/kyra/items_lok.cpp index 8eb62c20c2..2c2903d0b3 100644 --- a/engines/kyra/items_lok.cpp +++ b/engines/kyra/items_lok.cpp @@ -39,7 +39,7 @@ namespace Kyra { int KyraEngine_LoK::findDuplicateItemShape(int shape) { - static uint8 dupTable[] = { + static const uint8 dupTable[] = { 0x48, 0x46, 0x49, 0x47, 0x4a, 0x46, 0x4b, 0x47, 0x4c, 0x46, 0x4d, 0x47, 0x5b, 0x5a, 0x5c, 0x5a, 0x5d, 0x5a, 0x5e, 0x5a, 0xFF, 0xFF diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp index 57f0dcc24a..d3de621707 100644 --- a/engines/kyra/kyra_hof.cpp +++ b/engines/kyra/kyra_hof.cpp @@ -230,7 +230,7 @@ int KyraEngine_HoF::init() { _gui = new GUI_HoF(this); assert(_gui); _gui->initStaticData(); - _tim = new TIMInterpreter(this, _system); + _tim = new TIMInterpreter(this, _screen, _system); assert(_tim); if (_flags.isDemo && !_flags.isTalkie) { @@ -296,6 +296,9 @@ int KyraEngine_HoF::go() { _res->loadFileList("FILEDATA.FDT"); else _res->loadFileList(_ingamePakList, _ingamePakListSize); + + if (_flags.platform == Common::kPlatformPC98) + _res->loadPakFile("AUDIO.PAK"); } _menuDirectlyToLoad = (_menuChoice == 3) ? true : false; @@ -1561,7 +1564,7 @@ void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) { int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]); if (vocIndex != -1) _sound->voicePlay(_ingameSoundList[vocIndex], true); - else if (_flags.platform == Common::kPlatformPC) + else if (_flags.platform != Common::kPlatformFMTowns) // TODO ?? Maybe there is a way to let users select whether they want // voc, midi or adl sfx (even though it makes no sense to choose anything but voc). KyraEngine_v1::snd_playSoundEffect(track); @@ -2021,6 +2024,9 @@ void KyraEngine_HoF::writeSettings() { break; } + if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) + _flags.lang = _flags.fanLang; + ConfMan.set("language", Common::getLanguageCode(_flags.lang)); KyraEngine_v1::writeSettings(); diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp index 8a49b8e155..a4e5b58364 100644 --- a/engines/kyra/kyra_mr.cpp +++ b/engines/kyra/kyra_mr.cpp @@ -343,6 +343,14 @@ void KyraEngine_MR::initMainMenu() { 0x80, 0xFF }; + if (_flags.lang == Common::ES_ESP) { + for (int i = 0; i < 4; ++i) + data.strings[i] = _mainMenuSpanishFan[i]; + } else if (_flags.lang == Common::IT_ITA) { + for (int i = 0; i < 4; ++i) + data.strings[i] = _mainMenuItalianFan[i]; + } + MainMenu::Animation anim; anim.anim = _menuAnim; anim.startFrame = 29; @@ -1543,6 +1551,9 @@ void KyraEngine_MR::writeSettings() { break; } + if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) + _flags.lang = _flags.fanLang; + ConfMan.set("language", Common::getLanguageCode(_flags.lang)); ConfMan.setBool("studio_audience", _configStudio); diff --git a/engines/kyra/kyra_mr.h b/engines/kyra/kyra_mr.h index 5af138373c..5f9f6f91a3 100644 --- a/engines/kyra/kyra_mr.h +++ b/engines/kyra/kyra_mr.h @@ -184,9 +184,12 @@ private: private: // main menu - const char *const *_mainMenuStrings; + const char * const *_mainMenuStrings; int _mainMenuStringsSize; + static const char * const _mainMenuSpanishFan[]; + static const char * const _mainMenuItalianFan[]; + // animator uint8 *_gamePlayBuffer; void restorePage3(); diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index 1cc1d728bf..85c03dc1bb 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -107,14 +107,16 @@ int KyraEngine_v1::init() { // "KYRA1: Crash on exceeded polyphony" for more information). int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/); - if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { - // TODO: currently we don't support the PC98 sound data, - // but since it has the FM-Towns data files, we just use the - // FM-Towns driver + if (_flags.platform == Common::kPlatformFMTowns) { if (_flags.gameID == GI_KYRA1) _sound = new SoundTowns(this, _mixer); else - _sound = new SoundTowns_v2(this, _mixer); + _sound = new SoundTownsPC98_v2(this, _mixer); + } else if (_flags.platform == Common::kPlatformPC98) { + if (_flags.gameID == GI_KYRA1) + _sound = new SoundTowns/*SoundPC98*/(this, _mixer); + else + _sound = new SoundTownsPC98_v2(this, _mixer); } else if (midiDriver == MD_ADLIB) { _sound = new SoundAdlibPC(this, _mixer); assert(_sound); @@ -171,36 +173,6 @@ int KyraEngine_v1::init() { _gameToLoad = -1; } - _lang = 0; - Common::Language lang = Common::parseLanguage(ConfMan.get("language")); - - if (_flags.gameID == GI_KYRA2 || _flags.gameID == GI_KYRA3) { - switch (lang) { - case Common::EN_ANY: - case Common::EN_USA: - case Common::EN_GRB: - _lang = 0; - break; - - case Common::FR_FRA: - _lang = 1; - break; - - case Common::DE_DEU: - _lang = 2; - break; - - case Common::JA_JPN: - _lang = 3; - break; - - default: - warning("unsupported language, switching back to English"); - _lang = 0; - break; - } - } - return 0; } @@ -275,6 +247,14 @@ void KyraEngine_v1::delayWithTicks(int ticks) { void KyraEngine_v1::registerDefaultSettings() { if (_flags.gameID != GI_KYRA3) ConfMan.registerDefault("cdaudio", (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)); + if (_flags.fanLang != Common::UNK_LANG) { + // HACK/WORKAROUND: Since we can't use registerDefault here to overwrite + // the global subtitles settings, we're using this hack to enable subtitles + // for fan translations + const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain(); + if (!cur || (cur && cur->get("subtitles").empty())) + ConfMan.setBool("subtitles", true); + } } void KyraEngine_v1::readSettings() { diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h index 4f38ceca98..50cabc421e 100644 --- a/engines/kyra/kyra_v1.h +++ b/engines/kyra/kyra_v1.h @@ -44,6 +44,11 @@ namespace Kyra { struct GameFlags { Common::Language lang; + + // language overwrites of fan translations (only needed for multilingual games) + Common::Language fanLang; + Common::Language replacedLang; + Common::Platform platform; bool isDemo : 1; @@ -59,7 +64,8 @@ struct GameFlags { enum { GI_KYRA1 = 0, GI_KYRA2 = 1, - GI_KYRA3 = 2 + GI_KYRA3 = 2, + GI_LOL = 4 }; struct AudioDataStruct { @@ -212,7 +218,6 @@ protected: // detection GameFlags _flags; - int _lang; // opcode virtual void setupOpcodeTable() = 0; diff --git a/engines/kyra/kyra_v2.cpp b/engines/kyra/kyra_v2.cpp index 12da338843..2e704f2aa2 100644 --- a/engines/kyra/kyra_v2.cpp +++ b/engines/kyra/kyra_v2.cpp @@ -23,6 +23,8 @@ * */ +#include "common/config-manager.h" + #include "kyra/kyra_v2.h" #include "kyra/screen_v2.h" #include "kyra/debugger.h" @@ -70,6 +72,36 @@ KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const Engi memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory)); _pauseStart = 0; + + _lang = 0; + Common::Language lang = Common::parseLanguage(ConfMan.get("language")); + if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) + lang = _flags.replacedLang; + + switch (lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + case Common::JA_JPN: + _lang = 3; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + break; + } } KyraEngine_v2::~KyraEngine_v2() { diff --git a/engines/kyra/kyra_v2.h b/engines/kyra/kyra_v2.h index 24f7aad614..6fdf30fff8 100644 --- a/engines/kyra/kyra_v2.h +++ b/engines/kyra/kyra_v2.h @@ -94,6 +94,9 @@ protected: virtual void update() = 0; virtual void updateWithText() = 0; + // detection + int _lang; + // MainMenu MainMenu *_menu; diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp new file mode 100644 index 0000000000..ef1121baa0 --- /dev/null +++ b/engines/kyra/lol.cpp @@ -0,0 +1,806 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/lol.h" +#include "kyra/screen_lol.h" +#include "kyra/resource.h" +#include "kyra/sound.h" + +#include "common/endian.h" + +namespace Kyra { + +LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags) { + _screen = 0; + + switch (_flags.lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + break; + } + + _chargenWSA = 0; +} + +LoLEngine::~LoLEngine() { + setupPrologueData(false); + + delete _screen; + delete _tim; + + for (Common::Array<const TIMOpcode*>::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i) + delete *i; + _timIntroOpcodes.clear(); +} + +Screen *LoLEngine::screen() { + return _screen; +} + +int LoLEngine::init() { + _screen = new Screen_LoL(this, _system); + assert(_screen); + _screen->setResolution(); + + KyraEngine_v1::init(); + + _tim = new TIMInterpreter(this, _screen, _system); + assert(_tim); + + _screen->setAnimBlockPtr(10000); + _screen->setScreenDim(0); + + return 0; +} + +int LoLEngine::go() { + setupPrologueData(true); + showIntro(); + _sound->playTrack(6); + /*int character = */chooseCharacter(); + _sound->playTrack(1); + _screen->fadeToBlack(); + setupPrologueData(false); + + return 0; +} + +#pragma mark - Input + +int LoLEngine::checkInput(Button *buttonList, bool mainLoop) { + debugC(9, kDebugLevelMain, "LoLEngine::checkInput(%p, %d)", (const void*)buttonList, mainLoop); + updateInput(); + + int keys = 0; + int8 mouseWheel = 0; + + while (_eventList.size()) { + Common::Event event = *_eventList.begin(); + bool breakLoop = false; + + switch (event.type) { + case Common::EVENT_KEYDOWN: + /*if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' && + (event.kbd.flags == Common::KBD_CTRL || event.kbd.flags == Common::KBD_ALT) && mainLoop) { + const char *saveLoadSlot = getSavegameFilename(9 - (event.kbd.keycode - '0') + 990); + + if (event.kbd.flags == Common::KBD_CTRL) { + loadGame(saveLoadSlot); + _eventList.clear(); + breakLoop = true; + } else { + char savegameName[14]; + sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0'); + saveGame(saveLoadSlot, savegameName); + } + } else if (event.kbd.flags == Common::KBD_CTRL) { + if (event.kbd.keycode == 'd') + _debugger->attach(); + }*/ + break; + + case Common::EVENT_MOUSEMOVE: { + Common::Point pos = getMousePos(); + _mouseX = pos.x; + _mouseY = pos.y; + } break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: { + Common::Point pos = getMousePos(); + _mouseX = pos.x; + _mouseY = pos.y; + keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800)); + breakLoop = true; + } break; + + case Common::EVENT_WHEELUP: + mouseWheel = -1; + break; + + case Common::EVENT_WHEELDOWN: + mouseWheel = 1; + break; + + default: + break; + } + + //if (_debugger->isAttached()) + // _debugger->onFrame(); + + if (breakLoop) + break; + + _eventList.erase(_eventList.begin()); + } + + return /*gui_v2()->processButtonList(buttonList, keys | 0x8000, mouseWheel)*/keys; +} + +void LoLEngine::updateInput() { + Common::Event event; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + _quitFlag = true; + break; + + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == '.' || event.kbd.keycode == Common::KEYCODE_ESCAPE) + _eventList.push_back(Event(event, true)); + else if (event.kbd.keycode == 'q' && event.kbd.flags == Common::KBD_CTRL) + _quitFlag = true; + else + _eventList.push_back(event); + break; + + case Common::EVENT_LBUTTONDOWN: + _eventList.push_back(Event(event, true)); + break; + + case Common::EVENT_MOUSEMOVE: + _screen->updateScreen(); + // fall through + + case Common::EVENT_LBUTTONUP: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + _eventList.push_back(event); + break; + + default: + break; + } + } +} + +void LoLEngine::removeInputTop() { + if (!_eventList.empty()) + _eventList.erase(_eventList.begin()); +} + +bool LoLEngine::skipFlag() const { + for (Common::List<Event>::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) + return true; + } + return false; +} + +void LoLEngine::resetSkipFlag(bool removeEvent) { + for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) { + if (removeEvent) + _eventList.erase(i); + else + i->causedSkip = false; + return; + } + } +} + +#pragma mark - Intro + +void LoLEngine::setupPrologueData(bool load) { + static const char * const fileList[] = { + "xxx/general.pak", + "xxx/introvoc.pak", + "xxx/startup.pak", + "xxx/intro1.pak", + "xxx/intro2.pak", + "xxx/intro3.pak", + "xxx/intro4.pak", + "xxx/intro5.pak", + "xxx/intro6.pak", + "xxx/intro7.pak", + "xxx/intro8.pak", + "xxx/intro9.pak" + }; + + char filename[32]; + for (uint i = 0; i < ARRAYSIZE(fileList); ++i) { + strcpy(filename, fileList[i]); + memcpy(filename, _languageExt[_lang], 3); + + if (load) { + if (!_res->loadPakFile(filename)) + error("Couldn't load file: '%s'", filename); + } else { + _res->unloadPakFile(filename); + } + } + + if (load) { + _chargenWSA = new WSAMovie_v2(this, _screen); + assert(_chargenWSA); + + _charSelection = -1; + _charSelectionInfoResult = -1; + + _selectionAnimFrames[0] = _selectionAnimFrames[2] = 0; + _selectionAnimFrames[1] = _selectionAnimFrames[3] = 1; + + memset(_selectionAnimTimers, 0, sizeof(_selectionAnimTimers)); + memset(_screen->getPalette(1), 0, 768); + } else { + delete _chargenWSA; _chargenWSA = 0; + } +} + +void LoLEngine::showIntro() { + debugC(9, kDebugLevelMain, "LoLEngine::showIntro()"); + + TIM *intro = _tim->load("LOLINTRO.TIM", &_timIntroOpcodes); + + _screen->loadFont(Screen::FID_8_FNT, "NEW8P.FNT"); + _screen->loadFont(Screen::FID_INTRO_FNT, "INTRO.FNT"); + _screen->setFont(Screen::FID_8_FNT); + + _tim->resetFinishedFlag(); + _tim->setLangData("LOLINTRO.DIP"); + + _screen->hideMouse(); + + uint32 palNextFadeStep = 0; + while (!_tim->finished() && !_quitFlag && !skipFlag()) { + updateInput(); + _tim->exec(intro, false); + _screen->checkedPageUpdate(8, 4); + + if (_tim->_palDiff) { + if (palNextFadeStep < _system->getMillis()) { + _tim->_palDelayAcc += _tim->_palDelayInc; + palNextFadeStep = _system->getMillis() + ((_tim->_palDelayAcc >> 8) * _tickLength); + _tim->_palDelayAcc &= 0xFF; + + if (!_screen->fadePalStep(_screen->getPalette(0), _tim->_palDiff)) { + _screen->setScreenPalette(_screen->getPalette(0)); + _tim->_palDiff = 0; + } + } + } + + _system->delayMillis(10); + _screen->updateScreen(); + } + _screen->showMouse(); + _sound->voiceStop(); + + // HACK: Remove all input events + _eventList.clear(); + + _tim->unload(intro); + _tim->clearLangData(); + + _screen->fadePalette(_screen->getPalette(1), 30, 0); +} + +int LoLEngine::chooseCharacter() { + debugC(9, kDebugLevelMain, "LoLEngine::chooseCharacter()"); + + _tim->setLangData("LOLINTRO.DIP"); + + _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + + _screen->loadBitmap("ITEMICN.SHP", 3, 3, 0); + _screen->setMouseCursor(0, 0, _screen->getPtrToShape(_screen->getCPagePtr(3), 0)); + + while (!_screen->isMouseVisible()) + _screen->showMouse(); + + _screen->loadBitmap("CHAR.CPS", 2, 2, _screen->getPalette(0)); + _screen->loadBitmap("BACKGRND.CPS", 4, 4, _screen->getPalette(0)); + + if (!_chargenWSA->open("CHARGEN.WSA", 1, 0)) + error("Couldn't load CHARGEN.WSA"); + _chargenWSA->setX(113); + _chargenWSA->setY(0); + _chargenWSA->setDrawPage(2); + _chargenWSA->displayFrame(0, 0, 0, 0); + + _screen->setFont(Screen::FID_9_FNT); + _screen->_curPage = 2; + + for (int i = 0; i < 4; ++i) + _screen->fprintStringIntro(_charPreviews[i].name, _charPreviews[i].x + 16, _charPreviews[i].y + 36, 0xC0, 0x00, 0x9C, 0x120); + + for (int i = 0; i < 4; ++i) { + _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 48, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[0]); + _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 56, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[1]); + _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 64, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[2]); + } + + _screen->fprintStringIntro(_tim->getCTableEntry(51), 36, 173, 0x98, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(53), 36, 181, 0x98, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(55), 36, 189, 0x98, 0x00, 0x9C, 0x20); + + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + _screen->_curPage = 0; + + _screen->fadePalette(_screen->getPalette(0), 30, 0); + + bool kingIntro = true; + while (!_quitFlag) { + if (kingIntro) + kingSelectionIntro(); + + if (_charSelection < 0) + processCharacterSelection(); + + if (_quitFlag) + break; + + if (_charSelection == 100) { + kingIntro = true; + _charSelection = -1; + continue; + } + + _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + _screen->showMouse(); + + if (selectionCharInfo(_charSelection) == -1) { + _charSelection = -1; + kingIntro = false; + } else { + break; + } + } + + if (_quitFlag) + return -1; + + uint32 waitTime = _system->getMillis() + 420 * _tickLength; + while (waitTime > _system->getMillis() && !skipFlag() && !_quitFlag) { + updateInput(); + _system->delayMillis(10); + } + + // HACK: Remove all input events + _eventList.clear(); + + _tim->clearLangData(); + + return _charSelection; +} + +void LoLEngine::kingSelectionIntro() { + debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionIntro()"); + + _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); + int y = 38; + + _screen->fprintStringIntro(_tim->getCTableEntry(57), 8, y, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(58), 8, y + 10, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(59), 8, y + 20, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(60), 8, y + 30, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(61), 8, y + 40, 0x32, 0x00, 0x9C, 0x20); + + _sound->voicePlay("KING01"); + + _chargenWSA->setX(113); + _chargenWSA->setY(0); + _chargenWSA->setDrawPage(0); + + int index = 4; + while (_sound->voiceIsPlaying("KING01") && _charSelection == -1 && !_quitFlag && !skipFlag()) { + index = MAX(index, 4); + + _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0); + _screen->copyRegion(_selectionPosTable[_selectionChar1IdxTable[index]*2+0], _selectionPosTable[_selectionChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_selectionChar2IdxTable[index]*2+0], _selectionPosTable[_selectionChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_selectionChar3IdxTable[index]*2+0], _selectionPosTable[_selectionChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_selectionChar4IdxTable[index]*2+0], _selectionPosTable[_selectionChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0); + _screen->updateScreen(); + + uint32 waitEnd = _system->getMillis() + 7 * _tickLength; + while (waitEnd > _system->getMillis() && _charSelection == -1 && !_quitFlag && !skipFlag()) { + _charSelection = getCharSelection(); + _system->delayMillis(10); + } + + index = (index + 1) % 22; + } + + resetSkipFlag(); + + _chargenWSA->displayFrame(0x10, 0, 0, 0); + _screen->updateScreen(); + _sound->voiceStop("KING01"); +} + +void LoLEngine::kingSelectionReminder() { + debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionReminder()"); + + _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); + int y = 48; + + _screen->fprintStringIntro(_tim->getCTableEntry(62), 8, y, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(63), 8, y + 10, 0x32, 0x00, 0x9C, 0x20); + + _sound->voicePlay("KING02"); + + _chargenWSA->setX(113); + _chargenWSA->setY(0); + _chargenWSA->setDrawPage(0); + + int index = 0; + while (_sound->voiceIsPlaying("KING02") && _charSelection == -1 && !_quitFlag && index < 15) { + _chargenWSA->displayFrame(_chargenFrameTable[index+9], 0, 0, 0); + _screen->copyRegion(_selectionPosTable[_reminderChar1IdxTable[index]*2+0], _selectionPosTable[_reminderChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_reminderChar2IdxTable[index]*2+0], _selectionPosTable[_reminderChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_reminderChar3IdxTable[index]*2+0], _selectionPosTable[_reminderChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0); + _screen->copyRegion(_selectionPosTable[_reminderChar4IdxTable[index]*2+0], _selectionPosTable[_reminderChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0); + _screen->updateScreen(); + + uint32 waitEnd = _system->getMillis() + 8 * _tickLength; + while (waitEnd > _system->getMillis() && !_quitFlag) { + _charSelection = getCharSelection(); + _system->delayMillis(10); + } + + index = (index + 1) % 22; + } + + _sound->voiceStop("KING02"); +} + +void LoLEngine::kingSelectionOutro() { + debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionOutro()"); + + _sound->voicePlay("KING03"); + + _chargenWSA->setX(113); + _chargenWSA->setY(0); + _chargenWSA->setDrawPage(0); + + int index = 0; + while (_sound->voiceIsPlaying("KING03") && !_quitFlag && !skipFlag()) { + index = MAX(index, 4); + + _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0); + _screen->updateScreen(); + + uint32 waitEnd = _system->getMillis() + 8 * _tickLength; + while (waitEnd > _system->getMillis() && !_quitFlag && !skipFlag()) { + updateInput(); + _system->delayMillis(10); + } + + index = (index + 1) % 22; + } + + resetSkipFlag(); + + _chargenWSA->displayFrame(0x10, 0, 0, 0); + _screen->updateScreen(); + _sound->voiceStop("KING03"); +} + +void LoLEngine::processCharacterSelection() { + debugC(9, kDebugLevelMain, "LoLEngine::processCharacterSelection()"); + + _charSelection = -1; + while (!_quitFlag && _charSelection == -1) { + uint32 nextKingMessage = _system->getMillis() + 900 * _tickLength; + + while (nextKingMessage > _system->getMillis() && _charSelection == -1 && !_quitFlag) { + updateSelectionAnims(); + _charSelection = getCharSelection(); + _system->delayMillis(10); + } + + if (_charSelection == -1) + kingSelectionReminder(); + } +} + +void LoLEngine::updateSelectionAnims() { + debugC(9, kDebugLevelMain, "LoLEngine::updateSelectionAnims()"); + + for (int i = 0; i < 4; ++i) { + if (_system->getMillis() < _selectionAnimTimers[i]) + continue; + + const int index = _selectionAnimIndexTable[_selectionAnimFrames[i] + i * 2]; + _screen->copyRegion(_selectionPosTable[index*2+0], _selectionPosTable[index*2+1], _charPreviews[i].x, _charPreviews[i].y, 32, 32, 4, 0); + + int delayTime = 0; + if (_selectionAnimFrames[i] == 1) + delayTime = _rnd.getRandomNumberRng(0, 31) + 80; + else + delayTime = _rnd.getRandomNumberRng(0, 3) + 10; + + _selectionAnimTimers[i] = _system->getMillis() + delayTime * _tickLength; + _selectionAnimFrames[i] = (_selectionAnimFrames[i] + 1) % 2; + } + + _screen->updateScreen(); +} + +int LoLEngine::selectionCharInfo(int character) { + debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfo(%d)", character); + if (character < 0) + return -1; + + char filename[16]; + char vocFilename[6]; + strcpy(vocFilename, "000X0"); + + switch (character) { + case 0: + strcpy(filename, "face09.shp"); + vocFilename[3] = 'A'; + break; + + case 1: + strcpy(filename, "face01.shp"); + vocFilename[3] = 'M'; + break; + + case 2: + strcpy(filename, "face08.shp"); + vocFilename[3] = 'K'; + break; + + case 3: + strcpy(filename, "face05.shp"); + vocFilename[3] = 'C'; + break; + + default: + break; + }; + + _screen->loadBitmap(filename, 9, 9, 0); + _screen->copyRegion(0, 122, 0, 122, 320, 78, 4, 0, Screen::CR_NO_P_CHECK); + _screen->copyRegion(_charPreviews[character].x - 3, _charPreviews[character].y - 3, 8, 127, 38, 38, 2, 0); + + static const uint8 charSelectInfoIdx[] = { 0x1D, 0x22, 0x27, 0x2C }; + const int idx = charSelectInfoIdx[character]; + + _screen->fprintStringIntro(_tim->getCTableEntry(idx+0), 50, 127, 0x53, 0x00, 0xCF, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(idx+1), 50, 137, 0x53, 0x00, 0xCF, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(idx+2), 50, 147, 0x53, 0x00, 0xCF, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(idx+3), 50, 157, 0x53, 0x00, 0xCF, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(idx+4), 50, 167, 0x53, 0x00, 0xCF, 0x20); + + _screen->fprintStringIntro(_tim->getCTableEntry(69), 100, 168, 0x32, 0x00, 0xCF, 0x20); + + selectionCharInfoIntro(vocFilename); + if (_charSelectionInfoResult == -1) { + while (_charSelectionInfoResult == -1) { + _charSelectionInfoResult = selectionCharAccept(); + _system->delayMillis(10); + } + } + + if (_charSelectionInfoResult != 1) { + _charSelectionInfoResult = -1; + _screen->copyRegion(0, 122, 0, 122, 320, 78, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + return -1; + } + + _screen->copyRegion(48, 127, 48, 127, 272, 60, 4, 0, Screen::CR_NO_P_CHECK); + _screen->hideMouse(); + _screen->copyRegion(48, 127, 48, 160, 272, 35, 4, 0, Screen::CR_NO_P_CHECK); + _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK); + + _screen->fprintStringIntro(_tim->getCTableEntry(64), 3, 28, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(65), 3, 38, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(66), 3, 48, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(67), 3, 58, 0x32, 0x00, 0x9C, 0x20); + _screen->fprintStringIntro(_tim->getCTableEntry(68), 3, 68, 0x32, 0x00, 0x9C, 0x20); + + resetSkipFlag(); + kingSelectionOutro(); + return character; +} + +void LoLEngine::selectionCharInfoIntro(char *file) { + debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfoIntro(%p)", (const void *)file); + int index = 0; + file[4] = '0'; + + while (_charSelectionInfoResult == -1 && !_quitFlag) { + if (!_sound->voicePlay(file)) + break; + + int i = 0; + while (_sound->voiceIsPlaying(file) && _charSelectionInfoResult == -1 && !_quitFlag) { + _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), _charInfoFrameTable[i]), 11, 130, 0, 0); + _screen->updateScreen(); + + uint32 nextFrame = _system->getMillis() + 8 * _tickLength; + while (nextFrame > _system->getMillis() && _charSelectionInfoResult == -1) { + _charSelectionInfoResult = selectionCharAccept(); + _system->delayMillis(10); + } + + i = (i + 1) % 32; + } + + _sound->voiceStop(file); + file[4] = ++index + '0'; + } + + _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), 0), 11, 130, 0, 0); + _screen->updateScreen(); +} + +int LoLEngine::getCharSelection() { + int inputFlag = checkInput() & 0xCF; + removeInputTop(); + + if (inputFlag == 200) { + for (int i = 0; i < 4; ++i) { + if (_charPreviews[i].x <= _mouseX && _mouseX <= _charPreviews[i].x + 31 && + _charPreviews[i].y <= _mouseY && _mouseY <= _charPreviews[i].y + 31) + return i; + } + } + + return -1; +} + +int LoLEngine::selectionCharAccept() { + int inputFlag = checkInput() & 0xCF; + removeInputTop(); + + if (inputFlag == 200) { + if (88 <= _mouseX && _mouseX <= 128 && 180 <= _mouseY && _mouseY <= 194) + return 1; + if (196 <= _mouseX && _mouseX <= 236 && 180 <= _mouseY && _mouseY <= 194) + return 0; + } + + return -1; +} + +#pragma mark - Opcodes + +typedef Common::Functor2Mem<const TIM *, const uint16 *, int, LoLEngine> TIMOpcodeLoL; +#define SetTimOpcodeTable(x) timTable = &x; +#define OpcodeTim(x) timTable->push_back(new TIMOpcodeLoL(this, &LoLEngine::x)) +#define OpcodeTimUnImpl() timTable->push_back(new TIMOpcodeLoL(this, 0)) + +void LoLEngine::setupOpcodeTable() { + Common::Array<const TIMOpcode*> *timTable = 0; + + SetTimOpcodeTable(_timIntroOpcodes); + + // 0x00 + OpcodeTim(tlol_setupPaletteFade); + OpcodeTimUnImpl(); + OpcodeTim(tlol_loadPalette); + OpcodeTim(tlol_setupPaletteFadeEx); + + // 0x04 + OpcodeTim(tlol_processWsaFrame); + OpcodeTim(tlol_displayText); + OpcodeTimUnImpl(); + OpcodeTimUnImpl(); +} + +#pragma mark - + +int LoLEngine::tlol_setupPaletteFade(const TIM *tim, const uint16 *param) { + debugC(3, kDebugLevelScriptFuncs, "LoLEngine::t2_playSoundEffect(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); + _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff); + _tim->_palDelayAcc = 0; + return 1; +} + +int LoLEngine::tlol_loadPalette(const TIM *tim, const uint16 *param) { + debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_loadPalette(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); + const char *palFile = (const char *)(tim->text + READ_LE_UINT16(tim->text + (param[0]<<1))); + _res->loadFileToBuf(palFile, _screen->getPalette(0), 768); + return 1; +} + +int LoLEngine::tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param) { + debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_setupPaletteFadeEx(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]); + memcpy(_screen->getPalette(0), _screen->getPalette(1), 768); + + _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff); + _tim->_palDelayAcc = 0; + return 1; +} + +int LoLEngine::tlol_processWsaFrame(const TIM *tim, const uint16 *param) { + debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_processWsaFrame(%p, %p) (%d, %d, %d, %d, %d)", + (const void*)tim, (const void*)param, param[0], param[1], param[2], param[3], param[4]); + TIMInterpreter::Animation *anim = (TIMInterpreter::Animation *)tim->wsa[param[0]].anim; + const int frame = param[1]; + const int x2 = param[2]; + const int y2 = param[3]; + const int factor = MAX<int>(0, (int16)param[4]); + + const int x1 = anim->x; + const int y1 = anim->y; + + int w1 = anim->wsa->width(); + int h1 = anim->wsa->height(); + int w2 = (w1 * factor) / 100; + int h2 = (h1 * factor) / 100; + + anim->wsa->setDrawPage(2); + anim->wsa->setX(x1); + anim->wsa->setY(y1); + anim->wsa->displayFrame(frame, anim->wsaCopyParams & 0xF0FF, 0, 0); + _screen->wsaFrameAnimationStep(x1, y1, x2, y2, w1, h1, w2, h2, 2, 8, 0); + _screen->checkedPageUpdate(8, 4); + _screen->updateScreen(); + + return 1; +} + +int LoLEngine::tlol_displayText(const TIM *tim, const uint16 *param) { + debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_displayText(%p, %p) (%d, %d)", (const void*)tim, (const void*)param, param[0], (int16)param[1]); + _tim->displayText(param[0], param[1]); + return 1; +} + +} // end of namespace Kyra + diff --git a/engines/kyra/lol.h b/engines/kyra/lol.h new file mode 100644 index 0000000000..ee54f8abbb --- /dev/null +++ b/engines/kyra/lol.h @@ -0,0 +1,155 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef KYRA_LOL_H +#define KYRA_LOL_H + +#include "kyra/kyra_v1.h" +#include "kyra/script_tim.h" + +#include "common/list.h" + +namespace Kyra { + +class Screen_LoL; +class WSAMovie_v2; +struct Button; + +class LoLEngine : public KyraEngine_v1 { +public: + LoLEngine(OSystem *system, const GameFlags &flags); + ~LoLEngine(); + + Screen *screen(); +private: + Screen_LoL *_screen; + TIMInterpreter *_tim; + + int init(); + int go(); + + // input + void updateInput(); + int checkInput(Button *buttonList = 0, bool mainLoop = false); + void removeInputTop(); + + int _mouseX, _mouseY; + + struct Event { + Common::Event event; + bool causedSkip; + + Event() : event(), causedSkip(false) {} + Event(Common::Event e) : event(e), causedSkip(false) {} + Event(Common::Event e, bool skip) : event(e), causedSkip(skip) {} + + operator Common::Event() const { return event; } + }; + Common::List<Event> _eventList; + + virtual bool skipFlag() const; + virtual void resetSkipFlag(bool removeEvent = true); + + // intro + void setupPrologueData(bool load); + + void showIntro(); + + struct CharacterPrev { + const char *name; + int x, y; + int attrib[3]; + }; + + static const CharacterPrev _charPreviews[]; + + WSAMovie_v2 *_chargenWSA; + static const uint8 _chargenFrameTable[]; + int chooseCharacter(); + + void kingSelectionIntro(); + void kingSelectionReminder(); + void kingSelectionOutro(); + void processCharacterSelection(); + void updateSelectionAnims(); + int selectionCharInfo(int character); + void selectionCharInfoIntro(char *file); + + int getCharSelection(); + int selectionCharAccept(); + + int _charSelection; + int _charSelectionInfoResult; + + uint32 _selectionAnimTimers[4]; + uint8 _selectionAnimFrames[4]; + static const uint8 _selectionAnimIndexTable[]; + + static const uint16 _selectionPosTable[]; + + static const uint8 _selectionChar1IdxTable[]; + static const uint8 _selectionChar2IdxTable[]; + static const uint8 _selectionChar3IdxTable[]; + static const uint8 _selectionChar4IdxTable[]; + + static const uint8 _reminderChar1IdxTable[]; + static const uint8 _reminderChar2IdxTable[]; + static const uint8 _reminderChar3IdxTable[]; + static const uint8 _reminderChar4IdxTable[]; + + static const uint8 _charInfoFrameTable[]; + + // timer + void setupTimers() {} + + // sound + void snd_playVoiceFile(int) { /* XXX */ } + + // opcode + void setupOpcodeTable(); + + Common::Array<const TIMOpcode*> _timIntroOpcodes; + int tlol_setupPaletteFade(const TIM *tim, const uint16 *param); + int tlol_loadPalette(const TIM *tim, const uint16 *param); + int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param); + int tlol_processWsaFrame(const TIM *tim, const uint16 *param); + int tlol_displayText(const TIM *tim, const uint16 *param); + + // translation + int _lang; + + static const char * const _languageExt[]; + + // unneeded + void setWalkspeed(uint8) {} + void setHandItem(uint16) {} + void removeHandItem() {} + bool lineIsPassable(int, int) { return false; } +}; + +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index ebb63b4b4e..e059a8ce4b 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -21,6 +21,7 @@ MODULE_OBJS := \ kyra_v2.o \ kyra_hof.o \ kyra_mr.o \ + lol.o \ resource.o \ saveload.o \ saveload_lok.o \ @@ -33,6 +34,7 @@ MODULE_OBJS := \ scene_mr.o \ screen.o \ screen_lok.o \ + screen_lol.o \ screen_v2.o \ screen_hof.o \ screen_mr.o \ diff --git a/engines/kyra/resource.cpp b/engines/kyra/resource.cpp index afd7eacfda..92818aafe1 100644 --- a/engines/kyra/resource.cpp +++ b/engines/kyra/resource.cpp @@ -55,10 +55,12 @@ bool Resource::reset() { if (!dir.exists() || !dir.isDirectory()) error("invalid game path '%s'", dir.getPath().c_str()); - if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) { - Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website"; - _vm->GUIErrorMessage(errorMessage); - error(errorMessage.c_str()); + if (_vm->game() != GI_LOL) { + if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) { + Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website"; + _vm->GUIErrorMessage(errorMessage); + error(errorMessage.c_str()); + } } if (_vm->game() == GI_KYRA1) { @@ -99,6 +101,8 @@ bool Resource::reset() { loadFileList("FILEDATA.FDT"); return true; + } else if (_vm->game() == GI_LOL) { + return true; } FSList fslist; @@ -1120,7 +1124,7 @@ bool FileExpander::process(uint8 *dst, const uint8 *src, uint32 outsize, uint32 void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt) { const uint8 *tbl1 = _tables[srcIndex]; - const uint8 *tbl2 = _tables[dstIndex]; + uint8 *tbl2 = _tables[dstIndex]; const uint8 *tbl3 = dstIndex2 == 0xff ? 0 : _tables[dstIndex2]; if (!cnt) @@ -1185,7 +1189,7 @@ void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex } } - memset((void*) tbl2, 0, 512); + memset(tbl2, 0, 512); cnt--; s = tbl1 + cnt; diff --git a/engines/kyra/scene_hof.cpp b/engines/kyra/scene_hof.cpp index 1882386b03..62df683ea2 100644 --- a/engines/kyra/scene_hof.cpp +++ b/engines/kyra/scene_hof.cpp @@ -723,7 +723,7 @@ void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) { bool KyraEngine_HoF::lineIsPassable(int x, int y) { debugC(9, kDebugLevelMain, "KyraEngine_HoF::lineIsPassable(%d, %d)", x, y); - static int unkTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; + static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; if (_pathfinderFlag & 2) { if (x >= 320) @@ -743,7 +743,7 @@ bool KyraEngine_HoF::lineIsPassable(int x, int y) { if (y > 143) return false; - int unk1 = unkTable[getScale(x, y) >> 5]; + int unk1 = widthTable[getScale(x, y) >> 5]; if (y < 0) y = 0; diff --git a/engines/kyra/scene_lok.cpp b/engines/kyra/scene_lok.cpp index e4ae67f751..53c269a926 100644 --- a/engines/kyra/scene_lok.cpp +++ b/engines/kyra/scene_lok.cpp @@ -1144,11 +1144,11 @@ void KyraEngine_LoK::setCharactersInDefaultScene() { } void KyraEngine_LoK::setCharactersPositions(int character) { - static uint16 initXPosTable[] = { + static const uint16 initXPosTable[] = { 0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B, 0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042 }; - static uint8 initYPosTable[] = { + static const uint8 initYPosTable[] = { 0x00, 0xA2, 0x00, 0x42, 0x00, 0x67, 0x67, 0x60, 0x5A, 0x71, 0x76 diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp index f00c47ceea..74f7bc6de9 100644 --- a/engines/kyra/screen.cpp +++ b/engines/kyra/screen.cpp @@ -92,9 +92,19 @@ bool Screen::init() { if (_useSJIS) { if (!_sjisFontData) { - _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0); - if (!_sjisFontData) - error("missing font rom ('FMT_FNT.ROM') required for this version"); + // we use the FM-Towns font rom for PC-98, too, until we feel + // like adding support for the PC-98 font + //if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + // FM-Towns + _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0); + if (!_sjisFontData) + error("missing font rom ('FMT_FNT.ROM') required for this version"); + /*} else { + // PC-98 + _sjisFontData = _vm->resource()->fileData("FONT.ROM", 0); + if (!_sjisFontData) + error("missing font rom ('FONT.ROM') required for this version"); + }*/ } if (!_sjisTempPage) { @@ -370,61 +380,23 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u debugC(9, kDebugLevelScreen, "Screen::fadePalette(%p, %d, %p)", (const void *)palData, delay, (const void*)upFunc); updateScreen(); - uint8 fadePal[768]; - memcpy(fadePal, _screenPalette, 768); - uint8 diff, maxDiff = 0; - for (int i = 0; i < 768; ++i) { - diff = ABS(palData[i] - fadePal[i]); - if (diff > maxDiff) { - maxDiff = diff; - } - } - - int16 delayInc = delay << 8; - if (maxDiff != 0) - delayInc /= maxDiff; - - delay = delayInc; - for (diff = 1; diff <= maxDiff; ++diff) { - if (delayInc >= 512) - break; - delayInc += delay; - } + int diff = 0, delayInc = 0; + getFadeParams(palData, delay, delayInc, diff); int delayAcc = 0; while (!_vm->quit()) { delayAcc += delayInc; - bool needRefresh = false; - for (int i = 0; i < 768; ++i) { - int c1 = palData[i]; - int c2 = fadePal[i]; - if (c1 != c2) { - needRefresh = true; - if (c1 > c2) { - c2 += diff; - if (c1 < c2) - c2 = c1; - } - - if (c1 < c2) { - c2 -= diff; - if (c1 > c2) - c2 = c1; - } - - fadePal[i] = (uint8)c2; - } - } - if (!needRefresh) - break; + int refreshed = fadePalStep(palData, diff); - setScreenPalette(fadePal); if (upFunc && upFunc->isValid()) (*upFunc)(); else _system->updateScreen(); - //_system->delayMillis((delayAcc >> 8) * 1000 / 60); + + if (!refreshed) + break; + _vm->delay((delayAcc >> 8) * 1000 / 60); delayAcc &= 0xFF; } @@ -438,6 +410,61 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u } } +void Screen::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) { + debugC(9, kDebugLevelScreen, "Screen::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff); + uint8 maxDiff = 0; + for (int i = 0; i < 768; ++i) { + diff = ABS(palette[i] - _screenPalette[i]); + maxDiff = MAX<uint8>(maxDiff, diff); + } + + delayInc = delay << 8; + if (maxDiff != 0) + delayInc /= maxDiff; + delayInc &= 0x7FFF; + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 512) + break; + delayInc += delay; + } +} + +int Screen::fadePalStep(const uint8 *palette, int diff) { + debugC(9, kDebugLevelScreen, "Screen::fadePalStep(%p, %d)", (const void *)palette, diff); + + uint8 fadePal[768]; + memcpy(fadePal, _screenPalette, 768); + + bool needRefresh = false; + for (int i = 0; i < 768; ++i) { + int c1 = palette[i]; + int c2 = fadePal[i]; + if (c1 != c2) { + needRefresh = true; + if (c1 > c2) { + c2 += diff; + if (c1 < c2) + c2 = c1; + } + + if (c1 < c2) { + c2 -= diff; + if (c1 > c2) + c2 = c1; + } + + fadePal[i] = (uint8)c2; + } + } + + if (needRefresh) + setScreenPalette(fadePal); + + return needRefresh ? 1 : 0; +} + void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) { debugC(9, kDebugLevelScreen, "Screen::setPaletteIndex(%u, %u, %u, %u)", index, red, green, blue); _currentPalette[index * 3 + 0] = red; @@ -2432,7 +2459,7 @@ void Screen::setShapePages(int page1, int page2, int minY, int maxY) { _maskMaxY = maxY; } -void Screen::setMouseCursor(int x, int y, byte *shape) { +void Screen::setMouseCursor(int x, int y, const byte *shape) { debugC(9, kDebugLevelScreen, "Screen::setMouseCursor(%d, %d, %p)", x, y, (const void *)shape); if (!shape) return; diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h index f8c85a2bac..99ba2d7c5f 100644 --- a/engines/kyra/screen.h +++ b/engines/kyra/screen.h @@ -89,10 +89,12 @@ public: enum FontId { FID_6_FNT = 0, FID_8_FNT, + FID_9_FNT, FID_CRED6_FNT, FID_CRED8_FNT, FID_BOOKFONT_FNT, FID_GOLDFONT_FNT, + FID_INTRO_FNT, FID_NUM }; @@ -145,6 +147,8 @@ public: void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); void fadePalette(const uint8 *palData, int delay, const UpdateFunctor *upFunc = 0); + virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff); + int fadePalStep(const uint8 *palette, int diff); void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue); void setScreenPalette(const uint8 *palData); @@ -189,7 +193,7 @@ public: void hideMouse(); void showMouse(); bool isMouseVisible() const; - void setMouseCursor(int x, int y, byte *shape); + void setMouseCursor(int x, int y, const byte *shape); // rect handling virtual int getRectSize(int w, int h) = 0; diff --git a/engines/kyra/screen_lol.cpp b/engines/kyra/screen_lol.cpp new file mode 100644 index 0000000000..c6b47a9ca9 --- /dev/null +++ b/engines/kyra/screen_lol.cpp @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/screen_lol.h" +#include "kyra/lol.h" + +namespace Kyra { + +Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system), _vm(vm) { +} + +void Screen_LoL::setScreenDim(int dim) { + debugC(9, kDebugLevelScreen, "Screen_LoL::setScreenDim(%d)", dim); + assert(dim < _screenDimTableCount); + _curDim = &_screenDimTable[dim]; +} + +const ScreenDim *Screen_LoL::getScreenDim(int dim) { + debugC(9, kDebugLevelScreen, "Screen_LoL::getScreenDim(%d)", dim); + assert(dim < _screenDimTableCount); + return &_screenDimTable[dim]; +} + +void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...) { + debugC(9, kDebugLevelScreen, "Screen_LoL::fprintStringIntro('%s', %d, %d, %d, %d, %d, %d, ...)", format, x, y, c1, c2, c3, flags); + char buffer[400]; + + va_list args; + va_start(args, flags); + vsprintf(buffer, format, args); + va_end(args); + + if ((flags & 0x0F00) == 0x100) + x -= getTextWidth(buffer) >> 1; + if ((flags & 0x0F00) == 0x200) + x -= getTextWidth(buffer); + + if ((flags & 0x00F0) == 0x20) { + printText(buffer, x-1, y, c3, c2); + printText(buffer, x, y+1, c3, c2); + } + + printText(buffer, x, y, c1, c2); +} + +} // end of namespace Kyra + diff --git a/engines/kyra/screen_lol.h b/engines/kyra/screen_lol.h new file mode 100644 index 0000000000..38df3ca897 --- /dev/null +++ b/engines/kyra/screen_lol.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef KYRA_SCREEN_LOL_H +#define KYRA_SCREEN_LOL_H + +#include "kyra/screen_v2.h" + +namespace Kyra { + +class LoLEngine; + +class Screen_LoL : public Screen_v2 { +public: + Screen_LoL(LoLEngine *vm, OSystem *system); + + void setScreenDim(int dim); + const ScreenDim *getScreenDim(int dim); + + void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...); +private: + LoLEngine *_vm; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/screen_v2.cpp b/engines/kyra/screen_v2.cpp index e26ef87bad..c6ea6a93e8 100644 --- a/engines/kyra/screen_v2.cpp +++ b/engines/kyra/screen_v2.cpp @@ -111,6 +111,30 @@ int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *p return r; } +void Screen_v2::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) { + debugC(9, kDebugLevelScreen, "Screen_v2::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff); + + int maxDiff = 0; + diff = 0; + for (int i = 0; i < 768; ++i) { + diff = ABS(palette[i] - _screenPalette[i]); + maxDiff = MAX(maxDiff, diff); + } + + delayInc = delay << 8; + if (maxDiff != 0) { + delayInc /= maxDiff; + delayInc = MIN(delayInc, 0x7FFF); + } + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 256) + break; + delayInc += delay; + } +} + void Screen_v2::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) { uint8 *dstPtr = getPagePtr(_curPage); @@ -369,7 +393,7 @@ void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2, int t = (nb * h1) / h2; if (t != u) { u = t; - const uint8 *s = src + (x1 + t) * 320; + const uint8 *s = src + x1 + t * 320; uint8 *dt = (uint8 *)_wsaFrameAnimBuffer; t = w2 - w1; @@ -461,5 +485,27 @@ bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, i return (w1 == -1) ? false : true; } +void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) { + debugC(9, kDebugLevelScreen, "Screen_v2::checkedPageUpdate(%d, %d)", srcPage, dstPage); + + const uint32 *src = (const uint32 *)getPagePtr(srcPage); + uint32 *dst = (uint32 *)getPagePtr(dstPage); + uint32 *page0 = (uint32 *)getPagePtr(0); + + bool updated = false; + + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) { + if (*src != *dst) { + updated = true; + *dst = *page0 = *src; + } + } + } + + if (updated) + addDirtyRect(0, 0, 320, 200); +} + } // end of namespace Kyra diff --git a/engines/kyra/screen_v2.h b/engines/kyra/screen_v2.h index f624228445..7bbdc4b6c3 100644 --- a/engines/kyra/screen_v2.h +++ b/engines/kyra/screen_v2.h @@ -40,10 +40,14 @@ public: void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2); + void checkedPageUpdate(int srcPage, int dstPage); + // palette handling uint8 *generateOverlay(const uint8 *palette, uint8 *buffer, int color, uint16 factor); void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay); int findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *palette, uint16 numColors); + + virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff); // shape handling uint8 *getPtrToShape(uint8 *shpFile, int shape); diff --git a/engines/kyra/script.cpp b/engines/kyra/script.cpp index ef3e259cfa..b10a4b32bf 100644 --- a/engines/kyra/script.cpp +++ b/engines/kyra/script.cpp @@ -35,7 +35,7 @@ namespace Kyra { EMCInterpreter::EMCInterpreter(KyraEngine_v1 *vm) : _vm(vm) { #define COMMAND(x) { &EMCInterpreter::x, #x } - static CommandEntry commandProcs[] = { + static const CommandEntry commandProcs[] = { // 0x00 COMMAND(cmd_jmpTo), COMMAND(cmd_setRetValue), @@ -132,6 +132,8 @@ bool EMCInterpreter::load(const char *filename, EMCData *scriptData, const Commo scriptData->opcodes = opcodes; + strncpy(scriptData->filename, filename, 13); + return true; } @@ -205,7 +207,7 @@ bool EMCInterpreter::run(EMCState *script) { } if (opcode > 18) { - error("Script unknown command: %d", opcode); + error("Script unknown command: %d in file '%s' at offset 0x%.08X", opcode, script->dataPtr->filename, instOffset); } else { debugC(5, kDebugLevelScript, "[0x%.08X] EMCInterpreter::%s([%d/%u])", instOffset, _commands[opcode].desc, _parameter, (uint)_parameter); (this->*(_commands[opcode].proc))(script); @@ -388,7 +390,7 @@ void EMCInterpreter::cmd_execOpcode(EMCState* script) { script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script); } else { script->retValue = 0; - warning("calling unimplemented opcode(0x%.02X/%d)", opcode, opcode); + warning("Calling unimplemented opcode(0x%.02X/%d) from file '%s'", opcode, opcode, script->dataPtr->filename); } } diff --git a/engines/kyra/script.h b/engines/kyra/script.h index de52093f66..2b97a83289 100644 --- a/engines/kyra/script.h +++ b/engines/kyra/script.h @@ -36,6 +36,8 @@ struct EMCState; typedef Common::Functor1<EMCState*, int> Opcode; struct EMCData { + char filename[13]; + byte *text; uint16 *data; uint16 *ordr; diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp index 91fbfb3e49..e3a8bf95bc 100644 --- a/engines/kyra/script_hof.cpp +++ b/engines/kyra/script_hof.cpp @@ -799,10 +799,14 @@ int KyraEngine_HoF::o2_showLetter(EMCState *script) { _screen->fadeToBlack(0x14); - sprintf(filename, "LETTER%.1d.", letter); - strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT"); - + sprintf(filename, "LETTER%.1d.%s", letter, _languageExtension[_lang]); uint8 *letterBuffer = _res->fileData(filename, 0); + if (!letterBuffer) { + // some floppy versions use a TXT extension + sprintf(filename, "LETTER%.1d.TXT", letter); + letterBuffer = _res->fileData(filename, 0); + } + if (letterBuffer) { bookDecodeText(letterBuffer); bookPrintText(2, letterBuffer, 0xC, 0xA, 0x20); @@ -1488,7 +1492,7 @@ typedef Common::Functor1Mem<EMCState*, int, KyraEngine_HoF> OpcodeV2; typedef Common::Functor2Mem<const TIM*, const uint16*, int, KyraEngine_HoF> TIMOpcodeV2; #define OpcodeTim(x) _timOpcodes.push_back(new TIMOpcodeV2(this, &KyraEngine_HoF::x)) -#define OpcodeTimUnImpl() _timOpcodes.push_back(TIMOpcodeV2(this, 0)) +#define OpcodeTimUnImpl() _timOpcodes.push_back(new TIMOpcodeV2(this, 0)) void KyraEngine_HoF::setupOpcodeTable() { Common::Array<const Opcode*> *table = 0; diff --git a/engines/kyra/script_tim.cpp b/engines/kyra/script_tim.cpp index 4ad6464424..7993fb8de6 100644 --- a/engines/kyra/script_tim.cpp +++ b/engines/kyra/script_tim.cpp @@ -26,58 +26,75 @@ #include "kyra/script_tim.h" #include "kyra/script.h" #include "kyra/resource.h" +#include "kyra/sound.h" +#include "kyra/wsamovie.h" #include "common/endian.h" namespace Kyra { -TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _system(system), _currentTim(0) { +TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system) : _vm(vm), _screen(screen), _system(system), _currentTim(0) { #define COMMAND(x) { &TIMInterpreter::x, #x } #define COMMAND_UNIMPL() { 0, 0 } - static CommandEntry commandProcs[] = { +#define cmd_return(n) cmd_return_##n + static const CommandEntry commandProcs[] = { // 0x00 COMMAND(cmd_initFunc0), COMMAND(cmd_stopCurFunc), - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), + COMMAND(cmd_initWSA), + COMMAND(cmd_uninitWSA), // 0x04 COMMAND(cmd_initFunc), COMMAND(cmd_stopFunc), - COMMAND_UNIMPL(), + COMMAND(cmd_wsaDisplayFrame), COMMAND_UNIMPL(), // 0x08 - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), + COMMAND(cmd_loadVocFile), + COMMAND(cmd_unloadVocFile), + COMMAND(cmd_playVocFile), COMMAND_UNIMPL(), // 0x0C - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), + COMMAND(cmd_loadSoundFile), + COMMAND(cmd_return(1)), + COMMAND(cmd_playMusicTrack), COMMAND_UNIMPL(), // 0x10 - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), + COMMAND(cmd_return(1)), + COMMAND(cmd_return(1)), COMMAND_UNIMPL(), COMMAND_UNIMPL(), // 0x14 - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), - COMMAND_UNIMPL(), + COMMAND(cmd_setLoopIp), + COMMAND(cmd_continueLoop), + COMMAND(cmd_resetLoopIp), COMMAND(cmd_resetAllRuntimes), // 0x18 - COMMAND(cmd_return<1>), + COMMAND(cmd_return(1)), COMMAND(cmd_execOpcode), COMMAND(cmd_initFuncNow), COMMAND(cmd_stopFuncNow), // 0x1C - COMMAND(cmd_return<1>), - COMMAND(cmd_return<1>), - COMMAND(cmd_return<-1>) + COMMAND(cmd_return(1)), + COMMAND(cmd_return(1)), + COMMAND(cmd_return(n1)) }; +#undef cmd_return _commands = commandProcs; _commandsSize = ARRAYSIZE(commandProcs); + + memset(&_animations, 0, sizeof(_animations)); + _langData = 0; + _textDisplayed = false; + _textAreaBuffer = new uint8[320*40]; + assert(_textAreaBuffer); + + _palDelayInc = _palDiff = _palDelayAcc = 0; +} + +TIMInterpreter::~TIMInterpreter() { + delete[] _langData; + delete[] _textAreaBuffer; } TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes) { @@ -122,6 +139,8 @@ TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpc for (int i = 0; i < num; ++i) tim->func[i].avtl = tim->avtl + tim->avtl[i]; + strncpy(tim->filename, filename, 13); + return tim; } @@ -135,6 +154,11 @@ void TIMInterpreter::unload(TIM *&tim) const { tim = 0; } +void TIMInterpreter::setLangData(const char *filename) { + delete[] _langData; + _langData = _vm->resource()->fileData(filename, 0); +} + void TIMInterpreter::exec(TIM *tim, bool loop) { if (!tim) return; @@ -171,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) { _currentTim->procFunc = _currentFunc; break; + case 22: + cur.loopIp = 0; + break; + default: break; } @@ -197,22 +225,224 @@ void TIMInterpreter::refreshTimersAfterPause(uint32 elapsedTime) { } } +void TIMInterpreter::displayText(uint16 textId, int16 flags) { + char *text = getTableEntry(textId); + + if (_textDisplayed) { + _screen->copyBlockToPage(0, 0, 160, 320, 40, _textAreaBuffer); + _textDisplayed = false; + } + + if (!text) + return; + if (!text[0]) + return; + + char filename[16]; + memset(filename, 0, sizeof(filename)); + + if (text[0] == '$') { + const char *end = strchr(text+1, '$'); + if (end) + memcpy(filename, text+1, end-1-text); + } + + if (filename[0]) + _vm->sound()->voicePlay(filename); + + if (text[0] == '$') + text = strchr(text + 1, '$') + 1; + + setupTextPalette((flags < 0) ? 1 : flags, 0); + + if (flags < 0) { + static const uint8 colorMap[] = { 0x00, 0xF0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + _screen->setFont(Screen::FID_8_FNT); + _screen->setTextColorMap(colorMap); + _screen->_charWidth = -2; + } + + _screen->_charOffset = -4; + _screen->copyRegionToBuffer(0, 0, 160, 320, 40, _textAreaBuffer); + _textDisplayed = true; + + char backupChar = 0; + char *str = text; + int heightAdd = 0; + + while (str[0]) { + char *nextLine = strchr(str, '\r'); + + backupChar = 0; + if (nextLine) { + backupChar = nextLine[0]; + nextLine[0] = '\0'; + } + + int width = _screen->getTextWidth(str); + + if (flags >= 0) + _screen->printText(str, (320 - width) >> 1, 160 + heightAdd, 0xF0, 0x00); + else + _screen->printText(str, (320 - width) >> 1, 188, 0xF0, 0x00); + + heightAdd += _screen->getFontHeight(); + str += strlen(str); + + if (backupChar) { + nextLine[0] = backupChar; + ++str; + } + } + + _screen->_charOffset = 0; + + if (flags < 0) { + static const uint8 colorMap[] = { 0x00, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x00, 0x00, 0x00, 0x00 }; + + _screen->setFont(Screen::FID_INTRO_FNT); + _screen->setTextColorMap(colorMap); + _screen->_charWidth = 0; + } +} + +void TIMInterpreter::setupTextPalette(uint index, int fadePalette) { + static const uint16 palTable[] = { + 0x00, 0x00, 0x00, + 0x64, 0x64, 0x64, + 0x61, 0x51, 0x30, + 0x29, 0x48, 0x64, + 0x00, 0x4B, 0x3B, + 0x64, 0x1E, 0x1E, + }; + + for (int i = 0; i < 15; ++i) { + uint8 *palette = _screen->getPalette(0) + (240 + i) * 3; + + uint8 c1 = (((15 - i) << 2) * palTable[index*3+0]) / 100; + uint8 c2 = (((15 - i) << 2) * palTable[index*3+1]) / 100; + uint8 c3 = (((15 - i) << 2) * palTable[index*3+2]) / 100; + + palette[0] = c1; + palette[1] = c2; + palette[2] = c3; + } + + if (!fadePalette && !_palDiff) { + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + _screen->getFadeParams(_screen->getPalette(0), fadePalette, _palDelayInc, _palDiff); + _palDelayAcc = 0; + } +} + +TIMInterpreter::Animation *TIMInterpreter::initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags) { + Animation *anim = &_animations[index]; + anim->x = x; + anim->y = y; + anim->wsaCopyParams = wsaFlags; + + uint16 wsaOpenFlags = ((wsaFlags & 0x10) != 0) ? 2 : 0; + + char file[32]; + snprintf(file, 32, "%s.WSA", filename); + + if (_vm->resource()->exists(file)) { + anim->wsa = new WSAMovie_v2(_vm, _screen); + assert(anim->wsa); + + anim->wsa->open(file, wsaOpenFlags, (index == 1) ? _screen->getPalette(0) : 0); + } + + if (anim->wsa && anim->wsa->opened()) { + if (x == -1) + anim->x = x = 0; + if (y == -1) + anim->y = y = 0; + + if (wsaFlags & 2) { + _screen->fadePalette(_screen->getPalette(1), 15, 0); + _screen->clearPage(8); + _screen->checkedPageUpdate(8, 4); + _screen->updateScreen(); + } + + if (wsaFlags & 4) { + snprintf(file, 32, "%s.CPS", filename); + + if (_vm->resource()->exists(file)) { + _screen->loadBitmap(file, 3, 3, _screen->getPalette(0)); + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK); + _screen->checkedPageUpdate(8, 4); + _screen->updateScreen(); + } + + anim->wsa->setX(x); + anim->wsa->setY(y); + anim->wsa->setDrawPage(0); + anim->wsa->displayFrame(0, 0, 0, 0); + } + + if (wsaFlags & 2) + _screen->fadePalette(_screen->getPalette(0), 30, 0); + } else { + if (wsaFlags & 2) { + _screen->fadePalette(_screen->getPalette(1), 15, 0); + _screen->clearPage(8); + _screen->checkedPageUpdate(8, 4); + _screen->updateScreen(); + } + + snprintf(file, 32, "%s.CPS", filename); + + if (_vm->resource()->exists(file)) { + _screen->loadBitmap(file, 3, 3, _screen->getPalette(0)); + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK); + _screen->checkedPageUpdate(8, 4); + _screen->updateScreen(); + } + + if (wsaFlags & 2) + _screen->fadePalette(_screen->getPalette(0), 30, 0); + } + + return anim; +} + +char *TIMInterpreter::getTableEntry(uint idx) { + if (!_langData) + return 0; + else + return (char *)(_langData + READ_LE_UINT16(_langData + (idx<<1))); +} + +const char *TIMInterpreter::getCTableEntry(uint idx) const { + if (!_langData) + return 0; + else + return (const char *)(_langData + READ_LE_UINT16(_langData + (idx<<1))); +} + int TIMInterpreter::execCommand(int cmd, const uint16 *param) { if (cmd < 0 || cmd >= _commandsSize) { - warning("Calling unimplemented TIM command %d", cmd); + warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename); return 0; } if (_commands[cmd].proc == 0) { - warning("Calling unimplemented TIM command %d", cmd); + warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename); return 0; } - debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void*)param); + debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void* )param); return (this->*_commands[cmd].proc)(param); } int TIMInterpreter::cmd_initFunc0(const uint16 *param) { + for (int i = 0; i < TIM::kWSASlots; ++i) + memset(&_currentTim->wsa[i], 0, sizeof(TIM::WSASlot)); + _currentTim->func[0].ip = _currentTim->func[0].avtl; _currentTim->func[0].lastTime = _system->getMillis(); return 1; @@ -226,6 +456,46 @@ int TIMInterpreter::cmd_stopCurFunc(const uint16 *param) { return -2; } +int TIMInterpreter::cmd_initWSA(const uint16 *param) { + const int index = param[0]; + + TIM::WSASlot &slot = _currentTim->wsa[index]; + + slot.x = int16(param[2]); + slot.y = int16(param[3]); + slot.offscreen = param[4]; + slot.wsaFlags = param[5]; + const char *filename = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[1]<<1))); + + slot.anim = initAnimStruct(index, filename, slot.x, slot.y, 10, slot.offscreen, slot.wsaFlags); + return 1; +} + +int TIMInterpreter::cmd_uninitWSA(const uint16 *param) { + const int index = param[0]; + + TIM::WSASlot &slot = _currentTim->wsa[index]; + + if (!slot.anim) + return 0; + + Animation &anim = _animations[index]; + + if (slot.offscreen) { + delete anim.wsa; + anim.wsa = 0; + slot.anim = 0; + } else { + //XXX + + delete anim.wsa; + memset(&anim, 0, sizeof(Animation)); + memset(&slot, 0, sizeof(TIM::WSASlot)); + } + + return 1; +} + int TIMInterpreter::cmd_initFunc(const uint16 *param) { uint16 func = *param; assert(func < TIM::kCountFuncs); @@ -243,6 +513,97 @@ int TIMInterpreter::cmd_stopFunc(const uint16 *param) { return 1; } +int TIMInterpreter::cmd_wsaDisplayFrame(const uint16 *param) { + Animation &anim = _animations[param[0]]; + const int frame = param[1]; + + anim.wsa->setX(anim.x); + anim.wsa->setY(anim.y); + anim.wsa->setDrawPage((anim.wsaCopyParams & 0x4000) != 0 ? 2 : 8); + anim.wsa->displayFrame(frame, anim.wsaCopyParams & 0xF0FF, 0, 0); + return 1; +} + +int TIMInterpreter::cmd_displayText(const uint16 *param) { + displayText(param[0], param[1]); + return 1; +} + +int TIMInterpreter::cmd_loadVocFile(const uint16 *param) { + const int stringId = param[0]; + const int index = param[1]; + + _vocFiles[index] = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (stringId << 1))); + for (int i = 0; i < 4; ++i) + _vocFiles[index].deleteLastChar(); + return 1; +} + +int TIMInterpreter::cmd_unloadVocFile(const uint16 *param) { + const int index = param[0]; + _vocFiles[index].clear(); + return 1; +} + +int TIMInterpreter::cmd_playVocFile(const uint16 *param) { + const int index = param[0]; + const int volume = (param[1] * 255) / 100; + + if (index < ARRAYSIZE(_vocFiles) && !_vocFiles[index].empty()) + _vm->sound()->voicePlay(_vocFiles[index].c_str()/*, volume*/, true); + else + _vm->snd_playSoundEffect(index, volume); + + return 1; +} + +int TIMInterpreter::cmd_loadSoundFile(const uint16 *param) { + const char *file = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[0]<<1))); + + static char * fileList[] = { 0 }; + fileList[0] = _audioFilename; + static AudioDataStruct audioList = { fileList, 1, 0, 0 }; + + strncpy(_audioFilename, file, sizeof(_audioFilename)); + + _vm->sound()->setSoundList(&audioList); + _vm->sound()->loadSoundFile(0); + return 1; +} + +int TIMInterpreter::cmd_playMusicTrack(const uint16 *param) { + _vm->sound()->playTrack(param[0]); + return 1; +} + +int TIMInterpreter::cmd_setLoopIp(const uint16 *param) { + _currentTim->func[_currentFunc].loopIp = _currentTim->func[_currentFunc].ip; + return 1; +} + +int TIMInterpreter::cmd_continueLoop(const uint16 *param) { + TIM::Function &func = _currentTim->func[_currentFunc]; + + if (!func.loopIp) + return -2; + + func.ip = func.loopIp; + + uint16 factor = param[0]; + if (factor) { + const uint32 random = _vm->_rnd.getRandomNumberRng(0, 0x8000); + uint32 waitTime = (random * factor) / 0x8000; + func.nextTime += waitTime * _vm->tickLength(); + } + + return 1; +} + +int TIMInterpreter::cmd_resetLoopIp(const uint16 *param) { + _currentTim->func[_currentFunc].loopIp = 0; + return 1; +} + int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) { for (int i = 0; i < TIM::kCountFuncs; ++i) { if (_currentTim->func[i].ip) @@ -252,14 +613,20 @@ int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) { } int TIMInterpreter::cmd_execOpcode(const uint16 *param) { + const uint16 opcode = *param++; + if (!_currentTim->opcodes) { - warning("Trying to execute TIM opcode without opcode list"); + warning("Trying to execute TIM opcode %d without opcode list (file '%s')", opcode, _currentTim->filename); return 0; } - uint16 opcode = *param++; if (opcode > _currentTim->opcodes->size()) { - warning("Calling unimplemented TIM opcode(0x%.02X/%d)", opcode, opcode); + warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename); + return 0; + } + + if (!(*_currentTim->opcodes)[opcode]->isValid()) { + warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename); return 0; } diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h index cd715ff4ef..68ef23fd6c 100644 --- a/engines/kyra/script_tim.h +++ b/engines/kyra/script_tim.h @@ -30,13 +30,18 @@ #include "common/array.h" #include "common/func.h" +#include "common/str.h" namespace Kyra { +class WSAMovie_v2; +class Screen_v2; struct TIM; typedef Common::Functor2<const TIM*, const uint16*, int> TIMOpcode; struct TIM { + char filename[13]; + int16 procFunc; uint16 procParam; @@ -50,9 +55,23 @@ struct TIM { uint32 lastTime; uint32 nextTime; + const uint16 *loopIp; + const uint16 *avtl; } func[kCountFuncs]; + enum { + kWSASlots = 10 + }; + + struct WSASlot { + void *anim; + + int16 x, y; + uint16 wsaFlags; + uint16 offscreen; + } wsa[kWSASlots]; + uint16 *avtl; uint8 *text; @@ -61,10 +80,22 @@ struct TIM { class TIMInterpreter { public: - TIMInterpreter(KyraEngine_v1 *vm, OSystem *system); + struct Animation { + WSAMovie_v2 *wsa; + int16 x, y; + uint16 wsaCopyParams; + }; + + TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system); + ~TIMInterpreter(); TIM *load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes); void unload(TIM *&tim) const; + + void setLangData(const char *filename); + void clearLangData() { delete[] _langData; _langData = 0; } + + const char *getCTableEntry(uint idx) const; void resetFinishedFlag() { _finished = false; } bool finished() const { return _finished; } @@ -72,10 +103,15 @@ public: void exec(TIM *tim, bool loop); void stopCurFunc() { if (_currentTim) cmd_stopCurFunc(0); } - void play(const char *filename); void refreshTimersAfterPause(uint32 elapsedTime); + + void displayText(uint16 textId, int16 flags); + void setupTextPalette(uint index, int fadePalette); + + int _palDelayInc, _palDiff, _palDelayAcc; private: KyraEngine_v1 *_vm; + Screen_v2 *_screen; OSystem *_system; TIM *_currentTim; @@ -83,6 +119,19 @@ private: bool _finished; + Common::String _vocFiles[120]; + + Animation _animations[TIM::kWSASlots]; + + Animation *initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags); + + char _audioFilename[32]; + + uint8 *_langData; + char *getTableEntry(uint idx); + bool _textDisplayed; + uint8 *_textAreaBuffer; + int execCommand(int cmd, const uint16 *param); typedef int (TIMInterpreter::*CommandProc)(const uint16 *); @@ -96,14 +145,30 @@ private: int cmd_initFunc0(const uint16 *param); int cmd_stopCurFunc(const uint16 *param); + int cmd_initWSA(const uint16 *param); + int cmd_uninitWSA(const uint16 *param); int cmd_initFunc(const uint16 *param); int cmd_stopFunc(const uint16 *param); + int cmd_wsaDisplayFrame(const uint16 *param); + int cmd_displayText(const uint16 *param); + int cmd_loadVocFile(const uint16 *param); + int cmd_unloadVocFile(const uint16 *param); + int cmd_playVocFile(const uint16 *param); + int cmd_loadSoundFile(const uint16 *param); + int cmd_playMusicTrack(const uint16 *param); + int cmd_setLoopIp(const uint16 *param); + int cmd_continueLoop(const uint16 *param); + int cmd_resetLoopIp(const uint16 *param); int cmd_resetAllRuntimes(const uint16 *param); int cmd_execOpcode(const uint16 *param); int cmd_initFuncNow(const uint16 *param); int cmd_stopFuncNow(const uint16 *param); - template<int T> - int cmd_return(const uint16 *) { return T; } +#define cmd_return(n, v) \ + int cmd_return_##n(const uint16 *) { return v; } + + cmd_return( 1, 1); + cmd_return(n1, -1); +#undef cmd_return }; } // end of namespace Kyra diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp index 73d69ef10c..dfda5bf859 100644 --- a/engines/kyra/seqplayer.cpp +++ b/engines/kyra/seqplayer.cpp @@ -500,7 +500,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) { debugC(9, kDebugLevelSequence, "SeqPlayer::seq_playSequence(%p, %d)", (const void *)seqData, skipSeq); assert(seqData); - static SeqEntry floppySeqProcs[] = { + static const SeqEntry floppySeqProcs[] = { // 0x00 SEQOP(3, s1_wsaOpen), SEQOP(2, s1_wsaClose), @@ -541,7 +541,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) { SEQOP(1, s1_endOfScript) }; - static SeqEntry cdromSeqProcs[] = { + static const SeqEntry cdromSeqProcs[] = { // 0x00 SEQOP(3, s1_wsaOpen), SEQOP(2, s1_wsaClose), diff --git a/engines/kyra/sequences_lok.cpp b/engines/kyra/sequences_lok.cpp index b30568c7e2..3a497a258f 100644 --- a/engines/kyra/sequences_lok.cpp +++ b/engines/kyra/sequences_lok.cpp @@ -1083,7 +1083,7 @@ void KyraEngine_LoK::seq_playCredits() { _screen->_charWidth = -1; // we only need this for the fm-towns version - if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) + if (_flags.platform == Common::kPlatformFMTowns && _configMusic == 1) snd_playWanderScoreViaMap(53, 1); uint8 *buffer = 0; diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index f56c43aabd..c8749dc06b 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -202,8 +202,8 @@ bool SoundMidiPC::init() { } void SoundMidiPC::updateVolumeSettings() { - _musicVolume = ConfMan.getInt("music_volume"); - _sfxVolume = ConfMan.getInt("sfx_volume"); + _musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255); + _sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255); updateChannelVolume(_musicVolume); } @@ -243,27 +243,14 @@ int SoundMidiPC::open() { } void SoundMidiPC::close() { - if (_driver) + if (_driver) { _driver->close(); + delete _driver; + } _driver = 0; } void SoundMidiPC::send(uint32 b) { - // HACK: For Kyrandia, we make the simplifying assumption that a song - // either loops in its entirety, or not at all. So if we see a FOR_LOOP - // controller event, we turn on looping even if there isn't any - // corresponding NEXT_BREAK event. - // - // This is a gross over-simplification of how XMIDI handles loops. If - // anyone feels like doing a proper implementation, please refer to - // the Exult project, and do it in midiparser_xmidi.cpp - - if ((b & 0xFFF0) == 0x74B0 && _eventFromMusic) { - debugC(9, kDebugLevelMain | kDebugLevelSound, "SoundMidiPC: Looping song"); - _musicParser->property(MidiParser::mpAutoLoop, true); - return; - } - if (_passThrough) { if ((b & 0xFFF0) == 0x007BB0) return; diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index 1baeb3064a..cebfdf491f 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -73,7 +73,8 @@ public: kAdlib, kMidiMT32, kMidiGM, - kTowns + kTowns, + kPC98 }; virtual kType getMusicType() const = 0; @@ -382,7 +383,9 @@ private: Common::Mutex _mutex; }; -class SoundTowns_EuphonyDriver; +class Towns_EuphonyDriver; +class TownsPC98_OpnDriver; + class SoundTowns : public MidiDriver, public Sound { public: SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer); @@ -417,6 +420,7 @@ public: static float semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey, uint32 sampleRate, uint32 outputRate, int32 pitchWheel); + private: bool loadInstruments(); void playEuphonyTrack(uint32 offset, int loop); @@ -430,7 +434,7 @@ private: uint _sfxFileIndex; uint8 *_sfxFileData; - SoundTowns_EuphonyDriver * _driver; + Towns_EuphonyDriver * _driver; MidiParser * _parser; Common::Mutex _mutex; @@ -439,13 +443,38 @@ private: const uint8 *_sfxWDTable; }; -//class SoundTowns_v2_TwnDriver; -class SoundTowns_v2 : public Sound { +class SoundPC98 : public Sound { public: - SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); - ~SoundTowns_v2(); + SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundPC98(); - kType getMusicType() const { return kTowns; } + virtual kType getMusicType() const { return kPC98; } + + bool init(); + + void process() {} + void loadSoundFile(uint file) {} + + void playTrack(uint8 track); + void haltTrack(); + void beginFadeOut(); + + int32 voicePlay(const char *file, bool isSfx = false) { return -1; } + void playSoundEffect(uint8); + +protected: + int _lastTrack; + uint8 *_musicTrackData; + uint8 *_sfxTrackData; + TownsPC98_OpnDriver *_driver; +}; + +class SoundTownsPC98_v2 : public Sound { +public: + SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundTownsPC98_v2(); + + kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; } bool init(); void process(); @@ -457,15 +486,15 @@ public: void beginFadeOut(); int32 voicePlay(const char *file, bool isSfx = false); - void playSoundEffect(uint8) {} - -private: - int _lastTrack; + void playSoundEffect(uint8 track); +protected: Audio::AudioStream *_currentSFX; + int _lastTrack; + bool _useFmSfx; - //SoundTowns_v2_TwnDriver *_driver; - uint8 *_twnTrackData; + uint8 *_musicTrackData; + TownsPC98_OpnDriver *_driver; }; class MixedSoundDriver : public Sound { diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index 68a2f0be9c..0ceb288b8a 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -235,6 +235,10 @@ private: // * One for instruments, starting at offset 500. uint8 *getProgram(int progId) { + uint16 offset = READ_LE_UINT16(_soundData + 2 * progId); + //TODO: Check in LoL CD Adlib driver + if (offset == 0xFFFF) + return 0; return _soundData + READ_LE_UINT16(_soundData + 2 * progId); } @@ -1282,6 +1286,9 @@ int AdlibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 va return 0; uint8 *ptr = getProgram(value); + //TODO: Check in LoL CD Adlib driver + if (!ptr) + return 0; uint8 chan = *ptr++; uint8 priority = *ptr++; @@ -2213,7 +2220,7 @@ const int SoundAdlibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdlibPC::_kyra1S SoundAdlibPC::SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) { memset(_trackEntries, 0, sizeof(_trackEntries)); - _v2 = (_vm->gameFlags().gameID == GI_KYRA2); + _v2 = (_vm->gameFlags().gameID == GI_KYRA2) || (_vm->gameFlags().gameID == GI_LOL); _driver = new AdlibDriver(mixer, _v2); assert(_driver); diff --git a/engines/kyra/sound_lok.cpp b/engines/kyra/sound_lok.cpp index 8a1d16a6b1..b43d72ebce 100644 --- a/engines/kyra/sound_lok.cpp +++ b/engines/kyra/sound_lok.cpp @@ -43,19 +43,29 @@ void KyraEngine_LoK::snd_playWanderScoreViaMap(int command, int restart) { if (restart) _lastMusicCommand = -1; - if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { + if (_flags.platform == Common::kPlatformFMTowns) { if (command == 1) { _sound->beginFadeOut(); } else if (command >= 35 && command <= 38) { snd_playSoundEffect(command-20); } else if (command >= 2) { - if (_lastMusicCommand != command) { + if (_lastMusicCommand != command) // the original does -2 here we handle this inside _sound->playTrack() _sound->playTrack(command); - } } else { _sound->haltTrack(); } + _lastMusicCommand = command; + } else if (_flags.platform == Common::kPlatformPC98) { + if (command == 1) { + _sound->beginFadeOut(); + } else if (command >= 2) { + if (_lastMusicCommand != command) + _sound->playTrack(command); + } else { + _sound->haltTrack(); + } + _lastMusicCommand = command; } else { KyraEngine_v1::snd_playWanderScoreViaMap(command, restart); } diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp index 4265533507..0f2b916c9d 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -34,18 +34,16 @@ #include "common/util.h" -#include <math.h> - #define EUPHONY_FADEOUT_TICKS 600 namespace Kyra { -enum ChannelState { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing }; +enum EnvelopeState { s_ready, s_attacking, s_decaying, s_sustaining, s_releasing }; -class MidiChannel_EuD : public MidiChannel { +class Towns_EuphonyChannel : public MidiChannel { public: - MidiChannel_EuD() {} - ~MidiChannel_EuD() {} + Towns_EuphonyChannel() {} + ~Towns_EuphonyChannel() {} virtual void nextTick(int32 *outbuf, int buflen) = 0; virtual void rate(uint16 r) = 0; @@ -54,10 +52,10 @@ protected: uint16 _rate; }; -class MidiChannel_EuD_FM : public MidiChannel_EuD { +class Towns_EuphonyFmChannel : public Towns_EuphonyChannel { public: - MidiChannel_EuD_FM(); - virtual ~MidiChannel_EuD_FM(); + Towns_EuphonyFmChannel(); + virtual ~Towns_EuphonyFmChannel(); void nextTick(int32 *outbuf, int buflen); void rate(uint16 r); @@ -79,13 +77,13 @@ protected: Voice2612 *_voice; }; -class MidiChannel_EuD_WAVE : public MidiChannel_EuD { +class Towns_EuphonyPcmChannel : public Towns_EuphonyChannel { public: void nextTick(int32 *outbuf, int buflen); void rate(uint16 r); - MidiChannel_EuD_WAVE(); - virtual ~MidiChannel_EuD_WAVE(); + Towns_EuphonyPcmChannel(); + virtual ~Towns_EuphonyPcmChannel(); // MidiChannel interface MidiDriver *device() { return 0; } @@ -126,9 +124,9 @@ protected: int32 keyOffset; int32 keyNote; const int8 *_samples; - } * _snd[8]; + } *_snd[8]; struct Env { - ChannelState state; + EnvelopeState state; int32 currentLevel; int32 rate; int32 tickCount; @@ -141,40 +139,39 @@ protected: int32 releaseRate; int32 rootKeyOffset; int32 size; - } * _env[8]; - } * _voice; + } *_env[8]; + } *_voice; }; -class SoundTowns_EuphonyTrackQueue { +class Towns_EuphonyTrackQueue { public: - SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver *driver, SoundTowns_EuphonyTrackQueue *last); - ~SoundTowns_EuphonyTrackQueue() {} + Towns_EuphonyTrackQueue(Towns_EuphonyDriver *driver, Towns_EuphonyTrackQueue *last); + ~Towns_EuphonyTrackQueue() {} - void release(); + Towns_EuphonyTrackQueue *release(); void initDriver(); - void loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop = 0); - void loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop = 0); + void loadDataToCurrentPosition(uint8 *trackdata, uint32 size, bool loop = 0); + void loadDataToEndOfQueue(uint8 *trackdata, uint32 size, bool loop = 0); void setPlayBackStatus(bool playing); - SoundTowns_EuphonyTrackQueue * reset(); bool isPlaying() {return _playing; } - uint8 * trackData() {return _trackData; } + uint8 *trackData() {return _trackData; } bool _loop; - SoundTowns_EuphonyTrackQueue * _next; + Towns_EuphonyTrackQueue *_next; private: - uint8 * _trackData; - uint8 * _used; - uint8 * _fchan; - uint8 * _wchan; + uint8 *_trackData; + uint8 *_used; + uint8 *_fchan; + uint8 *_wchan; bool _playing; - SoundTowns_EuphonyDriver * _driver; - SoundTowns_EuphonyTrackQueue * _last; + Towns_EuphonyDriver *_driver; + Towns_EuphonyTrackQueue *_last; }; -class MidiParser_EuD : public MidiParser { +class Towns_EuphonyParser : public MidiParser { public: - MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue); + Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue); bool loadMusic (byte *data, uint32 size); int32 calculateTempo(int16 val); @@ -183,11 +180,11 @@ protected: void resetTracking(); void setup(); - byte * _enable; - byte * _mode; - byte * _channel; - byte * _adjVelo; - int8 * _adjNote; + byte *_enable; + byte *_mode; + byte *_channel; + byte *_adjVelo; + int8 *_adjNote; uint8 _firstBaseTickStep; uint8 _nextBaseTickStep; @@ -195,13 +192,13 @@ protected: uint32 _baseTick; byte _tempo[3]; - SoundTowns_EuphonyTrackQueue * _queue; + Towns_EuphonyTrackQueue *_queue; }; -class SoundTowns_EuphonyDriver : public MidiDriver_Emulated { +class Towns_EuphonyDriver : public MidiDriver_Emulated { public: - SoundTowns_EuphonyDriver(Audio::Mixer *mixer); - virtual ~SoundTowns_EuphonyDriver(); + Towns_EuphonyDriver(Audio::Mixer *mixer); + virtual ~Towns_EuphonyDriver(); int open(); void close(); @@ -213,7 +210,7 @@ public: void loadFmInstruments(const byte *instr); void loadWaveInstruments(const byte *instr); - SoundTowns_EuphonyTrackQueue * queue() { return _queue; } + Towns_EuphonyTrackQueue *queue() { return _queue; } MidiChannel *allocateChannel() { return 0; } MidiChannel *getPercussionChannel() { return 0; } @@ -237,10 +234,10 @@ protected: void generateSamples(int16 *buf, int len); - MidiChannel_EuD_FM *_fChannel[6]; - MidiChannel_EuD_WAVE *_wChannel[8]; - MidiChannel_EuD * _channel[16]; - SoundTowns_EuphonyTrackQueue * _queue; + Towns_EuphonyFmChannel *_fChannel[6]; + Towns_EuphonyPcmChannel *_wChannel[8]; + Towns_EuphonyChannel *_channel[16]; + Towns_EuphonyTrackQueue *_queue; int _volume; bool _fading; @@ -251,23 +248,23 @@ protected: int8 * _waveSounds[10]; }; -MidiChannel_EuD_FM::MidiChannel_EuD_FM() { +Towns_EuphonyFmChannel::Towns_EuphonyFmChannel() { _voice = new Voice2612; } -MidiChannel_EuD_FM::~MidiChannel_EuD_FM() { +Towns_EuphonyFmChannel::~Towns_EuphonyFmChannel() { delete _voice; } -void MidiChannel_EuD_FM::noteOn(byte note, byte onVelo) { +void Towns_EuphonyFmChannel::noteOn(byte note, byte onVelo) { _voice->noteOn(note, onVelo); } -void MidiChannel_EuD_FM::noteOff(byte note) { +void Towns_EuphonyFmChannel::noteOff(byte note) { _voice->noteOff(note); } -void MidiChannel_EuD_FM::controlChange(byte control, byte value) { +void Towns_EuphonyFmChannel::controlChange(byte control, byte value) { if (control == 121) { // Reset controller delete _voice; @@ -279,25 +276,25 @@ void MidiChannel_EuD_FM::controlChange(byte control, byte value) { } } -void MidiChannel_EuD_FM::sysEx_customInstrument(uint32, const byte *fmInst) { +void Towns_EuphonyFmChannel::sysEx_customInstrument(uint32, const byte *fmInst) { _voice->_rate = _rate; _voice->setInstrument(fmInst); } -void MidiChannel_EuD_FM::pitchBend(int16 value) { +void Towns_EuphonyFmChannel::pitchBend(int16 value) { _voice->pitchBend(value); } -void MidiChannel_EuD_FM::nextTick(int32 *outbuf, int buflen) { +void Towns_EuphonyFmChannel::nextTick(int32 *outbuf, int buflen) { _voice->nextTick((int*) outbuf, buflen); } -void MidiChannel_EuD_FM::rate(uint16 r) { +void Towns_EuphonyFmChannel::rate(uint16 r) { _rate = r; _voice->_rate = r; } -MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() { +Towns_EuphonyPcmChannel::Towns_EuphonyPcmChannel() { _voice = new Voice; for (uint8 i = 0; i < 8; i++) { _voice->_env[i] = new Voice::Env; @@ -310,7 +307,7 @@ MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() { _current = -1; } -MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() { +Towns_EuphonyPcmChannel::~Towns_EuphonyPcmChannel() { for (uint8 i = 0; i < 8; i++) { if (_voice->_snd[i]) delete _voice->_snd[i]; @@ -319,7 +316,7 @@ MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() { delete _voice; } -void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) { +void Towns_EuphonyPcmChannel::noteOn(byte note, byte onVelo) { _note = note; velocity(onVelo); _phase = 0; @@ -329,24 +326,24 @@ void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) { break; } - _voice->_env[_current]->state = _s_attacking; + _voice->_env[_current]->state = s_attacking; _voice->_env[_current]->currentLevel = 0; _voice->_env[_current]->rate = _rate; _voice->_env[_current]->tickCount = 0; } -void MidiChannel_EuD_WAVE::noteOff(byte note) { +void Towns_EuphonyPcmChannel::noteOff(byte note) { if (_current == -1) return; - if (_voice->_env[_current]->state == _s_ready) + if (_voice->_env[_current]->state == s_ready) return; - _voice->_env[_current]->state = _s_releasing; + _voice->_env[_current]->state = s_releasing; _voice->_env[_current]->releaseLevel = _voice->_env[_current]->currentLevel; _voice->_env[_current]->tickCount = 0; } -void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) { +void Towns_EuphonyPcmChannel::controlChange(byte control, byte value) { switch (control) { case 0x07: // volume @@ -377,7 +374,7 @@ void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) { } } -void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmInst) { +void Towns_EuphonyPcmChannel::sysEx_customInstrument(uint32 type, const byte *fmInst) { if (type == 0x80) { for (uint8 i = 0; i < 8; i++) { const byte * const* pos = (const byte * const*) fmInst; @@ -406,7 +403,7 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns _voice->split[i] = READ_LE_UINT16(fmInst + 16 + 2 * i); _voice->id[i] = READ_LE_UINT32(fmInst + 32 + 4 * i); _voice->_snd[i] = 0; - _voice->_env[i]->state = _s_ready; + _voice->_env[i]->state = s_ready; _voice->_env[i]->currentLevel = 0; _voice->_env[i]->totalLevel = *(fmInst + 64 + 8 * i); _voice->_env[i]->attackRate = *(fmInst + 65 + 8 * i) * 10; @@ -419,11 +416,11 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns } } -void MidiChannel_EuD_WAVE::pitchBend(int16 value) { +void Towns_EuphonyPcmChannel::pitchBend(int16 value) { _frequencyOffs = value; } -void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) { +void Towns_EuphonyPcmChannel::nextTick(int32 *outbuf, int buflen) { if (_current == -1 || !_voice->_snd[_current] || !_voice->_env[_current]->state || !_velocity) { velocity(0); _current = -1; @@ -475,13 +472,13 @@ void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) { } } -void MidiChannel_EuD_WAVE::evpNextTick() { +void Towns_EuphonyPcmChannel::evpNextTick() { switch (_voice->_env[_current]->state) { - case _s_ready: + case s_ready: _voice->_env[_current]->currentLevel = 0; return; - case _s_attacking: + case s_attacking: if (_voice->_env[_current]->attackRate == 0) _voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel; else if (_voice->_env[_current]->attackRate >= 1270) @@ -493,12 +490,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() { if (_voice->_env[_current]->currentLevel >= _voice->_env[_current]->totalLevel) { _voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel; - _voice->_env[_current]->state = _s_decaying; + _voice->_env[_current]->state = s_decaying; _voice->_env[_current]->tickCount = 0; } break; - case _s_decaying: + case s_decaying: if (_voice->_env[_current]->decayRate == 0) _voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel; else if (_voice->_env[_current]->decayRate >= 1270) @@ -512,12 +509,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() { if (_voice->_env[_current]->currentLevel <= _voice->_env[_current]->sustainLevel) { _voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel; - _voice->_env[_current]->state = _s_sustaining; + _voice->_env[_current]->state = s_sustaining; _voice->_env[_current]->tickCount = 0; } break; - case _s_sustaining: + case s_sustaining: if (_voice->_env[_current]->sustainRate == 0) _voice->_env[_current]->currentLevel = 0; else if (_voice->_env[_current]->sustainRate >= 2540) @@ -531,12 +528,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() { if (_voice->_env[_current]->currentLevel <= 0) { _voice->_env[_current]->currentLevel = 0; - _voice->_env[_current]->state = _s_ready; + _voice->_env[_current]->state = s_ready; _voice->_env[_current]->tickCount = 0; } break; - case _s_releasing: + case s_releasing: if (_voice->_env[_current]->releaseRate == 0) _voice->_env[_current]->currentLevel = 0; else if (_voice->_env[_current]->releaseRate >= 1270) @@ -550,7 +547,7 @@ void MidiChannel_EuD_WAVE::evpNextTick() { if (_voice->_env[_current]->currentLevel <= 0) { _voice->_env[_current]->currentLevel = 0; - _voice->_env[_current]->state = _s_ready; + _voice->_env[_current]->state = s_ready; } break; @@ -559,15 +556,15 @@ void MidiChannel_EuD_WAVE::evpNextTick() { } } -void MidiChannel_EuD_WAVE::rate(uint16 r) { +void Towns_EuphonyPcmChannel::rate(uint16 r) { _rate = r; } -void MidiChannel_EuD_WAVE::velocity(int velo) { +void Towns_EuphonyPcmChannel::velocity(int velo) { _velocity = velo; } -SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer) +Towns_EuphonyDriver::Towns_EuphonyDriver(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { _volume = 255; _fadestate = EUPHONY_FADEOUT_TICKS; @@ -576,9 +573,9 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer) MidiDriver_YM2612::createLookupTables(); for (uint8 i = 0; i < 6; i++) - _channel[i] = _fChannel[i] = new MidiChannel_EuD_FM; + _channel[i] = _fChannel[i] = new Towns_EuphonyFmChannel; for (uint8 i = 0; i < 8; i++) - _channel[i + 6] = _wChannel[i] = new MidiChannel_EuD_WAVE; + _channel[i + 6] = _wChannel[i] = new Towns_EuphonyPcmChannel; _channel[14] = _channel[15] = 0; _fmInstruments = _waveInstruments = 0; @@ -587,10 +584,10 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer) rate(getRate()); fading(0); - _queue = new SoundTowns_EuphonyTrackQueue(this, 0); + _queue = new Towns_EuphonyTrackQueue(this, 0); } -SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() { +Towns_EuphonyDriver::~Towns_EuphonyDriver() { for (int i = 0; i < 6; i++) delete _fChannel[i]; for (int i = 0; i < 8; i++) @@ -622,7 +619,7 @@ SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() { } } -int SoundTowns_EuphonyDriver::open() { +int Towns_EuphonyDriver::open() { if (_isOpen) return MERR_ALREADY_OPEN; MidiDriver_Emulated::open(); @@ -633,18 +630,18 @@ int SoundTowns_EuphonyDriver::open() { return 0; } -void SoundTowns_EuphonyDriver::close() { +void Towns_EuphonyDriver::close() { if (!_isOpen) return; _isOpen = false; _mixer->stopHandle(_mixerSoundHandle); } -void SoundTowns_EuphonyDriver::send(uint32 b) { +void Towns_EuphonyDriver::send(uint32 b) { send(b & 0xF, b & 0xFFFFFFF0); } -void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) { +void Towns_EuphonyDriver::send(byte chan, uint32 b) { byte param2 = (byte) ((b >> 16) & 0xFF); byte param1 = (byte) ((b >> 8) & 0xFF); byte cmd = (byte) (b & 0xF0); @@ -703,18 +700,18 @@ void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) { _channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000); break; default: - warning("SoundTowns_EuphonyDriver: Unknown send() command 0x%02X", cmd); + warning("Towns_EuphonyDriver: Unknown send() command 0x%02X", cmd); } } -void SoundTowns_EuphonyDriver::loadFmInstruments(const byte *instr) { +void Towns_EuphonyDriver::loadFmInstruments(const byte *instr) { if (_fmInstruments) delete[] _fmInstruments; _fmInstruments = new uint8[0x1800]; memcpy(_fmInstruments, instr, 0x1800); } -void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) { +void Towns_EuphonyDriver::loadWaveInstruments(const byte *instr) { if (_waveInstruments) delete[] _waveInstruments; _waveInstruments = new uint8[0x1000]; @@ -739,24 +736,24 @@ void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) { } -void SoundTowns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) { +void Towns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) { _channel[midiChannelNumber] = _fChannel[fmChannelNumber]; } -void SoundTowns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) { +void Towns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) { _channel[midiChannelNumber] = _wChannel[waveChannelNumber]; } -void SoundTowns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) { +void Towns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) { _channel[midiChannelNumber] = 0; } -void SoundTowns_EuphonyDriver::generateSamples(int16 *data, int len) { +void Towns_EuphonyDriver::generateSamples(int16 *data, int len) { memset(data, 0, 2 * sizeof(int16) * len); nextTick(data, len); } -void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) { +void Towns_EuphonyDriver::nextTick(int16 *buf1, int buflen) { int32 *buf0 = (int32 *)buf1; for (int i = 0; i < ARRAYSIZE(_channel); i++) { @@ -779,26 +776,26 @@ void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) { } } -void SoundTowns_EuphonyDriver::rate(uint16 r) { +void Towns_EuphonyDriver::rate(uint16 r) { for (uint8 i = 0; i < 16; i++) { if (_channel[i]) _channel[i]->rate(r); } } -void SoundTowns_EuphonyDriver::fading(bool status) { +void Towns_EuphonyDriver::fading(bool status) { _fading = status; if (!_fading) _fadestate = EUPHONY_FADEOUT_TICKS; } -MidiParser_EuD::MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue) : MidiParser(), +Towns_EuphonyParser::Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue) : MidiParser(), _firstBaseTickStep(0x33), _nextBaseTickStep(0x33) { _initialTempo = calculateTempo(0x5a); _queue = queue; } -void MidiParser_EuD::parseNextEvent(EventInfo &info) { +void Towns_EuphonyParser::parseNextEvent(EventInfo &info) { byte *pos = _position._play_pos; if (_queue->_next) { @@ -878,7 +875,7 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) { pos += 6; } } else if (cmd == 0xF2) { - static uint16 tickTable [] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 }; + static const uint16 tickTable[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 }; _baseTick += tickTable[_nextBaseTickStep >> 4] * ((_nextBaseTickStep & 0x0f) + 1); _nextBaseTickStep = pos[1]; pos += 6; @@ -920,15 +917,14 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) { _position._play_pos = pos; } -bool MidiParser_EuD::loadMusic(byte *data, uint32 size) { +bool Towns_EuphonyParser::loadMusic(byte *data, uint32 size) { bool loop = _autoLoop; if (_queue->isPlaying() && !_queue->_loop) { _queue->loadDataToEndOfQueue(data, size, loop); } else { unloadMusic(); - _queue = _queue->reset(); - _queue->release(); + _queue = _queue->release(); _queue->loadDataToCurrentPosition(data, size, loop); setup(); setTrack(0); @@ -937,7 +933,7 @@ bool MidiParser_EuD::loadMusic(byte *data, uint32 size) { return true; } -int32 MidiParser_EuD::calculateTempo(int16 val) { +int32 Towns_EuphonyParser::calculateTempo(int16 val) { int32 tempo = val; if (tempo < 0) @@ -953,7 +949,7 @@ int32 MidiParser_EuD::calculateTempo(int16 val) { return tempo; } -void MidiParser_EuD::resetTracking() { +void Towns_EuphonyParser::resetTracking() { MidiParser::resetTracking(); _nextBaseTickStep = _firstBaseTickStep; @@ -962,7 +958,7 @@ void MidiParser_EuD::resetTracking() { _queue->setPlayBackStatus(false); } -void MidiParser_EuD::setup() { +void Towns_EuphonyParser::setup() { uint8 *data = _queue->trackData(); if (!data) return; @@ -984,7 +980,7 @@ void MidiParser_EuD::setup() { _tracks[0] = data + 0x806; } -SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver * driver, SoundTowns_EuphonyTrackQueue * last) { +Towns_EuphonyTrackQueue::Towns_EuphonyTrackQueue(Towns_EuphonyDriver * driver, Towns_EuphonyTrackQueue * last) { _trackData = 0; _next = 0; _driver = driver; @@ -993,22 +989,15 @@ SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDri _playing = false; } -void SoundTowns_EuphonyTrackQueue::setPlayBackStatus(bool playing) { - SoundTowns_EuphonyTrackQueue * i = this; +void Towns_EuphonyTrackQueue::setPlayBackStatus(bool playing) { + Towns_EuphonyTrackQueue * i = this; do { i->_playing = playing; i = i->_next; } while (i); } -SoundTowns_EuphonyTrackQueue * SoundTowns_EuphonyTrackQueue::reset() { - SoundTowns_EuphonyTrackQueue * i = this; - while (i->_last) - i = i->_last; - return i; -} - -void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) { +void Towns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) { if (_trackData) delete[] _trackData; _trackData = new uint8[0xC58A]; @@ -1022,17 +1011,17 @@ void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, _playing = false; } -void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) { +void Towns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) { if (!_trackData) { loadDataToCurrentPosition(trackdata, size, loop); return; } - SoundTowns_EuphonyTrackQueue * i = this; + Towns_EuphonyTrackQueue * i = this; while (i->_next) i = i->_next; - i = i->_next = new SoundTowns_EuphonyTrackQueue(_driver, i); + i = i->_next = new Towns_EuphonyTrackQueue(_driver, i); i->_trackData = new uint8[0xC58A]; memset(i->_trackData, 0, 0xC58A); Screen::decodeFrame4(trackdata, i->_trackData, size); @@ -1044,29 +1033,39 @@ void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint3 i->_playing = _playing; } -void SoundTowns_EuphonyTrackQueue::release() { - SoundTowns_EuphonyTrackQueue * i = _next; - _next = 0; - _playing = false; - _used = _fchan = _wchan = 0; +Towns_EuphonyTrackQueue *Towns_EuphonyTrackQueue::release() { + Towns_EuphonyTrackQueue *i = this; + while (i->_next) + i = i->_next; - if (_trackData) { - delete[] _trackData; - _trackData = 0; - } + Towns_EuphonyTrackQueue *res = i; while (i) { + i->_playing = false; + i->_used = i->_fchan = i->_wchan = 0; if (i->_trackData) { delete[] i->_trackData; i->_trackData = 0; } - i = i->_next; - if (i) - delete i->_last; + i = i->_last; + if (i) { + res = i; + if (i->_next) { + delete i->_next; + i->_next = 0; + } + } } + + if (res->_trackData) { + delete[] res->_trackData; + res->_trackData = 0; + } + + return res; } -void SoundTowns_EuphonyTrackQueue::initDriver() { +void Towns_EuphonyTrackQueue::initDriver() { for (uint8 i = 0; i < 6; i++) { if (_used[_fchan[i]]) _driver->assignFmChannel(_fchan[i], i); @@ -1084,11 +1083,1685 @@ void SoundTowns_EuphonyTrackQueue::initDriver() { _driver->send(0x79B0); } +class TownsPC98_OpnOperator { +public: + TownsPC98_OpnOperator(double rate, const uint8 *rateTable, + const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable, + const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable); + ~TownsPC98_OpnOperator() {} + + void keyOn(); + void keyOff(); + void frequency(int freq); + void updatePhaseIncrement(); + void recalculateRates(); + void generateOutput(int phasebuf, int *_feedbuf, int &out); + + void feedbackLevel(int32 level) {_feedbackLevel = level ? level + 6 : 0; } + void detune(int value) { _detn = &_detnTbl[value << 5]; } + void multiple(uint32 value) { _multiple = value ? (value << 1) : 1; } + void attackRate(uint32 value) { _specifiedAttackRate = value; } + bool scaleRate(uint8 value); + void decayRate(uint32 value) { _specifiedDecayRate = value; recalculateRates(); } + void sustainRate(uint32 value) { _specifiedSustainRate = value; recalculateRates(); } + void sustainLevel(uint32 value) { _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; } + void releaseRate(uint32 value) { _specifiedReleaseRate = value; recalculateRates(); } + void totalLevel(uint32 value) { _totalLevel = value << 3; } + void reset(); + +protected: + EnvelopeState _state; + uint32 _feedbackLevel; + uint32 _multiple; + uint32 _totalLevel; + uint8 _keyScale1; + uint8 _keyScale2; + uint32 _specifiedAttackRate; + uint32 _specifiedDecayRate; + uint32 _specifiedSustainRate; + uint32 _specifiedReleaseRate; + uint32 _tickCount; + uint32 _sustainLevel; + + uint32 _frequency; + uint8 _kcode; + uint32 _phase; + uint32 _phaseIncrement; + const int32 *_detn; + + const uint8 *_rateTbl; + const uint8 *_rshiftTbl; + const uint8 *_adTbl; + const uint32 *_fTbl; + const uint32 *_sinTbl; + const int32 *_tLvlTbl; + const int32 *_detnTbl; + + const double _tickLength; + double _tick; + int32 _currentLevel; + + struct EvpState { + uint8 rate; + uint8 shift; + } fs_a, fs_d, fs_s, fs_r; +}; + +TownsPC98_OpnOperator::TownsPC98_OpnOperator(double rate, const uint8 *rateTable, + const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable, + const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) : + _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), + _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(rate * 65536.0), + _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0), + _phase(0), _state(s_ready) { + + reset(); +} + +void TownsPC98_OpnOperator::keyOn() { + _state = s_attacking; + _phase = 0; +} + +void TownsPC98_OpnOperator::keyOff() { + if (_state != s_ready) + _state = s_releasing; +} + +void TownsPC98_OpnOperator::frequency(int freq) { + uint8 block = (freq >> 11); + uint16 pos = (freq & 0x7ff); + uint8 c = pos >> 7; + _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6 )); + _frequency = _fTbl[pos << 1] >> (7 - block); +} + +void TownsPC98_OpnOperator::updatePhaseIncrement() { + _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1; + uint8 keyscale = _kcode >> _keyScale1; + if (_keyScale2 != keyscale) { + _keyScale2 = keyscale; + recalculateRates(); + } +} + +void TownsPC98_OpnOperator::recalculateRates() { + int k = _keyScale2; + int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; + fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; + fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; + + r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0; + fs_d.rate = _rateTbl[r + k]; + fs_d.shift = _rshiftTbl[r + k]; + + r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0; + fs_s.rate = _rateTbl[r + k]; + fs_s.shift = _rshiftTbl[r + k]; + + r = (_specifiedReleaseRate << 2) + 0x22; + fs_r.rate = _rateTbl[r + k]; + fs_r.shift = _rshiftTbl[r + k]; +} + +void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *_feedbuf, int &out) { + if (_state == s_ready) + return; + + _tick += _tickLength; + while (_tick > 0x30000) { + _tick -= 0x30000; + ++_tickCount; + + int32 levelIncrement = 0; + uint32 targetTime = 0; + int32 targetLevel = 0; + EnvelopeState next_state = s_ready; + + switch (_state) { + case s_ready: + return; + case s_attacking: + next_state = s_decaying; + targetTime = (1 << fs_a.shift) - 1; + targetLevel = 0; + levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4; + break; + case s_decaying: + targetTime = (1 << fs_d.shift) - 1; + next_state = s_sustaining; + targetLevel = _sustainLevel; + levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)]; + break; + case s_sustaining: + targetTime = (1 << fs_s.shift) - 1; + next_state = s_ready; + targetLevel = 1023; + levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)]; + break; + case s_releasing: + targetTime = (1 << fs_r.shift) - 1; + next_state = s_ready; + targetLevel = 1023; + levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)]; + break; + } + + if (!(_tickCount & targetTime)) { + _currentLevel += levelIncrement; + if ((!targetLevel && _currentLevel <= targetLevel) || (targetLevel && _currentLevel >= targetLevel)) { + if (_state != s_decaying) + _currentLevel = targetLevel; + if (_state != s_sustaining) + _state = next_state; + } + } + } + + uint32 lvlout = _totalLevel + (uint32) _currentLevel; + + int outp = 0; + int *i = &outp, *o = &outp; + int phaseShift = 0; + + if (_feedbuf) { + o = &_feedbuf[0]; + i = &_feedbuf[1]; + phaseShift = _feedbackLevel ? ((_feedbuf[0] + _feedbuf[1]) << _feedbackLevel) : 0; + if (phasebuf == -1) + *i = 0; + *o = *i; + } else { + phaseShift = phasebuf << 15; + } + + if (lvlout < 832) { + uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000) + + phaseShift)) >> 16) & 0x3ff]; + *i = ((index < 6656) ? _tLvlTbl[index] : 0); + } else { + *i = 0; + } + + _phase += _phaseIncrement; + out += *o; + if (out > 32767) + out = 32767; + if (out < -32767) + out = -32767; +} + +void TownsPC98_OpnOperator::reset(){ + keyOff(); + _tick = 0; + _keyScale2 = 0; + _currentLevel = 1023; + + frequency(0); + detune(0); + scaleRate(0); + multiple(0); + updatePhaseIncrement(); + attackRate(0); + decayRate(0); + releaseRate(0); + sustainRate(0); + feedbackLevel(0); + totalLevel(127); +} + +bool TownsPC98_OpnOperator::scaleRate(uint8 value) { + value = 3 - value; + if (_keyScale1 != value) { + _keyScale1 = value; + return true; + } + + int k = _keyScale2; + int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0; + fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136; + fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0; + return false; +} + +class TownsPC98_OpnDriver; +class TownsPC98_OpnChannel { +public: + TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, + uint8 key, uint8 prt, uint8 id); + virtual ~TownsPC98_OpnChannel(); + virtual void init(); + + typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para); + + typedef enum channelState { + CHS_RECALCFREQ = 0x01, + CHS_KEYOFF = 0x02, + CHS_SSG = 0x04, + CHS_PITCHWHEELOFF = 0x08, + CHS_ALL_BUT_EOT = 0x0f, + CHS_EOT = 0x80 + } ChannelState; + + virtual void loadData(uint8 *data); + virtual void processEvents(); + virtual void processFrequency(); + bool processControlEvent(uint8 cmd); + void writeReg(uint8 regAdress, uint8 value); + + virtual void keyOn(); + virtual void keyOff(); + + void setOutputLevel(); + void fadeStep(); + void reset(); + + void updateEnv(); + void generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed); + + bool _enableLeft; + bool _enableRight; + bool _updateEnvelopes; + const uint8 _idFlag; + int _feedbuf[3]; + +protected: + bool control_dummy(uint8 para); + bool control_f0_setPatch(uint8 para); + bool control_f1_presetOutputLevel(uint8 para); + bool control_f2_setKeyOffTime(uint8 para); + bool control_f3_setFreqLSB(uint8 para); + bool control_f4_setOutputLevel(uint8 para); + bool control_f5_setTempo(uint8 para); + bool control_f6_repeatSection(uint8 para); + bool control_f7_setupPitchWheel(uint8 para); + bool control_f8_togglePitchWheel(uint8 para); + bool control_fa_writeReg(uint8 para); + bool control_fb_incOutLevel(uint8 para); + bool control_fc_decOutLevel(uint8 para); + bool control_fd_jump(uint8 para); + bool control_ff_endOfTrack(uint8 para); + + bool control_f0_setPatchSSG(uint8 para); + bool control_f1_setTotalLevel(uint8 para); + bool control_f4_setAlgorithm(uint8 para); + bool control_f9_unkSSG(uint8 para); + bool control_fb_incOutLevelSSG(uint8 para); + bool control_fc_decOutLevelSSG(uint8 para); + bool control_ff_endOfTrackSSG(uint8 para); + + uint8 _ticksLeft; + uint8 _algorithm; + uint8 _instrID; + uint8 _totalLevel; + uint8 _frqBlockMSB; + int8 _frqLSB; + uint8 _keyOffTime; + bool _protect; + uint8 *_dataPtr; + uint8 _ptchWhlInitDelayLo; + uint8 _ptchWhlInitDelayHi; + int16 _ptchWhlModInitVal; + uint8 _ptchWhlDuration; + uint8 _ptchWhlCurDelay; + int16 _ptchWhlModCurVal; + uint8 _ptchWhlDurLeft; + uint16 frequency; + uint8 _regOffset; + uint8 _flags; + uint8 _ssg1; + uint8 _ssg2; + + const uint8 _chanNum; + const uint8 _keyNum; + const uint8 _part; + + TownsPC98_OpnDriver *_drv; + TownsPC98_OpnOperator **_opr; + uint16 _frqTemp; + + const ControlEventFunc *controlEvents; +}; + +class TownsPC98_OpnChannelSSG : public TownsPC98_OpnChannel { +public: + TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); + ~TownsPC98_OpnChannelSSG() {} + void init(); + + void processEvents(); + void processFrequency(); + + void keyOn(); + void keyOff(); + void loadData(uint8 *data); + +private: + void opn_SSG_UNK(uint8 a); +}; + + +class TownsPC98_OpnDriver : public Audio::AudioStream { +friend class TownsPC98_OpnChannel; +friend class TownsPC98_OpnChannelSSG; +public: + enum OpnType { + OD_TOWNS, + OD_TYPE26, + OD_TYPE86 + }; + + TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type); + ~TownsPC98_OpnDriver(); + + bool init(); + void loadData(uint8 *data, bool loadPaused = false); + void reset(); + void fadeOut(); + + void pause() { _playing = false; } + void cont() { _playing = true; } + + void callback(); + void nextTick(int16 *buffer, uint32 bufferSize); + + bool looping() { return _looping == _updateChannelsFlag ? true : false; } + + // AudioStream interface + int inline readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return true; } + bool endOfData() const { return false; } + int getRate() const { return _mixer->getOutputRate(); } + +protected: + void generateTables(); + + TownsPC98_OpnChannel **_channels; + TownsPC98_OpnChannelSSG **_ssgChannels; + //TownsPC98_OpnChannel *_adpcmChannel; + + void setTempo(uint8 tempo); + + void lock() { _mutex.lock(); } + void unlock() { _mutex.unlock(); } + + Audio::Mixer *_mixer; + Common::Mutex _mutex; + Audio::SoundHandle _soundHandle; + + const uint8 *_opnCarrier; + const uint8 *_opnFreqTable; + const uint8 *_opnFxCmdLen; + const uint8 *_opnLvlPresets; + + uint8 *_oprRates; + uint8 *_oprRateshift; + uint8 *_oprAttackDecay; + uint32 *_oprFrq; + uint32 *_oprSinTbl; + int32 *_oprLevelOut; + int32 *_oprDetune; + + uint8 *_trackData; + uint8 *_patches; + + uint8 _cbCounter; + uint8 _updateChannelsFlag; + uint8 _finishedChannelsFlag; + uint16 _tempo; + bool _playing; + bool _fading; + uint8 _looping; + uint32 _tickCounter; + + bool _updateEnvelopes; + int _ssgFlag; + + int32 _samplesTillCallback; + int32 _samplesTillCallbackRemainder; + int32 _samplesPerCallback; + int32 _samplesPerCallbackRemainder; + + const int _numChan; + const int _numSSG; + const bool _hasADPCM; + const bool _hasStereo; + + double _baserate; + static const uint8 _drvTables[]; + static const uint32 _adtStat[]; + bool _ready; +}; + +TownsPC98_OpnChannel::TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, + uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key), + _part(prt), _idFlag(id) { + + _ticksLeft = _algorithm = _instrID = _totalLevel = _frqBlockMSB = _keyOffTime = _ssg1 = _ssg2 = 0; + _ptchWhlInitDelayLo = _ptchWhlInitDelayHi = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0; + _frqLSB = 0; + _protect = _updateEnvelopes = false; + _enableLeft = _enableRight = true; + _dataPtr = 0; + _ptchWhlModInitVal = _ptchWhlModCurVal = 0; + frequency = _frqTemp = 0; + memset(&_feedbuf, 0, sizeof(int) * 3); + _opr = 0; +} + +TownsPC98_OpnChannel::~TownsPC98_OpnChannel() { + if (_opr) { + for (int i = 0; i < 4; i++) + delete _opr[i]; + delete [] _opr; + } +} + +void TownsPC98_OpnChannel::init() { + + _opr = new TownsPC98_OpnOperator*[4]; + for (int i = 0; i < 4; i++) + _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift, + _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune); + + #define Control(x) &TownsPC98_OpnChannel::control_##x + static const ControlEventFunc ctrlEvents[] = { + Control(f0_setPatch), + Control(f1_presetOutputLevel), + Control(f2_setKeyOffTime), + Control(f3_setFreqLSB), + Control(f4_setOutputLevel), + Control(f5_setTempo), + Control(f6_repeatSection), + Control(f7_setupPitchWheel), + Control(f8_togglePitchWheel), + Control(dummy), + Control(fa_writeReg), + Control(fb_incOutLevel), + Control(fc_decOutLevel), + Control(fd_jump), + Control(dummy), + Control(ff_endOfTrack) + }; + #undef Control + + controlEvents = ctrlEvents; +} + +void TownsPC98_OpnChannel::keyOff() { + // all operators off + uint8 value = _keyNum & 0x0f; + uint8 regAdress = 0x28; + writeReg(regAdress, value); + _flags |= CHS_KEYOFF; +} + +void TownsPC98_OpnChannel::keyOn() { + // all operators on + uint8 value = _keyNum | 0xf0; + uint8 regAdress = 0x28; + writeReg(regAdress, value); +} + +void TownsPC98_OpnChannel::loadData(uint8 *data) { + _flags = (_flags & ~CHS_EOT) | CHS_ALL_BUT_EOT; + _ticksLeft = 1; + _dataPtr = data; + _totalLevel = 0x7F; + + uint8 *src_b = _dataPtr; + int loop = 1; + uint8 cmd = 0; + while (loop) { + if (loop == 1) { + cmd = *src_b++; + if (cmd < 0xf0) { + src_b++; + loop = 1; + } else { + if (cmd == 0xff) { + loop = *src_b ? 2 : 0; + if (READ_LE_UINT16(src_b)) + _drv->_looping |= _idFlag; + } else if (cmd == 0xf6) { + loop = 3; + } else { + loop = 2; + } + } + } else if (loop == 2) { + src_b += _drv->_opnFxCmdLen[cmd - 240]; + loop = 1; + } else if (loop == 3) { + src_b[0] = src_b[1]; + src_b += 4; + loop = 1; + } + } +} + +void TownsPC98_OpnChannel::processEvents() { + if (_flags & CHS_EOT) + return; + + if (_protect == false && _ticksLeft == _keyOffTime) + keyOff(); + + if (--_ticksLeft) + return; + + if (_protect == false) + keyOff(); + + uint8 cmd = 0; + bool loop = true; + + while (loop) { + cmd = *_dataPtr++; + if (cmd < 0xf0) + loop = false; + else if (!processControlEvent(cmd)) + return; + } + + uint8 para = *_dataPtr++; + + if (cmd == 0x80) { + keyOff(); + _protect = false; + } else { + keyOn(); + + if (_protect == false || cmd != _frqBlockMSB) + _flags |= CHS_RECALCFREQ; + + _protect = (para & 0x80) ? true : false; + _frqBlockMSB = cmd; + } + + _ticksLeft = para & 0x7f; +} + +void TownsPC98_OpnChannel::processFrequency() { + if (_flags & CHS_RECALCFREQ) { + uint8 block = (_frqBlockMSB & 0x70) >> 1; + uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f]; + frequency = (bfreq + _frqLSB) | (block << 8); + + writeReg(_regOffset + 0xa4, (frequency >> 8)); + writeReg(_regOffset + 0xa0, (frequency & 0xff)); + + _ptchWhlCurDelay = _ptchWhlInitDelayHi; + if (_flags & CHS_KEYOFF) { + _ptchWhlModCurVal = _ptchWhlModInitVal; + _ptchWhlCurDelay += _ptchWhlInitDelayLo; + } + + _ptchWhlDurLeft = (_ptchWhlDuration >> 1); + _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); + } + + if (!(_flags & CHS_PITCHWHEELOFF)) { + if (--_ptchWhlCurDelay) + return; + _ptchWhlCurDelay = _ptchWhlInitDelayHi; + frequency += _ptchWhlModCurVal; + + writeReg(_regOffset + 0xa4, (frequency >> 8)); + writeReg(_regOffset + 0xa0, (frequency & 0xff)); + + if(!--_ptchWhlDurLeft) { + _ptchWhlDurLeft = _ptchWhlDuration; + _ptchWhlModCurVal = -_ptchWhlModCurVal; + } + } +} + +bool TownsPC98_OpnChannel::processControlEvent(uint8 cmd) { + uint8 para = *_dataPtr++; + return (this->*controlEvents[cmd & 0x0f])(para); +} + +void TownsPC98_OpnChannel::setOutputLevel() { + uint8 outopr = _drv->_opnCarrier[_algorithm]; + uint8 reg = 0x40 + _regOffset; + + for (int i = 0; i < 4; i++) { + if (outopr & 1) + writeReg(reg, _totalLevel); + outopr >>= 1; + reg += 4; + } +} + +void TownsPC98_OpnChannel::fadeStep() { + _totalLevel += 3; + if (_totalLevel > 0x7f) + _totalLevel = 0x7f; + setOutputLevel(); +} + +void TownsPC98_OpnChannel::reset() { + for (int i = 0; i < 4; i++) + _opr[i]->reset(); + + _updateEnvelopes = false; + _enableLeft = _enableRight = true; + memset(&_feedbuf, 0, sizeof(int) * 3); +} + +void TownsPC98_OpnChannel::updateEnv() { + for (int i = 0; i < 4 ; i++) + _opr[i]->updatePhaseIncrement(); +} + +void TownsPC98_OpnChannel::generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed) { + int phbuf1, phbuf2, output; + phbuf1 = phbuf2 = output = 0; + + switch (_algorithm) { + case 0: + _opr[0]->generateOutput(0, feed, phbuf1); + _opr[2]->generateOutput(*del, 0, phbuf2); + *del = 0; + _opr[1]->generateOutput(phbuf1, 0, *del); + _opr[3]->generateOutput(phbuf2, 0, output); + break; + case 1: + _opr[0]->generateOutput(0, feed, phbuf1); + _opr[2]->generateOutput(*del, 0, phbuf2); + _opr[1]->generateOutput(0, 0, phbuf1); + _opr[3]->generateOutput(phbuf2, 0, output); + *del = phbuf1; + break; + case 2: + _opr[0]->generateOutput(0, feed, phbuf2); + _opr[2]->generateOutput(*del, 0, phbuf2); + _opr[1]->generateOutput(0, 0, phbuf1); + _opr[3]->generateOutput(phbuf2, 0, output); + *del = phbuf1; + break; + case 3: + _opr[0]->generateOutput(0, feed, phbuf2); + _opr[2]->generateOutput(0, 0, *del); + _opr[1]->generateOutput(phbuf2, 0, phbuf1); + _opr[3]->generateOutput(*del, 0, output); + *del = phbuf1; + break; + case 4: + _opr[0]->generateOutput(0, feed, phbuf1); + _opr[2]->generateOutput(0, 0, phbuf2); + _opr[1]->generateOutput(phbuf1, 0, output); + _opr[3]->generateOutput(phbuf2, 0, output); + *del = 0; + break; + case 5: + *del = feed[1]; + _opr[0]->generateOutput(-1, feed, phbuf1); + _opr[2]->generateOutput(*del, 0, output); + _opr[1]->generateOutput(*del, 0, output); + _opr[3]->generateOutput(*del, 0, output); + break; + case 6: + _opr[0]->generateOutput(0, feed, phbuf1); + _opr[2]->generateOutput(0, 0, output); + _opr[1]->generateOutput(phbuf1, 0, output); + _opr[3]->generateOutput(0, 0, output); + *del = 0; + break; + case 7: + _opr[0]->generateOutput(0, feed, output); + _opr[2]->generateOutput(0, 0, output); + _opr[1]->generateOutput(0, 0, output); + _opr[3]->generateOutput(0, 0, output); + *del = 0; + break; + }; + + if (_enableLeft) { + int l = output + leftSample; + if (l > 32767) + l = 32767; + if (l < -32767) + l = -32767; + leftSample = (int16) l; + } + + if (_enableRight) { + int r = output + rightSample; + if (r > 32767) + r = 32767; + if (r < -32767) + r = -32767; + rightSample = (int16) r; + } +} + +void TownsPC98_OpnChannel::writeReg(uint8 regAdress, uint8 value) { + uint8 h = regAdress & 0xf0; + uint8 l = (regAdress & 0x0f); + static const uint8 oprOrdr[] = { 0, 2, 1, 3 }; + uint8 o = oprOrdr[(l - _regOffset) >> 2]; + + switch (h) { + case 0x00: + // ssg + warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); + break; + case 0x10: + // adpcm + warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); + break; + case 0x20: + if (l == 8) { + // Key on/off + for (int i = 0; i < 4; i++) { + if ((value >> (4 + i)) & 1) + _opr[i]->keyOn(); + else + _opr[i]->keyOff(); + } + } else if (l == 2) { + // LFO + warning("TownsPC98_OpnDriver: TRYING TO USE LFO (NOT SUPPORTED)"); + } else if (l == 7) { + // Timers; Ch 3/6 special mode + warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE (NOT SUPPORTED)"); + } else if (l == 4 || l == 5) { + // Timer A + warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_A (NOT SUPPORTED)"); + } else if (l == 6) { + // Timer B + warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_B (NOT SUPPORTED)"); + } else if (l == 10 || l == 11) { + // DAC + warning("TownsPC98_OpnDriver: TRYING TO USE DAC (NOT SUPPORTED)"); + } + break; + + case 0x30: + // detune, multiple + _opr[o]->detune((value >> 4) & 7); + _opr[o]->multiple(value & 0x0f); + _updateEnvelopes = true; + break; + + case 0x40: + // total level + _opr[o]->totalLevel(value & 0x7f); + break; + + case 0x50: + // rate scaling, attack rate + _opr[o]->attackRate(value & 0x1f); + if (_opr[o]->scaleRate(value >> 6)) + _updateEnvelopes = true; + break; + + case 0x60: + // first decay rate, amplitude modulation + _opr[o]->decayRate(value & 0x1f); + if (value & 0x80) + warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION (NOT SUPPORTED)"); + + break; + + case 0x70: + // secondary decay rate + _opr[o]->sustainRate(value & 0x1f); + break; + + case 0x80: + // secondary amplitude, release rate; + _opr[o]->sustainLevel(value >> 4); + _opr[o]->releaseRate(value & 0x0f); + break; + + case 0x90: + // ssg + warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); + break; + + case 0xa0: + // frequency + l -= _regOffset; + if (l == 0) { + _frqTemp = (_frqTemp & 0xff00) | value; + _updateEnvelopes = true; + for (int i = 0; i < 4; i++) + _opr[i]->frequency(_frqTemp); + } else if (l == 4) { + _frqTemp = (_frqTemp & 0xff) | (value << 8); + } else if (l == 8) { + // Ch 3/6 special mode frq + warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); + } else if (l == 12) { + // Ch 3/6 special mode frq + warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)"); + } + break; + + case 0xb0: + l -= _regOffset; + if (l == 0) { + // feedback, _algorithm + _opr[0]->feedbackLevel((value >> 3) & 7); + _opr[1]->feedbackLevel(0); + _opr[2]->feedbackLevel(0); + _opr[3]->feedbackLevel(0); + } else if (l == 4) { + // stereo, LFO sensitivity + _enableLeft = value & 0x80 ? true : false; + _enableRight = value & 0x40 ? true : false; + uint8 ams = (value & 0x3F) >> 3; + if (ams) + warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION SENSITIVITY (NOT SUPPORTED)"); + uint8 fms = value & 3; + if (fms) + warning("TownsPC98_OpnDriver: TRYING TO USE FREQ MODULATION SENSITIVITY (NOT SUPPORTED)"); + } + break; + + default: + warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress); + break; + } +} + +bool TownsPC98_OpnChannel::control_f0_setPatch(uint8 para) { + _instrID = para; + uint8 reg = _regOffset + 0x80; + + for (int i = 0; i < 4; i++) { + // set release rate for each operator + writeReg(reg, 0x0f); + reg += 4; + } + + const uint8 *tptr = _drv->_patches + ((uint32)_instrID << 5); + reg = _regOffset + 0x30; + + // write registers 0x30 to 0x8f + for (int i = 0; i < 6; i++) { + writeReg(reg, tptr[0]); + reg += 4; + writeReg(reg, tptr[2]); + reg += 4; + writeReg(reg, tptr[1]); + reg += 4; + writeReg(reg, tptr[3]); + reg += 4; + tptr += 4; + } + + reg = _regOffset + 0xB0; + _algorithm = tptr[0] & 7; + // set feedback and algorithm + writeReg(reg, tptr[0]); + + setOutputLevel(); + return true; +} + +bool TownsPC98_OpnChannel::control_f1_presetOutputLevel(uint8 para) { + if (_drv->_fading) + return true; + + _totalLevel = _drv->_opnLvlPresets[para]; + setOutputLevel(); + return true; +} + +bool TownsPC98_OpnChannel::control_f2_setKeyOffTime(uint8 para) { + _keyOffTime = para; + return true; +} + +bool TownsPC98_OpnChannel::control_f3_setFreqLSB(uint8 para) { + _frqLSB = (int8) para; + return true; +} + +bool TownsPC98_OpnChannel::control_f4_setOutputLevel(uint8 para) { + if (_drv->_fading) + return true; + + _totalLevel = para; + setOutputLevel(); + return true; +} + +bool TownsPC98_OpnChannel::control_f5_setTempo(uint8 para) { + _drv->setTempo(para); + return true; +} + +bool TownsPC98_OpnChannel::control_f6_repeatSection(uint8 para) { + _dataPtr--; + _dataPtr[0]--; + + if (*_dataPtr) { + // repeat section until counter has reached zero + _dataPtr = _drv->_trackData + READ_LE_UINT16(_dataPtr + 2); + } else { + // reset counter, advance to next section + _dataPtr[0] = _dataPtr[1]; + _dataPtr += 4; + } + return true; +} + +bool TownsPC98_OpnChannel::control_f7_setupPitchWheel(uint8 para) { + _ptchWhlInitDelayLo = _dataPtr[0]; + _ptchWhlInitDelayHi = para; + _ptchWhlModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1); + _ptchWhlDuration = _dataPtr[3]; + _dataPtr += 4; + _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF | CHS_RECALCFREQ; + return true; +} + +bool TownsPC98_OpnChannel::control_f8_togglePitchWheel(uint8 para) { + if (para == 0x10) { + if (*_dataPtr++) { + _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF; + } else { + _flags |= CHS_PITCHWHEELOFF; + } + } else { + //uint8 skipChannels = para / 36; + //uint8 entry = para % 36; + //TownsPC98_OpnDriver::TownsPC98_OpnChannel *t = &chan[skipChannels]; + ////// NOT IMPLEMENTED + //t->unnamedEntries[entry] = *_dataPtr++; + } + return true; +} + +bool TownsPC98_OpnChannel::control_fa_writeReg(uint8 para) { + writeReg(para, *_dataPtr++); + return true; +} + +bool TownsPC98_OpnChannel::control_fb_incOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + uint8 val = (_totalLevel + 3); + if (val > 0x7f) + val = 0x7f; + + _totalLevel = val; + setOutputLevel(); + return true; +} + +bool TownsPC98_OpnChannel::control_fc_decOutLevel(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + int8 val = (int8) (_totalLevel - 3); + if (val < 0) + val = 0; + + _totalLevel = (uint8) val; + setOutputLevel(); + return true; +} + +bool TownsPC98_OpnChannel::control_fd_jump(uint8 para) { + uint8 *tmp = _drv->_trackData + READ_LE_UINT16(_dataPtr - 1); + _dataPtr = (tmp[1] == 1) ? tmp : ++_dataPtr; + return true; +} + +bool TownsPC98_OpnChannel::control_dummy(uint8 para) { + _dataPtr--; + return true; +} + +bool TownsPC98_OpnChannel::control_ff_endOfTrack(uint8 para) { + uint16 val = READ_LE_UINT16(--_dataPtr); + if (val) { + // loop + _dataPtr = _drv->_trackData + val; + return true; + } else { + // quit parsing for active channel + --_dataPtr; + _flags |= CHS_EOT; + _drv->_finishedChannelsFlag |= _idFlag; + keyOff(); + return false; + } +} + +bool TownsPC98_OpnChannel::control_f0_setPatchSSG(uint8 para) { + _instrID = para << 4; + para = (para >> 3) & 0x1e; + if (para) + return control_f4_setAlgorithm(para | 0x40); + return true; +} + +bool TownsPC98_OpnChannel::control_f1_setTotalLevel(uint8 para) { + if (!_drv->_fading) + _totalLevel = para; + return true; +} + +bool TownsPC98_OpnChannel::control_f4_setAlgorithm(uint8 para) { + _algorithm = para; + return true; +} + +bool TownsPC98_OpnChannel::control_f9_unkSSG(uint8 para) { + _dataPtr += 5; + return true; +} + +bool TownsPC98_OpnChannel::control_fb_incOutLevelSSG(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + _totalLevel--; + if ((int8)_totalLevel < 0) + _totalLevel = 0; + + return true; +} + +bool TownsPC98_OpnChannel::control_fc_decOutLevelSSG(uint8 para) { + _dataPtr--; + if (_drv->_fading) + return true; + + if(_totalLevel + 1 < 0x10) + _totalLevel++; + + return true; +} + +bool TownsPC98_OpnChannel::control_ff_endOfTrackSSG(uint8 para) { + uint16 val = READ_LE_UINT16(--_dataPtr); + if (val) { + // loop + _dataPtr = _drv->_trackData + val; + return true; + } else { + // quit parsing for active channel + --_dataPtr; + _flags |= CHS_EOT; + //_finishedChannelsFlag |= _idFlag; + keyOff(); + return false; + } +} + +TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs, + uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : + TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) { +} + +void TownsPC98_OpnChannelSSG::init() { + _algorithm = 0x80; + + _opr = new TownsPC98_OpnOperator*[4]; + for (int i = 0; i < 4; i++) + _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift, + _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune); + + #define Control(x) &TownsPC98_OpnChannelSSG::control_##x + static const ControlEventFunc ctrlEventsSSG[] = { + Control(f0_setPatchSSG), + Control(f1_setTotalLevel), + Control(f2_setKeyOffTime), + Control(f3_setFreqLSB), + Control(f4_setOutputLevel), + Control(f5_setTempo), + Control(f6_repeatSection), + Control(f7_setupPitchWheel), + Control(f8_togglePitchWheel), + Control(f9_unkSSG), + Control(fa_writeReg), + Control(fb_incOutLevelSSG), + Control(fc_decOutLevelSSG), + Control(fd_jump), + Control(dummy), + Control(ff_endOfTrackSSG) + }; + #undef Control + + controlEvents = ctrlEventsSSG; +} + +void TownsPC98_OpnChannelSSG::processEvents() { + if (_flags & CHS_EOT) + return; + + _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0; + + if (_protect == false && _ticksLeft == _keyOffTime) + keyOff(); + + if (--_ticksLeft) + return; + + if (_protect == false) + keyOff(); + + uint8 cmd = 0; + bool loop = true; + + while (loop) { + cmd = *_dataPtr++; + if (cmd < 0xf0) + loop = false; + else if (!processControlEvent(cmd)) + return; + } + + uint8 para = *_dataPtr++; + + if (cmd == 0x80) { + keyOff(); + _protect = false; + } else { + keyOn(); + + if (_protect == false || cmd != _frqBlockMSB) + _flags |= CHS_RECALCFREQ; + + _protect = (para & 0x80) ? true : false; + _frqBlockMSB = cmd; + } + + _ticksLeft = para & 0x7f; + + if (!(_flags & CHS_SSG)) { + + } +} + +void TownsPC98_OpnChannelSSG::processFrequency() { + if (_flags & CHS_RECALCFREQ) { + uint8 block = (_frqBlockMSB & 0x70) >> 1; + uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f]; + frequency = (bfreq + _frqLSB) | (block << 8); + + writeReg(_regOffset + 0xa4, (frequency >> 8)); + writeReg(_regOffset + 0xa0, (frequency & 0xff)); + + _ptchWhlCurDelay = _ptchWhlInitDelayHi; + if (_flags & CHS_KEYOFF) { + _ptchWhlModCurVal = _ptchWhlModInitVal; + _ptchWhlCurDelay += _ptchWhlInitDelayLo; + } + + _ptchWhlDurLeft = (_ptchWhlDuration >> 1); + _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); + } + + if (!(_flags & CHS_PITCHWHEELOFF)) { + if (--_ptchWhlCurDelay) + return; + _ptchWhlCurDelay = _ptchWhlInitDelayHi; + frequency += _ptchWhlModCurVal; + + writeReg(_regOffset + 0xa4, (frequency >> 8)); + writeReg(_regOffset + 0xa0, (frequency & 0xff)); + + if(!--_ptchWhlDurLeft) { + _ptchWhlDurLeft = _ptchWhlDuration; + _ptchWhlModCurVal = -_ptchWhlModCurVal; + } + } +} + +void TownsPC98_OpnChannelSSG::keyOff() { + // all operators off + uint8 value = _keyNum & 0x0f; + uint8 regAdress = 0x28; + writeReg(regAdress, value); + _flags |= CHS_KEYOFF; +} + +void TownsPC98_OpnChannelSSG::keyOn() { + // all operators on + uint8 value = _keyNum | 0xf0; + uint8 regAdress = 0x28; + writeReg(regAdress, value); +} + +void TownsPC98_OpnChannelSSG::loadData(uint8 *data) { + _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0; + opn_SSG_UNK(0); + TownsPC98_OpnChannel::loadData(data); + _algorithm = 0x80; +} + +void TownsPC98_OpnChannelSSG::opn_SSG_UNK(uint8 a) { + _ssg1 = a; + uint16 h = (_totalLevel + 1) * a; + if ((h >> 8) == _ssg2) + return; + _ssg2 = (h >> 8); + writeReg(8 + _regOffset, _ssg2); +} + +TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) : + _mixer(mixer), _trackData(0), _playing(false), _fading(false), _channels(0), _ssgChannels(0), + _looping(0), _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 84), + _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 220)) , + _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), + _oprDetune(0), _cbCounter(4), _tickCounter(0), _updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F), + _finishedChannelsFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0), _ready(false), + _numSSG(type == OD_TOWNS ? 0 : 3), _hasADPCM(type == OD_TYPE86 ? true : false), + _numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true) { + setTempo(84); + _baserate = (double)getRate() / 10368.0; +} + +TownsPC98_OpnDriver::~TownsPC98_OpnDriver() { + _mixer->stopHandle(_soundHandle); + + if (_channels) { + for (int i = 0; i < _numChan; i++) + delete _channels[i]; + delete [] _channels; + } + + if (_ssgChannels) { + for (int i = 0; i < _numSSG; i++) + delete _ssgChannels[i]; + delete [] _ssgChannels; + } + + delete [] _oprRates; + delete [] _oprRateshift; + delete [] _oprFrq; + delete [] _oprAttackDecay; + delete [] _oprSinTbl; + delete [] _oprLevelOut; + delete [] _oprDetune; +} + +bool TownsPC98_OpnDriver::init() { + if (_ready) { + reset(); + return true; + } + + generateTables(); + + if (_channels) { + for (int i = 0; i < _numChan; i++) { + if (_channels[i]) + delete _channels[i]; + } + delete [] _channels; + } + _channels = new TownsPC98_OpnChannel*[_numChan]; + for (int i = 0; i < _numChan; i++) { + int ii = i * 6; + _channels[i] = new TownsPC98_OpnChannel(this, _drvTables[ii], _drvTables[ii + 1], + _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); + _channels[i]->init(); + } + + if (_ssgChannels) { + for (int i = 0; i < _numSSG; i++) { + if (_ssgChannels[i]) + delete _ssgChannels[i]; + } + delete [] _ssgChannels; + } + if (_numSSG) { + _ssgChannels = new TownsPC98_OpnChannelSSG*[_numSSG]; + for (int i = 0; i < _numSSG; i++) { + int ii = i * 6; + _ssgChannels[i] = new TownsPC98_OpnChannelSSG(this, _drvTables[ii], _drvTables[ii + 1], + _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]); + _ssgChannels[i]->init(); + } + } + + _mixer->playInputStream(Audio::Mixer::kMusicSoundType, + &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true); + + _ready = true; + return true; +} + +int inline TownsPC98_OpnDriver::readBuffer(int16 *buffer, const int numSamples) { + memset(buffer, 0, sizeof(int16) * numSamples); + int32 samplesLeft = numSamples >> 1; + while (samplesLeft) { + if (!_samplesTillCallback) { + callback(); + _samplesTillCallback = _samplesPerCallback; + _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; + if (_samplesTillCallbackRemainder >= _tempo) { + _samplesTillCallback++; + _samplesTillCallbackRemainder -= _tempo; + } + } + + int32 render = MIN(samplesLeft, _samplesTillCallback); + samplesLeft -= render; + _samplesTillCallback -= render; + + nextTick(buffer, render); + + for (int i = 0; i < render; ++i) { + buffer[i << 1] <<= 2; + buffer[(i << 1) + 1] <<= 2; + } + + buffer += (render << 1); + } + + return numSamples; +} + +void TownsPC98_OpnDriver::loadData(uint8 *data, bool loadPaused) { + if (!_ready) { + warning("TownsPC98_OpnDriver: Driver must be initialized before loading data"); + return; + } + + if (!data) { + warning("TownsPC98_OpnDriver: Invalid music file data"); + return; + } + + lock(); + _trackData = data; + + reset(); + + uint8 *src_a = data; + + for (uint8 i = 0; i < 3; i++) { + _channels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + for (int i = 0; i < _numSSG; i++) { + _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + for (uint8 i = 3; i < _numChan; i++) { + _channels[i]->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + if (_hasADPCM) { + //_adpcmChannel->loadData(data + READ_LE_UINT16(src_a)); + src_a += 2; + } + + _ssgFlag = 0; + + _patches = src_a + 4; + _cbCounter = 4; + _finishedChannelsFlag = 0; + + // AH 0x17 + unlock(); + _playing = (loadPaused ? false : true); +} + +void TownsPC98_OpnDriver::reset() { + for (int i = 0; i < (_numChan); i++) + _channels[i]->reset(); + for (int i = 0; i < (_numSSG); i++) + _ssgChannels[i]->reset(); + + _playing = _fading = false; + _looping = 0; + _tickCounter = 0; +} + +void TownsPC98_OpnDriver::fadeOut() { + if (!_playing) + return; + + _fading = true; + + for (int i = 0; i < 20; i++) { + lock(); + uint32 dTime = _tickCounter + 2; + for (int j = 0; j < _numChan; j++) { + if (_updateChannelsFlag & _channels[j]->_idFlag) + _channels[j]->fadeStep(); + } + for (int j = 0; j < _numSSG; j++) + _ssgChannels[j]->fadeStep(); + + unlock(); + + while (_playing) { + if (_tickCounter >= dTime) + break; + } + } + + _fading = false; + + reset(); +} + +void TownsPC98_OpnDriver::callback() { + if (!_playing || --_cbCounter) + return; + + _cbCounter = 4; + _tickCounter++; + + lock(); + + for (int i = 0; i < _numChan; i++) { + if (_updateChannelsFlag & _channels[i]->_idFlag) { + _channels[i]->processEvents(); + _channels[i]->processFrequency(); + } + } + + if (_numSSG) { + for (int i = 0; i < _numSSG; i++) { + _ssgChannels[i]->processEvents(); + _ssgChannels[i]->processFrequency(); + } + } + + _ssgFlag = 0; + + unlock(); + + if (_finishedChannelsFlag == _updateChannelsFlag) + reset(); +} + +void TownsPC98_OpnDriver::nextTick(int16 *buffer, uint32 bufferSize) { + if (!_playing) + return; + + for (int i = 0; i < _numChan ; i++) { + if (_channels[i]->_updateEnvelopes) { + _channels[i]->_updateEnvelopes = false; + _channels[i]->updateEnv(); + } + + for (uint32 ii = 0; ii < bufferSize ; ii++) + _channels[i]->generateOutput(buffer[ii * 2], + buffer[ii * 2 + 1], &_channels[i]->_feedbuf[2], _channels[i]->_feedbuf); + } + + for (int i = 0; i < _numSSG ; i++) { + if (_ssgChannels[i]->_updateEnvelopes) { + _ssgChannels[i]->_updateEnvelopes = false; + _ssgChannels[i]->updateEnv(); + } + + for (uint32 ii = 0; ii < bufferSize ; ii++) + _ssgChannels[i]->generateOutput(buffer[ii * 2], + buffer[ii * 2 + 1], &_ssgChannels[i]->_feedbuf[2], _ssgChannels[i]->_feedbuf); + } +} + +void TownsPC98_OpnDriver::generateTables() { + delete [] _oprRates; + _oprRates = new uint8[128]; + memset(_oprRates, 0x90, 32); + uint8 *dst = (uint8*) _oprRates + 32; + for (int i = 0; i < 48; i += 4) + WRITE_BE_UINT32(dst + i, 0x00081018); + dst += 48; + for (uint8 i = 0; i < 16; i ++) { + uint8 v = (i < 12) ? i : 12; + *dst++ = ((4 + v) << 3); + } + memset(dst, 0x80, 32); + + delete [] _oprRateshift; + _oprRateshift = new uint8[128]; + memset(_oprRateshift, 0, 128); + dst = (uint8*) _oprRateshift + 32; + for (int i = 11; i; i--) { + memset(dst, i, 4); + dst += 4; + } + + delete [] _oprFrq; + _oprFrq = new uint32[0x1000]; + for (uint32 i = 0; i < 0x1000; i++) + _oprFrq[i] = (uint32)(_baserate * (double)(i << 11)); + + delete [] _oprAttackDecay; + _oprAttackDecay = new uint8[152]; + memset(_oprAttackDecay, 0, 152); + for (int i = 0; i < 36; i++) + WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]); + + delete [] _oprSinTbl; + _oprSinTbl = new uint32[1024]; + for (int i = 0; i < 1024; i++) { + double val = sin((double) (((i << 1) + 1) * PI / 1024.0)); + double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0; + int32 i_dcb = (int32)(2.0 * d_dcb); + i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1); + _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1); + } + + delete [] _oprLevelOut; + _oprLevelOut = new int32[0x1a00]; + for (int i = 0; i < 256; i++) { + double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i))); + int32 val_int = ((int32) val) >> 4; + _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2; + _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1]; + for (int ii = 1; ii < 13; ii++) { + _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii; + _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)]; + } + } + + uint8 *dtt = new uint8[128]; + memset(dtt, 0, 36); + memset(dtt + 36, 1, 8); + memcpy(dtt + 44, _drvTables + 144, 84); + + delete [] _oprDetune; + _oprDetune = new int32[256]; + for (int i = 0; i < 128; i++) { + _oprDetune[i] = (int32) ((double)dtt[i] * _baserate * 64.0); + _oprDetune[i + 128] = -_oprDetune[i]; + } + + delete [] dtt; +} + +void TownsPC98_OpnDriver::setTempo(uint8 tempo) { + _tempo = tempo; + _samplesPerCallback = getRate() / _tempo; + _samplesPerCallbackRemainder = getRate() % _tempo; +} + +const uint8 TownsPC98_OpnDriver::_drvTables[] = { + // channel presets + 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x80, 0x01, 0x01, 0x00, 0x02, + 0x02, 0x80, 0x02, 0x02, 0x00, 0x04, + 0x00, 0x80, 0x03, 0x04, 0x01, 0x08, + 0x01, 0x80, 0x04, 0x05, 0x01, 0x10, + 0x02, 0x80, 0x05, 0x06, 0x01, 0x20, + + // control event size + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, + 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, + + // fmt level presets + 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38, + 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18, + 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90, + + // carriers + 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F, + + // frequencies + 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02, + 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03, + 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04, + 0x00, 0x00, 0x00, 0x00, + + // unused + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + + // detune + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, + 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, + 0x16, 0x16, 0x16, 0x16, + + // pc98 level presets + 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25, + 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, + 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90 +}; + +const uint32 TownsPC98_OpnDriver::_adtStat[] = { + 0x00010001, 0x00010001, 0x00010001, 0x01010001, + 0x00010101, 0x00010101, 0x00010101, 0x01010101, + 0x01010101, 0x01010101, 0x01010102, 0x01010102, + 0x01020102, 0x01020102, 0x01020202, 0x01020202, + 0x02020202, 0x02020202, 0x02020204, 0x02020204, + 0x02040204, 0x02040204, 0x02040404, 0x02040404, + 0x04040404, 0x04040404, 0x04040408, 0x04040408, + 0x04080408, 0x04080408, 0x04080808, 0x04080808, + 0x08080808, 0x08080808, 0x10101010, 0x10101010 +}; + SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer) : Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), _sfxFileData(0), _sfxFileIndex((uint)-1), _sfxWDTable(0), _sfxBTTable(0), _parser(0) { - _driver = new SoundTowns_EuphonyDriver(_mixer); + _driver = new Towns_EuphonyDriver(_mixer); int ret = open(); if (ret != MERR_ALREADY_OPEN && ret != 0) error("couldn't open midi driver"); @@ -1124,7 +2797,7 @@ void SoundTowns::playTrack(uint8 track) { return; track -= 2; - const int32 * const tTable = (const int32 * const) cdaData(); + const int32 *const tTable = (const int32 *const) cdaData(); int tTableIndex = 3 * track; int trackNum = (int) READ_LE_UINT32(&tTable[tTableIndex + 2]); @@ -1195,12 +2868,12 @@ void SoundTowns::playSoundEffect(uint8 track) { } } - uint8 * fileBody = _sfxFileData + 0x01b8; + uint8 *fileBody = _sfxFileData + 0x01b8; int32 offset = (int32)READ_LE_UINT32(_sfxFileData + (track - 0x0b) * 4); if (offset == -1) return; - uint32 * sfxHeader = (uint32*)(fileBody + offset); + uint32 *sfxHeader = (uint32*)(fileBody + offset); uint32 sfxHeaderID = READ_LE_UINT32(sfxHeader); uint32 sfxHeaderInBufferSize = READ_LE_UINT32(&sfxHeader[1]); @@ -1220,7 +2893,7 @@ void SoundTowns::playSoundEffect(uint8 track) { } else if (sfxHeaderID == 1) { Screen::decodeFrame4(sfxBody, sfxPlaybackBuffer, playbackBufferSize); } else if (_sfxWDTable) { - uint8 * tgt = sfxPlaybackBuffer; + uint8 *tgt = sfxPlaybackBuffer; uint32 sfx_BtTable_Offset = 0; uint32 sfx_WdTable_Offset = 0; uint32 sfx_WdTable_Number = 5; @@ -1285,7 +2958,7 @@ uint32 SoundTowns::getBaseTempo(void) { } bool SoundTowns::loadInstruments() { - uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0); + uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0); if (!twm) return false; _driver->queue()->loadDataToCurrentPosition(twm, 0x8BF0); @@ -1300,11 +2973,11 @@ bool SoundTowns::loadInstruments() { } void SoundTowns::playEuphonyTrack(uint32 offset, int loop) { - uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0); + uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0); Common::StackLock lock(_mutex); if (!_parser) { - _parser = new MidiParser_EuD(_driver->queue()); + _parser = new Towns_EuphonyParser(_driver->queue()); _parser->setMidiDriver(this); _parser->setTimerRate(getBaseTempo()); } @@ -1315,7 +2988,7 @@ void SoundTowns::playEuphonyTrack(uint32 offset, int loop) { delete[] twm; } -void SoundTowns::onTimer(void * data) { +void SoundTowns::onTimer(void *data) { SoundTowns *music = (SoundTowns *)data; Common::StackLock lock(music->_mutex); if (music->_parser) @@ -1356,22 +3029,75 @@ float SoundTowns::semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiTo return (float) sampleRate * 10.0f * rateshift / outputRate; } +SoundPC98::SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer) : + Sound(vm, mixer), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0) { +} + +SoundPC98::~SoundPC98() { + delete[] _musicTrackData; + delete[] _sfxTrackData; + delete _driver; +} + +bool SoundPC98::init() { + _driver = new TownsPC98_OpnDriver(_mixer, TownsPC98_OpnDriver::OD_TYPE26); + _sfxTrackData = _vm->resource()->fileData("se.dat", 0); + if (!_sfxTrackData) + return false; + return _driver->init(); +} + +void SoundPC98::playTrack(uint8 track) { + if (--track >= 56) + track -= 55; + + if (track == _lastTrack && _musicEnabled) + return; + + haltTrack(); + + char musicfile[13]; + sprintf(musicfile, fileListEntry(0), track); + delete[] _musicTrackData; + _musicTrackData = _vm->resource()->fileData(musicfile, 0); + if (_musicEnabled) + _driver->loadData(_musicTrackData); + + _lastTrack = track; +} + +void SoundPC98::haltTrack() { + _lastTrack = -1; + AudioCD.stop(); + AudioCD.updateCD(); + _driver->reset(); +} + +void SoundPC98::beginFadeOut() { + _driver->fadeOut(); + haltTrack(); +} + +void SoundPC98::playSoundEffect(uint8) { + /// TODO /// +} + + // KYRA 2 -SoundTowns_v2::SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) - : Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), /*_driver(0),*/ - _twnTrackData(0) { +SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) : + Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) { } -SoundTowns_v2::~SoundTowns_v2() { - /*if (_driver) - delete _driver;*/ - if (_twnTrackData) - delete[] _twnTrackData; +SoundTownsPC98_v2::~SoundTownsPC98_v2() { + delete[] _musicTrackData; + delete _driver; } -bool SoundTowns_v2::init() { - //_driver = new SoundTowns_v2_TwnDriver(_mixer); +bool SoundTownsPC98_v2::init() { + _driver = new TownsPC98_OpnDriver(_mixer, /*_vm->gameFlags().platform == Common::kPlatformPC98 ? + TownsPC98_OpnDriver::OD_TYPE86 :*/ TownsPC98_OpnDriver::OD_TOWNS); + _useFmSfx = _vm->gameFlags().platform == Common::kPlatformPC98 ? true : false; _vm->checkCD(); // FIXME: While checking for 'track1.XXX(X)' looks like // a good idea, we should definitely not be doing this @@ -1384,55 +3110,61 @@ bool SoundTowns_v2::init() { (Common::File::exists("track1.mp3") || Common::File::exists("track1.ogg") || Common::File::exists("track1.flac") || Common::File::exists("track1.fla"))) _musicEnabled = 2; - return true;//_driver->init(); + return _driver->init(); } -void SoundTowns_v2::process() { +void SoundTownsPC98_v2::process() { AudioCD.updateCD(); } -void SoundTowns_v2::playTrack(uint8 track) { +void SoundTownsPC98_v2::playTrack(uint8 track) { if (track == _lastTrack && _musicEnabled) return; - const uint16 * const cdaTracks = (const uint16 * const) cdaData(); + const uint16 *const cdaTracks = (const uint16 *const) cdaData(); int trackNum = -1; - for (int i = 0; i < cdaTrackNum(); i++) { - if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) { - trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1; - break; + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + for (int i = 0; i < cdaTrackNum(); i++) { + if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) { + trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1; + break; + } } } - haltTrack(); + beginFadeOut(); - // TODO: figure out when to loop and when not for CD Audio - bool loop = false; + char musicfile[13]; + sprintf(musicfile, fileListEntry(0), track); + delete[] _musicTrackData; + + _musicTrackData = _vm->resource()->fileData(musicfile, 0); + _driver->loadData(_musicTrackData, true); if (_musicEnabled == 2 && trackNum != -1) { - AudioCD.play(trackNum+1, loop ? -1 : 1, 0, 0); + AudioCD.play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0); AudioCD.updateCD(); } else if (_musicEnabled) { - char musicfile[13]; - sprintf(musicfile, fileListEntry(0), track); - if (_twnTrackData) - delete[] _twnTrackData; - _twnTrackData = _vm->resource()->fileData(musicfile, 0); - //_driver->loadData(_twnTrackData); + _driver->cont(); } _lastTrack = track; } -void SoundTowns_v2::haltTrack() { +void SoundTownsPC98_v2::haltTrack() { _lastTrack = -1; AudioCD.stop(); AudioCD.updateCD(); - //_driver->reset(); + _driver->reset(); } -int32 SoundTowns_v2::voicePlay(const char *file, bool) { +void SoundTownsPC98_v2::beginFadeOut() { + _driver->fadeOut(); + haltTrack(); +} + +int32 SoundTownsPC98_v2::voicePlay(const char *file, bool) { static const uint16 rates[] = { 0x10E1, 0x0CA9, 0x0870, 0x0654, 0x0438, 0x032A, 0x021C, 0x0194 }; int h = 0; @@ -1443,11 +3175,11 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) { return 0; } - char filename [13]; + char filename[13]; sprintf(filename, "%s.PCM", file); - uint8 * data = _vm->resource()->fileData(filename, 0); - uint8 * src = data; + uint8 *data = _vm->resource()->fileData(filename, 0); + uint8 *src = data; uint16 sfxRate = rates[READ_LE_UINT16(src)]; src += 2; @@ -1500,9 +3232,16 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) { return 1; } -void SoundTowns_v2::beginFadeOut() { - //_driver->fadeOut(); - haltTrack(); +void SoundTownsPC98_v2::playSoundEffect(uint8 track) { + if (!_useFmSfx) + return; + + uint8 *sd = _vm->resource()->fileData("sound.dat", 0); + + + //TODO + + delete [] sd; } } // end of namespace Kyra diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index abdf115c1e..9a4b40902e 100644 --- a/engines/kyra/staticres.cpp +++ b/engines/kyra/staticres.cpp @@ -23,16 +23,17 @@ * */ - #include "common/endian.h" #include "common/md5.h" #include "kyra/kyra_v1.h" #include "kyra/kyra_lok.h" +#include "kyra/lol.h" #include "kyra/kyra_v2.h" #include "kyra/kyra_hof.h" #include "kyra/kyra_mr.h" #include "kyra/screen.h" #include "kyra/screen_lok.h" +#include "kyra/screen_lol.h" #include "kyra/screen_hof.h" #include "kyra/screen_mr.h" #include "kyra/resource.h" @@ -287,8 +288,10 @@ bool StaticResource::init() { } else if (_vm->game() == GI_KYRA3) { _builtIn = 0; _filenameTable = kyra3StaticRes; + } else if (_vm->game() == GI_LOL) { + return true; } else { - error("unknown game ID"); + error("StaticResource: Unknown game ID"); } char errorBuffer[100]; @@ -1034,6 +1037,11 @@ void KyraEngine_LoK::initStaticResource() { } // audio data tables +#if 0 + static const char *tIntro98[] = { "intro%d.dat" }; + static const char *tIngame98[] = { "kyram%d.dat" }; +#endif + static const AudioDataStruct soundData_PC[] = { { _soundFilesIntro, _soundFilesIntroSize, 0, 0 }, { _soundFiles, _soundFilesSize, 0, 0 }, @@ -1045,7 +1053,22 @@ void KyraEngine_LoK::initStaticResource() { { _soundFiles, _soundFilesSize, _cdaTrackTable, _cdaTrackTableSize }, { 0, 0, 0, 0} }; - _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS; + +#if 0 + static const AudioDataStruct soundData_PC98[] = { + { tIntro98, 1, 0, 0 }, + { tIngame98, 1, 0, 0 }, + { 0, 0, 0, 0} + }; +#endif + + if (_flags.platform == Common::kPlatformPC) + _soundData = soundData_PC; + else if (_flags.platform == Common::kPlatformFMTowns) + _soundData = soundData_TOWNS; + else if (_flags.platform == Common::kPlatformPC98) + _soundData = soundData_TOWNS/*soundData_PC98*/; + } void KyraEngine_LoK::loadMouseShapes() { @@ -1243,6 +1266,12 @@ void KyraEngine_HoF::initStaticResource() { static const char *fmtMusicFileListFinale[] = { "finale%d.twn" }; static const char *fmtMusicFileListIngame[] = { "km%02d.twn" }; +#if 0 + static const char *pc98MusicFileListIntro[] = { "intro%d.86" }; + static const char *pc98MusicFileListFinale[] = { "finale%d.86" }; + static const char *pc98MusicFileListIngame[] = { "km%02d.86" }; +#endif + static const AudioDataStruct soundData_PC[] = { { _musicFileListIntro, _musicFileListIntroSize, 0, 0 }, { _musicFileListIngame, _musicFileListIngameSize, 0, 0}, @@ -1254,7 +1283,21 @@ void KyraEngine_HoF::initStaticResource() { { fmtMusicFileListIngame, 1, _cdaTrackTableIngame, _cdaTrackTableIngameSize >> 1 }, { fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 } }; - _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS; + +#if 0 + static const AudioDataStruct soundData_PC98[] = { + { pc98MusicFileListIntro, 1, 0, 0 }, + { pc98MusicFileListIngame, 1, 0, 0 }, + { pc98MusicFileListFinale, 1, 0, 0 } + }; +#endif + + if (_flags.platform == Common::kPlatformPC) + _soundData = soundData_PC; + else if (_flags.platform == Common::kPlatformFMTowns) + _soundData = soundData_TOWNS; + else if (_flags.platform == Common::kPlatformPC98) + _soundData = soundData_TOWNS/*soundData_PC98*/; // setup sequence data _sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize); @@ -1944,12 +1987,26 @@ const char *KyraEngine_MR::_languageExtension[] = { "TRE", "TRF", "TRG"/*, - "TRI", Italian and Spanish were never included - "TRS"*/ + "TRI", Italian and Spanish were never included, the supported fan translations are using + "TRS" English/French extensions thus overwriting these languages */ }; const int KyraEngine_MR::_languageExtensionSize = ARRAYSIZE(KyraEngine_MR::_languageExtension); +const char * const KyraEngine_MR::_mainMenuSpanishFan[] = { + "Nueva Partida", + "Ver Intro", + "Restaurar", + "Finalizar" +}; + +const char * const KyraEngine_MR::_mainMenuItalianFan[] = { + "Nuova Partita", + "Introduzione", + "Carica una partita", + "Esci dal gioco" +}; + const KyraEngine_MR::ShapeDesc KyraEngine_MR::_shapeDescs[] = { { 57, 91, -31, -82 }, { 57, 91, -31, -82 }, @@ -2182,5 +2239,105 @@ const int8 KyraEngine_MR::_albumWSAY[] = { -1, -2, 2, 2, -6, -6, -6, 0 }; +// lands of lore static res + +const ScreenDim Screen_LoL::_screenDimTable[] = { + { 0x00, 0x00, 0x28, 0xC8, 0xC7, 0xCF, 0x00, 0x00 } +}; + +const int Screen_LoL::_screenDimTableCount = ARRAYSIZE(Screen_LoL::_screenDimTable); + +const char * const LoLEngine::_languageExt[] = { + "ENG", + "FRE", + "GER" +}; + +const LoLEngine::CharacterPrev LoLEngine::_charPreviews[] = { + { "Ak\'shel", 0x060, 0x7F, { 0x0F, 0x08, 0x05 } }, + { "Michael", 0x09A, 0x7F, { 0x06, 0x0A, 0x0F } }, + { "Kieran", 0x0D4, 0x7F, { 0x08, 0x06, 0x08 } }, + { "Conrad", 0x10F, 0x7F, { 0x0A, 0x0C, 0x0A } } +}; + +const uint8 LoLEngine::_chargenFrameTable[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x04, 0x03, 0x02, 0x01, + 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12 +}; + +const uint16 LoLEngine::_selectionPosTable[] = { + 0x6F, 0x00, 0x8F, 0x00, 0xAF, 0x00, 0xCF, 0x00, + 0xEF, 0x00, 0x6F, 0x20, 0x8F, 0x20, 0xAF, 0x20, + 0xCF, 0x20, 0xEF, 0x20, 0x6F, 0x40, 0x8F, 0x40, + 0xAF, 0x40, 0xCF, 0x40, 0xEF, 0x40, 0x10F, 0x00 +}; + +const uint8 LoLEngine::_selectionChar1IdxTable[] = { + 0, 0, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 0, 0, 5, 5, 5, + 5, 5, 5, 5, 0, 0, 5, 5, + 5, 5, 5 +}; + +const uint8 LoLEngine::_selectionChar2IdxTable[] = { + 1, 1, 6, 6, 1, 1, 6, 6, + 6, 6, 6, 6, 6, 1, 1, 6, + 6, 6, 1, 1, 6, 6, 6, 6, + 6, 6, 6 +}; + +const uint8 LoLEngine::_selectionChar3IdxTable[] = { + 2, 2, 7, 7, 7, 7, 2, 2, + 7, 7, 7, 7, 7, 7, 7, 2, + 2, 7, 7, 7, 7, 2, 2, 7, + 7, 7, 7 +}; + +const uint8 LoLEngine::_selectionChar4IdxTable[] = { + 3, 3, 8, 8, 8, 8, 3, 3, + 8, 8, 3, 3, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 3, 3, 8, + 8, 8, 8 +}; + +const uint8 LoLEngine::_reminderChar1IdxTable[] = { + 4, 4, 4, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5 +}; + +const uint8 LoLEngine::_reminderChar2IdxTable[] = { + 9, 9, 9, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6 +}; + +const uint8 LoLEngine::_reminderChar3IdxTable[] = { + 0xE, 0xE, 0xE, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, + 0x7 +}; + +const uint8 LoLEngine::_reminderChar4IdxTable[] = { + 0xF, 0xF, 0xF, 0x8, 0x8, 0x8, 0x8, 0x8, + 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, + 0x8 +}; + +const uint8 LoLEngine::_selectionAnimIndexTable[] = { + 0, 5, 1, 6, 2, 7, 3, 8 +}; + +const uint8 LoLEngine::_charInfoFrameTable[] = { + 0x0, 0x7, 0x8, 0x9, 0xA, 0xB, 0xA, 0x9, + 0x8, 0x7, 0x0, 0x0, 0x7, 0x8, 0x9, 0xA, + 0xB, 0xA, 0x9, 0x8, 0x7, 0x0, 0x0, 0x7, + 0x8, 0x9, 0xA, 0xB, 0xA, 0x9, 0x8, 0x7 +}; + } // End of namespace Kyra diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h index 36cd75b1ab..1db8ee8474 100644 --- a/engines/kyra/wsamovie.h +++ b/engines/kyra/wsamovie.h @@ -109,7 +109,7 @@ private: class WSAMovie_v2 : public WSAMovie_v1 { public: - WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *scren); + WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *screen); int open(const char *filename, int unk1, uint8 *palette); diff --git a/engines/lure/lure.cpp b/engines/lure/lure.cpp index 06d3b1984e..ea760ddb4f 100644 --- a/engines/lure/lure.cpp +++ b/engines/lure/lure.cpp @@ -103,6 +103,7 @@ LureEngine::~LureEngine() { if (_initialised) { // Delete and deinitialise subsystems Surface::deinitialise(); + Sound.destroy(); delete _fights; delete _room; delete _menu; @@ -164,10 +165,6 @@ void LureEngine::pauseEngineIntern(bool pause) { } } -void LureEngine::quitGame() { - _system->quit(); -} - const char *LureEngine::generateSaveName(int slotNumber) { static char buffer[15]; diff --git a/engines/lure/lure.h b/engines/lure/lure.h index d66f446247..1c5b40e54b 100644 --- a/engines/lure/lure.h +++ b/engines/lure/lure.h @@ -70,7 +70,6 @@ public: virtual int init(); virtual int go(); virtual void pauseEngineIntern(bool pause); - void quitGame(); Disk &disk() { return *_disk; } diff --git a/engines/lure/luredefs.h b/engines/lure/luredefs.h index 603102a099..922e1207d0 100644 --- a/engines/lure/luredefs.h +++ b/engines/lure/luredefs.h @@ -36,7 +36,7 @@ namespace Lure { #define LURE_DAT_MAJOR 1 #define LURE_DAT_MINOR 29 #define LURE_MIN_SAVEGAME_MINOR 25 -#define LURE_SAVEGAME_MINOR 32 +#define LURE_SAVEGAME_MINOR 33 #define LURE_DEBUG 1 diff --git a/engines/lure/menu.cpp b/engines/lure/menu.cpp index cecc415499..0b4ef06081 100644 --- a/engines/lure/menu.cpp +++ b/engines/lure/menu.cpp @@ -57,6 +57,11 @@ MenuRecord::MenuRecord(const MenuRecordBounds *bounds, int numParams, ...) { _width = (bounds->contentsWidth + 3) << 3; } +MenuRecord::~MenuRecord() { + free(_entries); + _entries = NULL; +} + const char *MenuRecord::getEntry(uint8 index) { if (index >= _numEntries) error("Invalid menuitem index specified: %d", index); return _entries[index]; diff --git a/engines/lure/menu.h b/engines/lure/menu.h index b5b7769e34..fcc6308375 100644 --- a/engines/lure/menu.h +++ b/engines/lure/menu.h @@ -56,6 +56,7 @@ private: uint8 _numEntries; public: MenuRecord(const MenuRecordBounds *bounds, int numParams, ...); + ~MenuRecord(); uint16 xstart() { return _xstart; } uint16 width() { return _width; } diff --git a/engines/lure/palette.cpp b/engines/lure/palette.cpp index 03161032c0..badc3c96b0 100644 --- a/engines/lure/palette.cpp +++ b/engines/lure/palette.cpp @@ -106,6 +106,12 @@ Palette::Palette(uint16 resourceId, PaletteSource paletteSource) { delete srcData; } +// Destructor + +Palette::~Palette() { + delete _palette; +} + void Palette::convertRgb64Palette(const byte *srcPalette, uint16 srcNumEntries) { byte *pDest = _palette->data(); const byte *pSrc = srcPalette; diff --git a/engines/lure/palette.h b/engines/lure/palette.h index 1481e22775..9420079346 100644 --- a/engines/lure/palette.h +++ b/engines/lure/palette.h @@ -46,6 +46,7 @@ public: Palette(uint16 srcNumEntries, const byte *srcData, PaletteSource paletteSource); Palette(Palette &src); Palette(uint16 resourceId, PaletteSource paletteSource = DEFAULT); + ~Palette(); uint8 *data() { return _palette->data(); } MemoryBlock *palette() { return _palette; } diff --git a/engines/lure/res.cpp b/engines/lure/res.cpp index f2997d5d17..68de260061 100644 --- a/engines/lure/res.cpp +++ b/engines/lure/res.cpp @@ -349,6 +349,7 @@ void Resources::reloadData() { _indexedRoomExitHospots.push_back(RoomExitIndexedHotspotList::value_type(new RoomExitIndexedHotspotData(indexedRec))); indexedRec++; } + delete mb; // Initialise delay list _delayList.clear(true); diff --git a/engines/lure/res_struct.cpp b/engines/lure/res_struct.cpp index de09f982d1..92cea948f9 100644 --- a/engines/lure/res_struct.cpp +++ b/engines/lure/res_struct.cpp @@ -456,6 +456,8 @@ void HotspotData::saveToStream(WriteStream *stream) { stream->writeSint16LE(startY); stream->writeUint16LE(roomNumber); stream->writeByte(layer); + stream->writeUint16LE(walkX); + stream->writeUint16LE(walkY); stream->writeUint16LE(width); stream->writeUint16LE(height); @@ -503,6 +505,10 @@ void HotspotData::loadFromStream(ReadStream *stream) { uint8 saveVersion = LureEngine::getReference().saveVersion(); if (saveVersion >= 29) layer = stream->readByte(); + if (saveVersion >= 33) { + walkX = stream->readUint16LE(); + walkY = stream->readUint16LE(); + } width = stream->readUint16LE(); height = stream->readUint16LE(); diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp index 839298d1c5..285f66e4e2 100644 --- a/engines/lure/sound.cpp +++ b/engines/lure/sound.cpp @@ -85,8 +85,10 @@ SoundManager::~SoundManager() { if (_soundData) delete _soundData; - if (_driver) + if (_driver) { _driver->close(); + delete _driver; + } _driver = NULL; g_system->deleteMutex(_soundMutex); @@ -143,7 +145,7 @@ void SoundManager::bellsBodge() { Room &room = Room::getReference(); RoomData *roomData = res.getRoom(room.roomNumber()); - if (roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) { + if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) { res.fieldList().setField(AREA_FLAG, roomData->areaFlag); switch (roomData->areaFlag) { diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp index 80b21119ff..0488f17d8f 100644 --- a/engines/m4/assets.cpp +++ b/engines/m4/assets.cpp @@ -201,6 +201,7 @@ void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream* Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3); SpriteAssetFrame frame; for (curFrame = 0; curFrame < _frameCount; curFrame++) { + frame.stream = 0; frame.comp = 0; frameOffset = spriteStream->readUint32LE(); _frameOffsets.push_back(frameOffset); diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp index 024cd591f5..5b8bdab9d6 100644 --- a/engines/m4/converse.cpp +++ b/engines/m4/converse.cpp @@ -153,7 +153,7 @@ void ConversationView::setNode(int32 nodeIndex) { void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) { //if (!this->isVisible()) // return; - empty(); + clear(); if (_entriesShown) { // Write out the conversation options diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp index 12d9a24d37..58c68979d1 100644 --- a/engines/m4/globals.cpp +++ b/engines/m4/globals.cpp @@ -75,7 +75,7 @@ bool Kernel::sendTrigger(int32 triggerNum) { bool Kernel::handleTrigger(int32 triggerNum) { - printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum); + printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, (uint)triggerNum); if (betweenRooms) return true; @@ -271,11 +271,13 @@ Globals::Globals(M4Engine *vm): _vm(vm) { } Globals::~Globals() { - for(uint32 i = 0; i < _madsVocab.size(); i++) + uint32 i; + + for(i = 0; i < _madsVocab.size(); i++) free(_madsVocab[i]); _madsVocab.clear(); - for(uint32 i = 0; i < _madsQuotes.size(); i++) + for(i = 0; i < _madsQuotes.size(); i++) free(_madsQuotes[i]); _madsQuotes.clear(); @@ -351,7 +353,7 @@ void Globals::loadMadsMessagesInfo() { _vm->res()->toss("messages.dat"); } -char* Globals::loadMessage(uint32 index) { +char* Globals::loadMessage(uint index) { if (index > _madsMessages.size() - 1) { warning("Invalid message index: %i", index); return NULL; diff --git a/engines/m4/globals.h b/engines/m4/globals.h index a0133db2d6..a80e8bf710 100644 --- a/engines/m4/globals.h +++ b/engines/m4/globals.h @@ -177,7 +177,7 @@ public: void loadMadsMessagesInfo(); uint32 getMessagesSize() { return _madsMessages.size(); } - char* loadMessage(uint32 index); + char* loadMessage(uint index); }; #define PLAYER_FIELD_LENGTH 40 diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp index beda178344..1846f1c1e7 100644 --- a/engines/m4/graphics.cpp +++ b/engines/m4/graphics.cpp @@ -320,7 +320,7 @@ byte *M4Surface::getBasePtr(int x, int y) { void M4Surface::freeData() { } -void M4Surface::empty() { +void M4Surface::clear() { Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK); } @@ -389,7 +389,7 @@ void M4Surface::loadBackgroundRiddle(const char *sceneName) { } void M4Surface::loadBackground(int sceneNumber, RGBList **palData) { - this->empty(); // clear previous scene + clear(); // clear previous scene if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) { char resourceName[20]; @@ -502,7 +502,7 @@ void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) { //printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize); - newTile->empty(); + newTile->clear(); byte *compressedTileData = new byte[compressedTileDataSize]; diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h index 60e608c148..84fc77656f 100644 --- a/engines/m4/graphics.h +++ b/engines/m4/graphics.h @@ -128,7 +128,7 @@ public: byte *getData(); byte *getBasePtr(int x, int y); void freeData(); - void empty(); + void clear(); void frameRect(const Common::Rect &r, uint8 color); void fillRect(const Common::Rect &r, uint8 color); void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY, diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp index 9bf964ee96..777356467b 100644 --- a/engines/m4/m4_views.cpp +++ b/engines/m4/m4_views.cpp @@ -331,7 +331,7 @@ bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y, } void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) { - empty(); + clear(); _statusText.onRefresh(); _inventory.onRefresh(); diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp index 3e80d0f1e0..c51daa84c4 100644 --- a/engines/m4/mads_anim.cpp +++ b/engines/m4/mads_anim.cpp @@ -61,9 +61,9 @@ TextviewView::TextviewView(M4Engine *vm): _vm->_font->setColors(5, 6, 4); - empty(); - _bgSurface.empty(); - _textSurface.empty(); + clear(); + _bgSurface.clear(); + _textSurface.clear(); int y = (height() - MADS_SURFACE_HEIGHT) / 2; setColor(2); @@ -83,8 +83,8 @@ TextviewView::~TextviewView() { } void TextviewView::reset() { - _bgSurface.empty(); - _textSurface.empty(); + _bgSurface.clear(); + _textSurface.clear(); _animating = false; _panX = 0; _panY = 0; @@ -456,8 +456,8 @@ AnimviewView::AnimviewView(M4Engine *vm): // Set up system palette colors _vm->_palette->setMadsSystemPalette(); - empty(); - _bgSurface.empty(); + clear(); + _bgSurface.clear(); int y = (height() - MADS_SURFACE_HEIGHT) / 2; setColor(2); @@ -471,7 +471,7 @@ AnimviewView::~AnimviewView() { } void AnimviewView::reset() { - _bgSurface.empty(); + _bgSurface.clear(); _soundDriverLoaded = false; } diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp index 3a8b5d24a8..b74e598c6c 100644 --- a/engines/m4/viewmgr.cpp +++ b/engines/m4/viewmgr.cpp @@ -380,7 +380,7 @@ void ViewManager::updateState() { } void ViewManager::refreshAll() { - _vm->_screen->empty(); + _vm->_screen->clear(); for (ListIterator i = _views.begin(); i != _views.end(); ++i) { View *v = *i; diff --git a/engines/made/database.cpp b/engines/made/database.cpp index 55e0e90732..3497b5b46f 100644 --- a/engines/made/database.cpp +++ b/engines/made/database.cpp @@ -88,10 +88,7 @@ int16 Object::getVectorItem(int16 index) { if (getClass() == 0x7FFF) { byte *vector = (byte*)getData(); return vector[index]; - } else if (getClass() == 0x7FFE) { - int16 *vector = (int16*)getData(); - return READ_LE_UINT16(&vector[index]); - } else if (getClass() < 0x7FFE) { + } else if (getClass() <= 0x7FFE) { int16 *vector = (int16*)getData(); return READ_LE_UINT16(&vector[index]); } else { @@ -372,7 +369,7 @@ void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) { debug(2, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize); _gameState = new byte[_gameStateSize + 2]; - memset(_gameState, 0, _gameStateSize); + memset(_gameState, 0, _gameStateSize + 2); setVar(1, objectCount); sourceS.seek(textOffs); @@ -441,7 +438,7 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i int16 *propPtr2 = prop + count2; // First see if the property exists in the given object - while (count2-- > 0) { + while (count2--) { if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) { propertyFlag = obj->getFlags() & 1; return propPtr1; @@ -467,8 +464,8 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i propPtr1 = propPtr2 + count1 - count2; int16 *propertyPtr = prop + count1; - while (count2-- > 0) { - if (!(READ_LE_UINT16(prop) & 0x8000)) { + while (count2--) { + if ((READ_LE_UINT16(prop) & 0x8000) == 0) { if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) { propertyFlag = obj->getFlags() & 1; return propPtr1; diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp index dc7dbdee87..e5870bfeec 100644 --- a/engines/made/detection.cpp +++ b/engines/made/detection.cpp @@ -65,7 +65,7 @@ static const PlainGameDescriptor madeGames[] = { {"manhole", "The Manhole"}, {"rtz", "Return to Zork"}, {"lgop2", "Leather Goddesses of Phobos 2"}, - {"rodney", "Rodney's Fun Screen"}, + {"rodney", "Rodney's Funscreen"}, {0, 0} }; @@ -278,7 +278,7 @@ static const MadeGameDescription gameDescriptions[] = { }, { - // Rodney's Fun Screen + // Rodney's Funscreen { "rodney", "", diff --git a/engines/made/made.cpp b/engines/made/made.cpp index 59ec487c37..dc45dc4d2f 100644 --- a/engines/made/made.cpp +++ b/engines/made/made.cpp @@ -148,27 +148,31 @@ int MadeEngine::init() { return 0; } +int16 MadeEngine::getTicks() { + return g_system->getMillis() * 30 / 1000; +} + int16 MadeEngine::getTimer(int16 timerNum) { if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers) && _timers[timerNum - 1] != -1) - return (_system->getMillis() - _timers[timerNum - 1]) / kTimerResolution; + return (getTicks() - _timers[timerNum - 1]); else return 32000; } void MadeEngine::setTimer(int16 timerNum, int16 value) { if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers)) - _timers[timerNum - 1] = value * kTimerResolution; + _timers[timerNum - 1] = value; } void MadeEngine::resetTimer(int16 timerNum) { if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers)) - _timers[timerNum - 1] = _system->getMillis(); + _timers[timerNum - 1] = getTicks(); } int16 MadeEngine::allocTimer() { for (int i = 0; i < ARRAYSIZE(_timers); i++) { if (_timers[i] == -1) { - _timers[i] = _system->getMillis(); + _timers[i] = getTicks(); return i + 1; } } @@ -202,24 +206,20 @@ void MadeEngine::handleEvents() { break; case Common::EVENT_LBUTTONDOWN: - _eventNum = 1; + _eventNum = 2; break; - /* case Common::EVENT_LBUTTONUP: - _eventNum = 2; // TODO: Is this correct? + _eventNum = 1; break; - */ case Common::EVENT_RBUTTONDOWN: - _eventNum = 3; + _eventNum = 4; break; - /* case Common::EVENT_RBUTTONUP: - eventNum = 4; // TODO: Is this correct? + _eventNum = 3; break; - */ case Common::EVENT_KEYDOWN: _eventKey = event.kbd.ascii; @@ -239,7 +239,7 @@ void MadeEngine::handleEvents() { } } - + AudioCD.updateCD(); } diff --git a/engines/made/made.h b/engines/made/made.h index 461941e5cf..971961c867 100644 --- a/engines/made/made.h +++ b/engines/made/made.h @@ -120,6 +120,7 @@ public: int _engineVersion; int32 _timers[50]; + int16 getTicks(); int16 getTimer(int16 timerNum); void setTimer(int16 timerNum, int16 value); void resetTimer(int16 timerNum); diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp index 1a8ca9c50a..831f1fab8e 100644 --- a/engines/made/pmvplayer.cpp +++ b/engines/made/pmvplayer.cpp @@ -40,7 +40,10 @@ void PmvPlayer::play(const char *filename) { _surface = NULL; _fd = new Common::File(); - _fd->open(filename); + if (!_fd->open(filename)) { + delete _fd; + return; + } uint32 chunkType, chunkSize; diff --git a/engines/made/screen.cpp b/engines/made/screen.cpp index cecd0c8968..0c22d40259 100644 --- a/engines/made/screen.cpp +++ b/engines/made/screen.cpp @@ -688,7 +688,7 @@ void Screen::printText(const char *text) { for (int textPos = 0; textPos < textLen; textPos++) { - uint c = text[textPos]; + uint c = ((byte*)text)[textPos]; int charWidth = _font->getCharWidth(c); if (c == 9) { @@ -822,6 +822,8 @@ SpriteListItem Screen::getFromSpriteList(int16 index) { if (((uint) index) > _spriteList.size()) { SpriteListItem emptyItem; emptyItem.index = 0; + emptyItem.xofs = 0; + emptyItem.yofs = 0; return emptyItem; } else { return _spriteList[index - 1]; diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp index 932447a1eb..d697e24b04 100644 --- a/engines/made/scriptfuncs.cpp +++ b/engines/made/scriptfuncs.cpp @@ -106,7 +106,7 @@ void ScriptFunctions::setupExternalsTable() { External(sfStopSound); External(sfPlayVoice); - if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ) { + if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RODNEY) { External(sfPlayCd); External(sfStopCd); External(sfGetCdStatus); @@ -332,7 +332,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) { if (_vm->getGameID() == GID_RTZ) { // Unused in RTZ return 0; - } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { + } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) { return _vm->_screen->addToSpriteList(argv[2], argv[1], argv[0]); } else { return 0; @@ -341,7 +341,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) { int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) { _vm->_screen->clearChannels(); - if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { + if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) { _vm->_screen->clearSpriteList(); } return 0; @@ -350,7 +350,7 @@ int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) { int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) { if (_vm->getGameID() == GID_RTZ) { return _vm->_screen->drawSprite(argv[2], argv[1], argv[0]); - } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { + } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) { SpriteListItem item = _vm->_screen->getFromSpriteList(argv[2]); int16 channelIndex = _vm->_screen->drawSprite(item.index, argv[1] - item.xofs, argv[0] - item.yofs); _vm->_screen->setChannelUseMask(channelIndex); @@ -409,7 +409,7 @@ int16 ScriptFunctions::sfDrawText(int16 argc, int16 *argv) { if (_vm->getGameID() == GID_RTZ) { text = _vm->_dat->getObjectString(argv[argc - 1]); - } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) { + } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) { text = _vm->_dat->getString(argv[argc - 1]); } diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp new file mode 100644 index 0000000000..81b32adb15 --- /dev/null +++ b/engines/parallaction/balloons.cpp @@ -0,0 +1,728 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/util.h" + +#include "parallaction/graphics.h" +#include "parallaction/parallaction.h" + +namespace Parallaction { + + +#define BALLOON_TRANSPARENT_COLOR_NS 2 +#define BALLOON_TRANSPARENT_COLOR_BR 0 + +#define BALLOON_TAIL_WIDTH 12 +#define BALLOON_TAIL_HEIGHT 10 + + +byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = { + { + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, + 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + }, + { + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02 + } +}; + +class BalloonManager_ns : public BalloonManager { + + static int16 _dialogueBalloonX[5]; + + struct Balloon { + Common::Rect outerBox; + Common::Rect innerBox; + Graphics::Surface *surface; + GfxObj *obj; + } _intBalloons[5]; + + uint _numBalloons; + + void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height); + void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); + int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); + Balloon *getBalloon(uint id); + + Gfx *_gfx; + +public: + BalloonManager_ns(Gfx *gfx); + ~BalloonManager_ns(); + + void freeBalloons(); + int setLocationBalloon(char *text, bool endGame); + int setDialogueBalloon(char *text, uint16 winding, byte textColor); + int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); + void setBalloonText(uint id, char *text, byte textColor); + int hitTestDialogueBalloon(int x, int y); +}; + +int16 BalloonManager_ns::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 }; + +BalloonManager_ns::BalloonManager_ns(Gfx *gfx) : _numBalloons(0), _gfx(gfx) { + +} + +BalloonManager_ns::~BalloonManager_ns() { + +} + + +BalloonManager_ns::Balloon* BalloonManager_ns::getBalloon(uint id) { + assert(id < _numBalloons); + return &_intBalloons[id]; +} + +int BalloonManager_ns::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) { + assert(_numBalloons < 5); + + int id = _numBalloons; + + Balloon *balloon = &_intBalloons[id]; + + int16 real_h = (winding == -1) ? h : h + 9; + balloon->surface = new Graphics::Surface; + balloon->surface->create(w, real_h, 1); + balloon->surface->fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR_NS); + + Common::Rect r(w, h); + balloon->surface->fillRect(r, 0); + balloon->outerBox = r; + + r.grow(-borderThickness); + balloon->surface->fillRect(r, 1); + balloon->innerBox = r; + + if (winding != -1) { + // draws tail + // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill. + winding = (winding == 0 ? 1 : 0); + Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT); + s.moveTo(r.width()/2 - 5, r.bottom - 1); + _gfx->blt(s, _resBalloonTail[winding], balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR_NS); + } + + _numBalloons++; + + return id; +} + + +int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { + + int16 w, h; + + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + + int id = createBalloon(w+5, h, winding, 1); + Balloon *balloon = &_intBalloons[id]; + + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = x; + balloon->obj->y = y; + balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + + return id; +} + +int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textColor) { + + int16 w, h; + + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + + int id = createBalloon(w+5, h, winding, 1); + Balloon *balloon = &_intBalloons[id]; + + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = _dialogueBalloonX[id]; + balloon->obj->y = 10; + balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + + if (id > 0) { + balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].outerBox.height(); + } + + + return id; +} + +void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) { + Balloon *balloon = getBalloon(id); + balloon->surface->fillRect(balloon->innerBox, 1); + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); +} + + +int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) { + + int16 w, h; + + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + + int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR_NS); + Balloon *balloon = &_intBalloons[id]; + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = 5; + balloon->obj->y = 5; + balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS; + + return id; +} + +int BalloonManager_ns::hitTestDialogueBalloon(int x, int y) { + + Common::Point p; + + for (uint i = 0; i < _numBalloons; i++) { + p.x = x - _intBalloons[i].obj->x; + p.y = y - _intBalloons[i].obj->y; + + if (_intBalloons[i].innerBox.contains(p)) + return i; + } + + return -1; +} + +void BalloonManager_ns::freeBalloons() { + _gfx->destroyBalloons(); + + for (uint i = 0; i < _numBalloons; i++) { + _intBalloons[i].obj = 0; + _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons + } + + _numBalloons = 0; +} + +void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) { + + uint16 lines = 0; + uint16 linewidth = 0; + + uint16 rx = 10; + uint16 ry = 4; + + uint16 blankWidth = font->getStringWidth(" "); + uint16 tokenWidth = 0; + + char token[MAX_TOKEN_LEN]; + + if (wrapwidth == -1) + wrapwidth = _vm->_screenWidth; + + while (strlen(text) > 0) { + + text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true); + + if (!scumm_stricmp(token, "%p")) { + lines++; + rx = 10; + ry = 4 + lines*10; // y + + strcpy(token, "> ......."); + strncpy(token+2, _password, strlen(_password)); + tokenWidth = font->getStringWidth(token); + } else { + tokenWidth = font->getStringWidth(token); + + linewidth += tokenWidth; + + if (linewidth > wrapwidth) { + // wrap line + lines++; + rx = 10; // x + ry = 4 + lines*10; // y + linewidth = tokenWidth; + } + + if (!scumm_stricmp(token, "%s")) { + sprintf(token, "%d", _score); + } + + } + + _gfx->drawText(font, surf, rx, ry, token, color); + + rx += tokenWidth + blankWidth; + linewidth += blankWidth; + + text = Common::ltrim(text); + } + +} + +void BalloonManager_ns::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { + + uint16 lines = 0; + uint16 w = 0; + *width = 0; + + uint16 blankWidth = font->getStringWidth(" "); + uint16 tokenWidth = 0; + + char token[MAX_TOKEN_LEN]; + + while (strlen(text) != 0) { + + text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true); + tokenWidth = font->getStringWidth(token); + + w += tokenWidth; + + if (!scumm_stricmp(token, "%p")) { + lines++; + } else { + if (w > maxwidth) { + w -= tokenWidth; + lines++; + if (w > *width) + *width = w; + + w = tokenWidth; + } + } + + w += blankWidth; + text = Common::ltrim(text); + } + + if (*width < w) *width = w; + *width += 10; + + *height = lines * 10 + 20; + + return; +} + + + + + +class BalloonManager_br : public BalloonManager { + + struct Balloon { + Common::Rect box; + Graphics::Surface *surface; + GfxObj *obj; + } _intBalloons[3]; + + uint _numBalloons; + + Disk *_disk; + Gfx *_gfx; + + Frames *_leftBalloon; + Frames *_rightBalloon; + + void cacheAnims(); + void getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height); + void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); + int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); + Balloon *getBalloon(uint id); + Graphics::Surface *expandBalloon(Frames *data, int frameNum); + + void textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color); + void textEmitCenteredLine(); + void textAccum(const Common::String &token, uint16 width); + void textNewLine(); + + Common::String _textLine; + Graphics::Surface *_textSurf; + Font *_textFont; + uint16 _textX, _textY; + byte _textColor; + uint16 _textLines, _textWidth; + + void extentSetup(Font *font, int16 *width, int16 *height); + void extentAction(); + + int16 *_extentWidth, *_extentHeight; + + +public: + BalloonManager_br(Disk *disk, Gfx *gfx); + ~BalloonManager_br(); + + void freeBalloons(); + int setLocationBalloon(char *text, bool endGame); + int setDialogueBalloon(char *text, uint16 winding, byte textColor); + int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); + void setBalloonText(uint id, char *text, byte textColor); + int hitTestDialogueBalloon(int x, int y); +}; + + + +BalloonManager_br::Balloon* BalloonManager_br::getBalloon(uint id) { + assert(id < _numBalloons); + return &_intBalloons[id]; +} + +Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum) { + + Common::Rect rect; + data->getRect(frameNum, rect); + + rect.translate(-rect.left, -rect.top); + + Graphics::Surface *surf = new Graphics::Surface; + surf->create(rect.width(), rect.height(), 1); + + _gfx->unpackBlt(rect, data->getData(frameNum), data->getRawSize(frameNum), surf, 0, BALLOON_TRANSPARENT_COLOR_BR); + + return surf; +} + +int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { + cacheAnims(); + + int id = _numBalloons; + Frames *src = 0; + int srcFrame = 0; + + Balloon *balloon = &_intBalloons[id]; + + if (winding == 0) { + src = _rightBalloon; + srcFrame = 0; + } else + if (winding == 1) { + src = _leftBalloon; + srcFrame = 0; + } + + assert(src); + + balloon->surface = expandBalloon(src, srcFrame); + src->getRect(srcFrame, balloon->box); + + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = x + balloon->box.left; + balloon->obj->y = y + balloon->box.top; + balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; + + printf("balloon (%i, %i)\n", balloon->obj->x, balloon->obj->y); + + _numBalloons++; + + return id; +} + +int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textColor) { + cacheAnims(); + + int id = _numBalloons; + Frames *src = 0; + int srcFrame = 0; + + Balloon *balloon = &_intBalloons[id]; + + if (winding == 0) { + src = _rightBalloon; + srcFrame = id; + } else + if (winding == 1) { + src = _leftBalloon; + srcFrame = 0; + } + + assert(src); + + balloon->surface = expandBalloon(src, srcFrame); + src->getRect(srcFrame, balloon->box); + + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = balloon->box.left; + balloon->obj->y = balloon->box.top; + balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR; + + if (id > 0) { + balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].box.height(); + } + + _numBalloons++; + + return id; +} + +void BalloonManager_br::setBalloonText(uint id, char *text, byte textColor) { } + +int BalloonManager_br::setLocationBalloon(char *text, bool endGame) { +/* + int16 w, h; + + getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + + int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR); + Balloon *balloon = &_intBalloons[id]; + drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH); + + // TODO: extract some text to make a name for obj + balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0); + balloon->obj->x = 5; + balloon->obj->y = 5; +*/ + return 0; +} + +int BalloonManager_br::hitTestDialogueBalloon(int x, int y) { + + Common::Point p; + + for (uint i = 0; i < _numBalloons; i++) { + p.x = x - _intBalloons[i].obj->x; + p.y = y - _intBalloons[i].obj->y; + + if (_intBalloons[i].box.contains(p)) + return i; + } + + return -1; +} + +void BalloonManager_br::freeBalloons() { + _gfx->destroyBalloons(); + + for (uint i = 0; i < _numBalloons; i++) { + _intBalloons[i].obj = 0; + _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons + } + + _numBalloons = 0; +} + +void BalloonManager_br::cacheAnims() { + if (!_leftBalloon) { + _leftBalloon = _disk->loadFrames("fumetto.ani"); + _rightBalloon = _disk->loadFrames("fumdx.ani"); + } +} + + +void BalloonManager_br::extentSetup(Font *font, int16 *width, int16 *height) { + _extentWidth = width; + _extentHeight = height; + + _textLine.clear(); + _textLines = 0; + _textWidth = 0; + _textFont = font; +} + +void BalloonManager_br::extentAction() { + if (_textWidth > *_extentWidth) { + *_extentWidth = _textWidth; + } + *_extentHeight = _textLines * _textFont->height(); +} + +void BalloonManager_br::textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color) { + uint16 maxWidth = 216; + + int16 w, h; + getStringExtent(font, text.c_str(), maxWidth, &w, &h); + + w += 10; + h += 12; + + _textLine.clear(); + _textSurf = dest; + _textFont = font; + _textX = 0; + _textY = (_textSurf->h - h) / 2; + _textColor = color; + _textLines = 0; + _textWidth = 0; +} + +void BalloonManager_br::textEmitCenteredLine() { + if (_textLine.empty()) { + return; + } + uint16 rx = _textX + (_textSurf->w - _textWidth) / 2; + uint16 ry = _textY + _textLines * _textFont->height(); // y + _gfx->drawText(_textFont, _textSurf, rx, ry, _textLine.c_str(), _textColor); +} + +void BalloonManager_br::textAccum(const Common::String &token, uint16 width) { + if (token.empty()) { + return; + } + + _textWidth += width; + _textLine += token; +} + +void BalloonManager_br::textNewLine() { + _textLines++; + _textWidth = 0; + _textLine.clear(); +} + + +// TODO: really, base this and getStringExtent on some kind of LineTokenizer, instead of +// repeating the algorithm and changing a couple of lines. +void BalloonManager_br::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapWidth) { + textSetupRendering(text, surf, font, color); + + wrapWidth = 216; + + Common::StringTokenizer tokenizer(text, " "); + Common::String token; + Common::String blank(" "); + + uint16 blankWidth = font->getStringWidth(" "); + uint16 tokenWidth = 0; + + while (!tokenizer.empty()) { + token = tokenizer.nextToken(); + + if (token == '/') { + tokenWidth = 0; + textEmitCenteredLine(); + textNewLine(); + } else { + // todo: expand '%' + tokenWidth = font->getStringWidth(token.c_str()); + + if (_textWidth == 0) { + textAccum(token, tokenWidth); + } else { + if (_textWidth + blankWidth + tokenWidth <= wrapWidth) { + textAccum(blank, blankWidth); + textAccum(token, tokenWidth); + } else { + textEmitCenteredLine(); + textNewLine(); + textAccum(token, tokenWidth); + } + } + } + } + + textEmitCenteredLine(); +} + + + +void BalloonManager_br::getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height) { + extentSetup(font, width, height); + + Common::StringTokenizer tokenizer(text, " "); + Common::String token; + Common::String blank(" "); + + uint16 blankWidth = font->getStringWidth(" "); + uint16 tokenWidth = 0; + + while (!tokenizer.empty()) { + token = tokenizer.nextToken(); + + if (token == '/') { + tokenWidth = 0; + extentAction(); + textNewLine(); + } else { + // todo: expand '%' + tokenWidth = font->getStringWidth(token.c_str()); + + if (_textWidth == 0) { + textAccum(token, tokenWidth); + } else { + if (_textWidth + blankWidth + tokenWidth <= maxwidth) { + textAccum(blank, blankWidth); + textAccum(token, tokenWidth); + } else { + extentAction(); + textNewLine(); + textAccum(token, tokenWidth); + } + } + } + } + + extentAction(); +} + + + + +BalloonManager_br::BalloonManager_br(Disk *disk, Gfx *gfx) : _numBalloons(0), _disk(disk), _gfx(gfx), _leftBalloon(0), _rightBalloon(0) { +} + +BalloonManager_br::~BalloonManager_br() { + delete _leftBalloon; + delete _rightBalloon; +} + +void Parallaction::setupBalloonManager() { + if (_vm->getGameType() == GType_Nippon) { + _balloonMan = new BalloonManager_ns(_vm->_gfx); + } else + if (_vm->getGameType() == GType_BRA) { + _balloonMan = new BalloonManager_br(_vm->_disk, _vm->_gfx); + } else { + error("Unknown game type"); + } +} + + + +} // namespace Parallaction diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp index 68e6a70ffb..761e11dc7d 100644 --- a/engines/parallaction/callables_ns.cpp +++ b/engines/parallaction/callables_ns.cpp @@ -37,18 +37,6 @@ namespace Parallaction { -// part completion messages -static const char *endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; -static const char *endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"}; -static const char *endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"}; -static const char *endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"}; -// game completion messages -static const char *endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; -static const char *endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"}; -static const char *endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"}; -static const char *endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"}; - - /* intro callables data members */ @@ -143,18 +131,6 @@ static uint16 _rightHandPositions[684] = { 0x00e0, 0x007b, 0x00e0, 0x0077 }; -struct Credit { - const char *_role; - const char *_name; -} _credits[] = { - {"Music and Sound Effects", "MARCO CAPRELLI"}, - {"PC Version", "RICCARDO BALLARINO"}, - {"Project Manager", "LOVRANO CANEPA"}, - {"Production", "BRUNO BOZ"}, - {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"}, - {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"} -}; - /* game callables */ @@ -304,23 +280,19 @@ void Parallaction_ns::_c_trasformata(void *parm) { } void Parallaction_ns::_c_offMouse(void *parm) { - _input->showCursor(false); - _engineFlags |= kEngineBlockInput; - return; + _input->setMouseState(MOUSE_DISABLED); } void Parallaction_ns::_c_onMouse(void *parm) { - _engineFlags &= ~kEngineBlockInput; - _input->showCursor(true); - return; + _input->setMouseState(MOUSE_ENABLED_SHOW); } void Parallaction_ns::_c_setMask(void *parm) { - memset(_gfx->_backgroundInfo.mask.data + 3600, 0, 3600); - _gfx->_backgroundInfo.layers[1] = 500; + memset(_gfx->_backgroundInfo->mask.data + 3600, 0, 3600); + _gfx->_backgroundInfo->layers[1] = 500; return; } @@ -340,8 +312,8 @@ void Parallaction_ns::_c_endComment(void *param) { g_system->delayMillis(20); } - _input->waitUntilLeftClick(); - _gfx->freeBalloons(); + _input->waitForButtonEvent(kMouseLeftUp); + _balloonMan->freeBalloons(); return; } @@ -376,37 +348,12 @@ void Parallaction_ns::_c_finito(void *parm) { setPartComplete(_char); cleanInventory(); - _gfx->setPalette(_gfx->_palette); - - uint id[4]; - - if (allPartsComplete()) { - id[0] = _gfx->createLabel(_menuFont, endMsg4[_language], 1); - id[1] = _gfx->createLabel(_menuFont, endMsg5[_language], 1); - id[2] = _gfx->createLabel(_menuFont, endMsg6[_language], 1); - id[3] = _gfx->createLabel(_menuFont, endMsg7[_language], 1); - } else { - id[0] = _gfx->createLabel(_menuFont, endMsg0[_language], 1); - id[1] = _gfx->createLabel(_menuFont, endMsg1[_language], 1); - id[2] = _gfx->createLabel(_menuFont, endMsg2[_language], 1); - id[3] = _gfx->createLabel(_menuFont, endMsg3[_language], 1); - } - - _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70); - _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); - _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130); - _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160); - _input->waitUntilLeftClick(); + cleanupGame(); - _gfx->freeLabels(); + _gfx->setPalette(_gfx->_palette); - if (allPartsComplete()) { - scheduleLocationSwitch("estgrotta.drki"); - } else { - selectStartLocation(); - } + startEndPartSequence(); - cleanupGame(); return; } @@ -417,6 +364,14 @@ void Parallaction_ns::_c_ridux(void *parm) { } void Parallaction_ns::_c_testResult(void *parm) { + if (_inTestResult) { // NOTE: _inTestResult has been added because the scripts call _c_testResult multiple times to cope with + // the multiple buffering that was used in the original engine. _inTestResult now prevents the engine + // from crashing when the scripts are executed. + return; + } + _inTestResult = true; + + _gfx->freeLabels(); _gfx->updateScreen(); _disk->selectArchive("disk1"); @@ -459,52 +414,11 @@ void Parallaction_ns::_c_startIntro(void *parm) { _soundMan->playMusic(); } - _engineFlags |= kEngineBlockInput; - - return; + _input->setMouseState(MOUSE_DISABLED); } void Parallaction_ns::_c_endIntro(void *parm) { - - debugC(1, kDebugExec, "endIntro()"); - - uint id[2]; - for (uint16 _si = 0; _si < 6; _si++) { - id[0] = _gfx->createLabel(_menuFont, _credits[_si]._role, 1); - id[1] = _gfx->createLabel(_menuFont, _credits[_si]._name, 1); - - _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); - _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); - - _gfx->updateScreen(); - - _input->waitForButtonEvent(kMouseLeftUp, 5500); - - _gfx->freeLabels(); - } - debugC(1, kDebugExec, "endIntro(): done showing credits"); - - _soundMan->stopMusic(); - - if ((getFeatures() & GF_DEMO) == 0) { - - id[0] = _gfx->createLabel(_menuFont, "CLICK MOUSE BUTTON TO START", 1); - _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); - - _input->waitUntilLeftClick(); - - _gfx->freeLabels(); - - _engineFlags &= ~kEngineBlockInput; - selectStartLocation(); - - cleanupGame(); - - } else { - _input->waitUntilLeftClick(); - } - - return; + startCreditSequence(); } void Parallaction_ns::_c_moveSheet(void *parm) { @@ -588,11 +502,11 @@ void Parallaction_ns::_c_shade(void *parm) { _rightHandAnim->_top ); - uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo.mask.internalWidth; + uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo->mask.internalWidth; for (uint16 _si = r.top; _si < r.bottom; _si++) { - memset(_gfx->_backgroundInfo.mask.data + _di, 0, r.width()/4+1); - _di += _gfx->_backgroundInfo.mask.internalWidth; + memset(_gfx->_backgroundInfo->mask.data + _di, 0, r.width()/4+1); + _di += _gfx->_backgroundInfo->mask.internalWidth; } return; diff --git a/engines/parallaction/debug.cpp b/engines/parallaction/debug.cpp index 3c90a76f61..f57976594e 100644 --- a/engines/parallaction/debug.cpp +++ b/engines/parallaction/debug.cpp @@ -188,17 +188,15 @@ bool Debugger::Cmd_GfxObjects(int argc, const char **argv) { const char *objType[] = { "DOOR", "GET", "ANIM" }; DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n" - "| name | x | y | z | f | type | flag |\n" + "| name | x | y | z | f | type | visi |\n" "+--------------------+-----+-----+-----+-----+--------+--------+\n"); - for (uint i = 0; i < 3; i++) { - GfxObjList::iterator b = _vm->_gfx->_gfxobjList[i].begin(); - GfxObjList::iterator e = _vm->_gfx->_gfxobjList[i].end(); + GfxObjList::iterator b = _vm->_gfx->_gfxobjList.begin(); + GfxObjList::iterator e = _vm->_gfx->_gfxobjList.end(); - for ( ; b != e; b++) { - GfxObj *obj = *b; - DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], 6 ); - } + for ( ; b != e; b++) { + GfxObj *obj = *b; + DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], obj->isVisible() ); } DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n"); diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp index 8841b9ca40..0476b01454 100644 --- a/engines/parallaction/detection.cpp +++ b/engines/parallaction/detection.cpp @@ -154,7 +154,23 @@ static const PARALLACTIONGameDescription gameDescriptions[] = { Common::ADGF_NO_FLAGS }, GType_BRA, - GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT + GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT, + }, + + { + { + "bra", + "Demo", + { + { "russia.fnt", 0, "0dd55251d2886d6783718df2b184bf97", 10649 }, + { NULL, 0, NULL, 0} + }, + Common::UNK_LANG, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GType_BRA, + GF_LANG_EN | GF_DEMO, }, // TODO: Base the detection of Amiga BRA on actual data file, not executable file. @@ -171,9 +187,25 @@ static const PARALLACTIONGameDescription gameDescriptions[] = { Common::ADGF_NO_FLAGS }, GType_BRA, - GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT + GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT, }, + // TODO: Base the detection of Amiga BRA demo on actual data file, not executable file. + { + { + "bra", + "Demo", + { + { "bigred", 0, "b62a7b589fb5e9071f021227640893bf", 97004 }, + { NULL, 0, NULL, 0} + }, + Common::UNK_LANG, + Common::kPlatformAmiga, + Common::ADGF_DEMO + }, + GType_BRA, + GF_LANG_EN | GF_DEMO, + }, { AD_TABLE_END_MARKER, 0, 0 } }; diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp index 70db637699..21584a0525 100644 --- a/engines/parallaction/dialogue.cpp +++ b/engines/parallaction/dialogue.cpp @@ -33,7 +33,7 @@ namespace Parallaction { #define MAX_PASSWORD_LENGTH 7 - +/* #define QUESTION_BALLOON_X 140 #define QUESTION_BALLOON_Y 10 #define QUESTION_CHARACTER_X 190 @@ -41,118 +41,127 @@ namespace Parallaction { #define ANSWER_CHARACTER_X 10 #define ANSWER_CHARACTER_Y 80 +*/ +struct BalloonPositions { + Common::Point _questionBalloon; + Common::Point _questionChar; + + Common::Point _answerChar; +}; + +BalloonPositions _balloonPositions_NS = { + Common::Point(140, 10), + Common::Point(190, 80), + Common::Point(10, 80) +}; + +BalloonPositions _balloonPositions_BR = { + Common::Point(0, 0), + Common::Point(380, 80), + Common::Point(10, 80) +}; + class DialogueManager { + enum { + RUN_QUESTION, + RUN_ANSWER, + NEXT_QUESTION, + NEXT_ANSWER, + DIALOGUE_OVER + } _state; + Parallaction *_vm; - SpeakData *_data; Dialogue *_dialogue; bool _askPassword; + int _passwordLen; + bool _passwordChanged; bool isNpc; - Frames *_questioner; - Frames *_answerer; + GfxObj *_questioner; + GfxObj *_answerer; Question *_q; uint16 _visAnswers[5]; int _numVisAnswers; + int _answerId; + + int _selection, _oldSelection; + + uint32 _mouseButtons; + Common::Point _mousePos; + bool _isKeyDown; + uint16 _downKey; + + BalloonPositions _ballonPos; + public: - DialogueManager(Parallaction *vm, SpeakData *data) : _vm(vm), _data(data) { - _dialogue = _data->_dialogue; - isNpc = scumm_stricmp(_data->_name, "yourself") && _data->_name[0] != '\0'; - _questioner = isNpc ? _vm->_disk->loadTalk(_data->_name) : _vm->_char._talk; - _answerer = _vm->_char._talk; - } + DialogueManager(Parallaction *vm, ZonePtr z); + ~DialogueManager(); - ~DialogueManager() { - if (isNpc) { - delete _questioner; - } + bool isOver() { + return _state == DIALOGUE_OVER; } - void run(); + ZonePtr _z; + CommandList *_cmdList; + protected: - void displayQuestion(); + bool displayQuestion(); bool displayAnswers(); bool displayAnswer(uint16 i); - uint16 getAnswer(); - int16 selectAnswer(); - uint16 askPassword(); + int16 selectAnswer1(); + int16 selectAnswerN(); + int16 askPassword(); int16 getHoverAnswer(int16 x, int16 y); -}; - -uint16 DialogueManager::askPassword() { - debugC(3, kDebugExec, "checkDialoguePassword()"); - - uint16 passwordLen = 0; - _password[0] = '\0'; - - _vm->_gfx->setDialogueBalloon(_q->_answers[0]->_text, 1, 3); - int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); - _vm->_gfx->setItemFrame(id, 0); - - Common::Event e; - bool changed = true; // force first refresh - - while (true) { - e.kbd.ascii = 0; - - if (g_system->getEventManager()->pollEvent(e)) { - if (e.type == Common::EVENT_QUIT) { - // TODO: don't quit() here, just have caller routines to check - // on kEngineQuit and exit gracefully to allow the engine to shut down - _engineFlags |= kEngineQuit; - g_system->quit(); - } - - if ((e.type == Common::EVENT_KEYDOWN) && isdigit(e.kbd.ascii)) { - _password[passwordLen] = e.kbd.ascii; - passwordLen++; - _password[passwordLen] = '\0'; - changed = true; - } - } - - if (changed) { - _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 3); - _vm->_gfx->updateScreen(); - changed = false; - } - - if ((passwordLen == MAX_PASSWORD_LENGTH) || (e.kbd.ascii == Common::KEYCODE_RETURN)) { + void runQuestion(); + void runAnswer(); + void nextQuestion(); + void nextAnswer(); - if ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) || - (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) || - (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3))) { + bool checkPassword(); + void resetPassword(); + void accumPassword(uint16 ascii); +}; - break; +DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) { + int gtype = vm->getGameType(); + if (gtype == GType_Nippon) { + _ballonPos = _balloonPositions_NS; + } else + if (gtype == GType_BRA) { + _ballonPos = _balloonPositions_BR; + } else + error("unsupported game in DialogueManager"); + + _dialogue = _z->u.speak->_dialogue; + isNpc = scumm_stricmp(_z->u.speak->_name, "yourself") && _z->u.speak->_name[0] != '\0'; + _questioner = isNpc ? _vm->_disk->loadTalk(_z->u.speak->_name) : _vm->_char._talk; + _answerer = _vm->_char._talk; - } else { - passwordLen = 0; - _password[0] = '\0'; - changed = true; - } + _askPassword = false; + _q = _dialogue->_questions[0]; - } + _cmdList = 0; + _answerId = 0; - g_system->delayMillis(20); + _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; +} +DialogueManager::~DialogueManager() { + if (isNpc) { + delete _questioner; } - - _vm->_gfx->hideDialogueStuff(); - - return 0; - + _z = nullZonePtr; } - - bool DialogueManager::displayAnswer(uint16 i) { Answer *a = _q->_answers[i]; @@ -164,11 +173,11 @@ bool DialogueManager::displayAnswer(uint16 i) { // display suitable answers if (((a->_yesFlags & flags) == a->_yesFlags) && ((a->_noFlags & ~flags) == a->_noFlags)) { - int id = _vm->_gfx->setDialogueBalloon(a->_text, 1, 3); + int id = _vm->_balloonMan->setDialogueBalloon(a->_text, 1, 3); assert(id >= 0); _visAnswers[id] = i; - _askPassword = (strstr(a->_text, "%p") != NULL); + _askPassword = (strstr(a->_text, "%P") != NULL); _numVisAnswers++; return true; @@ -185,126 +194,243 @@ bool DialogueManager::displayAnswers() { displayAnswer(i); } + if (_askPassword) { + resetPassword(); +// _vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3); + int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); + _vm->_gfx->setItemFrame(id, 0); + } else + if (_numVisAnswers == 1) { + int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); + _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF); + _vm->_balloonMan->setBalloonText(0, _q->_answers[_visAnswers[0]]->_text, 0); + } else + if (_numVisAnswers > 1) { + int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y); + _vm->_gfx->setItemFrame(id, _q->_answers[_visAnswers[0]]->_mood & 0xF); + _oldSelection = -1; + _selection = 0; + } + return _numVisAnswers > 0; } -void DialogueManager::displayQuestion() { - - if (!scumm_stricmp(_q->_text, "NULL")) return; +bool DialogueManager::displayQuestion() { + if (!scumm_stricmp(_q->_text, "NULL")) return false; - _vm->_gfx->setSingleBalloon(_q->_text, QUESTION_BALLOON_X, QUESTION_BALLOON_Y, _q->_mood & 0x10, 0); - int id = _vm->_gfx->setItem(_questioner, QUESTION_CHARACTER_X, QUESTION_CHARACTER_Y); + _vm->_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->_mood & 0x10, 0); + int id = _vm->_gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y); _vm->_gfx->setItemFrame(id, _q->_mood & 0xF); - _vm->_gfx->updateScreen(); - _vm->_input->waitUntilLeftClick(); - _vm->_gfx->hideDialogueStuff(); + return true; +} - return; + +bool DialogueManager::checkPassword() { + return ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) || + (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) || + (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3))); } -uint16 DialogueManager::getAnswer() { +void DialogueManager::resetPassword() { + _passwordLen = 0; + _password[0] = '\0'; + _passwordChanged = true; +} + +void DialogueManager::accumPassword(uint16 ascii) { + if (!isdigit(ascii)) { + return; + } - uint16 answer = 0; + _password[_passwordLen] = ascii; + _passwordLen++; + _password[_passwordLen] = '\0'; + _passwordChanged = true; +} - if (_askPassword == false) { - answer = selectAnswer(); - } else { - answer = askPassword(); +int16 DialogueManager::askPassword() { + + if (_isKeyDown) { + accumPassword(_downKey); + } + + if (_passwordChanged) { + _vm->_balloonMan->setBalloonText(0, _q->_answers[0]->_text, 3); + _passwordChanged = false; } - debugC(3, kDebugExec, "runDialogue: user selected answer #%i", answer); + if ((_passwordLen == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) { + if (checkPassword()) { + return 0; + } else { + resetPassword(); + } + } - return answer; + return -1; } -void DialogueManager::run() { +int16 DialogueManager::selectAnswer1() { - _askPassword = false; - CommandList *cmdlist = NULL; + if (_mouseButtons == kMouseLeftUp) { + return 0; + } - _q = _dialogue->_questions[0]; - int16 answer; + return -1; +} - while (_q) { +int16 DialogueManager::selectAnswerN() { - answer = 0; + _selection = _vm->_balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y); - displayQuestion(); - if (_q->_answers[0] == NULL) break; + if (_selection != _oldSelection) { + if (_oldSelection != -1) { + _vm->_balloonMan->setBalloonText(_oldSelection, _q->_answers[_visAnswers[_oldSelection]]->_text, 3); + } - if (scumm_stricmp(_q->_answers[0]->_text, "NULL")) { - if (!displayAnswers()) break; - answer = getAnswer(); - cmdlist = &_q->_answers[answer]->_commands; + if (_selection != -1) { + _vm->_balloonMan->setBalloonText(_selection, _q->_answers[_visAnswers[_selection]]->_text, 0); + _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[_selection]]->_mood & 0xF); } + } + + _oldSelection = _selection; - _q = _q->_answers[answer]->_following._question; + if ((_mouseButtons == kMouseLeftUp) && (_selection != -1)) { + return _visAnswers[_selection]; } - if (cmdlist) - _vm->runCommands(*cmdlist); + return -1; +} + +void DialogueManager::runQuestion() { + debugC(9, kDebugDialogue, "runQuestion\n"); + + if (_mouseButtons == kMouseLeftUp) { + _vm->hideDialogueStuff(); + _state = NEXT_ANSWER; + } } -int16 DialogueManager::selectAnswer() { - int16 numAvailableAnswers = _numVisAnswers; +void DialogueManager::nextAnswer() { + debugC(9, kDebugDialogue, "nextAnswer\n"); - int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); - _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF); + if (_q->_answers[0] == NULL) { + _state = DIALOGUE_OVER; + return; + } - if (numAvailableAnswers == 1) { - _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 0); - _vm->_input->waitUntilLeftClick(); - _vm->_gfx->hideDialogueStuff(); - return 0; + if (!scumm_stricmp(_q->_answers[0]->_text, "NULL")) { + _answerId = 0; + _state = NEXT_QUESTION; + return; + } + + _state = displayAnswers() ? RUN_ANSWER : DIALOGUE_OVER; +} + +void DialogueManager::runAnswer() { + debugC(9, kDebugDialogue, "runAnswer\n"); + + if (_askPassword) { + _answerId = askPassword(); + } else + if (_numVisAnswers == 1) { + _answerId = selectAnswer1(); + } else { + _answerId = selectAnswerN(); + } + + if (_answerId != -1) { + _cmdList = &_q->_answers[_answerId]->_commands; + _vm->hideDialogueStuff(); + _state = NEXT_QUESTION; } +} + +void DialogueManager::nextQuestion() { + debugC(9, kDebugDialogue, "nextQuestion\n"); - int oldSelection = -1; - int selection; + _q = _q->_answers[_answerId]->_following._question; + if (_q == 0) { + _state = DIALOGUE_OVER; + } else { + _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; + } +} - uint32 event; - Common::Point p; - while (true) { - _vm->_input->readInput(); - _vm->_input->getCursorPos(p); - event = _vm->_input->getLastButtonEvent(); - selection = _vm->_gfx->hitTestDialogueBalloon(p.x, p.y); +void DialogueManager::run() { - if (selection != oldSelection) { - if (oldSelection != -1) { - _vm->_gfx->setBalloonText(oldSelection, _q->_answers[_visAnswers[oldSelection]]->_text, 3); - } + // cache event data + _mouseButtons = _vm->_input->getLastButtonEvent(); + _vm->_input->getCursorPos(_mousePos); + _isKeyDown = _vm->_input->getLastKeyDown(_downKey); - if (selection != -1) { - _vm->_gfx->setBalloonText(selection, _q->_answers[_visAnswers[selection]]->_text, 0); - _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[selection]]->_mood & 0xF); - } - } + switch (_state) { + case RUN_QUESTION: + runQuestion(); + break; - if ((selection != -1) && (event == kMouseLeftUp)) { - break; - } + case NEXT_ANSWER: + nextAnswer(); + break; - _vm->_gfx->updateScreen(); - g_system->delayMillis(20); + case NEXT_QUESTION: + nextQuestion(); + break; - oldSelection = selection; + case RUN_ANSWER: + runAnswer(); + break; + + case DIALOGUE_OVER: + break; + + default: + error("unknown state in DialogueManager"); + + } + +} + +void Parallaction::enterDialogueMode(ZonePtr z) { + debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u.speak->_name); + _dialogueMan = new DialogueManager(this, z); + _input->_inputMode = Input::kInputModeDialogue; +} + +void Parallaction::exitDialogueMode() { + debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()"); + _input->_inputMode = Input::kInputModeGame; + + if (_dialogueMan->_cmdList) { + _vm->_cmdExec->run(*_dialogueMan->_cmdList); } - _vm->_gfx->hideDialogueStuff(); + // The current instance of _dialogueMan must be destroyed before the zone commands + // are executed, because they may create another instance of _dialogueMan that + // overwrite the current one. This would cause headaches (and it did, actually). + ZonePtr z = _dialogueMan->_z; + delete _dialogueMan; + _dialogueMan = 0; - return _visAnswers[selection]; + _cmdExec->run(z->_commands, z); } +void Parallaction::runDialogueFrame() { + if (_input->_inputMode != Input::kInputModeDialogue) { + return; + } -void Parallaction::runDialogue(SpeakData *data) { - debugC(1, kDebugExec, "runDialogue: starting dialogue '%s'", data->_name); + _dialogueMan->run(); - DialogueManager man(this, data); - man.run(); + if (_dialogueMan->isOver()) { + exitDialogueMode(); + } return; } diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h index b76c66aead..341229a649 100644 --- a/engines/parallaction/disk.h +++ b/engines/parallaction/disk.h @@ -28,6 +28,8 @@ #define PATH_LEN 200 +#include "common/fs.h" + #include "common/file.h" #include "graphics/surface.h" @@ -55,12 +57,12 @@ public: virtual Script* loadLocation(const char *name) = 0; virtual Script* loadScript(const char* name) = 0; - virtual Frames* loadTalk(const char *name) = 0; - virtual Frames* loadObjects(const char *name) = 0; + virtual GfxObj* loadTalk(const char *name) = 0; + virtual GfxObj* loadObjects(const char *name) = 0; virtual Frames* loadPointer(const char *name) = 0; - virtual Frames* loadHead(const char* name) = 0; + virtual GfxObj* loadHead(const char* name) = 0; virtual Font* loadFont(const char* name) = 0; - virtual Frames* loadStatic(const char* name) = 0; + virtual GfxObj* loadStatic(const char* name) = 0; virtual Frames* loadFrames(const char* name) = 0; virtual void loadSlide(BackgroundInfo& info, const char *filename) = 0; virtual void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) = 0; @@ -147,12 +149,12 @@ public: Script* loadLocation(const char *name); Script* loadScript(const char* name); - Frames* loadTalk(const char *name); - Frames* loadObjects(const char *name); + GfxObj* loadTalk(const char *name); + GfxObj* loadObjects(const char *name); Frames* loadPointer(const char *name); - Frames* loadHead(const char* name); + GfxObj* loadHead(const char* name); Font* loadFont(const char* name); - Frames* loadStatic(const char* name); + GfxObj* loadStatic(const char* name); Frames* loadFrames(const char* name); void loadSlide(BackgroundInfo& info, const char *filename); void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path); @@ -181,12 +183,12 @@ public: Script* loadLocation(const char *name); Script* loadScript(const char* name); - Frames* loadTalk(const char *name); - Frames* loadObjects(const char *name); + GfxObj* loadTalk(const char *name); + GfxObj* loadObjects(const char *name); Frames* loadPointer(const char *name); - Frames* loadHead(const char* name); + GfxObj* loadHead(const char* name); Font* loadFont(const char* name); - Frames* loadStatic(const char* name); + GfxObj* loadStatic(const char* name); Frames* loadFrames(const char* name); void loadSlide(BackgroundInfo& info, const char *filename); void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path); @@ -202,15 +204,29 @@ public: class DosDisk_br : public Disk { protected: + uint16 _language; + Parallaction *_vm; - char _partPath[PATH_LEN]; - char _languageDir[2]; + + FilesystemNode _baseDir; + FilesystemNode _partDir; + + FilesystemNode _aniDir; + FilesystemNode _bkgDir; + FilesystemNode _mscDir; + FilesystemNode _mskDir; + FilesystemNode _pthDir; + FilesystemNode _rasDir; + FilesystemNode _scrDir; + FilesystemNode _sfxDir; + FilesystemNode _talDir; protected: - void errorFileNotFound(const char *s); + void errorFileNotFound(const FilesystemNode &dir, const Common::String &filename); Font *createFont(const char *name, Common::ReadStream &stream); Sprites* createSprites(Common::ReadStream &stream); void loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette); + GfxObj* createInventoryObjects(Common::SeekableReadStream &stream); public: DosDisk_br(Parallaction *vm); @@ -220,12 +236,12 @@ public: void setLanguage(uint16 language); Script* loadLocation(const char *name); Script* loadScript(const char* name); - Frames* loadTalk(const char *name); - Frames* loadObjects(const char *name); + GfxObj* loadTalk(const char *name); + GfxObj* loadObjects(const char *name); Frames* loadPointer(const char *name); - Frames* loadHead(const char* name); + GfxObj* loadHead(const char* name); Font* loadFont(const char* name); - Frames* loadStatic(const char* name); + GfxObj* loadStatic(const char* name); Frames* loadFrames(const char* name); void loadSlide(BackgroundInfo& info, const char *filename); void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path); @@ -234,26 +250,49 @@ public: Common::ReadStream* loadSound(const char* name); }; +class DosDemo_br : public DosDisk_br { + +public: + DosDemo_br(Parallaction *vm); + virtual ~DosDemo_br(); + + Common::String selectArchive(const Common::String& name); + +}; + class AmigaDisk_br : public DosDisk_br { protected: BackgroundInfo _backgroundTemp; - Sprites* createSprites(const char *name); + Sprites* createSprites(Common::ReadStream &stream); Font *createFont(const char *name, Common::SeekableReadStream &stream); - void loadMask(BackgroundInfo& info, const char *name); - void loadBackground(BackgroundInfo& info, const char *name); + void loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream); + + FilesystemNode _baseBkgDir; + FilesystemNode _fntDir; + FilesystemNode _commonAniDir; + FilesystemNode _commonBkgDir; + FilesystemNode _commonMscDir; + FilesystemNode _commonMskDir; + FilesystemNode _commonPthDir; + FilesystemNode _commonTalDir; public: AmigaDisk_br(Parallaction *vm); virtual ~AmigaDisk_br(); - Frames* loadTalk(const char *name); + GfxObj* loadTalk(const char *name); Font* loadFont(const char* name); - Frames* loadStatic(const char* name); + GfxObj* loadStatic(const char* name); Frames* loadFrames(const char* name); void loadSlide(BackgroundInfo& info, const char *filename); void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path); + GfxObj* loadObjects(const char *name); + Common::SeekableReadStream* loadMusic(const char* name); + Common::ReadStream* loadSound(const char* name); + Common::String selectArchive(const Common::String& name); + }; } // namespace Parallaction diff --git a/engines/parallaction/disk_br.cpp b/engines/parallaction/disk_br.cpp index 5e88327879..cd57ec8822 100644 --- a/engines/parallaction/disk_br.cpp +++ b/engines/parallaction/disk_br.cpp @@ -25,6 +25,7 @@ #include "graphics/iff.h" +#include "common/config-manager.h" #include "parallaction/parallaction.h" @@ -58,7 +59,7 @@ struct Sprites : public Frames { } ~Sprites() { - delete _sprites; + delete[] _sprites; } uint16 getNum() { @@ -90,101 +91,110 @@ struct Sprites : public Frames { -void DosDisk_br::errorFileNotFound(const char *s) { - error("File '%s' not found", s); +void DosDisk_br::errorFileNotFound(const FilesystemNode &dir, const Common::String &filename) { + error("File '%s' not found in directory '%s'", filename.c_str(), dir.getDisplayName().c_str()); } Common::String DosDisk_br::selectArchive(const Common::String& name) { debugC(5, kDebugDisk, "DosDisk_br::selectArchive"); - Common::String oldPath(_partPath); - strcpy(_partPath, name.c_str()); + Common::String oldPath; + if (_partDir.exists()) { + oldPath = _partDir.getDisplayName(); + } + + _partDir = _baseDir.getChild(name); + + _aniDir = _partDir.getChild("ani"); + _bkgDir = _partDir.getChild("bkg"); + _mscDir = _partDir.getChild("msc"); + _mskDir = _partDir.getChild("msk"); + _pthDir = _partDir.getChild("pth"); + _rasDir = _partDir.getChild("ras"); + _scrDir = _partDir.getChild("scripts"); + _sfxDir = _partDir.getChild("sfx"); + _talDir = _partDir.getChild("tal"); return oldPath; } void DosDisk_br::setLanguage(uint16 language) { debugC(5, kDebugDisk, "DosDisk_br::setLanguage"); - - switch (language) { - case 0: - strcpy(_languageDir, "it"); - break; - - case 1: - strcpy(_languageDir, "fr"); - break; - - case 2: - strcpy(_languageDir, "en"); - break; - - case 3: - strcpy(_languageDir, "ge"); - break; - - default: - error("unknown language"); - - } - - return; + assert(language < 4); + _language = language; } -DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm) { - +DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm), _baseDir(ConfMan.get("path")) { } DosDisk_br::~DosDisk_br() { } -Frames* DosDisk_br::loadTalk(const char *name) { +GfxObj* DosDisk_br::loadTalk(const char *name) { debugC(5, kDebugDisk, "DosDisk_br::loadTalk(%s)", name); - Common::File stream; - - char path[PATH_LEN]; - sprintf(path, "%s/tal/%s", _partPath, name); - if (!stream.open(path)) { - sprintf(path, "%s/tal/%s.tal", _partPath, name); - if (!stream.open(path)) - errorFileNotFound(path); + Common::String path(name); + FilesystemNode node = _talDir.getChild(path); + if (!node.exists()) { + path += ".tal"; + node = _talDir.getChild(path); + if (!node.exists()) + errorFileNotFound(_talDir, path); } - return createSprites(stream); + Common::File stream; + stream.open(node); + + // talk position is set to (0,0), because talks are always displayed at + // absolute coordinates, set in the dialogue manager. The original used + // to null out coordinates every time they were needed. We do it better! + Sprites *spr = createSprites(stream); + for (int i = 0; i < spr->getNum(); i++) { + spr->_sprites[i].x = 0; + spr->_sprites[i].y = 0; + } + return new GfxObj(0, spr, name); } Script* DosDisk_br::loadLocation(const char *name) { debugC(5, kDebugDisk, "DosDisk_br::loadLocation"); - Common::File *stream = new Common::File; - - char path[PATH_LEN]; - sprintf(path, "%s/%s/%s.slf", _partPath, _languageDir, name); - if (!stream->open(path)) { - sprintf(path, "%s/%s/%s.loc", _partPath, _languageDir, name); - if (!stream->open(path)) - errorFileNotFound(path); + Common::String langs[4] = { "it", "fr", "en", "ge" }; + FilesystemNode locDir = _partDir.getChild(langs[_language]); + + Common::String path(name); + path += ".slf"; + FilesystemNode node = locDir.getChild(path); + if (!node.exists()) { + path = Common::String(name) + ".loc"; + node = locDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(locDir, path); + } } + Common::File *stream = new Common::File; + stream->open(node); return new Script(stream, true); } Script* DosDisk_br::loadScript(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadScript"); - Common::File *stream = new Common::File; - - char path[PATH_LEN]; - sprintf(path, "%s/scripts/%s.scr", _partPath, name); - if (!stream->open(path)) - errorFileNotFound(path); + Common::String path(name); + path += ".scr"; + FilesystemNode node = _scrDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_scrDir, path); + } + Common::File *stream = new Common::File; + stream->open(node); return new Script(stream, true); } // there are no Head resources in Big Red Adventure -Frames* DosDisk_br::loadHead(const char* name) { +GfxObj* DosDisk_br::loadHead(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadHead"); return 0; } @@ -192,6 +202,7 @@ Frames* DosDisk_br::loadHead(const char* name) { void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette) { stream.skip(4); uint width = stream.readUint32BE(); + if (width & 1) width++; uint height = stream.readUint32BE(); stream.skip(20); @@ -208,12 +219,15 @@ void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surfac Frames* DosDisk_br::loadPointer(const char *name) { debugC(5, kDebugDisk, "DosDisk_br::loadPointer"); - char path[PATH_LEN]; - sprintf(path, "%s.ras", name); + Common::String path(name); + path += ".ras"; + FilesystemNode node = _baseDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_baseDir, path); + } Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); + stream.open(node); Graphics::Surface *surf = new Graphics::Surface; loadBitmap(stream, *surf, 0); @@ -224,39 +238,53 @@ Frames* DosDisk_br::loadPointer(const char *name) { Font* DosDisk_br::loadFont(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadFont"); - char path[PATH_LEN]; - sprintf(path, "%s.fnt", name); + Common::String path(name); + path += ".fnt"; + FilesystemNode node = _baseDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_baseDir, path); + } Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); - + stream.open(node); return createFont(name, stream); } -Frames* DosDisk_br::loadObjects(const char *name) { +GfxObj* DosDisk_br::loadObjects(const char *name) { debugC(5, kDebugDisk, "DosDisk_br::loadObjects"); - return 0; + + Common::String path(name); + FilesystemNode node = _partDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_partDir, path); + } + + Common::File stream; + stream.open(node); + + return createInventoryObjects(stream); } void genSlidePath(char *path, const char* name) { sprintf(path, "%s.bmp", name); } -Frames* DosDisk_br::loadStatic(const char* name) { +GfxObj* DosDisk_br::loadStatic(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadStatic"); - char path[PATH_LEN]; - sprintf(path, "%s/ras/%s", _partPath, name); - Common::File stream; - if (!stream.open(path)) { - errorFileNotFound(path); + Common::String path(name); + FilesystemNode node = _rasDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_rasDir, path); } + Common::File stream; + stream.open(node); + Graphics::Surface *surf = new Graphics::Surface; loadBitmap(stream, *surf, 0); - return new SurfaceToFrames(surf); + return new GfxObj(0, new SurfaceToFrames(surf), name); } Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) { @@ -283,14 +311,18 @@ Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) { Frames* DosDisk_br::loadFrames(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadFrames"); - char path[PATH_LEN]; - sprintf(path, "%s/ani/%s", _partPath, name); + Common::String path(name); + FilesystemNode node = _aniDir.getChild(path); + if (!node.exists()) { + path += ".ani"; + node = _aniDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_aniDir, path); + } + } Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); - - + stream.open(node); return createSprites(stream); } @@ -302,12 +334,15 @@ Frames* DosDisk_br::loadFrames(const char* name) { void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) { debugC(5, kDebugDisk, "DosDisk_br::loadSlide"); - char path[PATH_LEN]; - genSlidePath(path, name); + Common::String path(name); + path += ".bmp"; + FilesystemNode node = _baseDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_baseDir, path); + } Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); + stream.open(node); byte rgb[768]; @@ -325,13 +360,17 @@ void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) { void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) { debugC(5, kDebugDisk, "DosDisk_br::loadScenery"); - char filename[PATH_LEN]; + Common::String filepath; + FilesystemNode node; Common::File stream; if (name) { - sprintf(filename, "%s/bkg/%s.bkg", _partPath, name); - if (!stream.open(filename)) - errorFileNotFound(filename); + filepath = Common::String(name) + ".bkg"; + node = _bkgDir.getChild(filepath); + if (!node.exists()) { + errorFileNotFound(_bkgDir, filepath); + } + stream.open(node); byte rgb[768]; @@ -347,9 +386,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char } if (mask) { - sprintf(filename, "%s/msk/%s.msk", _partPath, mask); - if (!stream.open(filename)) - errorFileNotFound(filename); + filepath = Common::String(mask) + ".msk"; + node = _mskDir.getChild(filepath); + if (!node.exists()) { + errorFileNotFound(_mskDir, filepath); + } + stream.open(node); // NOTE: info.width and info.height are only valid if the background graphics // have already been loaded @@ -360,9 +402,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char } if (path) { - sprintf(filename, "%s/pth/%s.pth", _partPath, path); - if (!stream.open(filename)) - errorFileNotFound(filename); + filepath = Common::String(path) + ".pth"; + node = _pthDir.getChild(filepath); + if (!node.exists()) { + errorFileNotFound(_pthDir, filepath); + } + stream.open(node); // NOTE: info.width and info.height are only valid if the background graphics // have already been loaded @@ -377,15 +422,16 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char Table* DosDisk_br::loadTable(const char* name) { debugC(5, kDebugDisk, "DosDisk_br::loadTable"); - char path[PATH_LEN]; - sprintf(path, "%s/%s.tab", _partPath, name); - - Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); + Common::String path(name); + path += ".tab"; + FilesystemNode node = _partDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_partDir, path); + } + Common::File stream; + stream.open(node); Table *t = createTableFromStream(100, stream); - stream.close(); return t; @@ -407,56 +453,68 @@ Common::ReadStream* DosDisk_br::loadSound(const char* name) { -AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) { +DosDemo_br::DosDemo_br(Parallaction *vm) : DosDisk_br(vm) { } -AmigaDisk_br::~AmigaDisk_br() { +DosDemo_br::~DosDemo_br() { } +Common::String DosDemo_br::selectArchive(const Common::String& name) { + debugC(5, kDebugDisk, "DosDemo_br::selectArchive"); -/* - FIXME: mask values are not computed correctly for level 1 and 2 + Common::String oldPath; + if (_partDir.exists()) { + oldPath = _partDir.getDisplayName(); + } - NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded - into the main loop. -*/ -void buildMask2(byte* buf) { + _partDir = _baseDir; - byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA }; - byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 }; + _aniDir = _partDir.getChild("ani"); + _bkgDir = _partDir.getChild("bkg"); + _mscDir = _partDir.getChild("msc"); + _mskDir = _partDir.getChild("msk"); + _pthDir = _partDir.getChild("pth"); + _rasDir = _partDir.getChild("ras"); + _scrDir = _partDir.getChild("scripts"); + _sfxDir = _partDir.getChild("sfx"); + _talDir = _partDir.getChild("tal"); - byte plane0[40]; - byte plane1[40]; + return oldPath; +} - for (int32 i = 0; i < _vm->_screenHeight; i++) { - memcpy(plane0, buf, 40); - memcpy(plane1, buf+40, 40); - for (uint32 j = 0; j < 40; j++) { - *buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4]; - *buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF]; - } - } + + +AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) { + _fntDir = _baseDir.getChild("fonts"); + + _baseBkgDir = _baseDir.getChild("backs"); + + FilesystemNode commonDir = _baseDir.getChild("common"); + _commonAniDir = commonDir.getChild("anims"); + _commonBkgDir = commonDir.getChild("backs"); + _commonMscDir = commonDir.getChild("msc"); + _commonMskDir = commonDir.getChild("msk"); + _commonPthDir = commonDir.getChild("pth"); + _commonTalDir = commonDir.getChild("talks"); } -void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) { - char path[PATH_LEN]; - sprintf(path, "%s", name); +AmigaDisk_br::~AmigaDisk_br() { + +} - Common::File s; - if (!s.open(path)) - errorFileNotFound(path); +void AmigaDisk_br::loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream) { byte *pal; - Graphics::ILBMDecoder decoder(s, info.bg, pal); + Graphics::ILBMDecoder decoder(stream, info.bg, pal); decoder.decode(); uint i; @@ -480,58 +538,60 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) { return; } -void AmigaDisk_br::loadMask(BackgroundInfo& info, const char *name) { - debugC(5, kDebugDisk, "AmigaDisk_br::loadMask(%s)", name); - - Common::File s; - - if (!s.open(name)) - return; - - s.seek(0x30, SEEK_SET); - - byte r, g, b; - for (uint i = 0; i < 4; i++) { - r = s.readByte(); - g = s.readByte(); - b = s.readByte(); - - info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF; - } - - s.seek(0x126, SEEK_SET); // HACK: skipping IFF/ILBM header should be done by analysis, not magic - Graphics::PackBitsReadStream stream(s); - - info.mask.create(info.width, info.height); - stream.read(info.mask.data, info.mask.size); - buildMask2(info.mask.data); - - return; -} void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) { debugC(1, kDebugDisk, "AmigaDisk_br::loadScenery '%s', '%s' '%s'", name, mask, path); - char filename[PATH_LEN]; + Common::String filepath; + FilesystemNode node; Common::File stream; if (name) { - sprintf(filename, "%s/backs/%s.bkg", _partPath, name); - - loadBackground(info, filename); + filepath = Common::String(name) + ".bkg"; + node = _bkgDir.getChild(filepath); + if (!node.exists()) { + filepath = Common::String(name) + ".bkg"; + node = _commonBkgDir.getChild(filepath); + if (!node.exists()) { + errorFileNotFound(_bkgDir, filepath); + } + } + stream.open(node); + loadBackground(info, stream); + stream.close(); } +#if 0 + if (mask && _mskDir.exists()) { + filepath = Common::String(mask) + ".msk"; + node = _mskDir.getChild(filepath); + if (!node.exists()) { + filepath = Common::String(mask) + ".msk"; + node = _commonMskDir.getChild(filepath); + } - if (mask) { - sprintf(filename, "%s/msk/%s.msk", _partPath, name); - - loadMask(info, filename); + if (node.exists()) { + stream.open(node); + stream.seek(0x30, SEEK_SET); + Graphics::PackBitsReadStream unpackedStream(stream); + info.mask.create(info.width, info.height); + unpackedStream.read(info.mask.data, info.mask.size); + // TODO: there is another step to do after decompression... + loadMask(info, stream); + stream.close(); + } } - - if (path) { - sprintf(filename, "%s/pth/%s.pth", _partPath, path); - if (!stream.open(filename)) - errorFileNotFound(filename); - +#endif + if (path && _pthDir.exists()) { + filepath = Common::String(path) + ".pth"; + node = _pthDir.getChild(filepath); + if (!node.exists()) { + filepath = Common::String(path) + ".pth"; + node = _commonPthDir.getChild(filepath); + if (!node.exists()) { + errorFileNotFound(_pthDir, filepath); + } + } + stream.open(node); // NOTE: info.width and info.height are only valid if the background graphics // have already been loaded info.path.create(info.width, info.height); @@ -545,22 +605,28 @@ void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const cha void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) { debugC(1, kDebugDisk, "AmigaDisk_br::loadSlide '%s'", name); - char path[PATH_LEN]; - sprintf(path, "backs/%s.bkg", name); - - loadBackground(info, path); + Common::String path(name); + path += ".bkg"; + FilesystemNode node = _baseBkgDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_baseBkgDir, path); + } + Common::File stream; + stream.open(node); + loadBackground(info, stream); return; } -Frames* AmigaDisk_br::loadStatic(const char* name) { +GfxObj* AmigaDisk_br::loadStatic(const char* name) { debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name); - char path[PATH_LEN]; - sprintf(path, "%s/ras/%s", _partPath, name); - Common::File stream; - if (!stream.open(path)) { - errorFileNotFound(path); + Common::String path(name); + FilesystemNode node = _rasDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_rasDir, path); } + Common::File stream; + stream.open(node); byte *pal = 0; Graphics::Surface* surf = new Graphics::Surface; @@ -570,16 +636,10 @@ Frames* AmigaDisk_br::loadStatic(const char* name) { free(pal); - return new SurfaceToFrames(surf); + return new GfxObj(0, new SurfaceToFrames(surf)); } -Sprites* AmigaDisk_br::createSprites(const char *path) { - - Common::File stream; - if (!stream.open(path)) { - errorFileNotFound(path); - } - +Sprites* AmigaDisk_br::createSprites(Common::ReadStream &stream) { uint16 num = stream.readUint16BE(); Sprites *sprites = new Sprites(num); @@ -603,32 +663,165 @@ Sprites* AmigaDisk_br::createSprites(const char *path) { Frames* AmigaDisk_br::loadFrames(const char* name) { debugC(1, kDebugDisk, "AmigaDisk_br::loadFrames '%s'", name); - char path[PATH_LEN]; - sprintf(path, "%s/anims/%s", _partPath, name); + Common::String path(name); + FilesystemNode node = _aniDir.getChild(path); + if (!node.exists()) { + path += ".ani"; + node = _aniDir.getChild(path); + if (!node.exists()) { + path = Common::String(name); + node = _commonAniDir.getChild(path); + if (!node.exists()) { + path += ".ani"; + node = _commonAniDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_aniDir, path); + } + } + } + } - return createSprites(path); + Common::File stream; + stream.open(node); + return createSprites(stream); } -Frames* AmigaDisk_br::loadTalk(const char *name) { +GfxObj* AmigaDisk_br::loadTalk(const char *name) { debugC(1, kDebugDisk, "AmigaDisk_br::loadTalk '%s'", name); - char path[PATH_LEN]; - sprintf(path, "%s/talks/%s.tal", _partPath, name); + Common::String path(name); + FilesystemNode node = _talDir.getChild(path); + if (!node.exists()) { + path += ".tal"; + node = _talDir.getChild(path); + if (!node.exists()) { + path = Common::String(name); + node = _commonTalDir.getChild(path); + if (!node.exists()) { + path += ".tal"; + node = _commonTalDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_talDir, path); + } + } + } + } - return createSprites(path); + Common::File stream; + stream.open(node); + return new GfxObj(0, createSprites(stream)); } Font* AmigaDisk_br::loadFont(const char* name) { debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name); - char path[PATH_LEN]; - sprintf(path, "%s", name); + Common::String path(name); + path += ".font"; + FilesystemNode node = _fntDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_fntDir, path); + } + + Common::String fontDir; + Common::String fontFile; + byte ch; Common::File stream; - if (!stream.open(path)) - errorFileNotFound(path); + stream.open(node); + stream.seek(4, SEEK_SET); + while ((ch = stream.readByte()) != 0x2F) fontDir += ch; + while ((ch = stream.readByte()) != 0) fontFile += ch; + stream.close(); + + printf("fontDir = %s, fontFile = %s\n", fontDir.c_str(), fontFile.c_str()); + + node = _fntDir.getChild(fontDir); + if (!node.exists()) { + errorFileNotFound(_fntDir, fontDir); + } + node = node.getChild(fontFile); + if (!node.exists()) { + errorFileNotFound(node, fontFile); + } + stream.open(node); return createFont(name, stream); } +Common::SeekableReadStream* AmigaDisk_br::loadMusic(const char* name) { + debugC(5, kDebugDisk, "AmigaDisk_br::loadMusic"); + + Common::String path(name); + FilesystemNode node = _mscDir.getChild(path); + if (!node.exists()) { + // TODO (Kirben): error out when music file is not found? + return 0; + } + + Common::File *stream = new Common::File; + stream->open(node); + return stream; +} + + +Common::ReadStream* AmigaDisk_br::loadSound(const char* name) { + debugC(5, kDebugDisk, "AmigaDisk_br::loadSound"); + + Common::String path(name); + FilesystemNode node = _sfxDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_sfxDir, path); + } + + Common::File *stream = new Common::File; + stream->open(node); + return stream; +} + +GfxObj* AmigaDisk_br::loadObjects(const char *name) { + debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects"); + + Common::String path(name); + FilesystemNode node = _partDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_partDir, path); + } + + Common::File stream; + stream.open(node); + + byte *pal = 0; + Graphics::Surface* surf = new Graphics::Surface; + + Graphics::ILBMDecoder decoder(stream, *surf, pal); + decoder.decode(); + + free(pal); + + return new GfxObj(0, new SurfaceToFrames(surf)); +} + +Common::String AmigaDisk_br::selectArchive(const Common::String& name) { + debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive"); + + Common::String oldPath; + if (_partDir.exists()) { + oldPath = _partDir.getDisplayName(); + } + + _partDir = _baseDir.getChild(name); + + _aniDir = _partDir.getChild("anims"); + _bkgDir = _partDir.getChild("backs"); + _mscDir = _partDir.getChild("msc"); + _mskDir = _partDir.getChild("msk"); + _pthDir = _partDir.getChild("pth"); + _rasDir = _partDir.getChild("ras"); + _scrDir = _partDir.getChild("scripts"); + _sfxDir = _partDir.getChild("sfx"); + _talDir = _partDir.getChild("talks"); + + return oldPath; +} + } // namespace Parallaction diff --git a/engines/parallaction/disk_ns.cpp b/engines/parallaction/disk_ns.cpp index cdbe3458a7..55e6fc5e77 100644 --- a/engines/parallaction/disk_ns.cpp +++ b/engines/parallaction/disk_ns.cpp @@ -385,12 +385,12 @@ Cnv* DosDisk_ns::loadCnv(const char *filename) { return new Cnv(numFrames, width, height, data); } -Frames* DosDisk_ns::loadTalk(const char *name) { +GfxObj* DosDisk_ns::loadTalk(const char *name) { const char *ext = strstr(name, ".talk"); if (ext != NULL) { // npc talk - return loadCnv(name); + return new GfxObj(0, loadCnv(name), name); } @@ -401,7 +401,7 @@ Frames* DosDisk_ns::loadTalk(const char *name) { sprintf(v20, "%stal", name); } - return loadExternalCnv(v20); + return new GfxObj(0, loadExternalCnv(v20), name); } Script* DosDisk_ns::loadLocation(const char *name) { @@ -434,14 +434,14 @@ Script* DosDisk_ns::loadScript(const char* name) { return new Script(new DummyArchiveStream(_resArchive), true); } -Frames* DosDisk_ns::loadHead(const char* name) { +GfxObj* DosDisk_ns::loadHead(const char* name) { char path[PATH_LEN]; sprintf(path, "%shead", name); path[8] = '\0'; - return loadExternalStaticCnv(path); + return new GfxObj(0, loadExternalStaticCnv(path)); } @@ -457,15 +457,15 @@ Font* DosDisk_ns::loadFont(const char* name) { } -Frames* DosDisk_ns::loadObjects(const char *name) { +GfxObj* DosDisk_ns::loadObjects(const char *name) { char path[PATH_LEN]; sprintf(path, "%sobj", name); - return loadExternalCnv(path); + return new GfxObj(0, loadExternalCnv(path), name); } -Frames* DosDisk_ns::loadStatic(const char* name) { +GfxObj* DosDisk_ns::loadStatic(const char* name) { char path[PATH_LEN]; @@ -487,7 +487,7 @@ Frames* DosDisk_ns::loadStatic(const char* name) { Graphics::PackBitsReadStream decoder(_resArchive); decoder.read(cnv->pixels, w*h); - return new SurfaceToFrames(cnv); + return new GfxObj(0, new SurfaceToFrames(cnv), name); } Frames* DosDisk_ns::loadFrames(const char* name) { @@ -1025,7 +1025,7 @@ Frames* AmigaDisk_ns::loadPointer(const char* name) { return makeStaticCnv(stream); } -Frames* AmigaDisk_ns::loadStatic(const char* name) { +GfxObj* AmigaDisk_ns::loadStatic(const char* name) { debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name); Common::SeekableReadStream *s = openArchivedFile(name, true); @@ -1033,7 +1033,7 @@ Frames* AmigaDisk_ns::loadStatic(const char* name) { delete s; - return cnv; + return new GfxObj(0, cnv, name); } Common::SeekableReadStream *AmigaDisk_ns::openArchivedFile(const char* name, bool errorOnFileNotFound) { @@ -1276,7 +1276,7 @@ Frames* AmigaDisk_ns::loadFrames(const char* name) { return cnv; } -Frames* AmigaDisk_ns::loadHead(const char* name) { +GfxObj* AmigaDisk_ns::loadHead(const char* name) { debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name); char path[PATH_LEN]; @@ -1287,11 +1287,11 @@ Frames* AmigaDisk_ns::loadHead(const char* name) { delete s; - return cnv; + return new GfxObj(0, cnv, name); } -Frames* AmigaDisk_ns::loadObjects(const char *name) { +GfxObj* AmigaDisk_ns::loadObjects(const char *name) { debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects"); char path[PATH_LEN]; @@ -1305,11 +1305,11 @@ Frames* AmigaDisk_ns::loadObjects(const char *name) { Cnv *cnv = makeCnv(*s); delete s; - return cnv; + return new GfxObj(0, cnv, name); } -Frames* AmigaDisk_ns::loadTalk(const char *name) { +GfxObj* AmigaDisk_ns::loadTalk(const char *name) { debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name); Common::SeekableReadStream *s; @@ -1328,7 +1328,7 @@ Frames* AmigaDisk_ns::loadTalk(const char *name) { Cnv *cnv = makeCnv(*s); delete s; - return cnv; + return new GfxObj(0, cnv, name); } Table* AmigaDisk_ns::loadTable(const char* name) { @@ -1395,9 +1395,7 @@ Common::ReadStream* AmigaDisk_ns::loadSound(const char* name) { char path[PATH_LEN]; sprintf(path, "%s.snd", name); - openArchivedFile(path); - - return new DummyArchiveStream(_resArchive); + return openArchivedFile(path); } } // namespace Parallaction diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h new file mode 100644 index 0000000000..22e75744f1 --- /dev/null +++ b/engines/parallaction/exec.h @@ -0,0 +1,255 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + + +#ifndef PARALLACTION_EXEC_H +#define PARALLACTION_EXEC_H + +#include "common/util.h" +#include "parallaction/objects.h" + + +namespace Parallaction { + +typedef Common::Functor0<void> Opcode; +typedef Common::Array<const Opcode*> OpcodeSet; + +#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op() +#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op() + +class Parallaction_ns; +class Parallaction_br; + +class CommandExec { +protected: + struct ParallactionStruct1 { + CommandPtr cmd; + ZonePtr z; + bool suspend; + } _ctxt; + + OpcodeSet _opcodes; + + struct SuspendedContext { + bool valid; + CommandList::iterator first; + CommandList::iterator last; + ZonePtr zone; + } _suspendedCtxt; + + ZonePtr _execZone; + void runList(CommandList::iterator first, CommandList::iterator last); + void createSuspendList(CommandList::iterator first, CommandList::iterator last); + void cleanSuspendedList(); + +public: + virtual void init() = 0; + virtual void run(CommandList &list, ZonePtr z = nullZonePtr); + void runSuspended(); + + CommandExec() { + _suspendedCtxt.valid = false; + } + virtual ~CommandExec() { + for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete *i; + _opcodes.clear(); + } +}; + +class CommandExec_ns : public CommandExec { + + Parallaction_ns *_vm; + +protected: + void updateGetZone(ZonePtr z, bool visible); + + DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(set); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(get); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); + +public: + void init(); + + CommandExec_ns(Parallaction_ns* vm); + ~CommandExec_ns(); +}; + +class CommandExec_br : public CommandExec_ns { + +protected: + Parallaction_br *_vm; + + DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(character); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(add); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(let); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(music); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(give); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(text); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(part); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave); + DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave); + +public: + void init(); + + CommandExec_br(Parallaction_br* vm); + ~CommandExec_br(); +}; + +class ProgramExec { +protected: + struct ParallactionStruct2 { + AnimationPtr anim; + ProgramPtr program; + InstructionList::iterator inst; + InstructionList::iterator ip; + uint16 modCounter; + bool suspend; + } _ctxt; + + const char **_instructionNames; + + OpcodeSet _opcodes; + + uint16 _modCounter; + void runScript(ProgramPtr script, AnimationPtr a); + +public: + virtual void init() = 0; + virtual void runScripts(ProgramList::iterator first, ProgramList::iterator last); + ProgramExec() : _modCounter(0) { + } + virtual ~ProgramExec() { + for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete *i; + _opcodes.clear(); + } +}; + +class ProgramExec_ns : public ProgramExec { + + Parallaction_ns *_vm; + +protected: + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); + +public: + void init(); + + ProgramExec_ns(Parallaction_ns *vm); + ~ProgramExec_ns(); +}; + +class ProgramExec_br : public ProgramExec_ns { + + Parallaction_br *_vm; + +protected: + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif); + DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop); + +public: + void init(); + ProgramExec_br(Parallaction_br *vm); + ~ProgramExec_br(); +}; + +} // namespace Parallaction + +#endif diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp index 3b67b4c370..0b7400f0f7 100644 --- a/engines/parallaction/exec_br.cpp +++ b/engines/parallaction/exec_br.cpp @@ -23,6 +23,7 @@ * */ +#include "parallaction/exec.h" #include "parallaction/input.h" #include "parallaction/parallaction.h" @@ -60,16 +61,17 @@ namespace Parallaction { #define INST_STOP 30 #define INST_ENDSCRIPT 31 - - #define SetOpcodeTable(x) table = &x; -typedef Common::Functor0Mem<void, Parallaction_br> OpcodeV2; -#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::cmdOp_##op)) -#define DECLARE_COMMAND_OPCODE(op) void Parallaction_br::cmdOp_##op() +typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1; +#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_br::cmdOp_##op)) +#define DECLARE_COMMAND_OPCODE(op) void CommandExec_br::cmdOp_##op() -#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::instOp_##op)) -#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_br::instOp_##op() +typedef Common::Functor0Mem<void, ProgramExec_br> OpcodeV2; +#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_br::instOp_##op)) +#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_br::instOp_##op() + +extern const char *_instructionNamesRes_br[]; void Parallaction_br::setupSubtitles(char *s, char *s2, int y) { debugC(5, kDebugExec, "setupSubtitles(%s, %s, %i)", s, s2, y); @@ -100,8 +102,13 @@ void Parallaction_br::setupSubtitles(char *s, char *s2, int y) { } void Parallaction_br::clearSubtitles() { - _gfx->freeLabels(); - _subtitle[0] = _subtitle[1] = -1; + if (_subtitle[0] != -1) { + _gfx->hideLabel(_subtitle[0]); + } + + if (_subtitle[1] != -1) { + _gfx->hideLabel(_subtitle[1]); + } } @@ -109,22 +116,30 @@ DECLARE_COMMAND_OPCODE(location) { warning("Parallaction_br::cmdOp_location command not yet implemented"); // TODO: handle startPos and startPos2 - scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string); + _vm->scheduleLocationSwitch(_ctxt.cmd->u._string); } DECLARE_COMMAND_OPCODE(open) { warning("Parallaction_br::cmdOp_open command not yet implemented"); + _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed; + if (_ctxt.cmd->u._zone->u.door->gfxobj) { + _vm->updateDoor(_ctxt.cmd->u._zone); + } } DECLARE_COMMAND_OPCODE(close) { warning("Parallaction_br::cmdOp_close not yet implemented"); + _ctxt.cmd->u._zone->_flags |= kFlagsClosed; + if (_ctxt.cmd->u._zone->u.door->gfxobj) { + _vm->updateDoor(_ctxt.cmd->u._zone); + } } DECLARE_COMMAND_OPCODE(on) { - CommandData *data = &_cmdRunCtxt.cmd->u; + CommandData *data = &_ctxt.cmd->u; ZonePtr z = data->_zone; if (z) { @@ -132,52 +147,53 @@ DECLARE_COMMAND_OPCODE(on) { z->_flags &= ~kFlagsRemove; if ((z->_type & 0xFFFF) & kZoneGet) { - _gfx->showGfxObj(z->u.get->gfxobj, true); + _vm->_gfx->showGfxObj(z->u.get->gfxobj, true); } } } DECLARE_COMMAND_OPCODE(off) { - CommandData *data = &_cmdRunCtxt.cmd->u; + CommandData *data = &_ctxt.cmd->u; ZonePtr z = data->_zone; if (z) { z->_flags |= kFlagsRemove; if ((z->_type & 0xFFFF) & kZoneGet) { - _gfx->showGfxObj(z->u.get->gfxobj, false); + _vm->_gfx->showGfxObj(z->u.get->gfxobj, false); } } } DECLARE_COMMAND_OPCODE(call) { - callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z); + _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z); } DECLARE_COMMAND_OPCODE(drop) { - warning("Parallaction_br::cmdOp_drop not yet implemented"); + _vm->dropItem(_ctxt.cmd->u._object); } DECLARE_COMMAND_OPCODE(move) { - warning("Parallaction_br::cmdOp_move not yet implemented"); + _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y); + _ctxt.suspend = true; } DECLARE_COMMAND_OPCODE(start) { - _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing; + _ctxt.cmd->u._zone->_flags |= kFlagsActing; } DECLARE_COMMAND_OPCODE(stop) { - _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing; + _ctxt.cmd->u._zone->_flags &= ~kFlagsActing; } DECLARE_COMMAND_OPCODE(character) { - debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _cmdRunCtxt.cmd->u._string); - changeCharacter(_cmdRunCtxt.cmd->u._string); + debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _ctxt.cmd->u._string); + _vm->changeCharacter(_ctxt.cmd->u._string); } @@ -187,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) { DECLARE_COMMAND_OPCODE(onmouse) { - _input->showCursor(true); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); } DECLARE_COMMAND_OPCODE(offmouse) { - _input->showCursor(false); + _vm->_input->setMouseState(MOUSE_DISABLED); } DECLARE_COMMAND_OPCODE(add) { - warning("Parallaction_br::cmdOp_add not yet implemented"); + _vm->addInventoryItem(_ctxt.cmd->u._object); } @@ -207,42 +223,42 @@ DECLARE_COMMAND_OPCODE(leave) { DECLARE_COMMAND_OPCODE(inc) { - _counters[_cmdRunCtxt.cmd->u._lvalue] += _cmdRunCtxt.cmd->u._rvalue; + _vm->_counters[_ctxt.cmd->u._lvalue] += _ctxt.cmd->u._rvalue; } DECLARE_COMMAND_OPCODE(dec) { - _counters[_cmdRunCtxt.cmd->u._lvalue] -= _cmdRunCtxt.cmd->u._rvalue; + _vm->_counters[_ctxt.cmd->u._lvalue] -= _ctxt.cmd->u._rvalue; } DECLARE_COMMAND_OPCODE(ifeq) { - if (_counters[_cmdRunCtxt.cmd->u._lvalue] == _cmdRunCtxt.cmd->u._rvalue) { - setLocationFlags(kFlagsTestTrue); + if (_vm->_counters[_ctxt.cmd->u._lvalue] == _ctxt.cmd->u._rvalue) { + _vm->setLocationFlags(kFlagsTestTrue); } else { - clearLocationFlags(kFlagsTestTrue); + _vm->clearLocationFlags(kFlagsTestTrue); } } DECLARE_COMMAND_OPCODE(iflt) { - if (_counters[_cmdRunCtxt.cmd->u._lvalue] < _cmdRunCtxt.cmd->u._rvalue) { - setLocationFlags(kFlagsTestTrue); + if (_vm->_counters[_ctxt.cmd->u._lvalue] < _ctxt.cmd->u._rvalue) { + _vm->setLocationFlags(kFlagsTestTrue); } else { - clearLocationFlags(kFlagsTestTrue); + _vm->clearLocationFlags(kFlagsTestTrue); } } DECLARE_COMMAND_OPCODE(ifgt) { - if (_counters[_cmdRunCtxt.cmd->u._lvalue] > _cmdRunCtxt.cmd->u._rvalue) { - setLocationFlags(kFlagsTestTrue); + if (_vm->_counters[_ctxt.cmd->u._lvalue] > _ctxt.cmd->u._rvalue) { + _vm->setLocationFlags(kFlagsTestTrue); } else { - clearLocationFlags(kFlagsTestTrue); + _vm->clearLocationFlags(kFlagsTestTrue); } } DECLARE_COMMAND_OPCODE(let) { - _counters[_cmdRunCtxt.cmd->u._lvalue] = _cmdRunCtxt.cmd->u._rvalue; + _vm->_counters[_ctxt.cmd->u._lvalue] = _ctxt.cmd->u._rvalue; } @@ -252,25 +268,25 @@ DECLARE_COMMAND_OPCODE(music) { DECLARE_COMMAND_OPCODE(fix) { - _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsFixed; + _ctxt.cmd->u._zone->_flags |= kFlagsFixed; } DECLARE_COMMAND_OPCODE(unfix) { - _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed; + _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed; } DECLARE_COMMAND_OPCODE(zeta) { - _location._zeta0 = _cmdRunCtxt.cmd->u._zeta0; - _location._zeta1 = _cmdRunCtxt.cmd->u._zeta1; - _location._zeta2 = _cmdRunCtxt.cmd->u._zeta2; + _vm->_location._zeta0 = _ctxt.cmd->u._zeta0; + _vm->_location._zeta1 = _ctxt.cmd->u._zeta1; + _vm->_location._zeta2 = _ctxt.cmd->u._zeta2; } DECLARE_COMMAND_OPCODE(scroll) { warning("Parallaction_br::cmdOp_scroll not yet implemented"); - _gfx->setVar("scroll_x", _cmdRunCtxt.cmd->u._rvalue ); + _vm->_gfx->setVar("scroll_x", _ctxt.cmd->u._rvalue ); } @@ -285,8 +301,8 @@ DECLARE_COMMAND_OPCODE(give) { DECLARE_COMMAND_OPCODE(text) { - CommandData *data = &_cmdRunCtxt.cmd->u; - setupSubtitles(data->_string, data->_string2, data->_zeta0); + CommandData *data = &_ctxt.cmd->u; + _vm->setupSubtitles(data->_string, data->_string2, data->_zeta0); } @@ -297,7 +313,7 @@ DECLARE_COMMAND_OPCODE(part) { DECLARE_COMMAND_OPCODE(testsfx) { warning("Parallaction_br::cmdOp_testsfx not completely implemented"); - clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled + _vm->clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled } @@ -319,7 +335,7 @@ DECLARE_COMMAND_OPCODE(offsave) { DECLARE_INSTRUCTION_OPCODE(on) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; ZonePtr z = inst->_z; if (z) { @@ -327,28 +343,28 @@ DECLARE_INSTRUCTION_OPCODE(on) { z->_flags &= ~kFlagsRemove; if ((z->_type & 0xFFFF) & kZoneGet) { - _gfx->showGfxObj(z->u.get->gfxobj, true); + _vm->_gfx->showGfxObj(z->u.get->gfxobj, true); } } } DECLARE_INSTRUCTION_OPCODE(off) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; ZonePtr z = inst->_z; if (z) { z->_flags |= kFlagsRemove; if ((z->_type & 0xFFFF) & kZoneGet) { - _gfx->showGfxObj(z->u.get->gfxobj, false); + _vm->_gfx->showGfxObj(z->u.get->gfxobj, false); } } } DECLARE_INSTRUCTION_OPCODE(set) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; int16 rvalue = inst->_opB.getRValue(); int16* lvalue = inst->_opA.getLValue(); @@ -358,22 +374,15 @@ DECLARE_INSTRUCTION_OPCODE(set) { } -DECLARE_INSTRUCTION_OPCODE(loop) { - InstructionPtr inst = *_instRunCtxt.inst; - - _instRunCtxt.program->_loopCounter = inst->_opB.getRValue(); - _instRunCtxt.program->_loopStart = _instRunCtxt.inst; -} - DECLARE_INSTRUCTION_OPCODE(inc) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; int16 rvalue = inst->_opB.getRValue(); if (inst->_flags & kInstMod) { // mod int16 _bx = (rvalue > 0 ? rvalue : -rvalue); - if (_instRunCtxt.modCounter % _bx != 0) return; + if (_ctxt.modCounter % _bx != 0) return; rvalue = (rvalue > 0 ? 1 : -1); } @@ -420,12 +429,12 @@ DECLARE_INSTRUCTION_OPCODE(wait) { DECLARE_INSTRUCTION_OPCODE(start) { - (*_instRunCtxt.inst)->_z->_flags |= kFlagsActing; + (*_ctxt.inst)->_z->_flags |= kFlagsActing; } DECLARE_INSTRUCTION_OPCODE(process) { - _activeZone2 = (*_instRunCtxt.inst)->_z; + _vm->_activeZone2 = (*_ctxt.inst)->_z; } @@ -435,18 +444,18 @@ DECLARE_INSTRUCTION_OPCODE(move) { DECLARE_INSTRUCTION_OPCODE(color) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; int16 entry = inst->_opB.getRValue(); - _gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]); + _vm->_gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]); } DECLARE_INSTRUCTION_OPCODE(mask) { #if 0 - Instruction *inst = *_instRunCtxt.inst; + Instruction *inst = *_ctxt.inst; _gfx->_bgLayers[0] = inst->_opA.getRValue(); _gfx->_bgLayers[1] = inst->_opB.getRValue(); _gfx->_bgLayers[2] = inst->_opC.getRValue(); @@ -459,8 +468,8 @@ DECLARE_INSTRUCTION_OPCODE(print) { } DECLARE_INSTRUCTION_OPCODE(text) { - InstructionPtr inst = (*_instRunCtxt.inst); - setupSubtitles(inst->_text, inst->_text2, inst->_y); + InstructionPtr inst = (*_ctxt.inst); + _vm->setupSubtitles(inst->_text, inst->_text2, inst->_y); } @@ -488,22 +497,11 @@ DECLARE_INSTRUCTION_OPCODE(stop) { warning("Parallaction_br::instOp_stop not yet implemented"); } -DECLARE_INSTRUCTION_OPCODE(endscript) { - if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) { - _instRunCtxt.anim->_flags &= ~kFlagsActing; - runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim); - _instRunCtxt.program->_status = kProgramDone; - } - _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin(); - - _instRunCtxt.suspend = true; -} - -void Parallaction_br::initOpcodes() { +void CommandExec_br::init() { Common::Array<const Opcode*> *table = 0; - SetOpcodeTable(_commandOpcodes); + SetOpcodeTable(_opcodes); COMMAND_OPCODE(invalid); COMMAND_OPCODE(set); COMMAND_OPCODE(clear); @@ -546,8 +544,21 @@ void Parallaction_br::initOpcodes() { COMMAND_OPCODE(ret); COMMAND_OPCODE(onsave); COMMAND_OPCODE(offsave); +} + +CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec_ns(vm), _vm(vm) { + +} + +CommandExec_br::~CommandExec_br() { + +} - SetOpcodeTable(_instructionOpcodes); +void ProgramExec_br::init() { + + Common::Array<const Opcode*> *table = 0; + + SetOpcodeTable(_opcodes); INSTRUCTION_OPCODE(invalid); INSTRUCTION_OPCODE(on); INSTRUCTION_OPCODE(off); @@ -557,7 +568,7 @@ void Parallaction_br::initOpcodes() { INSTRUCTION_OPCODE(set); // f INSTRUCTION_OPCODE(loop); INSTRUCTION_OPCODE(endloop); - INSTRUCTION_OPCODE(null); // show + INSTRUCTION_OPCODE(show); // show INSTRUCTION_OPCODE(inc); INSTRUCTION_OPCODE(inc); // dec INSTRUCTION_OPCODE(set); @@ -582,6 +593,13 @@ void Parallaction_br::initOpcodes() { INSTRUCTION_OPCODE(endscript); } +ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) { + _instructionNames = _instructionNamesRes_br; +} + +ProgramExec_br::~ProgramExec_br() { +} + #if 0 void Parallaction_br::jobWaitRemoveLabelJob(void *parm, Job *job) { diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp index a4b372f42a..99a492863b 100644 --- a/engines/parallaction/exec_ns.cpp +++ b/engines/parallaction/exec_ns.cpp @@ -23,6 +23,7 @@ * */ +#include "parallaction/exec.h" #include "parallaction/input.h" #include "parallaction/parallaction.h" #include "parallaction/sound.h" @@ -52,18 +53,19 @@ namespace Parallaction { #define SetOpcodeTable(x) table = &x; -typedef Common::Functor0Mem<void, Parallaction_ns> OpcodeV2; -#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::cmdOp_##op)) -#define DECLARE_COMMAND_OPCODE(op) void Parallaction_ns::cmdOp_##op() - -#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::instOp_##op)) -#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_ns::instOp_##op() +typedef Common::Functor0Mem<void, CommandExec_ns> OpcodeV1; +#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_ns::cmdOp_##op)) +#define DECLARE_COMMAND_OPCODE(op) void CommandExec_ns::cmdOp_##op() +typedef Common::Functor0Mem<void, ProgramExec_ns> OpcodeV2; +#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_ns::instOp_##op)) +#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_ns::instOp_##op() +extern const char *_instructionNamesRes_ns[]; DECLARE_INSTRUCTION_OPCODE(on) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; inst->_a->_flags |= kFlagsActive; inst->_a->_flags &= ~kFlagsRemove; @@ -71,31 +73,31 @@ DECLARE_INSTRUCTION_OPCODE(on) { DECLARE_INSTRUCTION_OPCODE(off) { - (*_instRunCtxt.inst)->_a->_flags |= kFlagsRemove; + (*_ctxt.inst)->_a->_flags |= kFlagsRemove; } DECLARE_INSTRUCTION_OPCODE(loop) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; - _instRunCtxt.program->_loopCounter = inst->_opB.getRValue(); - _instRunCtxt.program->_loopStart = _instRunCtxt.inst; + _ctxt.program->_loopCounter = inst->_opB.getRValue(); + _ctxt.program->_loopStart = _ctxt.ip; } DECLARE_INSTRUCTION_OPCODE(endloop) { - if (--_instRunCtxt.program->_loopCounter > 0) { - _instRunCtxt.inst = _instRunCtxt.program->_loopStart; + if (--_ctxt.program->_loopCounter > 0) { + _ctxt.ip = _ctxt.program->_loopStart; } } DECLARE_INSTRUCTION_OPCODE(inc) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; int16 _si = inst->_opB.getRValue(); if (inst->_flags & kInstMod) { // mod int16 _bx = (_si > 0 ? _si : -_si); - if (_instRunCtxt.modCounter % _bx != 0) return; + if (_ctxt.modCounter % _bx != 0) return; _si = (_si > 0 ? 1 : -1); } @@ -116,7 +118,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) { DECLARE_INSTRUCTION_OPCODE(set) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; int16 _si = inst->_opB.getRValue(); int16 *lvalue = inst->_opA.getLValue(); @@ -127,7 +129,7 @@ DECLARE_INSTRUCTION_OPCODE(set) { DECLARE_INSTRUCTION_OPCODE(put) { - InstructionPtr inst = *_instRunCtxt.inst; + InstructionPtr inst = *_ctxt.inst; Graphics::Surface v18; v18.w = inst->_a->width(); v18.h = inst->_a->height(); @@ -137,162 +139,175 @@ DECLARE_INSTRUCTION_OPCODE(put) { int16 y = inst->_opB.getRValue(); bool mask = (inst->_flags & kInstMaskedPut) == kInstMaskedPut; - _gfx->patchBackground(v18, x, y, mask); + _vm->_gfx->patchBackground(v18, x, y, mask); } -DECLARE_INSTRUCTION_OPCODE(null) { - +DECLARE_INSTRUCTION_OPCODE(show) { + _ctxt.suspend = true; } DECLARE_INSTRUCTION_OPCODE(invalid) { - error("Can't execute invalid opcode %i", (*_instRunCtxt.inst)->_index); + error("Can't execute invalid opcode %i", (*_ctxt.inst)->_index); } DECLARE_INSTRUCTION_OPCODE(call) { - callFunction((*_instRunCtxt.inst)->_immediate, 0); + _vm->callFunction((*_ctxt.inst)->_immediate, 0); } DECLARE_INSTRUCTION_OPCODE(wait) { - if (_engineFlags & kEngineWalking) - _instRunCtxt.suspend = true; + if (_engineFlags & kEngineWalking) { + _ctxt.ip--; + _ctxt.suspend = true; + } } DECLARE_INSTRUCTION_OPCODE(start) { - (*_instRunCtxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive); + (*_ctxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive); } DECLARE_INSTRUCTION_OPCODE(sound) { - _activeZone = (*_instRunCtxt.inst)->_z; + _vm->_activeZone = (*_ctxt.inst)->_z; } DECLARE_INSTRUCTION_OPCODE(move) { - InstructionPtr inst = (*_instRunCtxt.inst); + InstructionPtr inst = (*_ctxt.inst); int16 x = inst->_opA.getRValue(); int16 y = inst->_opB.getRValue(); - _char.scheduleWalk(x, y); + _vm->_char.scheduleWalk(x, y); } DECLARE_INSTRUCTION_OPCODE(endscript) { - if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) { - _instRunCtxt.anim->_flags &= ~kFlagsActing; - runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim); - _instRunCtxt.program->_status = kProgramDone; + if ((_ctxt.anim->_flags & kFlagsLooping) == 0) { + _ctxt.anim->_flags &= ~kFlagsActing; + _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim); + _ctxt.program->_status = kProgramDone; } - _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin(); - _instRunCtxt.suspend = true; + _ctxt.ip = _ctxt.program->_instructions.begin(); + _ctxt.suspend = true; } DECLARE_COMMAND_OPCODE(invalid) { - error("Can't execute invalid command '%i'", _cmdRunCtxt.cmd->_id); + error("Can't execute invalid command '%i'", _ctxt.cmd->_id); } DECLARE_COMMAND_OPCODE(set) { - if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { - _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; - _commandFlags |= _cmdRunCtxt.cmd->u._flags; + if (_ctxt.cmd->u._flags & kFlagsGlobal) { + _ctxt.cmd->u._flags &= ~kFlagsGlobal; + _commandFlags |= _ctxt.cmd->u._flags; } else { - setLocationFlags(_cmdRunCtxt.cmd->u._flags); + _vm->setLocationFlags(_ctxt.cmd->u._flags); } } DECLARE_COMMAND_OPCODE(clear) { - if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { - _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; - _commandFlags &= ~_cmdRunCtxt.cmd->u._flags; + if (_ctxt.cmd->u._flags & kFlagsGlobal) { + _ctxt.cmd->u._flags &= ~kFlagsGlobal; + _commandFlags &= ~_ctxt.cmd->u._flags; } else { - clearLocationFlags(_cmdRunCtxt.cmd->u._flags); + _vm->clearLocationFlags(_ctxt.cmd->u._flags); } } DECLARE_COMMAND_OPCODE(start) { - _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing; + _ctxt.cmd->u._zone->_flags |= kFlagsActing; } DECLARE_COMMAND_OPCODE(speak) { - _activeZone = _cmdRunCtxt.cmd->u._zone; + if ((_ctxt.cmd->u._zone->_type & 0xFFFF) == kZoneSpeak) { + _vm->enterDialogueMode(_ctxt.cmd->u._zone); + } else { + _vm->_activeZone = _ctxt.cmd->u._zone; + } } DECLARE_COMMAND_OPCODE(get) { - _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed; - runZone(_cmdRunCtxt.cmd->u._zone); + _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed; + _vm->runZone(_ctxt.cmd->u._zone); } DECLARE_COMMAND_OPCODE(location) { - scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string); + _vm->scheduleLocationSwitch(_ctxt.cmd->u._string); } DECLARE_COMMAND_OPCODE(open) { - _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsClosed; - if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) { - updateDoor(_cmdRunCtxt.cmd->u._zone); + _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed; + if (_ctxt.cmd->u._zone->u.door->gfxobj) { + _vm->updateDoor(_ctxt.cmd->u._zone); } } DECLARE_COMMAND_OPCODE(close) { - _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsClosed; - if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) { - updateDoor(_cmdRunCtxt.cmd->u._zone); + _ctxt.cmd->u._zone->_flags |= kFlagsClosed; + if (_ctxt.cmd->u._zone->u.door->gfxobj) { + _vm->updateDoor(_ctxt.cmd->u._zone); } } +void CommandExec_ns::updateGetZone(ZonePtr z, bool visible) { + if (!z) { + return; + } + + if ((z->_type & 0xFFFF) == kZoneGet) { + _vm->_gfx->showGfxObj(z->u.get->gfxobj, visible); + } +} DECLARE_COMMAND_OPCODE(on) { - ZonePtr z = _cmdRunCtxt.cmd->u._zone; - // WORKAROUND: the original DOS-based engine didn't check u->_zone before dereferencing - // the pointer to get structure members, thus leading to crashes in systems with memory - // protection. - // As a side note, the overwritten address is the 5th entry in the DOS interrupt table - // (print screen handler): this suggests that a system would hang when the print screen - // key is pressed after playing Nippon Safes, provided that this code path is taken. + ZonePtr z = _ctxt.cmd->u._zone; + if (z) { z->_flags &= ~kFlagsRemove; z->_flags |= kFlagsActive; - if ((z->_type & 0xFFFF) == kZoneGet) { - _gfx->showGfxObj(z->u.get->gfxobj, true); - } + updateGetZone(z, true); } } DECLARE_COMMAND_OPCODE(off) { - _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsRemove; + ZonePtr z = _ctxt.cmd->u._zone; + + if (z) { + _ctxt.cmd->u._zone->_flags |= kFlagsRemove; + updateGetZone(z, false); + } } DECLARE_COMMAND_OPCODE(call) { - callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z); + _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z); } DECLARE_COMMAND_OPCODE(toggle) { - if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) { - _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal; - _commandFlags ^= _cmdRunCtxt.cmd->u._flags; + if (_ctxt.cmd->u._flags & kFlagsGlobal) { + _ctxt.cmd->u._flags &= ~kFlagsGlobal; + _commandFlags ^= _ctxt.cmd->u._flags; } else { - toggleLocationFlags(_cmdRunCtxt.cmd->u._flags); + _vm->toggleLocationFlags(_ctxt.cmd->u._flags); } } DECLARE_COMMAND_OPCODE(drop){ - dropItem( _cmdRunCtxt.cmd->u._object ); + _vm->dropItem( _ctxt.cmd->u._object ); } @@ -302,70 +317,103 @@ DECLARE_COMMAND_OPCODE(quit) { DECLARE_COMMAND_OPCODE(move) { - _char.scheduleWalk(_cmdRunCtxt.cmd->u._move.x, _cmdRunCtxt.cmd->u._move.y); + _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y); } DECLARE_COMMAND_OPCODE(stop) { - _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing; + _ctxt.cmd->u._zone->_flags &= ~kFlagsActing; } void Parallaction_ns::drawAnimations() { + debugC(9, kDebugExec, "Parallaction_ns::drawAnimations()\n"); uint16 layer = 0; for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) { - AnimationPtr v18 = *it; - GfxObj *obj = v18->gfxobj; + AnimationPtr anim = *it; + GfxObj *obj = anim->gfxobj; - if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0)) { + // Validation is performed here, so that every animation is affected, instead that only the ones + // who *own* a script. In fact, some scripts can change values in other animations. + // The right way to do this would be to enforce validation when any variable is modified from + // a script. + anim->validateScriptVars(); - int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1); - if (v18->_flags & kFlagsNoMasked) + if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0)) { + + if (anim->_flags & kFlagsNoMasked) layer = 3; else - layer = _gfx->_backgroundInfo.getLayer(v18->_top + v18->height()); + layer = _gfx->_backgroundInfo->getLayer(anim->_top + anim->height()); if (obj) { _gfx->showGfxObj(obj, true); - obj->frame = frame; - obj->x = v18->_left; - obj->y = v18->_top; - obj->z = v18->_z; + obj->frame = anim->_frame; + obj->x = anim->_left; + obj->y = anim->_top; + obj->z = anim->_z; obj->layer = layer; } } - if (((v18->_flags & kFlagsActive) == 0) && (v18->_flags & kFlagsRemove)) { - v18->_flags &= ~kFlagsRemove; - v18->_oldPos.x = -1000; + if (((anim->_flags & kFlagsActive) == 0) && (anim->_flags & kFlagsRemove)) { + anim->_flags &= ~kFlagsRemove; + anim->_oldPos.x = -1000; } - if ((v18->_flags & kFlagsActive) && (v18->_flags & kFlagsRemove)) { - v18->_flags &= ~kFlagsActive; - v18->_flags |= kFlagsRemove; + if ((anim->_flags & kFlagsActive) && (anim->_flags & kFlagsRemove)) { + anim->_flags &= ~kFlagsActive; + anim->_flags |= kFlagsRemove; if (obj) { _gfx->showGfxObj(obj, false); } } } + debugC(9, kDebugExec, "Parallaction_ns::drawAnimations done()\n"); + return; } +void ProgramExec::runScript(ProgramPtr script, AnimationPtr a) { + debugC(9, kDebugExec, "runScript(Animation = %s)", a->_name); + + _ctxt.ip = script->_ip; + _ctxt.anim = a; + _ctxt.program = script; + _ctxt.suspend = false; + _ctxt.modCounter = _modCounter; + + InstructionList::iterator inst; + for ( ; (a->_flags & kFlagsActing) ; ) { + + inst = _ctxt.ip; + _ctxt.inst = inst; + _ctxt.ip++; + + debugC(9, kDebugExec, "inst [%02i] %s\n", (*inst)->_index, _instructionNames[(*inst)->_index - 1]); + + script->_status = kProgramRunning; + + (*_opcodes[(*inst)->_index])(); + + if (_ctxt.suspend) + break; -void Parallaction_ns::runScripts() { - if (_engineFlags & kEnginePauseJobs) { - return; } + script->_ip = _ctxt.ip; - debugC(9, kDebugExec, "runScripts"); +} - static uint16 modCounter = 0; +void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) { + if (_engineFlags & kEnginePauseJobs) { + return; + } - for (ProgramList::iterator it = _location._programs.begin(); it != _location._programs.end(); it++) { + for (ProgramList::iterator it = first; it != last; it++) { AnimationPtr a = (*it)->_anim; @@ -375,116 +423,182 @@ void Parallaction_ns::runScripts() { if ((a->_flags & kFlagsActing) == 0) continue; - InstructionList::iterator inst = (*it)->_ip; - while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) { + runScript(*it, a); - (*it)->_status = kProgramRunning; + if (a->_flags & kFlagsCharacter) + a->_z = a->_top + a->height(); + } - debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]); + _modCounter++; - _instRunCtxt.inst = inst; - _instRunCtxt.anim = AnimationPtr(a); - _instRunCtxt.program = *it; - _instRunCtxt.modCounter = modCounter; - _instRunCtxt.suspend = false; + return; +} + +void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) { - (*_instructionOpcodes[(*inst)->_index])(); + uint32 useFlags = 0; + bool useLocalFlags; - inst = _instRunCtxt.inst; // handles endloop correctly + _ctxt.suspend = false; - if (_instRunCtxt.suspend) - goto label1; + for ( ; first != last; first++) { + if (_engineFlags & kEngineQuit) + break; - inst++; + CommandPtr cmd = *first; + + if (cmd->_flagsOn & kFlagsGlobal) { + useFlags = _commandFlags | kFlagsGlobal; + useLocalFlags = false; + } else { + useFlags = _vm->getLocationFlags(); + useLocalFlags = true; } - (*it)->_ip = ++inst; + bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn; + bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff; -label1: - if (a->_flags & kFlagsCharacter) - a->_z = a->_top + a->height(); - } + debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id, cmd->_flagsOn, cmd->_flagsOff, + useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags); + + if (!onMatch || !offMatch) continue; + + _ctxt.z = _execZone; + _ctxt.cmd = cmd; + + (*_opcodes[cmd->_id])(); - _char._ani->_z = _char._ani->height() + _char._ani->_top; - if (_char._ani->gfxobj) { - _char._ani->gfxobj->z = _char._ani->_z; + if (_ctxt.suspend) { + createSuspendList(++first, last); + return; + } } - modCounter++; - return; } - -void Parallaction::runCommands(CommandList& list, ZonePtr z) { - if (list.size() == 0) +void CommandExec::run(CommandList& list, ZonePtr z) { + if (list.size() == 0) { + debugC(3, kDebugExec, "runCommands: nothing to do"); return; + } - debugC(3, kDebugExec, "runCommands"); - - CommandList::iterator it = list.begin(); - for ( ; it != list.end(); it++) { + _execZone = z; - CommandPtr cmd = *it; - uint32 v8 = getLocationFlags(); + debugC(3, kDebugExec, "runCommands starting"); + runList(list.begin(), list.end()); + debugC(3, kDebugExec, "runCommands completed"); +} - if (_engineFlags & kEngineQuit) - break; +void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) { + if (first == last) { + return; + } - if (cmd->_flagsOn & kFlagsGlobal) { - v8 = _commandFlags | kFlagsGlobal; - } + debugC(3, kDebugExec, "CommandExec::createSuspendList()"); - if ((cmd->_flagsOn & v8) != cmd->_flagsOn) continue; - if ((cmd->_flagsOff & ~v8) != cmd->_flagsOff) continue; + _suspendedCtxt.valid = true; + _suspendedCtxt.first = first; + _suspendedCtxt.last = last; + _suspendedCtxt.zone = _execZone; +} -// debugC(3, kDebugExec, "runCommands[%i]: %s (on: %x, off: %x)", cmd->_id, _commandsNamesRes[cmd->_id-1], cmd->_flagsOn, cmd->_flagsOff); +void CommandExec::cleanSuspendedList() { + debugC(3, kDebugExec, "CommandExec::cleanSuspended()"); - _cmdRunCtxt.z = z; - _cmdRunCtxt.cmd = cmd; + _suspendedCtxt.valid = false; + _suspendedCtxt.first = _suspendedCtxt.last; + _suspendedCtxt.zone = nullZonePtr; +} - (*_commandOpcodes[cmd->_id])(); +void CommandExec::runSuspended() { + if (_engineFlags & kEngineWalking) { + return; } - debugC(3, kDebugExec, "runCommands completed"); + if (_suspendedCtxt.valid) { + debugC(3, kDebugExec, "CommandExec::runSuspended()"); - return; + _execZone = _suspendedCtxt.zone; + runList(_suspendedCtxt.first, _suspendedCtxt.last); + cleanSuspendedList(); + } +} + +CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) { } +CommandExec_ns::~CommandExec_ns() { + +} // // ZONE TYPE: EXAMINE // -void Parallaction::displayComment(ExamineData *data) { +void Parallaction::enterCommentMode(ZonePtr z) { + if (!z) { + return; + } + + _commentZone = z; + + ExamineData *data = _commentZone->u.examine; + if (!data->_description) { return; } - int id; + // TODO: move this balloons stuff into DialogueManager and BalloonManager + if (getGameType() == GType_Nippon) { + int id; + if (data->_filename) { + if (data->_cnv == 0) { + data->_cnv = _disk->loadStatic(data->_filename); + } - if (data->_filename) { - if (data->_cnv == 0) { - data->_cnv = _disk->loadStatic(data->_filename); + _gfx->setHalfbriteMode(true); + _balloonMan->setSingleBalloon(data->_description, 0, 90, 0, 0); + Common::Rect r; + data->_cnv->getRect(0, r); + id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2); + _gfx->setItemFrame(id, 0); + id = _gfx->setItem(_char._head, 100, 152); + _gfx->setItemFrame(id, 0); + } else { + _balloonMan->setSingleBalloon(data->_description, 140, 10, 0, 0); + id = _gfx->setItem(_char._talk, 190, 80); + _gfx->setItemFrame(id, 0); } - - _gfx->setHalfbriteMode(true); - _gfx->setSingleBalloon(data->_description, 0, 90, 0, 0); - Common::Rect r; - data->_cnv->getRect(0, r); - id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2); - _gfx->setItemFrame(id, 0); - id = _gfx->setItem(_char._head, 100, 152); - _gfx->setItemFrame(id, 0); - } else { - _gfx->setSingleBalloon(data->_description, 140, 10, 0, 0); - id = _gfx->setItem(_char._talk, 190, 80); + } else + if (getGameType() == GType_BRA) { + _balloonMan->setSingleBalloon(data->_description, 0, 0, 1, 0); + int id = _gfx->setItem(_char._talk, 10, 80); _gfx->setItemFrame(id, 0); } _input->_inputMode = Input::kInputModeComment; } +void Parallaction::exitCommentMode() { + _input->_inputMode = Input::kInputModeGame; + + hideDialogueStuff(); + _gfx->setHalfbriteMode(false); + + _cmdExec->run(_commentZone->_commands, _commentZone); + _commentZone = nullZonePtr; +} + +void Parallaction::runCommentFrame() { + if (_input->_inputMode != Input::kInputModeComment) { + return; + } + + if (_input->getLastButtonEvent() == kMouseLeftUp) { + exitCommentMode(); + } +} uint16 Parallaction::runZone(ZonePtr z) { @@ -496,8 +610,8 @@ uint16 Parallaction::runZone(ZonePtr z) { switch(subtype) { case kZoneExamine: - displayComment(z->u.examine); - break; + enterCommentMode(z); + return 0; case kZoneGet: if (z->_flags & kFlagsFixed) break; @@ -518,14 +632,13 @@ uint16 Parallaction::runZone(ZonePtr z) { break; case kZoneSpeak: - runDialogue(z->u.speak); - break; - + enterDialogueMode(z); + return 0; } debugC(3, kDebugExec, "runZone completed"); - runCommands(z->_commands, z); + _cmdExec->run(z->_commands, z); return 0; } @@ -652,11 +765,34 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) { } -void Parallaction_ns::initOpcodes() { +void CommandExec_ns::init() { + Common::Array<const Opcode*> *table = 0; + + SetOpcodeTable(_opcodes); + COMMAND_OPCODE(invalid); + COMMAND_OPCODE(set); + COMMAND_OPCODE(clear); + COMMAND_OPCODE(start); + COMMAND_OPCODE(speak); + COMMAND_OPCODE(get); + COMMAND_OPCODE(location); + COMMAND_OPCODE(open); + COMMAND_OPCODE(close); + COMMAND_OPCODE(on); + COMMAND_OPCODE(off); + COMMAND_OPCODE(call); + COMMAND_OPCODE(toggle); + COMMAND_OPCODE(drop); + COMMAND_OPCODE(quit); + COMMAND_OPCODE(move); + COMMAND_OPCODE(stop); +} + +void ProgramExec_ns::init() { Common::Array<const Opcode*> *table = 0; - SetOpcodeTable(_instructionOpcodes); + SetOpcodeTable(_opcodes); INSTRUCTION_OPCODE(invalid); INSTRUCTION_OPCODE(on); INSTRUCTION_OPCODE(off); @@ -666,7 +802,7 @@ void Parallaction_ns::initOpcodes() { INSTRUCTION_OPCODE(set); // f INSTRUCTION_OPCODE(loop); INSTRUCTION_OPCODE(endloop); - INSTRUCTION_OPCODE(null); + INSTRUCTION_OPCODE(show); INSTRUCTION_OPCODE(inc); INSTRUCTION_OPCODE(inc); // dec INSTRUCTION_OPCODE(set); @@ -678,25 +814,13 @@ void Parallaction_ns::initOpcodes() { INSTRUCTION_OPCODE(move); INSTRUCTION_OPCODE(endscript); - SetOpcodeTable(_commandOpcodes); - COMMAND_OPCODE(invalid); - COMMAND_OPCODE(set); - COMMAND_OPCODE(clear); - COMMAND_OPCODE(start); - COMMAND_OPCODE(speak); - COMMAND_OPCODE(get); - COMMAND_OPCODE(location); - COMMAND_OPCODE(open); - COMMAND_OPCODE(close); - COMMAND_OPCODE(on); - COMMAND_OPCODE(off); - COMMAND_OPCODE(call); - COMMAND_OPCODE(toggle); - COMMAND_OPCODE(drop); - COMMAND_OPCODE(quit); - COMMAND_OPCODE(move); - COMMAND_OPCODE(stop); } +ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) { + _instructionNames = _instructionNamesRes_ns; +} + +ProgramExec_ns::~ProgramExec_ns() { +} } // namespace Parallaction diff --git a/engines/parallaction/font.cpp b/engines/parallaction/font.cpp index 91848b30a4..e84dad34aa 100644 --- a/engines/parallaction/font.cpp +++ b/engines/parallaction/font.cpp @@ -35,6 +35,7 @@ extern byte _amigaTopazFont[]; class BraFont : public Font { +protected: byte *_cp; uint _bufPitch; @@ -45,15 +46,15 @@ class BraFont : public Font { uint *_offsets; byte *_data; - - static byte _charMap[]; + const byte *_charMap; byte mapChar(byte c) { - return _charMap[c]; + return (_charMap == 0) ? c : _charMap[c]; } public: - BraFont(Common::ReadStream &stream) { + BraFont(Common::ReadStream &stream, const byte *charMap = 0) { + _charMap = charMap; _numGlyphs = stream.readByte(); _height = stream.readUint32BE(); @@ -137,7 +138,7 @@ public: }; -byte BraFont::_charMap[] = { +const byte _braDosFullCharMap[256] = { // 0 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, // 1 @@ -172,6 +173,111 @@ byte BraFont::_charMap[] = { 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 }; +const byte _braDosDemoComicCharMap[] = { +// 0 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 1 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 2 + 0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34, +// 3 + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42, +// 4 + 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, +// 5 + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34, +// 6 + 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, +// 7 + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, +// 8 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 9 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// A + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// B + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// C + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// D + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// E + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// F + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 +}; + +const byte _braDosDemoRussiaCharMap[] = { +// 0 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 1 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 2 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 3 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 4 + 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, +// 5 + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34, +// 6 + 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, +// 7 + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, +// 8 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// 9 + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// A + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// B + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// C + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// D + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// E + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, +// F + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 +}; + +class BraInventoryObjects : public BraFont, public Frames { + +public: + BraInventoryObjects(Common::ReadStream &stream) : BraFont(stream) { + } + + // Frames implementation + uint16 getNum() { + return _numGlyphs; + } + + byte* getData(uint16 index) { + assert(index < _numGlyphs); + return _data + (_height * _widths[index]) * index;; + } + + void getRect(uint16 index, Common::Rect &r) { + assert(index < _numGlyphs); + r.left = 0; + r.top = 0; + r.setWidth(_widths[index]); + r.setHeight(_height); + } + + uint getRawSize(uint16 index) { + assert(index < _numGlyphs); + return _widths[index] * _height; + } + + uint getSize(uint16 index) { + assert(index < _numGlyphs); + return _widths[index] * _height; + } + +}; class DosFont : public Font { @@ -537,7 +643,19 @@ Font *AmigaDisk_ns::createFont(const char *name, Common::SeekableReadStream &str Font *DosDisk_br::createFont(const char *name, Common::ReadStream &stream) { // printf("DosDisk_br::createFont(%s)\n", name); - return new BraFont(stream); + Font *font; + + if (_vm->getFeatures() & GF_DEMO) { + if (!scumm_stricmp(name, "russia")) { + font = new BraFont(stream, _braDosDemoRussiaCharMap); + } else { + font = new BraFont(stream, _braDosDemoComicCharMap); + } + } else { + font = new BraFont(stream, _braDosFullCharMap); + } + + return font; } Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &stream) { @@ -545,6 +663,12 @@ Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &str return new AmigaFont(stream); } +GfxObj* DosDisk_br::createInventoryObjects(Common::SeekableReadStream &stream) { + Frames *frames = new BraInventoryObjects(stream); + return new GfxObj(0, frames, "inventoryobjects"); +} + + void Parallaction_ns::initFonts() { if (getPlatform() == Common::kPlatformPC) { @@ -573,8 +697,8 @@ void Parallaction_br::initFonts() { // fonts/sonya/18 // fonts/vanya/16 - _menuFont = _disk->loadFont("fonts/natasha/16"); - _dialogueFont = _disk->loadFont("fonts/sonya/18"); + _menuFont = _disk->loadFont("natasha"); + _dialogueFont = _disk->loadFont("vanya"); Common::MemoryReadStream stream(_amigaTopazFont, 2600, false); _labelFont = new AmigaFont(stream); } diff --git a/engines/parallaction/gfxbase.cpp b/engines/parallaction/gfxbase.cpp index 6599a1f81c..1c373dda44 100644 --- a/engines/parallaction/gfxbase.cpp +++ b/engines/parallaction/gfxbase.cpp @@ -32,7 +32,7 @@ namespace Parallaction { -GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : type(objType), _frames(frames), x(0), y(0), z(0), frame(0), layer(3), _flags(0), _keep(true) { +GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : _frames(frames), _keep(true), x(0), y(0), z(0), _flags(kGfxObjNormal), type(objType), frame(0), layer(3) { if (name) { _name = strdup(name); } else { @@ -86,93 +86,265 @@ void GfxObj::clearFlags(uint32 flags) { } GfxObj* Gfx::loadAnim(const char *name) { - Frames *frames = _disk->loadFrames(name); + Frames* frames = _disk->loadFrames(name); + assert(frames); + GfxObj *obj = new GfxObj(kGfxObjTypeAnim, frames, name); assert(obj); + // animation Z is not set here, but controlled by game scripts and user interaction. + // it is always >=0 and <screen height + obj->transparentKey = 0; + _gfxobjList.push_back(obj); return obj; } GfxObj* Gfx::loadGet(const char *name) { - Frames *frames = _disk->loadStatic(name); - GfxObj *obj = new GfxObj(kGfxObjTypeGet, frames, name); + GfxObj *obj = _disk->loadStatic(name); assert(obj); + obj->z = kGfxObjGetZ; // this preset Z value ensures that get zones are drawn after doors but before animations + obj->type = kGfxObjTypeGet; + obj->transparentKey = 0; + _gfxobjList.push_back(obj); return obj; } GfxObj* Gfx::loadDoor(const char *name) { Frames *frames = _disk->loadFrames(name); + assert(frames); + GfxObj *obj = new GfxObj(kGfxObjTypeDoor, frames, name); assert(obj); + obj->z = kGfxObjDoorZ; // this preset Z value ensures that doors are drawn first + obj->transparentKey = 0; + _gfxobjList.push_back(obj); return obj; } -void Gfx::clearGfxObjects() { - _gfxobjList[0].clear(); - _gfxobjList[1].clear(); - _gfxobjList[2].clear(); +void Gfx::clearGfxObjects(uint filter) { + + GfxObjList::iterator b = _gfxobjList.begin(); + GfxObjList::iterator e = _gfxobjList.end(); + + for ( ; b != e; ) { + if (((*b)->_flags & filter) != 0) { + b = _gfxobjList.erase(b); + } else { + b++; + } + } + } void Gfx::showGfxObj(GfxObj* obj, bool visible) { - if (!obj || obj->isVisible() == visible) { + if (!obj) { return; } if (visible) { obj->setFlags(kGfxObjVisible); - _gfxobjList[obj->type].push_back(obj); } else { obj->clearFlags(kGfxObjVisible); - _gfxobjList[obj->type].remove(obj); } - } -bool compareAnimationZ(const GfxObj* a1, const GfxObj* a2) { +bool compareZ(const GfxObj* a1, const GfxObj* a2) { return a1->z < a2->z; } void Gfx::sortAnimations() { - GfxObjList::iterator first = _gfxobjList[kGfxObjTypeAnim].begin(); - GfxObjList::iterator last = _gfxobjList[kGfxObjTypeAnim].end(); + GfxObjList::iterator first = _gfxobjList.begin(); + GfxObjList::iterator last = _gfxobjList.end(); - Common::sort(first, last, compareAnimationZ); + Common::sort(first, last, compareZ); } -void Gfx::drawGfxObjects(Graphics::Surface &surf) { + +void Gfx::drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene) { + if (!obj->isVisible()) { + return; + } Common::Rect rect; byte *data; + uint scrollX = (scene) ? -_varScrollX : 0; + + obj->getRect(obj->frame, rect); + rect.translate(obj->x + scrollX, obj->y); + data = obj->getData(obj->frame); + + if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) { + blt(rect, data, &surf, obj->layer, obj->transparentKey); + } else { + unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, obj->transparentKey); + } + +} + + +void Gfx::drawGfxObjects(Graphics::Surface &surf) { + sortAnimations(); // TODO: some zones don't appear because of wrong masking (3 or 0?) - // TODO: Dr.Ki is not visible inside the club + + GfxObjList::iterator b = _gfxobjList.begin(); + GfxObjList::iterator e = _gfxobjList.end(); + + for (; b != e; b++) { + drawGfxObject(*b, surf, true); + } +} - for (uint i = 0; i < 3; i++) { - GfxObjList::iterator b = _gfxobjList[i].begin(); - GfxObjList::iterator e = _gfxobjList[i].end(); +void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) { + byte *dst = (byte*)surf->getBasePtr(x, y); + font->setColor(color); + font->drawString(dst, surf->w, text); +} + - for (; b != e; b++) { - GfxObj *obj = *b; - if (obj->isVisible()) { - obj->getRect(obj->frame, rect); - rect.translate(obj->x - _varScrollX, obj->y); - data = obj->getData(obj->frame); - if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) { - blt(rect, data, &surf, obj->layer, 0); - } else { - unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, 0); +#if 0 +void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { + + byte *d = _unpackedBitmap; + + while (size > 0) { + + uint8 p = *data++; + size--; + uint8 color = p & 0xF; + uint8 repeat = (p & 0xF0) >> 4; + if (repeat == 0) { + repeat = *data++; + size--; + } + + memset(d, color, repeat); + d += repeat; + } + + blt(r, _unpackedBitmap, surf, z, transparentColor); +} +#endif +void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { + + byte *d = _unpackedBitmap; + uint pixelsLeftInLine = r.width(); + + while (size > 0) { + uint8 p = *data++; + size--; + uint8 color = p & 0xF; + uint8 repeat = (p & 0xF0) >> 4; + if (repeat == 0) { + repeat = *data++; + size--; + } + if (repeat == 0) { + // end of line + repeat = pixelsLeftInLine; + pixelsLeftInLine = r.width(); + } else { + pixelsLeftInLine -= repeat; + } + + memset(d, color, repeat); + d += repeat; + } + + blt(r, _unpackedBitmap, surf, z, transparentColor); +} + + +void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) { + + Common::Point dp; + Common::Rect q(r); + + Common::Rect clipper(surf->w, surf->h); + + q.clip(clipper); + if (!q.isValidRect()) return; + + dp.x = q.left; + dp.y = q.top; + + q.translate(-r.left, -r.top); + + byte *s = data + q.left + q.top * r.width(); + byte *d = (byte*)surf->getBasePtr(dp.x, dp.y); + + uint sPitch = r.width() - q.width(); + uint dPitch = surf->w - q.width(); + + + if (_varRenderMode == 2) { + + for (uint16 i = 0; i < q.height(); i++) { + + for (uint16 j = 0; j < q.width(); j++) { + if (*s != transparentColor) { + if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) { + byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i); + if (z >= v) *d = 5; + } else { + *d = 5; + } } + + s++; + d++; } + + s += sPitch; + d += dPitch; + } + + } else { + if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) { + + for (uint16 i = 0; i < q.height(); i++) { + + for (uint16 j = 0; j < q.width(); j++) { + if (*s != transparentColor) { + byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i); + if (z >= v) *d = *s; + } + + s++; + d++; + } + + s += sPitch; + d += dPitch; + } + + } else { + + for (uint16 i = q.top; i < q.bottom; i++) { + for (uint16 j = q.left; j < q.right; j++) { + if (*s != transparentColor) + *d = *s; + + s++; + d++; + } + + s += sPitch; + d += dPitch; + } + } } + } + } // namespace Parallaction diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp index 58fb02a750..c19d6ae5e5 100644 --- a/engines/parallaction/graphics.cpp +++ b/engines/parallaction/graphics.cpp @@ -33,6 +33,11 @@ namespace Parallaction { +// this is the size of the receiving buffer for unpacked frames, +// since BRA uses some insanely big animations. +#define MAXIMUM_UNPACKED_BITMAP_SIZE 640*401 + + void Gfx::registerVar(const Common::String &name, int32 initialValue) { if (_vars.contains(name)) { warning("Variable '%s' already registered, ignoring initial value.\n", name.c_str()); @@ -64,10 +69,6 @@ int32 Gfx::getVar(const Common::String &name) { #define LABEL_TRANSPARENT_COLOR 0xFF -#define BALLOON_TRANSPARENT_COLOR 2 - - -int16 Gfx::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 }; void halfbritePixel(int x, int y, int color, void *data) { byte *buffer = (byte*)data; @@ -152,6 +153,13 @@ void Palette::setEntry(uint index, int red, int green, int blue) { _data[index*3+2] = blue & 0xFF; } +void Palette::getEntry(uint index, int &red, int &green, int &blue) { + assert(index < _colors); + red = _data[index*3]; + green = _data[index*3+1]; + blue = _data[index*3+2]; +} + void Palette::makeGrayscale() { byte v; for (uint16 i = 0; i < _colors; i++) { @@ -238,37 +246,6 @@ void Palette::rotate(uint first, uint last, bool forward) { } -#define BALLOON_TAIL_WIDTH 12 -#define BALLOON_TAIL_HEIGHT 10 - - -byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = { - { - 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, - 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - }, - { - 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, - 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, - 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02 - } -}; - void Gfx::setPalette(Palette pal) { byte sysPal[256*4]; @@ -292,7 +269,7 @@ void Gfx::animatePalette() { PaletteFxRange *range; for (uint16 i = 0; i < 4; i++) { - range = &_backgroundInfo.ranges[i]; + range = &_backgroundInfo->ranges[i]; if ((range->_flags & 1) == 0) continue; // animated palette range->_timer += range->_step * 2; // update timer @@ -337,10 +314,14 @@ void Gfx::setProjectorProgram(int16 *data) { } void Gfx::drawInventory() { - +/* if ((_engineFlags & kEngineInventory) == 0) { return; } +*/ + if (_vm->_input->_inputMode != Input::kInputModeInventory) { + return; + } Common::Rect r; _vm->_inventoryRenderer->getRect(r); @@ -356,21 +337,19 @@ void Gfx::drawItems() { Graphics::Surface *surf = g_system->lockScreen(); for (uint i = 0; i < _numItems; i++) { - blt(_items[i].rect, _items[i].data->getData(_items[i].frame), surf, LAYER_FOREGROUND, _items[i].transparentColor); + drawGfxObject(_items[i].data, *surf, false); } g_system->unlockScreen(); } void Gfx::drawBalloons() { - if (_numBalloons == 0) { + if (_balloons.size() == 0) { return; } Graphics::Surface *surf = g_system->lockScreen(); - for (uint i = 0; i < _numBalloons; i++) { - Common::Rect r(_balloons[i].surface.w, _balloons[i].surface.h); - r.moveTo(_balloons[i].x, _balloons[i].y); - blt(r, (byte*)_balloons[i].surface.getBasePtr(0, 0), surf, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR); + for (uint i = 0; i < _balloons.size(); i++) { + drawGfxObject(_balloons[i], *surf, false); } g_system->unlockScreen(); } @@ -380,29 +359,37 @@ void Gfx::clearScreen() { } void Gfx::beginFrame() { - - int32 oldBackgroundMode = _varBackgroundMode; - _varBackgroundMode = getVar("background_mode"); - - if (oldBackgroundMode != _varBackgroundMode) { - switch (_varBackgroundMode) { - case 1: - _bitmapMask.free(); - break; - case 2: - _bitmapMask.create(_backgroundInfo.width, _backgroundInfo.height, 1); - byte *data = (byte*)_bitmapMask.pixels; - for (uint y = 0; y < _bitmapMask.h; y++) { - for (uint x = 0; x < _bitmapMask.w; x++) { - *data++ = _backgroundInfo.mask.getValue(x, y); + _skipBackground = (_backgroundInfo->bg.pixels == 0); // don't render frame if background is missing + + if (!_skipBackground) { + int32 oldBackgroundMode = _varBackgroundMode; + _varBackgroundMode = getVar("background_mode"); + if (oldBackgroundMode != _varBackgroundMode) { + switch (_varBackgroundMode) { + case 1: + _bitmapMask.free(); + break; + case 2: + _bitmapMask.create(_backgroundInfo->width, _backgroundInfo->height, 1); + byte *data = (byte*)_bitmapMask.pixels; + for (uint y = 0; y < _bitmapMask.h; y++) { + for (uint x = 0; x < _bitmapMask.w; x++) { + *data++ = _backgroundInfo->mask.getValue(x, y); + } } + break; } - break; } } + _varDrawPathZones = getVar("draw_path_zones"); + if (_varDrawPathZones == 1 && _vm->getGameType() != GType_BRA) { + setVar("draw_path_zones", 0); + _varDrawPathZones = 0; + warning("Path zones are supported only in Big Red Adventure"); + } - if (_vm->_screenWidth >= _backgroundInfo.width) { + if (_skipBackground || (_vm->_screenWidth >= _backgroundInfo->width)) { _varScrollX = 0; } else { _varScrollX = getVar("scroll_x"); @@ -427,24 +414,38 @@ int32 Gfx::getRenderMode(const char *type) { void Gfx::updateScreen() { - // background may not cover the whole screen, so adjust bulk update size - uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo.width); - uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo.height); - - byte *backgroundData = 0; - uint16 backgroundPitch = 0; - switch (_varBackgroundMode) { - case 1: - backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0); - backgroundPitch = _backgroundInfo.bg.pitch; - break; - case 2: - backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); - backgroundPitch = _bitmapMask.pitch; - break; + if (!_skipBackground) { + // background may not cover the whole screen, so adjust bulk update size + uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo->width); + uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo->height); + + byte *backgroundData = 0; + uint16 backgroundPitch = 0; + switch (_varBackgroundMode) { + case 1: + backgroundData = (byte*)_backgroundInfo->bg.getBasePtr(_varScrollX, 0); + backgroundPitch = _backgroundInfo->bg.pitch; + break; + case 2: + backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); + backgroundPitch = _bitmapMask.pitch; + break; + } + g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->x, _backgroundInfo->y, w, h); } - g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h); + if (_varDrawPathZones == 1) { + Graphics::Surface *surf = g_system->lockScreen(); + ZoneList::iterator b = _vm->_location._zones.begin(); + ZoneList::iterator e = _vm->_location._zones.end(); + for (; b != e; b++) { + ZonePtr z = *b; + if (z->_type & kZonePath) { + surf->frameRect(Common::Rect(z->_left, z->_top, z->_right, z->_bottom), 2); + } + } + g_system->unlockScreen(); + } _varRenderMode = _varAnimRenderMode; @@ -498,17 +499,17 @@ void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask) Common::Rect r(surf.w, surf.h); r.moveTo(x, y); - uint16 z = (mask) ? _backgroundInfo.getLayer(y) : LAYER_FOREGROUND; - blt(r, (byte*)surf.pixels, &_backgroundInfo.bg, z, 0); + uint16 z = (mask) ? _backgroundInfo->getLayer(y) : LAYER_FOREGROUND; + blt(r, (byte*)surf.pixels, &_backgroundInfo->bg, z, 0); } void Gfx::fillBackground(const Common::Rect& r, byte color) { - _backgroundInfo.bg.fillRect(r, color); + _backgroundInfo->bg.fillRect(r, color); } void Gfx::invertBackground(const Common::Rect& r) { - byte *d = (byte*)_backgroundInfo.bg.getBasePtr(r.left, r.top); + byte *d = (byte*)_backgroundInfo->bg.getBasePtr(r.left, r.top); for (int i = 0; i < r.height(); i++) { for (int j = 0; j < r.width(); j++) { @@ -516,146 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) { d++; } - d += (_backgroundInfo.bg.pitch - r.width()); - } - -} - -// this is the maximum size of an unpacked frame in BRA -byte _unpackedBitmap[640*401]; - -#if 0 -void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { - - byte *d = _unpackedBitmap; - - while (size > 0) { - - uint8 p = *data++; - size--; - uint8 color = p & 0xF; - uint8 repeat = (p & 0xF0) >> 4; - if (repeat == 0) { - repeat = *data++; - size--; - } - - memset(d, color, repeat); - d += repeat; - } - - blt(r, _unpackedBitmap, surf, z, transparentColor); -} -#endif -void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) { - - byte *d = _unpackedBitmap; - uint pixelsLeftInLine = r.width(); - - while (size > 0) { - uint8 p = *data++; - size--; - uint8 color = p & 0xF; - uint8 repeat = (p & 0xF0) >> 4; - if (repeat == 0) { - repeat = *data++; - size--; - } - if (repeat == 0) { - // end of line - repeat = pixelsLeftInLine; - pixelsLeftInLine = r.width(); - } else { - pixelsLeftInLine -= repeat; - } - - memset(d, color, repeat); - d += repeat; - } - - blt(r, _unpackedBitmap, surf, z, transparentColor); -} - - -void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) { - - Common::Point dp; - Common::Rect q(r); - - Common::Rect clipper(surf->w, surf->h); - - q.clip(clipper); - if (!q.isValidRect()) return; - - dp.x = q.left; - dp.y = q.top; - - q.translate(-r.left, -r.top); - - byte *s = data + q.left + q.top * r.width(); - byte *d = (byte*)surf->getBasePtr(dp.x, dp.y); - - uint sPitch = r.width() - q.width(); - uint dPitch = surf->w - q.width(); - - - if (_varRenderMode == 2) { - - for (uint16 i = 0; i < q.height(); i++) { - - for (uint16 j = 0; j < q.width(); j++) { - if (*s != transparentColor) { - if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) { - byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i); - if (z >= v) *d = 5; - } else { - *d = 5; - } - } - - s++; - d++; - } - - s += sPitch; - d += dPitch; - } - - } else { - if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) { - - for (uint16 i = 0; i < q.height(); i++) { - - for (uint16 j = 0; j < q.width(); j++) { - if (*s != transparentColor) { - byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i); - if (z >= v) *d = *s; - } - - s++; - d++; - } - - s += sPitch; - d += dPitch; - } - - } else { - - for (uint16 i = q.top; i < q.bottom; i++) { - for (uint16 j = q.left; j < q.right; j++) { - if (*s != transparentColor) - *d = *s; - - s++; - d++; - } - - s += sPitch; - d += dPitch; - } - - } + d += (_backgroundInfo->bg.pitch - r.width()); } } @@ -669,10 +531,9 @@ void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) { surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR); } -Label *Gfx::renderFloatingLabel(Font *font, char *text) { +uint Gfx::renderFloatingLabel(Font *font, char *text) { - Label *label = new Label; - Graphics::Surface *cnv = &label->_cnv; + Graphics::Surface *cnv = new Graphics::Surface; uint w, h; @@ -698,80 +559,38 @@ Label *Gfx::renderFloatingLabel(Font *font, char *text) { drawText(font, cnv, 0, 0, text, 0); } - return label; -} + GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel"); + obj->transparentKey = LABEL_TRANSPARENT_COLOR; + obj->layer = LAYER_FOREGROUND; -uint Gfx::createLabel(Font *font, const char *text, byte color) { - assert(_numLabels < MAX_NUM_LABELS); - - Label *label = new Label; - Graphics::Surface *cnv = &label->_cnv; - - uint w, h; - - if (_vm->getPlatform() == Common::kPlatformAmiga) { - w = font->getStringWidth(text) + 2; - h = font->height() + 2; - - setupLabelSurface(*cnv, w, h); - - drawText(font, cnv, 0, 2, text, 0); - drawText(font, cnv, 2, 0, text, color); - } else { - w = font->getStringWidth(text); - h = font->height(); - - setupLabelSurface(*cnv, w, h); - - drawText(font, cnv, 0, 0, text, color); - } - - uint id = _numLabels; - _labels[id] = label; - _numLabels++; + uint id = _labels.size(); + _labels.insert_at(id, obj); return id; } -void Gfx::showLabel(uint id, int16 x, int16 y) { - assert(id < _numLabels); - _labels[id]->_visible = true; +void Gfx::showFloatingLabel(uint label) { + assert(label < _labels.size()); - if (x == CENTER_LABEL_HORIZONTAL) { - x = CLIP<int16>((_vm->_screenWidth - _labels[id]->_cnv.w) / 2, 0, _vm->_screenWidth/2); - } - - if (y == CENTER_LABEL_VERTICAL) { - y = CLIP<int16>((_vm->_screenHeight - _labels[id]->_cnv.h) / 2, 0, _vm->_screenHeight/2); - } + hideFloatingLabel(); - _labels[id]->_pos.x = x; - _labels[id]->_pos.y = y; -} + _labels[label]->x = -1000; + _labels[label]->y = -1000; + _labels[label]->setFlags(kGfxObjVisible); -void Gfx::hideLabel(uint id) { - assert(id < _numLabels); - _labels[id]->_visible = false; + _floatingLabel = label; } -void Gfx::freeLabels() { - for (uint i = 0; i < _numLabels; i++) { - delete _labels[i]; +void Gfx::hideFloatingLabel() { + if (_floatingLabel != NO_FLOATING_LABEL) { + _labels[_floatingLabel]->clearFlags(kGfxObjVisible); } - _numLabels = 0; + _floatingLabel = NO_FLOATING_LABEL; } -void Gfx::setFloatingLabel(Label *label) { - _floatingLabel = label; - - if (_floatingLabel) { - _floatingLabel->resetPosition(); - } -} - void Gfx::updateFloatingLabel() { - if (!_floatingLabel) { + if (_floatingLabel == NO_FLOATING_LABEL) { return; } @@ -780,113 +599,115 @@ void Gfx::updateFloatingLabel() { Common::Point cursor; _vm->_input->getCursorPos(cursor); + Common::Rect r; + _labels[_floatingLabel]->getRect(0, r); + if (_vm->_input->_activeItem._id != 0) { - _si = cursor.x + 16 - _floatingLabel->_cnv.w/2; + _si = cursor.x + 16 - r.width()/2; _di = cursor.y + 34; } else { - _si = cursor.x + 8 - _floatingLabel->_cnv.w/2; + _si = cursor.x + 8 - r.width()/2; _di = cursor.y + 21; } if (_si < 0) _si = 0; if (_di > 190) _di = 190; - if (_floatingLabel->_cnv.w + _si > _vm->_screenWidth) - _si = _vm->_screenWidth - _floatingLabel->_cnv.w; + if (r.width() + _si > _vm->_screenWidth) + _si = _vm->_screenWidth - r.width(); - _floatingLabel->_pos.x = _si; - _floatingLabel->_pos.y = _di; + _labels[_floatingLabel]->x = _si; + _labels[_floatingLabel]->y = _di; } -void Gfx::drawLabels() { - if ((!_floatingLabel) && (_numLabels == 0)) { - return; - } - updateFloatingLabel(); - Graphics::Surface* surf = g_system->lockScreen(); - for (uint i = 0; i < _numLabels; i++) { - if (_labels[i]->_visible) { - Common::Rect r(_labels[i]->_cnv.w, _labels[i]->_cnv.h); - r.moveTo(_labels[i]->_pos); - blt(r, (byte*)_labels[i]->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR); - } - } - if (_floatingLabel) { - Common::Rect r(_floatingLabel->_cnv.w, _floatingLabel->_cnv.h); - r.moveTo(_floatingLabel->_pos); - blt(r, (byte*)_floatingLabel->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR); - } +uint Gfx::createLabel(Font *font, const char *text, byte color) { + assert(_labels.size() < MAX_NUM_LABELS); - g_system->unlockScreen(); -} + Graphics::Surface *cnv = new Graphics::Surface; -Label::Label() { - resetPosition(); - _visible = false; -} + uint w, h; -Label::~Label() { - free(); -} + if (_vm->getPlatform() == Common::kPlatformAmiga) { + w = font->getStringWidth(text) + 2; + h = font->height() + 2; -void Label::free() { - _cnv.free(); - resetPosition(); -} + setupLabelSurface(*cnv, w, h); -void Label::resetPosition() { - _pos.x = -1000; - _pos.y = -1000; -} + drawText(font, cnv, 0, 2, text, 0); + drawText(font, cnv, 2, 0, text, color); + } else { + w = font->getStringWidth(text); + h = font->height(); + setupLabelSurface(*cnv, w, h); -void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { + drawText(font, cnv, 0, 0, text, color); + } - uint16 lines = 0; - uint16 w = 0; - *width = 0; + GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label"); + obj->transparentKey = LABEL_TRANSPARENT_COLOR; + obj->layer = LAYER_FOREGROUND; - uint16 blankWidth = font->getStringWidth(" "); - uint16 tokenWidth = 0; + int id = _labels.size(); - char token[MAX_TOKEN_LEN]; + _labels.insert_at(id, obj); - while (strlen(text) != 0) { + return id; +} - text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true); - tokenWidth = font->getStringWidth(token); +void Gfx::showLabel(uint id, int16 x, int16 y) { + assert(id < _labels.size()); + _labels[id]->setFlags(kGfxObjVisible); - w += tokenWidth; + Common::Rect r; + _labels[id]->getRect(0, r); - if (!scumm_stricmp(token, "%p")) { - lines++; - } else { - if (w > maxwidth) { - w -= tokenWidth; - lines++; - if (w > *width) - *width = w; + if (x == CENTER_LABEL_HORIZONTAL) { + x = CLIP<int16>((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2); + } - w = tokenWidth; - } - } + if (y == CENTER_LABEL_VERTICAL) { + y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2); + } - w += blankWidth; - text = Common::ltrim(text); + _labels[id]->x = x; + _labels[id]->y = y; +} + +void Gfx::hideLabel(uint id) { + assert(id < _labels.size()); + _labels[id]->clearFlags(kGfxObjVisible); +} + +void Gfx::freeLabels() { + for (uint i = 0; i < _labels.size(); i++) { + delete _labels[i]; } + _labels.clear(); + _floatingLabel = NO_FLOATING_LABEL; +} - if (*width < w) *width = w; - *width += 10; +void Gfx::drawLabels() { + if (_labels.size() == 0) { + return; + } - *height = lines * 10 + 20; + updateFloatingLabel(); - return; + Graphics::Surface* surf = g_system->lockScreen(); + + for (uint i = 0; i < _labels.size(); i++) { + drawGfxObject(_labels[i], *surf, false); + } + + g_system->unlockScreen(); } + void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) { byte *s = (byte*)src.getBasePtr(r.left, r.top); @@ -903,7 +724,7 @@ void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surf } void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) { - copyRect(r, _backgroundInfo.bg, dst); + copyRect(r, _backgroundInfo->bg, dst); } @@ -917,17 +738,20 @@ Gfx::Gfx(Parallaction* vm) : setPalette(_palette); - _numBalloons = 0; _numItems = 0; - _numLabels = 0; - _floatingLabel = 0; + _floatingLabel = NO_FLOATING_LABEL; _screenX = 0; _screenY = 0; + _backgroundInfo = 0; + _halfbrite = false; _hbCircleRadius = 0; + _unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE]; + assert(_unpackedBitmap); + registerVar("background_mode", 1); _varBackgroundMode = 1; @@ -937,26 +761,39 @@ Gfx::Gfx(Parallaction* vm) : registerVar("anim_render_mode", 1); registerVar("misc_render_mode", 1); + registerVar("draw_path_zones", 0); + + if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { + // this loads the backup palette needed by the PC version of BRA (see setBackground()). + BackgroundInfo paletteInfo; + _disk->loadSlide(paletteInfo, "pointer"); + _backupPal.clone(paletteInfo.palette); + } + return; } Gfx::~Gfx() { - freeBackground(); + delete _backgroundInfo; + + freeLabels(); + + delete []_unpackedBitmap; return; } -int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) { +int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) { int id = _numItems; _items[id].data = frames; - _items[id].x = x; - _items[id].y = y; - - _items[id].transparentColor = transparentColor; + _items[id].data->x = x; + _items[id].data->y = y; + _items[id].data->layer = LAYER_FOREGROUND; + _items[id].data->transparentKey = transparentColor; _numItems++; @@ -965,223 +802,58 @@ int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) { void Gfx::setItemFrame(uint item, uint16 f) { assert(item < _numItems); - _items[item].frame = f; - _items[item].data->getRect(f, _items[item].rect); - _items[item].rect.moveTo(_items[item].x, _items[item].y); -} - -Gfx::Balloon* Gfx::getBalloon(uint id) { - assert(id < _numBalloons); - return &_balloons[id]; -} - -int Gfx::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) { - assert(_numBalloons < 5); - - int id = _numBalloons; - - Gfx::Balloon *balloon = &_balloons[id]; - - int16 real_h = (winding == -1) ? h : h + 9; - balloon->surface.create(w, real_h, 1); - balloon->surface.fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR); - - Common::Rect r(w, h); - balloon->surface.fillRect(r, 0); - balloon->outerBox = r; - - r.grow(-borderThickness); - balloon->surface.fillRect(r, 1); - balloon->innerBox = r; - - if (winding != -1) { - // draws tail - // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill. - winding = (winding == 0 ? 1 : 0); - Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT); - s.moveTo(r.width()/2 - 5, r.bottom - 1); - blt(s, _resBalloonTail[winding], &balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR); - } - - _numBalloons++; - - return id; -} - -int Gfx::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) { - - int16 w, h; - - getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); - - int id = createBalloon(w+5, h, winding, 1); - Gfx::Balloon *balloon = &_balloons[id]; - - drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); - - balloon->x = x; - balloon->y = y; - - return id; -} - -int Gfx::setDialogueBalloon(char *text, uint16 winding, byte textColor) { - - int16 w, h; - - getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); - - int id = createBalloon(w+5, h, winding, 1); - Gfx::Balloon *balloon = &_balloons[id]; - - drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); - - balloon->x = _dialogueBalloonX[id]; - balloon->y = 10; - - if (id > 0) { - balloon->y += _balloons[id - 1].y + _balloons[id - 1].outerBox.height(); - } - - - return id; + _items[item].data->frame = f; + _items[item].data->setFlags(kGfxObjVisible); } -void Gfx::setBalloonText(uint id, char *text, byte textColor) { - Gfx::Balloon *balloon = getBalloon(id); - balloon->surface.fillRect(balloon->innerBox, 1); - drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH); -} +GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) { -int Gfx::setLocationBalloon(char *text, bool endGame) { + GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text); - int16 w, h; + obj->layer = LAYER_FOREGROUND; + obj->frame = 0; + obj->setFlags(kGfxObjVisible); - getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h); + _balloons.push_back(obj); - int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR); - Gfx::Balloon *balloon = &_balloons[id]; - drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, 0, MAX_BALLOON_WIDTH); - - balloon->x = 5; - balloon->y = 5; - - return id; + return obj; } -int Gfx::hitTestDialogueBalloon(int x, int y) { - - Common::Point p; - - for (uint i = 0; i < _numBalloons; i++) { - p.x = x - _balloons[i].x; - p.y = y - _balloons[i].y; - - if (_balloons[i].innerBox.contains(p)) - return i; +void Gfx::destroyBalloons() { + for (uint i = 0; i < _balloons.size(); i++) { + delete _balloons[i]; } - - return -1; -} - - -void Gfx::freeBalloons() { - for (uint i = 0; i < _numBalloons; i++) { - _balloons[i].surface.free(); - } - _numBalloons = 0; + _balloons.clear(); } void Gfx::freeItems() { _numItems = 0; } -void Gfx::hideDialogueStuff() { - freeItems(); - freeBalloons(); -} - -void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) { - byte *dst = (byte*)surf->getBasePtr(x, y); - font->setColor(color); - font->drawString(dst, surf->w, text); -} - -void Gfx::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) { - - uint16 lines = 0; - uint16 linewidth = 0; - - uint16 rx = 10; - uint16 ry = 4; - - uint16 blankWidth = font->getStringWidth(" "); - uint16 tokenWidth = 0; - - char token[MAX_TOKEN_LEN]; - - if (wrapwidth == -1) - wrapwidth = _vm->_screenWidth; - - while (strlen(text) > 0) { - - text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true); - - if (!scumm_stricmp(token, "%p")) { - lines++; - rx = 10; - ry = 4 + lines*10; // y - - strcpy(token, "> ......."); - strncpy(token+2, _password, strlen(_password)); - tokenWidth = font->getStringWidth(token); - } else { - tokenWidth = font->getStringWidth(token); - - linewidth += tokenWidth; +void Gfx::setBackground(uint type, BackgroundInfo *info) { + delete _backgroundInfo; + _backgroundInfo = info; - if (linewidth > wrapwidth) { - // wrap line - lines++; - rx = 10; // x - ry = 4 + lines*10; // y - linewidth = tokenWidth; - } - - if (!scumm_stricmp(token, "%s")) { - sprintf(token, "%d", _score); + if (type == kBackgroundLocation) { + // The PC version of BRA needs the entries 20-31 of the palette to be constant, but + // the background resource files are screwed up. The right colors come from an unused + // bitmap (pointer.bmp). Nothing is known about the Amiga version so far. + if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { + int r, g, b; + for (uint i = 16; i < 32; i++) { + _backupPal.getEntry(i, r, g, b); + _backgroundInfo->palette.setEntry(i, r, g, b); } - } - drawText(font, surf, rx, ry, token, color); - - rx += tokenWidth + blankWidth; - linewidth += blankWidth; - - text = Common::ltrim(text); - } - -} - -void Gfx::freeBackground() { - _backgroundInfo.free(); -} - -void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) { - - freeBackground(); - - if (type == kBackgroundLocation) { - _disk->loadScenery(_backgroundInfo, name, mask, path); - setPalette(_backgroundInfo.palette); - _palette.clone(_backgroundInfo.palette); + setPalette(_backgroundInfo->palette); + _palette.clone(_backgroundInfo->palette); } else { - _disk->loadSlide(_backgroundInfo, name); - setPalette(_backgroundInfo.palette); + for (uint i = 0; i < 6; i++) + _backgroundInfo->ranges[i]._flags = 0; // disable palette cycling for slides + setPalette(_backgroundInfo->palette); } - } } // namespace Parallaction diff --git a/engines/parallaction/graphics.h b/engines/parallaction/graphics.h index 894e0fd678..23b4569c6a 100644 --- a/engines/parallaction/graphics.h +++ b/engines/parallaction/graphics.h @@ -95,6 +95,7 @@ public: } ~SurfaceToFrames() { + _surf->free(); delete _surf; } @@ -156,11 +157,11 @@ struct SurfaceToMultiFrames : public Frames { r.setHeight(_height); } uint getRawSize(uint16 index) { - assert(index == 0); + assert(index < _num); return getSize(index); } uint getSize(uint16 index) { - assert(index == 0); + assert(index < _num); return _width * _height; } @@ -260,6 +261,7 @@ public: void makeBlack(); void setEntries(byte* data, uint first, uint num); + void getEntry(uint index, int &red, int &green, int &blue); void setEntry(uint index, int red, int green, int blue); void makeGrayscale(); void fadeTo(const Palette& target, uint step); @@ -325,20 +327,6 @@ public: #define CENTER_LABEL_HORIZONTAL -1 #define CENTER_LABEL_VERTICAL -1 -struct Label { - Graphics::Surface _cnv; - - Common::Point _pos; - bool _visible; - - Label(); - ~Label(); - - void free(); - void resetPosition(); -}; - - #define MAX_BALLOON_WIDTH 130 @@ -353,25 +341,39 @@ class Disk; enum { kGfxObjVisible = 1, + kGfxObjNormal = 2, + kGfxObjCharacter = 4, kGfxObjTypeDoor = 0, kGfxObjTypeGet = 1, - kGfxObjTypeAnim = 2 + kGfxObjTypeAnim = 2, + kGfxObjTypeLabel = 3, + kGfxObjTypeBalloon = 4, + kGfxObjTypeCharacter = 8 +}; + +enum { + kGfxObjDoorZ = -200, + kGfxObjGetZ = -100 }; class GfxObj { char *_name; Frames *_frames; - uint32 _flags; bool _keep; public: int16 x, y; - uint16 z; + + int32 z; + + uint32 _flags; + uint type; uint frame; uint layer; + uint transparentKey; GfxObj(uint type, Frames *frames, const char *name = NULL); virtual ~GfxObj(); @@ -434,7 +436,7 @@ struct BackgroundInfo { return LAYER_FOREGROUND; } - void free() { + ~BackgroundInfo() { bg.free(); mask.free(); path.free(); @@ -452,49 +454,65 @@ enum { kBackgroundSlide = 2 }; + +class BalloonManager { +public: + virtual ~BalloonManager() { } + + virtual void freeBalloons() = 0; + virtual int setLocationBalloon(char *text, bool endGame) = 0; + virtual int setDialogueBalloon(char *text, uint16 winding, byte textColor) = 0; + virtual int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) = 0; + virtual void setBalloonText(uint id, char *text, byte textColor) = 0; + virtual int hitTestDialogueBalloon(int x, int y) = 0; +}; + + typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> VarMap; class Gfx { +protected: + Parallaction* _vm; + public: Disk *_disk; VarMap _vars; - GfxObjList _gfxobjList[3]; + GfxObjList _gfxobjList; GfxObj* loadAnim(const char *name); GfxObj* loadGet(const char *name); GfxObj* loadDoor(const char *name); void drawGfxObjects(Graphics::Surface &surf); void showGfxObj(GfxObj* obj, bool visible); - void clearGfxObjects(); + void clearGfxObjects(uint filter); void sortAnimations(); + // labels - void setFloatingLabel(Label *label); - Label *renderFloatingLabel(Font *font, char *text); + void showFloatingLabel(uint label); + void hideFloatingLabel(); + + uint renderFloatingLabel(Font *font, char *text); uint createLabel(Font *font, const char *text, byte color); void showLabel(uint id, int16 x, int16 y); void hideLabel(uint id); void freeLabels(); // dialogue balloons - int setLocationBalloon(char *text, bool endGame); - int setDialogueBalloon(char *text, uint16 winding, byte textColor); - int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor); - void setBalloonText(uint id, char *text, byte textColor); - int hitTestDialogueBalloon(int x, int y); - void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height); + GfxObj* registerBalloon(Frames *frames, const char *text); + void destroyBalloons(); // other items - int setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor = 0); + int setItem(GfxObj* obj, uint16 x, uint16 y, byte transparentColor = 0); void setItemFrame(uint item, uint16 f); void hideDialogueStuff(); void freeBalloons(); void freeItems(); // background surface - BackgroundInfo _backgroundInfo; - void setBackground(uint type, const char* name, const char* mask, const char* path); + BackgroundInfo *_backgroundInfo; + void setBackground(uint type, BackgroundInfo *info); void patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask = false); void grabBackground(const Common::Rect& r, Graphics::Surface &dst); void fillBackground(const Common::Rect& r, byte color); @@ -532,52 +550,45 @@ public: uint _screenX; // scrolling position uint _screenY; + byte *_unpackedBitmap; + protected: - Parallaction* _vm; bool _halfbrite; + bool _skipBackground; + Common::Point _hbCirclePos; int _hbCircleRadius; + // BRA specific + Palette _backupPal; + // frame data stored in programmable variables int32 _varBackgroundMode; // 1 = normal, 2 = only mask int32 _varScrollX; int32 _varAnimRenderMode; // 1 = normal, 2 = flat int32 _varMiscRenderMode; // 1 = normal, 2 = flat int32 _varRenderMode; + int32 _varDrawPathZones; // 0 = don't draw, 1 = draw Graphics::Surface _bitmapMask; int32 getRenderMode(const char *type); -protected: - static int16 _dialogueBalloonX[5]; - - struct Balloon { - uint16 x; - uint16 y; - Common::Rect outerBox; - Common::Rect innerBox; - uint16 winding; - Graphics::Surface surface; - } _balloons[5]; - - uint _numBalloons; +public: struct Item { - uint16 x; - uint16 y; - uint16 frame; - Frames *data; - - byte transparentColor; - Common::Rect rect; + GfxObj *data; } _items[14]; uint _numItems; - #define MAX_NUM_LABELS 5 - Label* _labels[MAX_NUM_LABELS]; - uint _numLabels; - Label *_floatingLabel; + #define MAX_NUM_LABELS 20 + #define NO_FLOATING_LABEL 1000 + + typedef Common::Array<GfxObj*> GfxObjArray; + GfxObjArray _labels; + GfxObjArray _balloons; + + uint _floatingLabel; void drawInventory(); void updateFloatingLabel(); @@ -587,13 +598,10 @@ protected: void copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst); - int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness); - Balloon *getBalloon(uint id); - // low level text and patches void drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color); - void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth); + void drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene); void blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor); void unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor); }; diff --git a/engines/parallaction/gui.cpp b/engines/parallaction/gui.cpp new file mode 100644 index 0000000000..2dbe64fcf6 --- /dev/null +++ b/engines/parallaction/gui.cpp @@ -0,0 +1,92 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "parallaction/gui.h" + +namespace Parallaction { + +bool MenuInputHelper::run() { + if (_newState == 0) { + debugC(3, kDebugExec, "MenuInputHelper has set NULL state"); + return false; + } + + if (_newState != _state) { + debugC(3, kDebugExec, "MenuInputHelper changing state to '%s'", _newState->_name.c_str()); + + _newState->enter(); + _state = _newState; + } + + _newState = _state->run(); + + return true; +} + +MenuInputHelper::~MenuInputHelper() { + StateMap::iterator b = _map.begin(); + for ( ; b != _map.end(); b++) { + delete b->_value; + } + _map.clear(); +} + + +void Parallaction::runGuiFrame() { + if (_input->_inputMode != Input::kInputModeMenu) { + return; + } + + if (!_menuHelper) { + error("No menu helper defined!"); + } + + bool res = _menuHelper->run(); + + if (!res) { + cleanupGui(); + _input->_inputMode = Input::kInputModeGame; + } + +} + +void Parallaction::cleanupGui() { + delete _menuHelper; + _menuHelper = 0; +} + +void Parallaction::setInternLanguage(uint id) { + //TODO: assert id! + + _language = id; + _disk->setLanguage(id); +} + +uint Parallaction::getInternLanguage() { + return _language; +} + + +} // namespace Parallaction diff --git a/engines/parallaction/gui.h b/engines/parallaction/gui.h new file mode 100644 index 0000000000..dc6d1bc71b --- /dev/null +++ b/engines/parallaction/gui.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PARALLACTION_GUI_H +#define PARALLACTION_GUI_H + +#include "common/system.h" +#include "common/hashmap.h" + +#include "parallaction/input.h" +#include "parallaction/parallaction.h" +#include "parallaction/sound.h" + + +namespace Parallaction { + +class MenuInputState; + +class MenuInputHelper { + typedef Common::HashMap<Common::String, MenuInputState*> StateMap; + + StateMap _map; + MenuInputState *_state; + MenuInputState *_newState; + +public: + MenuInputHelper() : _state(0) { + } + + ~MenuInputHelper(); + + void setState(const Common::String &name) { + // bootstrap routine + _newState = getState(name); + assert(_newState); + } + + void addState(const Common::String &name, MenuInputState *state) { + _map.setVal(name, state); + } + + MenuInputState *getState(const Common::String &name) { + return _map[name]; + } + + bool run(); +}; + +class MenuInputState { + +protected: + MenuInputHelper *_helper; + +public: + MenuInputState(const Common::String &name, MenuInputHelper *helper) : _helper(helper), _name(name) { + debugC(3, kDebugExec, "MenuInputState(%s)", name.c_str()); + _helper->addState(name, this); + } + + Common::String _name; + + virtual ~MenuInputState() { } + + virtual MenuInputState* run() = 0; + virtual void enter() = 0; +}; + + +} // namespace Parallaction + +#endif diff --git a/engines/parallaction/gui_br.cpp b/engines/parallaction/gui_br.cpp index c515299a34..3315433762 100644 --- a/engines/parallaction/gui_br.cpp +++ b/engines/parallaction/gui_br.cpp @@ -25,184 +25,268 @@ #include "common/system.h" - +#include "parallaction/gui.h" #include "parallaction/input.h" #include "parallaction/parallaction.h" namespace Parallaction { -enum MenuOptions { - kMenuPart0 = 0, - kMenuPart1 = 1, - kMenuPart2 = 2, - kMenuPart3 = 3, - kMenuPart4 = 4, - kMenuLoadGame = 5, - kMenuQuit = 6 -}; - +class SplashInputState_BR : public MenuInputState { +protected: + Common::String _slideName; + uint32 _timeOut; + Common::String _nextState; + uint32 _startTime; + Palette blackPal; + Palette pal; -void Parallaction_br::guiStart() { + Parallaction_br *_vm; + int _fadeSteps; - // TODO: load progress value from special save game - _progress = 3; +public: + SplashInputState_BR(Parallaction_br *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) { + } - int option = guiShowMenu(); - switch (option) { - case kMenuQuit: - _engineFlags |= kEngineQuit; - break; + virtual MenuInputState* run() { + if (_fadeSteps > 0) { + pal.fadeTo(blackPal, 1); + _vm->_gfx->setPalette(pal); + _fadeSteps--; + // TODO: properly implement timers to avoid delay calls + _vm->_system->delayMillis(20); + return this; + } - case kMenuLoadGame: - warning("loadgame not yet implemented"); - break; + if (_fadeSteps == 0) { + _vm->freeBackground(); + return _helper->getState(_nextState); + } - default: - _part = option; - _disk->selectArchive(_partNames[_part]); - startPart(); + uint32 curTime = _vm->_system->getMillis(); + if (curTime - _startTime > _timeOut) { + _fadeSteps = 64; + pal.clone(_vm->_gfx->_backgroundInfo->palette); + } + return this; } -} -void Parallaction_br::guiSplash(const char *name) { + virtual void enter() { + _vm->_gfx->clearScreen(); + _vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL); + _vm->_input->setMouseState(MOUSE_DISABLED); - _gfx->clearScreen(); - _gfx->setBackground(kBackgroundSlide, name, 0, 0); - _gfx->_backgroundInfo.x = (_screenWidth - _gfx->_backgroundInfo.width) >> 1; - _gfx->_backgroundInfo.y = (_screenHeight - _gfx->_backgroundInfo.height) >> 1; - _gfx->updateScreen(); - _system->delayMillis(600); + _startTime = g_system->getMillis(); + _fadeSteps = -1; + } +}; - Palette blackPal; - Palette pal(_gfx->_backgroundInfo.palette); - for (uint i = 0; i < 64; i++) { - pal.fadeTo(blackPal, 1); - _gfx->setPalette(pal); - _gfx->updateScreen(); - _system->delayMillis(20); +class SplashInputState0_BR : public SplashInputState_BR { + +public: + SplashInputState0_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro0", helper) { + _slideName = "dyna"; + _timeOut = 600; + _nextState = "intro1"; } +}; -} +class SplashInputState1_BR : public SplashInputState_BR { -#define MENUITEMS_X 250 -#define MENUITEMS_Y 200 +public: + SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) { + _slideName = "core"; + _timeOut = 600; + _nextState = "mainmenu"; + } +}; -#define MENUITEM_WIDTH 190 -#define MENUITEM_HEIGHT 18 +class MainMenuInputState_BR : public MenuInputState { + Parallaction_br *_vm; -Frames* Parallaction_br::guiRenderMenuItem(const char *text) { - // this builds a surface containing two copies of the text. - // one is in normal color, the other is inverted. - // the two 'frames' are used to display selected/unselected menu items + #define MENUITEMS_X 250 + #define MENUITEMS_Y 200 - Graphics::Surface *surf = new Graphics::Surface; - surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); + #define MENUITEM_WIDTH 190 + #define MENUITEM_HEIGHT 18 - // build first frame to be displayed when item is not selected - if (getPlatform() == Common::kPlatformPC) { - _menuFont->setColor(0); - } else { - _menuFont->setColor(7); - } - _menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text); + Frames* renderMenuItem(const char *text) { + // this builds a surface containing two copies of the text. + // one is in normal color, the other is inverted. + // the two 'frames' are used to display selected/unselected menu items - // build second frame to be displayed when item is selected - _menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text); - byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT); - for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) { - *s++ ^= 0xD; - } + Graphics::Surface *surf = new Graphics::Surface; + surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); - // wrap the surface into the suitable Frames adapter - return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); -} + // build first frame to be displayed when item is not selected + if (_vm->getPlatform() == Common::kPlatformPC) { + _vm->_menuFont->setColor(0); + } else { + _vm->_menuFont->setColor(7); + } + _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text); + // build second frame to be displayed when item is selected + _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text); + byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT); + for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) { + *s++ ^= 0xD; + } -int Parallaction_br::guiShowMenu() { - // TODO: filter menu entries according to progress in game + // wrap the surface into the suitable Frames adapter + return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); + } - #define NUM_MENULINES 7 - Frames *_lines[NUM_MENULINES]; - - const char *menuStrings[NUM_MENULINES] = { - "SEE INTRO", - "NEW GAME", - "SAVED GAME", - "EXIT TO DOS", - "PART 2", - "PART 3", - "PART 4" + enum MenuOptions { + kMenuPart0 = 0, + kMenuPart1 = 1, + kMenuPart2 = 2, + kMenuPart3 = 3, + kMenuPart4 = 4, + kMenuLoadGame = 5, + kMenuQuit = 6 }; - MenuOptions options[NUM_MENULINES] = { - kMenuPart0, - kMenuPart1, - kMenuLoadGame, - kMenuQuit, - kMenuPart2, - kMenuPart3, - kMenuPart4 - }; + #define NUM_MENULINES 7 + GfxObj *_lines[NUM_MENULINES]; - _gfx->clearScreen(); - _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0); - if (getPlatform() == Common::kPlatformPC) { - _gfx->_backgroundInfo.x = 20; - _gfx->_backgroundInfo.y = 50; - } + static const char *_menuStrings[NUM_MENULINES]; + static const MenuOptions _options[NUM_MENULINES]; - int availItems = 4 + _progress; + int _availItems; + int _selection; - // TODO: keep track of and destroy menu item frames/surfaces + void cleanup() { + _vm->_system->showMouse(false); + _vm->hideDialogueStuff(); - int i; - for (i = 0; i < availItems; i++) { - _lines[i] = guiRenderMenuItem(menuStrings[i]); - uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF); - _gfx->setItemFrame(id, 0); + for (int i = 0; i < _availItems; i++) { + delete _lines[i]; + } } - int selectedItem = -1; + void performChoice(int selectedItem) { + switch (selectedItem) { + case kMenuQuit: + _engineFlags |= kEngineQuit; + break; - setMousePointer(0); + case kMenuLoadGame: + warning("loadgame not yet implemented"); + break; - uint32 event; - Common::Point p; - while (true) { + default: + _vm->startPart(selectedItem); + } + } - _input->readInput(); +public: + MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) { + } - event = _input->getLastButtonEvent(); - if ((event == kMouseLeftUp) && selectedItem >= 0) - break; + virtual MenuInputState* run() { - _input->getCursorPos(p); + int event = _vm->_input->getLastButtonEvent(); + if ((event == kMouseLeftUp) && _selection >= 0) { + cleanup(); + performChoice(_options[_selection]); + return 0; + } + + Common::Point p; + _vm->_input->getCursorPos(p); if ((p.x > MENUITEMS_X) && (p.x < (MENUITEMS_X+MENUITEM_WIDTH)) && (p.y > MENUITEMS_Y)) { - selectedItem = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT; + _selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT; - if (!(selectedItem < availItems)) - selectedItem = -1; + if (!(_selection < _availItems)) + _selection = -1; } else - selectedItem = -1; + _selection = -1; - for (i = 0; i < availItems; i++) { - _gfx->setItemFrame(i, selectedItem == i ? 1 : 0); + for (int i = 0; i < _availItems; i++) { + _vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0); } - _gfx->updateScreen(); - _system->delayMillis(20); - } - _system->showMouse(false); - _gfx->hideDialogueStuff(); + return this; + } - for (i = 0; i < availItems; i++) { - delete _lines[i]; + virtual void enter() { + _vm->_gfx->clearScreen(); + int x = 0, y = 0; + if (_vm->getPlatform() == Common::kPlatformPC) { + x = 20; + y = 50; + } + _vm->showSlide("tbra", x, y); + + // TODO: load progress from savefile + int progress = 3; + _availItems = 4 + progress; + + // TODO: keep track of and destroy menu item frames/surfaces + int i; + for (i = 0; i < _availItems; i++) { + _lines[i] = new GfxObj(0, renderMenuItem(_menuStrings[i]), "MenuItem"); + uint id = _vm->_gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF); + _vm->_gfx->setItemFrame(id, 0); + } + _selection = -1; + _vm->setArrowCursor(); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); } - return options[selectedItem]; +}; + +const char *MainMenuInputState_BR::_menuStrings[NUM_MENULINES] = { + "SEE INTRO", + "NEW GAME", + "SAVED GAME", + "EXIT TO DOS", + "PART 2", + "PART 3", + "PART 4" +}; + +const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_options[NUM_MENULINES] = { + kMenuPart0, + kMenuPart1, + kMenuLoadGame, + kMenuQuit, + kMenuPart2, + kMenuPart3, + kMenuPart4 +}; + + + + + + + +void Parallaction_br::startGui() { + _menuHelper = new MenuInputHelper; + new SplashInputState0_BR(this, _menuHelper); + new SplashInputState1_BR(this, _menuHelper); + new MainMenuInputState_BR(this, _menuHelper); + + _menuHelper->setState("intro0"); + _input->_inputMode = Input::kInputModeMenu; + + do { + _input->readInput(); + if (!_menuHelper->run()) break; + _gfx->beginFrame(); + _gfx->updateScreen(); + } while (true); + + delete _menuHelper; + _menuHelper = 0; + + _input->_inputMode = Input::kInputModeGame; } + + } // namespace Parallaction diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp index 1d4d44fa46..815c27bd1c 100644 --- a/engines/parallaction/gui_ns.cpp +++ b/engines/parallaction/gui_ns.cpp @@ -24,7 +24,9 @@ */ #include "common/system.h" +#include "common/hashmap.h" +#include "parallaction/gui.h" #include "parallaction/input.h" #include "parallaction/parallaction.h" #include "parallaction/sound.h" @@ -32,311 +34,567 @@ namespace Parallaction { -const char *introMsg1[] = { - "INSERISCI IL CODICE", - "ENTREZ CODE", - "ENTER CODE", - "GIB DEN KODE EIN" -}; +class SplashInputState_NS : public MenuInputState { +protected: + Common::String _slideName; + uint32 _timeOut; + Common::String _nextState; + uint32 _startTime; -const char *introMsg2[] = { - "CODICE ERRATO", - "CODE ERRONE", - "WRONG CODE", - "GIB DEN KODE EIN" -}; + Parallaction_ns *_vm; -const char *introMsg3[] = { - "PRESS LEFT MOUSE BUTTON", - "TO SEE INTRO", - "PRESS RIGHT MOUSE BUTTON", - "TO START" -}; +public: + SplashInputState_NS(Parallaction_ns *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) { + } -const char *newGameMsg[] = { - "NUOVO GIOCO", - "NEUF JEU", - "NEW GAME", - "NEUES SPIEL" + virtual MenuInputState* run() { + uint32 curTime = g_system->getMillis(); + if (curTime - _startTime > _timeOut) { + _vm->freeBackground(); + return _helper->getState(_nextState); + } + return this; + } + + virtual void enter() { + _vm->_input->setMouseState(MOUSE_DISABLED); + _vm->showSlide(_slideName.c_str()); + _startTime = g_system->getMillis(); + } }; -const char *loadGameMsg[] = { - "GIOCO SALVATO", - "JEU SAUVE'", - "SAVED GAME", - "SPIEL GESPEICHERT" +class SplashInputState0_NS : public SplashInputState_NS { + +public: + SplashInputState0_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro0", helper) { + _slideName = "intro"; + _timeOut = 2000; + _nextState = "intro1"; + } }; +class SplashInputState1_NS : public SplashInputState_NS { -#define BLOCK_WIDTH 16 -#define BLOCK_HEIGHT 24 +public: + SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) { + _slideName = "minintro"; + _timeOut = 2000; + _nextState = "chooselanguage"; + } +}; -#define BLOCK_X 112 -#define BLOCK_Y 130 -#define BLOCK_SELECTION_X (BLOCK_X-1) -#define BLOCK_SELECTION_Y (BLOCK_Y-1) +class ChooseLanguageInputState_NS : public MenuInputState { + #define BLOCK_WIDTH 16 + #define BLOCK_HEIGHT 24 -#define BLOCK_X_OFFSET (BLOCK_WIDTH+1) -#define BLOCK_Y_OFFSET 9 + #define BLOCK_X 112 + #define BLOCK_Y 130 -// destination slots for code blocks -// -#define SLOT_X 61 -#define SLOT_Y 64 -#define SLOT_WIDTH (BLOCK_WIDTH+2) + #define BLOCK_SELECTION_X (BLOCK_X-1) + #define BLOCK_SELECTION_Y (BLOCK_Y-1) -#define PASSWORD_LEN 6 + #define BLOCK_X_OFFSET (BLOCK_WIDTH+1) + #define BLOCK_Y_OFFSET 9 -#define CHAR_DINO 0 -#define CHAR_DONNA 1 -#define CHAR_DOUGH 2 + // destination slots for code blocks + // + #define SLOT_X 61 + #define SLOT_Y 64 + #define SLOT_WIDTH (BLOCK_WIDTH+2) -static const uint16 _amigaKeys[][PASSWORD_LEN] = { - { 5, 3, 6, 2, 2, 7 }, // dino - { 0, 3, 6, 2, 2, 6 }, // donna - { 1, 3 ,7, 2, 4, 6 } // dough -}; + int _language; + bool _allowChoice; + Common::String _nextState; -static const uint16 _pcKeys[][PASSWORD_LEN] = { - { 5, 3, 6, 1, 4, 7 }, // dino - { 0, 2, 8, 5, 5, 1 }, // donna - { 1, 7 ,7, 2, 2, 6 } // dough -}; + static const Common::Rect _dosLanguageSelectBlocks[4]; + static const Common::Rect _amigaLanguageSelectBlocks[4]; + const Common::Rect *_blocks; -static const char *_charStartLocation[] = { - "test.dino", - "test.donna", - "test.dough" -}; + Parallaction_ns *_vm; -enum { - NEW_GAME, - LOAD_GAME -}; +public: + ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) { + _allowChoice = false; + _nextState = "selectgame"; -enum { - START_DEMO, - START_INTRO, - GAME_LOADED, - SELECT_CHARACTER -}; + if (_vm->getPlatform() == Common::kPlatformAmiga) { + if (!(_vm->getFeatures() & GF_LANG_MULT)) { + if (_vm->getFeatures() & GF_DEMO) { + _language = 1; // Amiga Demo supports English + _nextState = "startdemo"; + return; + } else { + _language = 0; // The only other non multi-lingual version just supports Italian + return; + } + } -void Parallaction_ns::guiStart() { + _blocks = _amigaLanguageSelectBlocks; + } else { + _blocks = _dosLanguageSelectBlocks; + } - _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); + _language = -1; + _allowChoice = true; + } - guiSplash(); + virtual MenuInputState* run() { + if (!_allowChoice) { + _vm->setInternLanguage(_language); + return _helper->getState(_nextState); + } - _language = guiChooseLanguage(); - _disk->setLanguage(_language); + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } - int event; + Common::Point p; + _vm->_input->getCursorPos(p); - if (getFeatures() & GF_DEMO) { - event = START_DEMO; - } else { - if (guiSelectGame() == NEW_GAME) { - event = guiNewGame(); - } else { - event = loadGame() ? GAME_LOADED : START_INTRO; + for (uint16 i = 0; i < 4; i++) { + if (_blocks[i].contains(p)) { + _vm->setInternLanguage(i); + _vm->beep(); + _vm->_gfx->freeLabels(); + return _helper->getState(_nextState); + } } + + return this; } - switch (event) { - case START_DEMO: - strcpy(_location._name, "fognedemo.dough"); - break; + virtual void enter() { + if (!_allowChoice) { + return; + } - case START_INTRO: - strcpy(_location._name, "fogne.dough"); - break; + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); - case GAME_LOADED: - // nothing to do here - return; + // user can choose language in this version + _vm->showSlide("lingua"); - case SELECT_CHARACTER: - selectStartLocation(); - break; + uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1); + _vm->_gfx->showLabel(id, 60, 30); + _vm->setArrowCursor(); } +}; - return; -} +const Common::Rect ChooseLanguageInputState_NS::_dosLanguageSelectBlocks[4] = { + Common::Rect( 80, 110, 128, 180 ), // Italian + Common::Rect( 129, 85, 177, 155 ), // French + Common::Rect( 178, 60, 226, 130 ), // English + Common::Rect( 227, 35, 275, 105 ) // German +}; -void Parallaction_ns::selectStartLocation() { - int character = guiSelectCharacter(); - if (character == -1) - error("invalid character selected from menu screen"); +const Common::Rect ChooseLanguageInputState_NS::_amigaLanguageSelectBlocks[4] = { + Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version + Common::Rect( 129, 85, 177, 155 ), // French + Common::Rect( 178, 60, 226, 130 ), // English + Common::Rect( 227, 35, 275, 105 ) // German +}; - scheduleLocationSwitch(_charStartLocation[character]); -} +class SelectGameInputState_NS : public MenuInputState { + int _choice, _oldChoice; + Common::String _nextState[2]; -void Parallaction_ns::guiSplash() { + uint _labels[2]; - showSlide("intro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); + Parallaction_ns *_vm; - showSlide("minintro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); -} + static const char *newGameMsg[4]; + static const char *loadGameMsg[4]; -int Parallaction_ns::guiNewGame() { +public: + SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) { + _choice = 0; + _oldChoice = -1; - const char **v14 = introMsg3; + _nextState[0] = "newgame"; + _nextState[1] = "loadgame"; + } - _disk->selectArchive("disk1"); - setBackground("test", NULL, NULL); + virtual MenuInputState *run() { + int event = _vm->_input->getLastButtonEvent(); - _gfx->updateScreen(); + if (event == kMouseLeftUp) { + _vm->_gfx->freeLabels(); + return _helper->getState(_nextState[_choice]); + } - uint id[4]; - id[0] = _gfx->createLabel(_menuFont, v14[0], 1); - id[1] = _gfx->createLabel(_menuFont, v14[1], 1); - id[2] = _gfx->createLabel(_menuFont, v14[2], 1); - id[3] = _gfx->createLabel(_menuFont, v14[3], 1); - _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50); - _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70); - _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100); - _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120); + Common::Point p; + _vm->_input->getCursorPos(p); + _choice = (p.x > 160) ? 1 : 0; - _input->showCursor(false); + if (_choice != _oldChoice) { + if (_oldChoice != -1) + _vm->_gfx->hideLabel(_labels[_oldChoice]); - _gfx->updateScreen(); + if (_choice != -1) + _vm->_gfx->showLabel(_labels[_choice], 60, 30); - _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp); - uint32 event = _input->getLastButtonEvent(); + _oldChoice = _choice; + } - _input->showCursor(true); + return this; + } - _gfx->freeLabels(); + virtual void enter() { + _vm->showSlide("restore"); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); - if (event != kMouseRightUp) { - return START_INTRO; + _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1); + _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1); } - return SELECT_CHARACTER; -} +}; -static const Common::Rect _dosLanguageSelectBlocks[4] = { - Common::Rect( 80, 110, 128, 180 ), // Italian - Common::Rect( 129, 85, 177, 155 ), // French - Common::Rect( 178, 60, 226, 130 ), // English - Common::Rect( 227, 35, 275, 105 ) // German +const char *SelectGameInputState_NS::newGameMsg[4] = { + "NUOVO GIOCO", + "NEUF JEU", + "NEW GAME", + "NEUES SPIEL" }; -static const Common::Rect _amigaLanguageSelectBlocks[4] = { - Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version - Common::Rect( 129, 85, 177, 155 ), // French - Common::Rect( 178, 60, 226, 130 ), // English - Common::Rect( 227, 35, 275, 105 ) // German +const char *SelectGameInputState_NS::loadGameMsg[4] = { + "GIOCO SALVATO", + "JEU SAUVE'", + "SAVED GAME", + "SPIEL GESPEICHERT" }; -uint16 Parallaction_ns::guiChooseLanguage() { - const Common::Rect *blocks; +class LoadGameInputState_NS : public MenuInputState { + bool _result; + Parallaction_ns *_vm; + +public: + LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { } + + virtual MenuInputState* run() { + if (!_result) { + _vm->scheduleLocationSwitch("fogne.dough"); + } + return 0; + } + + virtual void enter() { + _result = _vm->loadGame(); + } +}; + + - if (getPlatform() == Common::kPlatformAmiga) { - if (!(getFeatures() & GF_LANG_MULT)) { - if (getFeatures() & GF_DEMO) { - return 1; // Amiga Demo supports English - } else { - return 0; // The only other non multi-lingual version just supports Italian +class NewGameInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + + static const char *introMsg3[4]; + +public: + NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) { + } + + virtual MenuInputState* run() { + int event = _vm->_input->getLastButtonEvent(); + + if (event == kMouseLeftUp || event == kMouseRightUp) { + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + _vm->_gfx->freeLabels(); + + if (event == kMouseLeftUp) { + _vm->scheduleLocationSwitch("fogne.dough"); + return 0; } + + return _helper->getState("selectcharacter"); } - blocks = _amigaLanguageSelectBlocks; - } else { - blocks = _dosLanguageSelectBlocks; + return this; + } + + virtual void enter() { + _vm->_disk->selectArchive("disk1"); + _vm->setBackground("test", NULL, NULL); + _vm->_input->setMouseState(MOUSE_ENABLED_HIDE); + + uint id[4]; + id[0] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[0], 1); + id[1] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[1], 1); + id[2] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[2], 1); + id[3] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[3], 1); + _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50); + _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70); + _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100); + _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120); + } +}; + +const char *NewGameInputState_NS::introMsg3[4] = { + "PRESS LEFT MOUSE BUTTON", + "TO SEE INTRO", + "PRESS RIGHT MOUSE BUTTON", + "TO START" +}; + + + +class StartDemoInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + +public: + StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) { } - // user can choose language in dos version - showSlide("lingua"); + virtual MenuInputState* run() { + _vm->scheduleLocationSwitch("fognedemo.dough"); + return 0; + } - uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1); - _gfx->showLabel(id, 60, 30); + virtual void enter() { + _vm->_input->setMouseState(MOUSE_DISABLED); + } +}; - setArrowCursor(); +class SelectCharacterInputState_NS : public MenuInputState { - Common::Point p; + #define PASSWORD_LEN 6 - int selection = -1; - while (selection == -1) { - _input->waitUntilLeftClick(); - _input->getCursorPos(p); - for (uint16 i = 0; i < 4; i++) { - if (blocks[i].contains(p)) { + #define CHAR_DINO 0 + #define CHAR_DONNA 1 + #define CHAR_DOUGH 2 + + static const Common::Rect codeSelectBlocks[9]; + static const Common::Rect codeTrueBlocks[9]; + + Parallaction_ns *_vm; + + int guiGetSelectedBlock(const Common::Point &p) { + + int selection = -1; + + for (uint16 i = 0; i < 9; i++) { + if (codeSelectBlocks[i].contains(p)) { selection = i; break; } } + + if ((selection != -1) && (_vm->getPlatform() == Common::kPlatformAmiga)) { + _vm->_gfx->invertBackground(codeTrueBlocks[selection]); + _vm->_gfx->updateScreen(); + _vm->beep(); + g_system->delayMillis(100); + _vm->_gfx->invertBackground(codeTrueBlocks[selection]); + _vm->_gfx->updateScreen(); + } + + return selection; } - beep(); + byte _points[3]; + bool _fail; + const uint16 (*_keys)[PASSWORD_LEN]; + Graphics::Surface _block; + Graphics::Surface _emptySlots; - _gfx->freeLabels(); + uint _labels[2]; + uint _len; + uint32 _startTime; - return selection; -} + enum { + CHOICE, + FAIL, + SUCCESS, + DELAY + }; + uint _state; + static const char *introMsg1[4]; + static const char *introMsg2[4]; -uint16 Parallaction_ns::guiSelectGame() { -// printf("selectGame()\n"); + static const uint16 _amigaKeys[3][PASSWORD_LEN]; + static const uint16 _pcKeys[3][PASSWORD_LEN]; + static const char *_charStartLocation[3]; - showSlide("restore"); - uint16 _si = 0; - uint16 _di = 3; +public: + SelectCharacterInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectcharacter", helper), _vm(vm) { + _keys = (_vm->getPlatform() == Common::kPlatformAmiga && (_vm->getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys; + _block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1); + } - uint id0, id1; - id0 = _gfx->createLabel(_introFont, loadGameMsg[_language], 1); - id1 = _gfx->createLabel(_introFont, newGameMsg[_language], 1); + ~SelectCharacterInputState_NS() { + _block.free(); + _emptySlots.free(); + } - Common::Point p; + void cleanup() { + _points[0] = _points[1] = _points[2] = 0; + _vm->_gfx->hideLabel(_labels[1]); + _vm->_gfx->showLabel(_labels[0], 60, 30); + _fail = false; + _len = 0; + } - _input->readInput(); - uint32 event = _input->getLastButtonEvent(); + void delay() { + if (g_system->getMillis() - _startTime < 2000) { + return; + } + cleanup(); + _state = CHOICE; + } - while (event != kMouseLeftUp) { + void choice() { + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return; + } - _input->readInput(); - _input->getCursorPos(p); - event = _input->getLastButtonEvent(); + Common::Point p; + _vm->_input->getCursorPos(p); + int _si = guiGetSelectedBlock(p); - _si = (p.x > 160) ? 1 : 0; + if (_si != -1) { + _vm->_gfx->grabBackground(codeTrueBlocks[_si], _block); + _vm->_gfx->patchBackground(_block, _len * SLOT_WIDTH + SLOT_X, SLOT_Y, false); - if (_si != _di) { - if (_si != 0) { - // load a game - _gfx->hideLabel(id1); - _gfx->showLabel(id0, 60, 30); - } else { - // new game - _gfx->hideLabel(id0); - _gfx->showLabel(id1, 60, 30); + if (_keys[0][_len] != _si && _keys[1][_len] != _si && _keys[2][_len] != _si) { + _fail = true; } - _di = _si; + + // build user preference + _points[0] += (_keys[0][_len] == _si); + _points[1] += (_keys[1][_len] == _si); + _points[2] += (_keys[2][_len] == _si); + + _len++; + } + + if (_len == PASSWORD_LEN) { + _state = _fail ? FAIL : SUCCESS; } + } - _gfx->updateScreen(); - g_system->delayMillis(30); + void fail() { + _vm->_gfx->patchBackground(_emptySlots, SLOT_X, SLOT_Y, false); + _vm->_gfx->hideLabel(_labels[0]); + _vm->_gfx->showLabel(_labels[1], 60, 30); + _startTime = g_system->getMillis(); + _state = DELAY; } - _gfx->freeLabels(); + void success() { + _vm->_gfx->freeLabels(); + _vm->_gfx->setBlackPalette(); + _emptySlots.free(); + + // actually select character + int character = -1; + if (_points[0] >= _points[1] && _points[0] >= _points[2]) { + character = CHAR_DINO; + } else + if (_points[1] >= _points[0] && _points[1] >= _points[2]) { + character = CHAR_DONNA; + } else + if (_points[2] >= _points[0] && _points[2] >= _points[1]) { + character = CHAR_DOUGH; + } else { + error("If you read this, either your CPU or transivity is broken (we believe the former)."); + } - return _si ? LOAD_GAME : NEW_GAME; -} + _vm->_inTestResult = false; + _vm->cleanupGame(); + _vm->scheduleLocationSwitch(_charStartLocation[character]); + } + + virtual MenuInputState* run() { + MenuInputState* nextState = this; + + switch (_state) { + case DELAY: + delay(); + break; + + case CHOICE: + choice(); + break; + + case FAIL: + fail(); + break; + + case SUCCESS: + success(); + nextState = 0; + break; + + default: + error("unknown state in SelectCharacterInputState"); + } + + return nextState; + } + + virtual void enter() { + _vm->_soundMan->stopMusic(); + _vm->_disk->selectArchive((_vm->getFeatures() & GF_DEMO) ? "disk0" : "disk1"); + _vm->showSlide("password"); -static const Common::Rect codeSelectBlocks[9] = { + _emptySlots.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1); + Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT); + _vm->_gfx->grabBackground(rect, _emptySlots); + + _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, introMsg1[_vm->getInternLanguage()], 1); + _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, introMsg2[_vm->getInternLanguage()], 1); + + cleanup(); + + _vm->setArrowCursor(); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + _state = CHOICE; + } +}; + +const char *SelectCharacterInputState_NS::introMsg1[4] = { + "INSERISCI IL CODICE", + "ENTREZ CODE", + "ENTER CODE", + "GIB DEN KODE EIN" +}; + +const char *SelectCharacterInputState_NS::introMsg2[4] = { + "CODICE ERRATO", + "CODE ERRONE", + "WRONG CODE", + "GIB DEN KODE EIN" +}; + +const uint16 SelectCharacterInputState_NS::_amigaKeys[][PASSWORD_LEN] = { + { 5, 3, 6, 2, 2, 7 }, // dino + { 0, 3, 6, 2, 2, 6 }, // donna + { 1, 3 ,7, 2, 4, 6 } // dough +}; + +const uint16 SelectCharacterInputState_NS::_pcKeys[][PASSWORD_LEN] = { + { 5, 3, 6, 1, 4, 7 }, // dino + { 0, 2, 8, 5, 5, 1 }, // donna + { 1, 7 ,7, 2, 2, 6 } // dough +}; + +const char *SelectCharacterInputState_NS::_charStartLocation[] = { + "test.dino", + "test.donna", + "test.dough" +}; + + +const Common::Rect SelectCharacterInputState_NS::codeSelectBlocks[9] = { Common::Rect( 111, 129, 127, 153 ), // na Common::Rect( 128, 120, 144, 144 ), // wa Common::Rect( 145, 111, 161, 135 ), // ra @@ -348,7 +606,7 @@ static const Common::Rect codeSelectBlocks[9] = { Common::Rect( 247, 57, 263, 81 ) // ka }; -static const Common::Rect codeTrueBlocks[9] = { +const Common::Rect SelectCharacterInputState_NS::codeTrueBlocks[9] = { Common::Rect( 112, 130, 128, 154 ), Common::Rect( 129, 121, 145, 145 ), Common::Rect( 146, 112, 162, 136 ), @@ -361,146 +619,219 @@ static const Common::Rect codeTrueBlocks[9] = { }; -int Parallaction_ns::guiGetSelectedBlock(const Common::Point &p) { +class ShowCreditsInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + int _current; + uint32 _startTime; - int selection = -1; + struct Credit { + const char *_role; + const char *_name; + }; - for (uint16 i = 0; i < 9; i++) { - if (codeSelectBlocks[i].contains(p)) { - selection = i; - break; - } + static const Credit _credits[6]; + +public: + ShowCreditsInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("showcredits", helper), _vm(vm) { } - if ((selection != -1) && (getPlatform() == Common::kPlatformAmiga)) { - _gfx->invertBackground(codeTrueBlocks[selection]); - _gfx->updateScreen(); - beep(); - g_system->delayMillis(100); - _gfx->invertBackground(codeTrueBlocks[selection]); - _gfx->updateScreen(); + void drawCurrentLabel() { + uint id[2]; + id[0] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._role, 1); + id[1] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._name, 1); + _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80); + _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); } - return selection; -} + virtual MenuInputState* run() { + if (_current == -1) { + _startTime = g_system->getMillis(); + _current = 0; + drawCurrentLabel(); + return this; + } -// -// character selection and protection -// -int Parallaction_ns::guiSelectCharacter() { - debugC(1, kDebugMenu, "Parallaction_ns::guiselectCharacter()"); + int event = _vm->_input->getLastButtonEvent(); + uint32 curTime = g_system->getMillis(); + if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) { + _current++; + _startTime = curTime; + _vm->_gfx->freeLabels(); - setArrowCursor(); - _soundMan->stopMusic(); + if (_current == 6) { + return _helper->getState("endintro"); + } - _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); + drawCurrentLabel(); + } - showSlide("password"); + return this; + } + virtual void enter() { + _current = -1; + _vm->_input->setMouseState(MOUSE_DISABLED); + } +}; - const uint16 (*keys)[PASSWORD_LEN] = (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys; - uint16 _di = 0; - byte points[3] = { 0, 0, 0 }; +const ShowCreditsInputState_NS::Credit ShowCreditsInputState_NS::_credits[6] = { + {"Music and Sound Effects", "MARCO CAPRELLI"}, + {"PC Version", "RICCARDO BALLARINO"}, + {"Project Manager", "LOVRANO CANEPA"}, + {"Production", "BRUNO BOZ"}, + {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"}, + {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"} +}; - bool fail; +class EndIntroInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + bool _isDemo; - uint id[2]; - id[0] = _gfx->createLabel(_introFont, introMsg1[_language], 1); - id[1] = _gfx->createLabel(_introFont, introMsg2[_language], 1); +public: + EndIntroInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endintro", helper), _vm(vm) { + _isDemo = (_vm->getFeatures() & GF_DEMO) != 0; + } - Graphics::Surface v14; - v14.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1); - Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT); - _gfx->grabBackground(rect, v14); + virtual MenuInputState* run() { - Graphics::Surface block; - block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1); + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } - Common::Point p; + if (_isDemo) { + _engineFlags |= kEngineQuit; + return 0; + } - while (true) { + _vm->_gfx->freeLabels(); + return _helper->getState("selectcharacter"); + } - points[0] = 0; - points[1] = 0; - points[2] = 0; - fail = false; + virtual void enter() { + _vm->_soundMan->stopMusic(); + _vm->_input->setMouseState(MOUSE_DISABLED); - _gfx->hideLabel(id[1]); - _gfx->showLabel(id[0], 60, 30); + if (!_isDemo) { + int label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1); + _vm->_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 80); + } + } +}; - _di = 0; - while (_di < PASSWORD_LEN) { - _input->waitUntilLeftClick(); - _input->getCursorPos(p); +class EndPartInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + bool _allPartsComplete; - int _si = guiGetSelectedBlock(p); + // part completion messages + static const char *endMsg0[4]; + static const char *endMsg1[4]; + static const char *endMsg2[4]; + static const char *endMsg3[4]; + // game completion messages + static const char *endMsg4[4]; + static const char *endMsg5[4]; + static const char *endMsg6[4]; + static const char *endMsg7[4]; - if (_si != -1) { - _gfx->grabBackground(codeTrueBlocks[_si], block); - _gfx->patchBackground(block, _di * SLOT_WIDTH + SLOT_X, SLOT_Y, false); - if (keys[0][_di] == _si) { - points[0]++; - } else - if (keys[1][_di] == _si) { - points[1]++; - } else - if (keys[2][_di] == _si) { - points[2]++; - } else { - fail = true; - } +public: + EndPartInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endpart", helper), _vm(vm) { + } - // build user preference - points[0] += (keys[0][_di] == _si); - points[1] += (keys[1][_di] == _si); - points[2] += (keys[2][_di] == _si); + virtual MenuInputState* run() { + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } - _di++; - } + _vm->_gfx->freeLabels(); + if (_allPartsComplete) { + _vm->scheduleLocationSwitch("estgrotta.drki"); + return 0; } - if (!fail) { - break; + return _helper->getState("selectcharacter"); + } + + virtual void enter() { + _allPartsComplete = _vm->allPartsComplete(); + _vm->_input->setMouseState(MOUSE_DISABLED); + + uint id[4]; + if (_allPartsComplete) { + id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg4[_language], 1); + id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg5[_language], 1); + id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg6[_language], 1); + id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg7[_language], 1); + } else { + id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg0[_language], 1); + id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg1[_language], 1); + id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg2[_language], 1); + id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg3[_language], 1); } - _gfx->patchBackground(v14, SLOT_X, SLOT_Y, false); + _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70); + _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100); + _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130); + _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160); + } +}; - _gfx->hideLabel(id[0]); - _gfx->showLabel(id[1], 60, 30); +// part completion messages +const char *EndPartInputState_NS::endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; +const char *EndPartInputState_NS::endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"}; +const char *EndPartInputState_NS::endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"}; +const char *EndPartInputState_NS::endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"}; +// game completion messages +const char *EndPartInputState_NS::endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"}; +const char *EndPartInputState_NS::endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"}; +const char *EndPartInputState_NS::endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"}; +const char *EndPartInputState_NS::endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"}; + +void Parallaction_ns::startGui() { + _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); - _gfx->updateScreen(); + _menuHelper = new MenuInputHelper; + assert(_menuHelper); - g_system->delayMillis(2000); - } + new SelectGameInputState_NS(this, _menuHelper); + new LoadGameInputState_NS(this, _menuHelper); + new NewGameInputState_NS(this, _menuHelper); + new StartDemoInputState_NS(this, _menuHelper); + new SelectCharacterInputState_NS(this, _menuHelper); + new ChooseLanguageInputState_NS(this, _menuHelper); + new SplashInputState1_NS(this, _menuHelper); + new SplashInputState0_NS(this, _menuHelper); + _menuHelper->setState("intro0"); - _gfx->freeLabels(); + _input->_inputMode = Input::kInputModeMenu; +} - _gfx->setBlackPalette(); - _gfx->updateScreen(); +void Parallaction_ns::startCreditSequence() { + _menuHelper = new MenuInputHelper; + assert(_menuHelper); - v14.free(); + new ShowCreditsInputState_NS(this, _menuHelper); + new EndIntroInputState_NS(this, _menuHelper); + new SelectCharacterInputState_NS(this, _menuHelper); + _menuHelper->setState("showcredits"); + _input->_inputMode = Input::kInputModeMenu; +} - // actually select character +void Parallaction_ns::startEndPartSequence() { + _menuHelper = new MenuInputHelper; + assert(_menuHelper); - int character = -1; - if (points[0] >= points[1] && points[0] >= points[2]) { - character = CHAR_DINO; - } else - if (points[1] >= points[0] && points[1] >= points[2]) { - character = CHAR_DONNA; - } else - if (points[2] >= points[0] && points[2] >= points[1]) { - character = CHAR_DOUGH; - } else { - error("If you read this, either your CPU or transivity is broken (we believe the former)."); - } + new EndPartInputState_NS(this, _menuHelper); + new SelectCharacterInputState_NS(this, _menuHelper); + _menuHelper->setState("endpart"); - return character; + _input->_inputMode = Input::kInputModeMenu; } diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp index 28d0ad888d..287618e803 100644 --- a/engines/parallaction/input.cpp +++ b/engines/parallaction/input.cpp @@ -36,23 +36,23 @@ namespace Parallaction { // loops which could possibly be merged into this one with some effort in changing // caller code, i.e. adding condition checks. // -uint16 Input::readInput() { +void Input::readInput() { Common::Event e; - uint16 KeyDown = 0; _mouseButtons = kMouseNone; + _hasKeyPressEvent = false; Common::EventManager *eventMan = _vm->_system->getEventManager(); while (eventMan->pollEvent(e)) { switch (e.type) { case Common::EVENT_KEYDOWN: + _hasKeyPressEvent = true; + _keyPressed = e.kbd; + if (e.kbd.flags == Common::KBD_CTRL && e.kbd.keycode == 'd') _vm->_debugger->attach(); - if (_vm->getFeatures() & GF_DEMO) break; - if (e.kbd.keycode == Common::KEYCODE_l) KeyDown = kEvLoadGame; - if (e.kbd.keycode == Common::KEYCODE_s) KeyDown = kEvSaveGame; break; case Common::EVENT_LBUTTONDOWN: @@ -80,11 +80,8 @@ uint16 Input::readInput() { break; case Common::EVENT_QUIT: - // TODO: don't quit() here, just have caller routines to check - // on kEngineQuit and exit gracefully to allow the engine to shut down _engineFlags |= kEngineQuit; - _vm->_system->quit(); - break; + return; default: break; @@ -96,10 +93,15 @@ uint16 Input::readInput() { if (_vm->_debugger->isAttached()) _vm->_debugger->onFrame(); - return KeyDown; + return; } +bool Input::getLastKeyDown(uint16 &ascii) { + ascii = _keyPressed.ascii; + return (_hasKeyPressEvent); +} + // FIXME: see comment for readInput() void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) { @@ -124,64 +126,36 @@ void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) { } -// FIXME: see comment for readInput() -void Input::waitUntilLeftClick() { - - do { - readInput(); - _vm->_gfx->updateScreen(); - _vm->_system->delayMillis(30); - } while (_mouseButtons != kMouseLeftUp); - - return; -} - void Input::updateGameInput() { - int16 keyDown = readInput(); - - debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)", - !_mouseHidden, - (_engineFlags & kEngineBlockInput) == 0, - (_engineFlags & kEngineWalking) == 0, - (_engineFlags & kEngineChangeLocation) == 0 - ); + readInput(); - if ((_mouseHidden) || - (_engineFlags & kEngineBlockInput) || + if (!isMouseEnabled() || (_engineFlags & kEngineWalking) || (_engineFlags & kEngineChangeLocation)) { + debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)", + isMouseEnabled(), + (_engineFlags & kEngineWalking) == 0, + (_engineFlags & kEngineChangeLocation) == 0 + ); + return; } - if (keyDown == kEvQuitGame) { - _inputData._event = kEvQuitGame; - } else - if (keyDown == kEvSaveGame) { - _inputData._event = kEvSaveGame; - } else - if (keyDown == kEvLoadGame) { - _inputData._event = kEvLoadGame; - } else { + if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { + if (_keyPressed.keycode == Common::KEYCODE_l) _inputData._event = kEvLoadGame; + if (_keyPressed.keycode == Common::KEYCODE_s) _inputData._event = kEvSaveGame; + } + + if (_inputData._event == kEvNone) { _inputData._mousePos = _mousePos; - _inputData._event = kEvNone; - if (!translateGameInput()) { - translateInventoryInput(); - } + translateGameInput(); } } -void Input::updateCommentInput() { - waitUntilLeftClick(); - - _vm->_gfx->hideDialogueStuff(); - _vm->_gfx->setHalfbriteMode(false); - - _inputMode = kInputModeGame; -} InputData* Input::updateInput() { @@ -189,84 +163,109 @@ InputData* Input::updateInput() { switch (_inputMode) { case kInputModeComment: - updateCommentInput(); + case kInputModeDialogue: + case kInputModeMenu: + readInput(); break; case kInputModeGame: updateGameInput(); break; + + case kInputModeInventory: + readInput(); + updateInventoryInput(); + break; } return &_inputData; } +void Input::trackMouse(ZonePtr z) { + if ((z != _hoverZone) && (_hoverZone)) { + stopHovering(); + return; + } + + if (!z) { + return; + } + + if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) { + _hoverZone = z; + _vm->_gfx->showFloatingLabel(_hoverZone->_label); + return; + } +} + +void Input::stopHovering() { + _hoverZone = nullZonePtr; + _vm->_gfx->hideFloatingLabel(); +} + +void Input::takeAction(ZonePtr z) { + stopHovering(); + _vm->pauseJobs(); + _vm->runZone(z); + _vm->resumeJobs(); +} + +void Input::walkTo(const Common::Point &dest) { + stopHovering(); + _vm->setArrowCursor(); + _vm->_char.scheduleWalk(dest.x, dest.y); +} + bool Input::translateGameInput() { - if ((_engineFlags & kEnginePauseJobs) || (_engineFlags & kEngineInventory)) { + if (_engineFlags & kEnginePauseJobs) { return false; } - if (_actionAfterWalk) { + if (_hasDelayedAction) { // if walking is over, then take programmed action - _inputData._event = kEvAction; - _actionAfterWalk = false; + takeAction(_delayedActionZone); + _hasDelayedAction = false; + _delayedActionZone = nullZonePtr; return true; } if (_mouseButtons == kMouseRightDown) { // right button down shows inventory - - if (_vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y) && (_activeItem._id != 0)) { - _activeItem._index = (_activeItem._id >> 16) & 0xFFFF; - _engineFlags |= kEngineDragging; - } - - _inputData._event = kEvOpenInventory; - _transCurrentHoverItem = -1; + enterInventoryMode(); return true; } // test if mouse is hovering on an interactive zone for the currently selected inventory item ZonePtr z = _vm->hitZone(_activeItem._id, _mousePos.x, _mousePos.y); + Common::Point dest(_mousePos); if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((_engineFlags & kEngineWalking) == 0)) && ((!z) || ((z->_type & 0xFFFF) != kZoneCommand))) { - _inputData._event = kEvWalk; + walkTo(dest); return true; } - if ((z != _hoverZone) && (_hoverZone)) { - _hoverZone = nullZonePtr; - _inputData._event = kEvExitZone; - return true; - } - - if (!z) { - _inputData._event = kEvNone; - return true; - } - - if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) { - _hoverZone = z; - _inputData._event = kEvEnterZone; - _inputData._label = z->_label; - return true; - } + trackMouse(z); + if (!z) { + return true; + } if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || ((z->_type & 0xFFFF) == kZoneCommand))) { _inputData._zone = z; if (z->_flags & kFlagsNoWalk) { // character doesn't need to walk to take specified action - _inputData._event = kEvAction; - + takeAction(z); } else { // action delayed: if Zone defined a moveto position the character is programmed to move there, // else it will move to the mouse position - _inputData._event = kEvWalk; - _actionAfterWalk = true; + _delayedActionZone = z; + _hasDelayedAction = true; if (z->_moveTo.y != 0) { - _inputData._mousePos = z->_moveTo; + dest = z->_moveTo; } + + walkTo(dest); } _vm->beep(); @@ -275,58 +274,103 @@ bool Input::translateGameInput() { } return true; - } -bool Input::translateInventoryInput() { - if ((_engineFlags & kEngineInventory) == 0) { - return false; +void Input::enterInventoryMode() { + bool hitCharacter = _vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y); + + if (hitCharacter) { + if (_activeItem._id != 0) { + _activeItem._index = (_activeItem._id >> 16) & 0xFFFF; + _engineFlags |= kEngineDragging; + } else { + _vm->setArrowCursor(); + } } - // in inventory - int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); + stopHovering(); + _vm->pauseJobs(); + _vm->openInventory(); - if (_mouseButtons == kMouseRightUp) { - // right up hides inventory + _transCurrentHoverItem = -1; - _inputData._event = kEvCloseInventory; - _inputData._inventoryIndex = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); - _vm->highlightInventoryItem(-1); // disable + _inputMode = kInputModeInventory; +} - if ((_engineFlags & kEngineDragging) == 0) { - return true; - } +void Input::exitInventoryMode() { + // right up hides inventory + + int pos = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); + _vm->highlightInventoryItem(-1); // disable + + if ((_engineFlags & kEngineDragging)) { _engineFlags &= ~kEngineDragging; - ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(_inputData._inventoryIndex)); + ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos)); if (z) { _vm->dropItem(z->u.merge->_obj1); _vm->dropItem(z->u.merge->_obj2); _vm->addInventoryItem(z->u.merge->_obj3); - _vm->runCommands(z->_commands); + _vm->_cmdExec->run(z->_commands); } - return true; } - if (_si == _transCurrentHoverItem) { - _inputData._event = kEvNone; + _vm->closeInventory(); + if (pos == -1) { + _vm->setArrowCursor(); + } else { + const InventoryItem *item = _vm->getInventoryItem(pos); + if (item->_index != 0) { + _activeItem._id = item->_id; + _vm->setInventoryCursor(item->_index); + } + } + _vm->resumeJobs(); + + _inputMode = kInputModeGame; +} + +bool Input::updateInventoryInput() { + if (_mouseButtons == kMouseRightUp) { + exitInventoryMode(); return true; } - _transCurrentHoverItem = _si; - _inputData._event = kEvHoverInventory; - _inputData._inventoryIndex = _si; + int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); + if (_si != _transCurrentHoverItem) { + _transCurrentHoverItem = _si; + _vm->highlightInventoryItem(_si); // enable + } + return true; } -void Input::showCursor(bool visible) { - _mouseHidden = !visible; - _vm->_system->showMouse(visible); +void Input::setMouseState(MouseTriState state) { + assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED); + _mouseState = state; + + switch (_mouseState) { + case MOUSE_ENABLED_HIDE: + case MOUSE_DISABLED: + _vm->_system->showMouse(false); + break; + + case MOUSE_ENABLED_SHOW: + _vm->_system->showMouse(true); + break; + } +} + +MouseTriState Input::getMouseState() { + return _mouseState; } +bool Input::isMouseEnabled() { + return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE); +} } // namespace Parallaction diff --git a/engines/parallaction/input.h b/engines/parallaction/input.h index 46dbb08c4e..c1e912db74 100644 --- a/engines/parallaction/input.h +++ b/engines/parallaction/input.h @@ -26,6 +26,8 @@ #ifndef PARALLACTION_INPUT_H #define PARALLACTION_INPUT_H +#include "common/keyboard.h" + #include "parallaction/objects.h" #include "parallaction/inventory.h" @@ -44,52 +46,68 @@ struct InputData { Common::Point _mousePos; int16 _inventoryIndex; ZonePtr _zone; - Label* _label; + uint _label; +}; + +enum MouseTriState { + MOUSE_ENABLED_SHOW, + MOUSE_ENABLED_HIDE, + MOUSE_DISABLED }; class Input { void updateGameInput(); - void updateCommentInput(); // input-only InputData _inputData; - bool _actionAfterWalk; // actived when the character needs to move before taking an action - // these two could/should be merged as they carry on the same duty in two member functions, - // respectively processInput and translateInput + + bool _hasKeyPressEvent; + Common::KeyState _keyPressed; + + bool _hasDelayedAction; // actived when the character needs to move before taking an action + ZonePtr _delayedActionZone; + int16 _transCurrentHoverItem; InputData *translateInput(); bool translateGameInput(); - bool translateInventoryInput(); + bool updateInventoryInput(); + void takeAction(ZonePtr z); + void walkTo(const Common::Point &dest); Parallaction *_vm; Common::Point _mousePos; uint16 _mouseButtons; - bool _mouseHidden; ZonePtr _hoverZone; + void enterInventoryMode(); + void exitInventoryMode(); + public: enum { kInputModeGame = 0, - kInputModeComment = 1 + kInputModeComment = 1, + kInputModeDialogue = 2, + kInputModeInventory = 3, + kInputModeMenu = 4 }; Input(Parallaction *vm) : _vm(vm) { _transCurrentHoverItem = 0; - _actionAfterWalk = false; // actived when the character needs to move before taking an action - _mouseHidden = false; + _hasDelayedAction = false; // actived when the character needs to move before taking an action + _mouseState = MOUSE_DISABLED; _activeItem._index = 0; _activeItem._id = 0; _mouseButtons = 0; + _delayedActionZone = nullZonePtr; } virtual ~Input() { } - void showCursor(bool visible); void getCursorPos(Common::Point& p) { p = _mousePos; } @@ -97,16 +115,20 @@ public: int _inputMode; InventoryItem _activeItem; - uint16 readInput(); + void readInput(); InputData* updateInput(); - void waitUntilLeftClick(); + void trackMouse(ZonePtr z); void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1); uint32 getLastButtonEvent() { return _mouseButtons; } + bool getLastKeyDown(uint16 &ascii); - void stopHovering() { - _hoverZone = nullZonePtr; - } + void stopHovering(); + + MouseTriState _mouseState; + void setMouseState(MouseTriState state); + MouseTriState getMouseState(); + bool isMouseEnabled(); }; } // namespace Parallaction diff --git a/engines/parallaction/inventory.cpp b/engines/parallaction/inventory.cpp index 58848196d7..7b92974205 100644 --- a/engines/parallaction/inventory.cpp +++ b/engines/parallaction/inventory.cpp @@ -30,23 +30,58 @@ namespace Parallaction { -// -// inventory is a grid made of (at most) 30 cells, 24x24 pixels each, -// arranged in 6 lines -// -// inventory items are stored in cnv files in a 32x24 grid -// but only 24x24 pixels are actually copied to graphic memory -// + +/* +#define INVENTORYITEM_PITCH 32 +#define INVENTORYITEM_WIDTH 24 +#define INVENTORYITEM_HEIGHT 24 #define INVENTORY_MAX_ITEMS 30 -#define INVENTORY_FIRST_ITEM 4 // first four entries are used up by verbs #define INVENTORY_ITEMS_PER_LINE 5 #define INVENTORY_LINES 6 #define INVENTORY_WIDTH (INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH) #define INVENTORY_HEIGHT (INVENTORY_LINES*INVENTORYITEM_HEIGHT) - +*/ + +InventoryItem _verbs_NS[] = { + { 1, kZoneDoor }, + { 3, kZoneExamine }, + { 2, kZoneGet }, + { 4, kZoneSpeak }, + { 0, 0 } +}; + +InventoryItem _verbs_BR[] = { + { 1, kZoneBox }, + { 2, kZoneGet }, + { 3, kZoneExamine }, + { 4, kZoneSpeak }, + { 0, 0 } +}; + +InventoryProperties _invProps_NS = { + 32, // INVENTORYITEM_PITCH + 24, // INVENTORYITEM_WIDTH + 24, // INVENTORYITEM_HEIGHT + 30, // INVENTORY_MAX_ITEMS + 5, // INVENTORY_ITEMS_PER_LINE + 6, // INVENTORY_LINES + 5 * 24, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH) + 6 * 24 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT) +}; + +InventoryProperties _invProps_BR = { + 51, // INVENTORYITEM_PITCH + 51, // INVENTORYITEM_WIDTH + 51, // INVENTORYITEM_HEIGHT + 48, // INVENTORY_MAX_ITEMS + 6, // INVENTORY_ITEMS_PER_LINE + 8, // INVENTORY_LINES + 6 * 51, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH) + 8 * 51 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT) +}; int16 Parallaction::getHoverInventoryItem(int16 x, int16 y) { return _inventoryRenderer->hitTest(Common::Point(x,y)); @@ -91,8 +126,19 @@ int16 Parallaction::getInventoryItemIndex(int16 pos) { } void Parallaction::initInventory() { - _inventory = new Inventory(INVENTORY_MAX_ITEMS); - _inventoryRenderer = new InventoryRenderer(this); + InventoryProperties *props; + InventoryItem *verbs; + + if (getGameType() == GType_Nippon) { + props = &_invProps_NS; + verbs = _verbs_NS; + } else { + props = &_invProps_BR; + verbs = _verbs_BR; + } + + _inventory = new Inventory(props, verbs); + _inventoryRenderer = new InventoryRenderer(this, props); _inventoryRenderer->bindInventory(_inventory); } @@ -119,8 +165,8 @@ void Parallaction::closeInventory() { -InventoryRenderer::InventoryRenderer(Parallaction *vm) : _vm(vm) { - _surf.create(INVENTORY_WIDTH, INVENTORY_HEIGHT, 1); +InventoryRenderer::InventoryRenderer(Parallaction *vm, InventoryProperties *props) : _vm(vm), _props(props) { + _surf.create(_props->_width, _props->_height, 1); } InventoryRenderer::~InventoryRenderer() { @@ -131,15 +177,13 @@ void InventoryRenderer::showInventory() { if (!_inv) error("InventoryRenderer not bound to inventory"); - _engineFlags |= kEngineInventory; - uint16 lines = getNumLines(); Common::Point p; _vm->_input->getCursorPos(p); - _pos.x = CLIP(p.x - (INVENTORY_WIDTH / 2), 0, (int)(_vm->_screenWidth - INVENTORY_WIDTH)); - _pos.y = CLIP(p.y - 2 - (lines * INVENTORYITEM_HEIGHT), 0, (int)(_vm->_screenHeight - lines * INVENTORYITEM_HEIGHT)); + _pos.x = CLIP((int)(p.x - (_props->_width / 2)), 0, (int)(_vm->_screenWidth - _props->_width)); + _pos.y = CLIP((int)(p.y - 2 - (lines * _props->_itemHeight)), 0, (int)(_vm->_screenHeight - lines * _props->_itemHeight)); refresh(); } @@ -147,13 +191,11 @@ void InventoryRenderer::showInventory() { void InventoryRenderer::hideInventory() { if (!_inv) error("InventoryRenderer not bound to inventory"); - - _engineFlags &= ~kEngineInventory; } void InventoryRenderer::getRect(Common::Rect& r) const { - r.setWidth(INVENTORY_WIDTH); - r.setHeight(INVENTORYITEM_HEIGHT * getNumLines()); + r.setWidth(_props->_width); + r.setHeight(_props->_itemHeight * getNumLines()); r.moveTo(_pos); } @@ -163,35 +205,36 @@ ItemPosition InventoryRenderer::hitTest(const Common::Point &p) const { if (!r.contains(p)) return -1; - return ((p.x - _pos.x) / INVENTORYITEM_WIDTH) + (INVENTORY_ITEMS_PER_LINE * ((p.y - _pos.y) / INVENTORYITEM_HEIGHT)); + return ((p.x - _pos.x) / _props->_itemWidth) + (_props->_itemsPerLine * ((p.y - _pos.y) / _props->_itemHeight)); } - void InventoryRenderer::drawItem(ItemPosition pos, ItemName name) { - Common::Rect r; getItemRect(pos, r); + byte* d = (byte*)_surf.getBasePtr(r.left, r.top); + drawItem(name, d, _surf.pitch); +} - // FIXME: this will end up in a general blit function - +void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) { byte* s = _vm->_char._objs->getData(name); - byte* d = (byte*)_surf.getBasePtr(r.left, r.top); - for (uint32 i = 0; i < INVENTORYITEM_HEIGHT; i++) { - memcpy(d, s, INVENTORYITEM_WIDTH); + byte* d = buffer; + for (uint i = 0; i < _props->_itemHeight; i++) { + memcpy(d, s, _props->_itemWidth); - d += INVENTORY_WIDTH; - s += INVENTORYITEM_PITCH; + s += _props->_itemPitch; + d += pitch; } } + int16 InventoryRenderer::getNumLines() const { int16 num = _inv->getNumItems(); - return (num / INVENTORY_ITEMS_PER_LINE) + ((num % INVENTORY_ITEMS_PER_LINE) > 0 ? 1 : 0); + return (num / _props->_itemsPerLine) + ((num % _props->_itemsPerLine) > 0 ? 1 : 0); } void InventoryRenderer::refresh() { - for (uint16 i = 0; i < INVENTORY_MAX_ITEMS; i++) { + for (uint16 i = 0; i < _props->_maxItems; i++) { ItemName name = _inv->getItemName(i); drawItem(i, name); } @@ -212,25 +255,24 @@ void InventoryRenderer::highlightItem(ItemPosition pos, byte color) { void InventoryRenderer::getItemRect(ItemPosition pos, Common::Rect &r) { - r.setHeight(INVENTORYITEM_HEIGHT); - r.setWidth(INVENTORYITEM_WIDTH); + r.setHeight(_props->_itemHeight); + r.setWidth(_props->_itemWidth); - uint16 line = pos / INVENTORY_ITEMS_PER_LINE; - uint16 col = pos % INVENTORY_ITEMS_PER_LINE; + uint16 line = pos / _props->_itemsPerLine; + uint16 col = pos % _props->_itemsPerLine; - r.moveTo(col * INVENTORYITEM_WIDTH, line * INVENTORYITEM_HEIGHT); + r.moveTo(col * _props->_itemWidth, line * _props->_itemHeight); } +Inventory::Inventory(InventoryProperties *props, InventoryItem *verbs) : _numItems(0), _props(props) { + _items = (InventoryItem*)calloc(_props->_maxItems, sizeof(InventoryItem)); - -Inventory::Inventory(uint16 maxItems) : _maxItems(maxItems), _numItems(0) { - _items = (InventoryItem*)calloc(_maxItems, sizeof(InventoryItem)); - - addItem(1, kZoneDoor); - addItem(3, kZoneExamine); - addItem(2, kZoneGet); - addItem(4, kZoneSpeak); + int i = 0; + for ( ; verbs[i]._id; i++) { + addItem(verbs[i]._id, verbs[i]._index); + } + _numVerbs = i; } @@ -241,7 +283,7 @@ Inventory::~Inventory() { ItemPosition Inventory::addItem(ItemName name, uint32 value) { debugC(1, kDebugInventory, "addItem(%i, %i)", name, value); - if (_numItems == INVENTORY_MAX_ITEMS) { + if (_numItems == _props->_maxItems) { debugC(3, kDebugInventory, "addItem: inventory is full"); return -1; } @@ -300,9 +342,9 @@ void Inventory::removeItem(ItemName name) { void Inventory::clear(bool keepVerbs) { debugC(1, kDebugInventory, "clearInventory()"); - uint first = (keepVerbs ? INVENTORY_FIRST_ITEM : 0); + uint first = (keepVerbs ? _numVerbs : 0); - for (uint16 slot = first; slot < _maxItems; slot++) { + for (uint16 slot = first; slot < _numVerbs; slot++) { _items[slot]._id = 0; _items[slot]._index = 0; } @@ -312,7 +354,7 @@ void Inventory::clear(bool keepVerbs) { ItemName Inventory::getItemName(ItemPosition pos) const { - return (pos >= 0 && pos < INVENTORY_MAX_ITEMS) ? _items[pos]._index : 0; + return (pos >= 0 && pos < _props->_maxItems) ? _items[pos]._index : 0; } const InventoryItem* Inventory::getItem(ItemPosition pos) const { diff --git a/engines/parallaction/inventory.h b/engines/parallaction/inventory.h index 8c32c09219..f041627810 100644 --- a/engines/parallaction/inventory.h +++ b/engines/parallaction/inventory.h @@ -38,9 +38,19 @@ struct InventoryItem { uint16 _index; // index to frame in objs file }; -#define INVENTORYITEM_PITCH 32 -#define INVENTORYITEM_WIDTH 24 -#define INVENTORYITEM_HEIGHT 24 +struct InventoryProperties { + uint _itemPitch; + uint _itemWidth; + uint _itemHeight; + + int _maxItems; + + int _itemsPerLine; + int _maxLines; + + int _width; + int _height; +}; #define MAKE_INVENTORY_ID(x) (((x) & 0xFFFF) << 16) @@ -50,12 +60,14 @@ typedef uint16 ItemName; class Inventory { protected: + uint16 _numVerbs; + InventoryItem *_items; - uint16 _maxItems; uint16 _numItems; + InventoryProperties *_props; public: - Inventory(uint16 maxItems); + Inventory(InventoryProperties *props, InventoryItem *verbs); virtual ~Inventory(); ItemPosition addItem(ItemName name, uint32 value); @@ -75,6 +87,8 @@ public: class InventoryRenderer { Parallaction *_vm; + InventoryProperties *_props; + Inventory *_inv; Common::Point _pos; @@ -87,7 +101,7 @@ protected: void refresh(); public: - InventoryRenderer(Parallaction *vm); + InventoryRenderer(Parallaction *vm, InventoryProperties *props); virtual ~InventoryRenderer(); void bindInventory(Inventory *inv) { _inv = inv; } @@ -97,6 +111,7 @@ public: ItemPosition hitTest(const Common::Point &p) const; void highlightItem(ItemPosition pos, byte color); + void drawItem(ItemName name, byte *buffer, uint pitch); byte* getData() const { return (byte*)_surf.pixels; } diff --git a/engines/parallaction/module.mk b/engines/parallaction/module.mk index 2478b4b2e1..9d44422541 100644 --- a/engines/parallaction/module.mk +++ b/engines/parallaction/module.mk @@ -1,6 +1,7 @@ MODULE := engines/parallaction MODULE_OBJS := \ + balloons.o \ callables_br.o \ callables_ns.o \ debug.o \ @@ -13,6 +14,7 @@ MODULE_OBJS := \ font.o \ gfxbase.o \ graphics.o \ + gui.o \ gui_br.o \ gui_ns.o \ input.o \ diff --git a/engines/parallaction/objects.cpp b/engines/parallaction/objects.cpp index cac31911f4..c387484de7 100644 --- a/engines/parallaction/objects.cpp +++ b/engines/parallaction/objects.cpp @@ -54,19 +54,20 @@ Animation::Animation() { Animation::~Animation() { free(_scriptName); + gfxobj->release(); } uint16 Animation::width() const { if (!gfxobj) return 0; Common::Rect r; - gfxobj->getRect(0, r); + gfxobj->getRect(_frame, r); return r.width(); } uint16 Animation::height() const { if (!gfxobj) return 0; Common::Rect r; - gfxobj->getRect(0, r); + gfxobj->getRect(_frame, r); return r.height(); } @@ -80,6 +81,12 @@ byte* Animation::getFrameData(uint32 index) const { return gfxobj->getData(index); } +void Animation::validateScriptVars() { + // this is used to clip values of _frame, _left and _top + // which can be screwed up by buggy scripts. + + _frame = CLIP(_frame, (int16)0, (int16)(getFrameNum() - 1)); +} #define NUM_LOCALS 10 char _localNames[NUM_LOCALS][10]; @@ -182,7 +189,8 @@ Zone::~Zone() { break; } - delete _label; + + free(_linkedName); } void Zone::getRect(Common::Rect& r) const { @@ -207,6 +215,16 @@ uint16 Zone::height() const { return _bottom - _top; } +Dialogue::Dialogue() { + memset(_questions, 0, sizeof(_questions)); +} + +Dialogue::~Dialogue() { + for (int i = 0; i < NUM_QUESTIONS; i++) { + delete _questions[i]; + } +} + Answer::Answer() { _text = NULL; _mood = 0; diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h index c2c2c154b5..a3bf757bdb 100644 --- a/engines/parallaction/objects.h +++ b/engines/parallaction/objects.h @@ -54,6 +54,7 @@ typedef Common::SharedPtr<Instruction> InstructionPtr; typedef Common::List<InstructionPtr> InstructionList; extern InstructionPtr nullInstructionPtr; +typedef Common::List<Common::Point> PointList; enum ZoneTypes { kZoneExamine = 1, // zone displays comment if activated @@ -67,7 +68,11 @@ enum ZoneTypes { kZoneNone = 0x100, // used to prevent parsing on peculiar Animations kZoneTrap = 0x200, // zone activated when character enters kZoneYou = 0x400, // marks the character - kZoneCommand = 0x800 + kZoneCommand = 0x800, + + // BRA specific + kZonePath = 0x1000, // defines nodes for assisting walk calculation routines + kZoneBox = 0x2000 }; @@ -89,6 +94,7 @@ enum ZoneFlags { kFlagsYourself = 0x1000, kFlagsScaled = 0x2000, kFlagsSelfuse = 0x4000, + kFlagsIsAnimation = 0x1000000, // BRA: used in walk code (trap check), to tell is a Zone is an Animation kFlagsAnimLinked = 0x2000000 }; @@ -181,6 +187,9 @@ struct Question { struct Dialogue { Question *_questions[NUM_QUESTIONS]; + + Dialogue(); + ~Dialogue(); }; struct GetData { // size = 24 @@ -206,7 +215,7 @@ struct SpeakData { // size = 36 } }; struct ExamineData { // size = 28 - Frames *_cnv; + GfxObj *_cnv; uint16 _opBase; // unused uint16 field_12; // unused char* _description; @@ -253,6 +262,15 @@ struct MergeData { // size = 12 _obj1 = _obj2 = _obj3 = 0; } }; +#define MAX_WALKPOINT_LISTS 20 +struct PathData { + int _numLists; + PointList _lists[MAX_WALKPOINT_LISTS]; + + PathData() { + _numLists = 0; + } +}; struct TypeData { GetData *get; @@ -261,6 +279,8 @@ struct TypeData { DoorData *door; HearData *hear; MergeData *merge; + // BRA specific field + PathData *path; TypeData() { get = NULL; @@ -269,6 +289,7 @@ struct TypeData { door = NULL; hear = NULL; merge = NULL; + path = NULL; } }; @@ -284,7 +305,7 @@ struct Zone { int16 _bottom; uint32 _type; uint32 _flags; - Label *_label; + uint _label; uint16 field_2C; // unused uint16 field_2E; // unused TypeData u; @@ -429,6 +450,8 @@ struct Animation : public Zone { virtual uint16 height() const; uint16 getFrameNum() const; byte* getFrameData(uint32 index) const; + + void validateScriptVars(); }; class Table { diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp index d66b1af1f1..bb306c3299 100644 --- a/engines/parallaction/parallaction.cpp +++ b/engines/parallaction/parallaction.cpp @@ -85,20 +85,28 @@ Parallaction::Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gam Parallaction::~Parallaction() { delete _debugger; - delete _globalTable; - delete _callableNames; - delete _localFlagNames; + delete _cmdExec; + delete _programExec; + _gfx->clearGfxObjects(kGfxObjCharacter | kGfxObjNormal); + hideDialogueStuff(); + delete _balloonMan; freeLocation(); freeCharacter(); destroyInventory(); + cleanupGui(); + + delete _comboArrow; + + delete _localFlagNames; delete _gfx; delete _soundMan; delete _disk; + delete _input; } @@ -132,18 +140,24 @@ int Parallaction::init() { _debugger = new Debugger(this); - return 0; -} - + _menuHelper = 0; + setupBalloonManager(); + return 0; +} +void Parallaction::clearSet(OpcodeSet &opcodes) { + for (Common::Array<const Opcode*>::iterator i = opcodes.begin(); i != opcodes.end(); ++i) + delete *i; + opcodes.clear(); +} void Parallaction::updateView() { - if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) { + if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) { return; } @@ -153,6 +167,11 @@ void Parallaction::updateView() { } +void Parallaction::hideDialogueStuff() { + _gfx->freeItems(); + _balloonMan->freeBalloons(); +} + void Parallaction::freeCharacter() { debugC(1, kDebugExec, "freeCharacter()"); @@ -160,6 +179,8 @@ void Parallaction::freeCharacter() { delete _objectsNames; _objectsNames = 0; + _gfx->clearGfxObjects(kGfxObjCharacter); + _char.free(); return; @@ -189,6 +210,9 @@ AnimationPtr Parallaction::findAnimation(const char *name) { } void Parallaction::freeAnimations() { + for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) { + (*it)->_commands.clear(); // See comment for freeZones(), about circular references. + } _location._animations.clear(); return; } @@ -237,10 +261,9 @@ void Parallaction::freeLocation() { _localFlagNames->clear(); - _location._walkNodes.clear(); + _location._walkPoints.clear(); - _gfx->clearGfxObjects(); - freeBackground(); + _gfx->clearGfxObjects(kGfxObjNormal); _location._programs.clear(); freeZones(); @@ -260,76 +283,32 @@ void Parallaction::freeLocation() { void Parallaction::freeBackground() { - _gfx->freeBackground(); _pathBuffer = 0; } void Parallaction::setBackground(const char* name, const char* mask, const char* path) { - _gfx->setBackground(kBackgroundLocation, name, mask, path); - _pathBuffer = &_gfx->_backgroundInfo.path; + BackgroundInfo *info = new BackgroundInfo; + _disk->loadScenery(*info, name, mask, path); + + _gfx->setBackground(kBackgroundLocation, info); + _pathBuffer = &info->path; return; } void Parallaction::showLocationComment(const char *text, bool end) { - _gfx->setLocationBalloon(const_cast<char*>(text), end); + _balloonMan->setLocationBalloon(const_cast<char*>(text), end); } void Parallaction::processInput(InputData *data) { + if (!data) { + return; + } switch (data->_event) { - case kEvEnterZone: - debugC(2, kDebugInput, "processInput: kEvEnterZone"); - _gfx->setFloatingLabel(data->_label); - break; - - case kEvExitZone: - debugC(2, kDebugInput, "processInput: kEvExitZone"); - _gfx->setFloatingLabel(0); - break; - - case kEvAction: - debugC(2, kDebugInput, "processInput: kEvAction"); - _input->stopHovering(); - pauseJobs(); - runZone(data->_zone); - resumeJobs(); - break; - - case kEvOpenInventory: - _input->stopHovering(); - _gfx->setFloatingLabel(0); - if (hitZone(kZoneYou, data->_mousePos.x, data->_mousePos.y) == 0) { - setArrowCursor(); - } - pauseJobs(); - openInventory(); - break; - - case kEvCloseInventory: // closes inventory and possibly select item - closeInventory(); - setInventoryCursor(data->_inventoryIndex); - resumeJobs(); - break; - - case kEvHoverInventory: - highlightInventoryItem(data->_inventoryIndex); // enable - break; - - case kEvWalk: - debugC(2, kDebugInput, "processInput: kEvWalk"); - _input->stopHovering(); - setArrowCursor(); - _char.scheduleWalk(data->_mousePos.x, data->_mousePos.y); - break; - - case kEvQuitGame: - _engineFlags |= kEngineQuit; - break; - case kEvSaveGame: _input->stopHovering(); saveGame(); @@ -350,28 +329,39 @@ void Parallaction::processInput(InputData *data) { void Parallaction::runGame() { InputData *data = _input->updateInput(); - if (data->_event != kEvNone) { + if (_engineFlags & kEngineQuit) + return; + + runGuiFrame(); + runDialogueFrame(); + runCommentFrame(); + + if (_input->_inputMode == Input::kInputModeGame) { processInput(data); - } + runPendingZones(); - runPendingZones(); + if (_engineFlags & kEngineQuit) + return; - if (_engineFlags & kEngineChangeLocation) { - changeLocation(_location._name); + if (_engineFlags & kEngineChangeLocation) { + changeLocation(_location._name); + } } - _gfx->beginFrame(); if (_input->_inputMode == Input::kInputModeGame) { - runScripts(); - walk(); + _programExec->runScripts(_location._programs.begin(), _location._programs.end()); + _char._ani->_z = _char._ani->height() + _char._ani->_top; + if (_char._ani->gfxobj) { + _char._ani->gfxobj->z = _char._ani->_z; + } + _char._walker->walk(); drawAnimations(); } // change this to endFrame? updateView(); - } @@ -400,14 +390,13 @@ void Parallaction::doLocationEnterTransition() { pal.makeGrayscale(); _gfx->setPalette(pal); - runScripts(); + _programExec->runScripts(_location._programs.begin(), _location._programs.end()); drawAnimations(); - + showLocationComment(_location._comment, false); _gfx->updateScreen(); - showLocationComment(_location._comment, false); - _input->waitUntilLeftClick(); - _gfx->freeBalloons(); + _input->waitForButtonEvent(kMouseLeftUp); + _balloonMan->freeBalloons(); // fades maximum intensity palette towards approximation of main palette for (uint16 _si = 0; _si<6; _si++) { @@ -467,6 +456,9 @@ void Parallaction::freeZones() { debugC(2, kDebugExec, "freeZones preserving zone '%s'", z->_name); it++; } else { + (*it)->_commands.clear(); // Since commands may reference zones, and both commands and zones are kept stored into + // SharedPtr's, we need to kill commands explicitly to destroy any potential circular + // reference. it = _location._zones.erase(it); } } @@ -475,16 +467,47 @@ void Parallaction::freeZones() { } +enum { + WALK_LEFT = 0, + WALK_RIGHT = 1, + WALK_DOWN = 2, + WALK_UP = 3 +}; + +struct WalkFrames { + int16 stillFrame[4]; + int16 firstWalkFrame[4]; + int16 numWalkFrames[4]; + int16 frameRepeat[4]; +}; + +WalkFrames _char20WalkFrames = { + { 0, 7, 14, 17 }, + { 1, 8, 15, 18 }, + { 6, 6, 2, 2 }, + { 2, 2, 4, 4 } +}; + +WalkFrames _char24WalkFrames = { + { 0, 9, 18, 21 }, + { 1, 10, 19, 22 }, + { 8, 8, 2, 2 }, + { 2, 2, 4, 4 } +}; + const char Character::_prefixMini[] = "mini"; const char Character::_suffixTras[] = "tras"; const char Character::_empty[] = "\0"; -Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(_ani) { +Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation) { _talk = NULL; _head = NULL; _objs = NULL; + _direction = WALK_DOWN; + _step = 0; + _dummy = false; _ani->_left = 150; @@ -496,24 +519,61 @@ Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder( _ani->_flags = kFlagsActive | kFlagsNoName; _ani->_type = kZoneYou; strncpy(_ani->_name, "yourself", ZONENAME_LENGTH); + + // TODO: move creation into Parallaction. Needs to make Character a pointer first. + if (_vm->getGameType() == GType_Nippon) { + _builder = new PathBuilder_NS(this); + _walker = new PathWalker_NS(this); + } else { + _builder = new PathBuilder_BR(this); + _walker = new PathWalker_BR(this); + } +} + +Character::~Character() { + delete _builder; + _builder = 0; + + delete _walker; + _walker = 0; + + free(); } void Character::getFoot(Common::Point &foot) { - foot.x = _ani->_left + _ani->width() / 2; - foot.y = _ani->_top + _ani->height(); + Common::Rect rect; + _ani->gfxobj->getRect(_ani->_frame, rect); + + foot.x = _ani->_left + (rect.left + rect.width() / 2); + foot.y = _ani->_top + (rect.top + rect.height()); } void Character::setFoot(const Common::Point &foot) { - _ani->_left = foot.x - _ani->width() / 2; - _ani->_top = foot.y - _ani->height(); + Common::Rect rect; + _ani->gfxobj->getRect(_ani->_frame, rect); + + _ani->_left = foot.x - (rect.left + rect.width() / 2); + _ani->_top = foot.y - (rect.top + rect.height()); +} + +#if 0 +void dumpPath(const PointList &list, const char* text) { + for (PointList::iterator it = list.begin(); it != list.end(); it++) + printf("node (%i, %i)\n", it->x, it->y); + + return; } +#endif void Character::scheduleWalk(int16 x, int16 y) { if ((_ani->_flags & kFlagsRemove) || (_ani->_flags & kFlagsActive) == 0) { return; } - _walkPath = _builder.buildPath(x, y); + _builder->buildPath(x, y); +#if 0 + dumpPath(_walkPath, _name); +#endif _engineFlags |= kEngineWalking; } @@ -522,11 +582,12 @@ void Character::free() { delete _talk; delete _head; delete _objs; + delete _ani->gfxobj; - _ani->gfxobj = NULL; _talk = NULL; _head = NULL; _objs = NULL; + _ani->gfxobj = NULL; return; } @@ -548,10 +609,14 @@ void Character::setName(const char *name) { const char *end = begin + strlen(name); _prefix = _empty; + _suffix = _empty; _dummy = IS_DUMMY_CHARACTER(name); if (!_dummy) { + if (!strstr(name, "donna")) { + _engineFlags &= ~kEngineTransformedDonna; + } else if (_engineFlags & kEngineTransformedDonna) { _suffix = _suffixTras; } else { @@ -560,8 +625,6 @@ void Character::setName(const char *name) { _engineFlags |= kEngineTransformedDonna; _suffix = _suffixTras; end = s; - } else { - _suffix = _empty; } } if (IS_MINI_CHARACTER(name)) { @@ -597,9 +660,35 @@ void Parallaction::beep() { } void Parallaction::scheduleLocationSwitch(const char *location) { + debugC(9, kDebugExec, "scheduleLocationSwitch(%s)\n", location); strcpy(_location._name, location); _engineFlags |= kEngineChangeLocation; } + + + +void Character::updateDirection(const Common::Point& pos, const Common::Point& to) { + + Common::Point dist(to.x - pos.x, to.y - pos.y); + WalkFrames *frames = (_ani->getFrameNum() == 20) ? &_char20WalkFrames : &_char24WalkFrames; + + _step++; + + if (dist.x == 0 && dist.y == 0) { + _ani->_frame = frames->stillFrame[_direction]; + return; + } + + if (dist.x < 0) + dist.x = -dist.x; + if (dist.y < 0) + dist.y = -dist.y; + + _direction = (dist.x > dist.y) ? ((to.x > pos.x) ? WALK_LEFT : WALK_RIGHT) : ((to.y > pos.y) ? WALK_DOWN : WALK_UP); + _ani->_frame = frames->firstWalkFrame[_direction] + (_step / frames->frameRepeat[_direction]) % frames->numWalkFrames[_direction]; +} + + } // namespace Parallaction diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h index 6e5957d3cd..e5c5221414 100644 --- a/engines/parallaction/parallaction.h +++ b/engines/parallaction/parallaction.h @@ -33,6 +33,7 @@ #include "engines/engine.h" +#include "parallaction/exec.h" #include "parallaction/input.h" #include "parallaction/inventory.h" #include "parallaction/parser.h" @@ -100,10 +101,8 @@ enum { enum EngineFlags { kEngineQuit = (1 << 0), kEnginePauseJobs = (1 << 1), - kEngineInventory = (1 << 2), kEngineWalking = (1 << 3), kEngineChangeLocation = (1 << 4), - kEngineBlockInput = (1 << 5), kEngineDragging = (1 << 6), kEngineTransformedDonna = (1 << 7), @@ -113,14 +112,6 @@ enum EngineFlags { enum { kEvNone = 0, - kEvEnterZone = 1, - kEvExitZone = 2, - kEvAction = 3, - kEvOpenInventory = 4, - kEvCloseInventory = 5, - kEvHoverInventory = 6, - kEvWalk = 7, - kEvQuitGame = 1000, kEvSaveGame = 2000, kEvLoadGame = 4000 }; @@ -164,6 +155,8 @@ class Debugger; class Gfx; class SoundMan; class Input; +class DialogueManager; +class MenuInputHelper; struct Location { @@ -184,7 +177,7 @@ struct Location { char _soundFile[50]; // NS specific - WalkNodeList _walkNodes; + PointList _walkPoints; char _slideText[2][MAX_TOKEN_LEN]; // BRA specific @@ -202,13 +195,16 @@ struct Character { AnimationPtr _ani; - Frames *_head; - Frames *_talk; - Frames *_objs; - PathBuilder _builder; - WalkNodeList *_walkPath; + GfxObj *_head; + GfxObj *_talk; + GfxObj *_objs; + PathBuilder *_builder; + PathWalker *_walker; + PointList _walkPath; Character(Parallaction *vm); + ~Character(); + void getFoot(Common::Point &foot); void setFoot(const Common::Point &foot); void scheduleWalk(int16 x, int16 y); @@ -228,18 +224,19 @@ protected: static const char _suffixTras[]; static const char _empty[]; + int16 _direction, _step; + public: void setName(const char *name); const char *getName() const; const char *getBaseName() const; const char *getFullName() const; bool dummy() const; -}; + void updateDirection(const Common::Point& pos, const Common::Point& to); +}; -#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op() -#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op() #define NUM_LOCATIONS 120 @@ -259,41 +256,16 @@ public: Input *_input; - OpcodeSet _commandOpcodes; - - struct ParallactionStruct1 { - CommandPtr cmd; - ZonePtr z; - } _cmdRunCtxt; - - OpcodeSet _instructionOpcodes; - - struct ParallactionStruct2 { - AnimationPtr anim; - ProgramPtr program; - InstructionList::iterator inst; - uint16 modCounter; - bool suspend; - } _instRunCtxt; - void processInput(InputData* data); void pauseJobs(); void resumeJobs(); - void finalizeWalk(WalkNodeList *list); - int16 selectWalkFrame(const Common::Point& pos, const WalkNodePtr from); - void clipMove(Common::Point& pos, const WalkNodePtr from); - ZonePtr findZone(const char *name); ZonePtr hitZone(uint32 type, uint16 x, uint16 y); uint16 runZone(ZonePtr z); void freeZones(); - void runDialogue(SpeakData*); - - void runCommands(CommandList& list, ZonePtr z = nullZonePtr); - AnimationPtr findAnimation(const char *name); void freeAnimations(); @@ -327,6 +299,8 @@ public: Gfx* _gfx; Disk* _disk; + CommandExec* _cmdExec; + ProgramExec* _programExec; Character _char; void setLocationFlags(uint32 flags); @@ -351,6 +325,7 @@ public: Common::RandomSource _rnd; Debugger *_debugger; + Frames *_comboArrow; protected: // data @@ -367,10 +342,8 @@ protected: // members void runGame(); void updateView(); - void scheduleLocationSwitch(const char *location); void doLocationEnterTransition(); virtual void changeLocation(char *location) = 0; - virtual void changeCharacter(const char *name) = 0; virtual void runPendingZones() = 0; void allocateLocationSlot(const char *name); void finalizeLocationParsing(); @@ -379,28 +352,33 @@ protected: // members void displayComment(ExamineData *data); - uint16 checkDoor(); - void freeCharacter(); int16 pickupItem(ZonePtr z); + void clearSet(OpcodeSet &opcodes); + + public: + void scheduleLocationSwitch(const char *location); + virtual void changeCharacter(const char *name) = 0; + virtual void callFunction(uint index, void* parm) { } virtual void setArrowCursor() = 0; - virtual void setInventoryCursor(int pos) = 0; + virtual void setInventoryCursor(ItemName name) = 0; virtual void parseLocation(const char* name) = 0; void updateDoor(ZonePtr z); - virtual void runScripts() = 0; - virtual void walk() = 0; virtual void drawAnimations() = 0; void beep(); + ZonePtr _zoneTrap; + PathBuilder* getPathBuilder(Character *ch); + public: // const char **_zoneFlagNamesRes; // const char **_zoneTypeNamesRes; @@ -425,6 +403,27 @@ public: Inventory *_inventory; InventoryRenderer *_inventoryRenderer; + BalloonManager *_balloonMan; + + void setupBalloonManager(); + + void hideDialogueStuff(); + DialogueManager *_dialogueMan; + void enterDialogueMode(ZonePtr z); + void exitDialogueMode(); + void runDialogueFrame(); + + MenuInputHelper *_menuHelper; + void runGuiFrame(); + void cleanupGui(); + + ZonePtr _commentZone; + void enterCommentMode(ZonePtr z); + void exitCommentMode(); + void runCommentFrame(); + + void setInternLanguage(uint id); + uint getInternLanguage(); }; @@ -483,12 +482,18 @@ public: typedef void (Parallaction_ns::*Callable)(void*); virtual void callFunction(uint index, void* parm); - void setMousePointer(uint32 value); bool loadGame(); bool saveGame(); void switchBackground(const char* background, const char* mask); + void showSlide(const char *name, int x = 0, int y = 0); + void setArrowCursor(); + + // TODO: this should be private!!!!!!! + bool _inTestResult; + void cleanupGame(); + bool allPartsComplete(); private: LocationParser_ns *_locationParser; @@ -500,17 +505,14 @@ private: Common::String genSaveFileName(uint slot, bool oldStyle = false); Common::InSaveFile *getInSaveFile(uint slot); Common::OutSaveFile *getOutSaveFile(uint slot); - bool allPartsComplete(); void setPartComplete(const Character& character); private: void changeLocation(char *location); void changeCharacter(const char *name); void runPendingZones(); - void cleanupGame(); - void setArrowCursor(); - void setInventoryCursor(int pos); + void setInventoryCursor(ItemName name); void doLoadGame(uint16 slot); @@ -520,11 +522,9 @@ private: void initResources(); void initCursors(); - void initParsers(); static byte _resMouseArrow[256]; byte *_mouseArrow; - Frames *_mouseComposedArrow; static const Callable _dosCallables[25]; static const Callable _amigaCallables[25]; @@ -580,60 +580,16 @@ private: const Callable *_callables; protected: - void runScripts(); - void walk(); void drawAnimations(); void parseLocation(const char *filename); void loadProgram(AnimationPtr a, const char *filename); - void initOpcodes(); - - DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(set); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(get); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); - - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); - void selectStartLocation(); - void guiStart(); - int guiSelectCharacter(); - void guiSplash(); - int guiNewGame(); - uint16 guiChooseLanguage(); - uint16 guiSelectGame(); - int guiGetSelectedBlock(const Common::Point &p); - - void showSlide(const char *name); + void startGui(); + void startCreditSequence(); + void startEndPartSequence(); }; @@ -655,6 +611,9 @@ public: typedef void (Parallaction_br::*Callable)(void*); virtual void callFunction(uint index, void* parm); void changeCharacter(const char *name); + void setupSubtitles(char *s, char *s2, int y); + void clearSubtitles(); + public: Table *_countersNames; @@ -674,7 +633,8 @@ public: int32 _counters[32]; uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES]; - + void startPart(uint part); + void setArrowCursor(); private: LocationParser_br *_locationParser; ProgramParser_br *_programParser; @@ -682,20 +642,15 @@ private: void initResources(); void initFonts(); void freeFonts(); - void initOpcodes(); - void initParsers(); - void setArrowCursor(); - void setInventoryCursor(int pos); + void setInventoryCursor(ItemName name); void changeLocation(char *location); void runPendingZones(); void initPart(); void freePart(); - void startPart(); - void setMousePointer(int16 index); void initCursors(); Frames *_dinoCursor; @@ -706,10 +661,7 @@ private: static const char *_partNames[]; - void guiStart(); - int guiShowMenu(); - void guiSplash(const char *name); - Frames* guiRenderMenuItem(const char *text); + void startGui(); static const Callable _dosCallables[6]; @@ -725,68 +677,6 @@ private: void parseLocation(const char* name); void loadProgram(AnimationPtr a, const char *filename); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(location); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(open); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(close); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(on); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(off); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(call); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(move); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(start); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(character); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(add); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(let); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(music); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(give); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(text); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(part); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave); - DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave); - - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop); - DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript); - - void setupSubtitles(char *s, char *s2, int y); - void clearSubtitles(); #if 0 void jobWaitRemoveLabelJob(void *parm, Job *job); void jobPauseSfx(void *parm, Job *job); diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp index 0f5cc2a0c4..761c8d1b74 100644 --- a/engines/parallaction/parallaction_br.cpp +++ b/engines/parallaction/parallaction_br.cpp @@ -32,6 +32,27 @@ namespace Parallaction { +struct MouseComboProperties { + int _xOffset; + int _yOffset; + int _width; + int _height; +}; +/* +// TODO: improve NS's handling of normal cursor before merging cursor code. +MouseComboProperties _mouseComboProps_NS = { + 7, // combo x offset (the icon from the inventory will be rendered from here) + 7, // combo y offset (ditto) + 32, // combo (arrow + icon) width + 32 // combo (arrow + icon) height +}; +*/ +MouseComboProperties _mouseComboProps_BR = { + 8, // combo x offset (the icon from the inventory will be rendered from here) + 8, // combo y offset (ditto) + 68, // combo (arrow + icon) width + 68 // combo (arrow + icon) height +}; const char *Parallaction_br::_partNames[] = { "PART0", @@ -56,7 +77,11 @@ int Parallaction_br::init() { if (getGameType() == GType_BRA) { if (getPlatform() == Common::kPlatformPC) { - _disk = new DosDisk_br(this); + if (getFeatures() & GF_DEMO) { + _disk = new DosDemo_br(this); + } else { + _disk = new DosDisk_br(this); + } _disk->setLanguage(2); // NOTE: language is now hardcoded to English. Original used command-line parameters. _soundMan = new DummySoundMan(this); } else { @@ -72,14 +97,21 @@ int Parallaction_br::init() { initResources(); initFonts(); initCursors(); - initOpcodes(); _locationParser = new LocationParser_br(this); _locationParser->init(); _programParser = new ProgramParser_br(this); _programParser->init(); + _cmdExec = new CommandExec_br(this); + _cmdExec->init(); + _programExec = new ProgramExec_br(this); + _programExec->init(); + _part = -1; + _subtitle[0] = -1; + _subtitle[1] = -1; + Parallaction::init(); return 0; @@ -92,6 +124,7 @@ Parallaction_br::~Parallaction_br() { delete _dougCursor; delete _donnaCursor; + delete _mouseArrow; } void Parallaction_br::callFunction(uint index, void* parm) { @@ -102,13 +135,14 @@ void Parallaction_br::callFunction(uint index, void* parm) { int Parallaction_br::go() { - guiSplash("dyna"); - guiSplash("core"); + if (getFeatures() & GF_DEMO) { + startPart(1); + } else { + startGui(); + } while ((_engineFlags & kEngineQuit) == 0) { - guiStart(); - // initCharacter(); _input->_inputMode = Input::kInputModeGame; @@ -142,6 +176,12 @@ void Parallaction_br::initCursors() { _dougCursor = _disk->loadPointer("pointer2"); _donnaCursor = _disk->loadPointer("pointer3"); + Graphics::Surface *surf = new Graphics::Surface; + surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, 1); + _comboArrow = new SurfaceToFrames(surf); + + // TODO: choose the pointer depending on the active character + // For now, we pick Donna's _mouseArrow = _donnaCursor; } else { // TODO: Where are the Amiga cursors? @@ -149,19 +189,6 @@ void Parallaction_br::initCursors() { } -void Parallaction_br::setMousePointer(int16 index) { - // FIXME: Where are the Amiga cursors? - if (getPlatform() == Common::kPlatformAmiga) - return; - - Common::Rect r; - _mouseArrow->getRect(0, r); - - _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); - _system->showMouse(true); - -} - void Parallaction_br::initPart() { memset(_counters, 0, ARRAYSIZE(_counters)); @@ -170,7 +197,12 @@ void Parallaction_br::initPart() { _objectsNames = _disk->loadTable("objects"); _countersNames = _disk->loadTable("counters"); -// _disk->loadObjects("icone.ico"); + // TODO: maybe handle this into Disk + if (getPlatform() == Common::kPlatformPC) { + _char._objs = _disk->loadObjects("icone.ico"); + } else { + _char._objs = _disk->loadObjects("icons.ico"); + } } @@ -185,11 +217,17 @@ void Parallaction_br::freePart() { _countersNames = 0; } -void Parallaction_br::startPart() { +void Parallaction_br::startPart(uint part) { + _part = part; + _disk->selectArchive(_partNames[_part]); initPart(); - strcpy(_location._name, partFirstLocation[_part]); + if (getFeatures() & GF_DEMO) { + strcpy(_location._name, "camalb"); + } else { + strcpy(_location._name, partFirstLocation[_part]); + } parseLocation("common"); changeLocation(_location._name); @@ -199,16 +237,26 @@ void Parallaction_br::startPart() { void Parallaction_br::runPendingZones() { ZonePtr z; + _cmdExec->runSuspended(); + if (_activeZone) { z = _activeZone; // speak Zone or sound _activeZone = nullZonePtr; - runZone(z); // FIXME: BRA doesn't handle sound yet + if ((z->_type & 0xFFFF) == kZoneSpeak) { + enterDialogueMode(z); + } else { + runZone(z); // FIXME: BRA doesn't handle sound yet + } } if (_activeZone2) { z = _activeZone2; // speak Zone or sound _activeZone2 = nullZonePtr; - runZone(z); + if ((z->_type & 0xFFFF) == kZoneSpeak) { + enterDialogueMode(z); + } else { + runZone(z); // FIXME: BRA doesn't handle sound yet + } } } @@ -218,21 +266,35 @@ void Parallaction_br::changeLocation(char *location) { // free open location stuff clearSubtitles(); freeBackground(); - _gfx->clearGfxObjects(); + _gfx->clearGfxObjects(kGfxObjNormal); + _gfx->freeLabels(); + _subtitle[0] = _subtitle[1] = -1; + _location._programs.clear(); + + _location._animations.remove(_char._ani); + freeZones(); freeAnimations(); + + _location._animations.push_front(_char._ani); + // free(_location._comment); // _location._comment = 0; -// _location._commands.clear(); -// _location._aCommands.clear(); - + _location._commands.clear(); + _location._aCommands.clear(); // load new location parseLocation(location); - runCommands(_location._commands); + + // kFlagsRemove is cleared because the character defaults to visible on new locations + // script command can hide the character, anyway, so that's why the flag is cleared + // before _location._commands are executed + _char._ani->_flags &= ~kFlagsRemove; + + _cmdExec->run(_location._commands); // doLocationEnterTransition(); - runCommands(_location._aCommands); + _cmdExec->run(_location._aCommands); _engineFlags &= ~kEngineChangeLocation; } @@ -282,25 +344,45 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) { void Parallaction_br::changeCharacter(const char *name) { const char *charName = _char.getName(); - if (!scumm_stricmp(charName, name)) { - return; + + if (scumm_stricmp(charName, name)) { + debugC(1, kDebugExec, "changeCharacter(%s)", name); + + _char.setName(name); + _char._ani->gfxobj = _gfx->loadAnim(name); + _char._ani->gfxobj->setFlags(kGfxObjCharacter); + _char._ani->gfxobj->clearFlags(kGfxObjNormal); + _char._talk = _disk->loadTalk(name); } - _char.setName(name); - _char._talk = _disk->loadTalk(name); + _char._ani->_flags |= kFlagsActive; } void Parallaction_br::setArrowCursor() { + // FIXME: Where are the Amiga cursors? + if (getPlatform() == Common::kPlatformAmiga) + return; + Common::Rect r; + _mouseArrow->getRect(0, r); + _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0); + _system->showMouse(true); + _input->_activeItem._id = 0; } -void Parallaction_br::setInventoryCursor(int pos) { - +void Parallaction_br::setInventoryCursor(ItemName name) { + assert(name > 0); + byte *src = _mouseArrow->getData(0); + byte *dst = _comboArrow->getData(0); + memcpy(dst, src, _comboArrow->getSize(0)); + // FIXME: destination offseting is not clear + _inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width); + _system->setMouseCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0); } } // namespace Parallaction diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp index 2cca3a6a4a..e81492e655 100644 --- a/engines/parallaction/parallaction_ns.cpp +++ b/engines/parallaction/parallaction_ns.cpp @@ -34,6 +34,7 @@ namespace Parallaction { + #define MOUSEARROW_WIDTH 16 #define MOUSEARROW_HEIGHT 16 @@ -135,18 +136,24 @@ int Parallaction_ns::init() { initResources(); initFonts(); initCursors(); - initOpcodes(); _locationParser = new LocationParser_ns(this); _locationParser->init(); _programParser = new ProgramParser_ns(this); _programParser->init(); + _cmdExec = new CommandExec_ns(this); + _cmdExec->init(); + _programExec = new ProgramExec_ns(this); + _programExec->init(); + _introSarcData1 = 0; _introSarcData2 = 1; _introSarcData3 = 200; num_foglie = 0; + _inTestResult = false; + _location._animations.push_front(_char._ani); Parallaction::init(); @@ -157,7 +164,8 @@ int Parallaction_ns::init() { Parallaction_ns::~Parallaction_ns() { freeFonts(); - delete _mouseComposedArrow; + delete _locationParser; + delete _programParser; _location._animations.remove(_char._ani); @@ -174,7 +182,7 @@ void Parallaction_ns::freeFonts() { } void Parallaction_ns::initCursors() { - _mouseComposedArrow = _disk->loadPointer("pointer"); + _comboArrow = _disk->loadPointer("pointer"); _mouseArrow = _resMouseArrow; } @@ -183,40 +191,20 @@ void Parallaction_ns::setArrowCursor() { debugC(1, kDebugInput, "setting mouse cursor to arrow"); // this stuff is needed to avoid artifacts with labels and selected items when switching cursors - _gfx->setFloatingLabel(0); + _input->stopHovering(); _input->_activeItem._id = 0; _system->setMouseCursor(_mouseArrow, MOUSEARROW_WIDTH, MOUSEARROW_HEIGHT, 0, 0, 0); - _system->showMouse(true); - } -void Parallaction_ns::setInventoryCursor(int pos) { - - if (pos == -1) - return; +void Parallaction_ns::setInventoryCursor(ItemName name) { + assert(name > 0); - const InventoryItem *item = getInventoryItem(pos); - if (item->_index == 0) - return; - - _input->_activeItem._id = item->_id; - - byte *v8 = _mouseComposedArrow->getData(0); + byte *v8 = _comboArrow->getData(0); // FIXME: destination offseting is not clear - byte* s = _char._objs->getData(item->_index); - byte* d = v8 + 7 + MOUSECOMBO_WIDTH * 7; - - for (uint i = 0; i < INVENTORYITEM_HEIGHT; i++) { - memcpy(d, s, INVENTORYITEM_WIDTH); - - s += INVENTORYITEM_PITCH; - d += MOUSECOMBO_WIDTH; - } - + _inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH + 7, MOUSECOMBO_WIDTH); _system->setMouseCursor(v8, MOUSECOMBO_WIDTH, MOUSECOMBO_HEIGHT, 0, 0, 0); - } @@ -232,11 +220,8 @@ int Parallaction_ns::go() { _globalTable = _disk->loadTable("global"); - guiStart(); + startGui(); - changeLocation(_location._name); - - _input->_inputMode = Input::kInputModeGame; while ((_engineFlags & kEngineQuit) == 0) { runGame(); } @@ -268,8 +253,14 @@ void Parallaction_ns::switchBackground(const char* background, const char* mask) } -void Parallaction_ns::showSlide(const char *name) { - _gfx->setBackground(kBackgroundSlide, name, 0, 0); +void Parallaction_ns::showSlide(const char *name, int x, int y) { + BackgroundInfo *info = new BackgroundInfo; + _disk->loadSlide(*info, name); + + info->x = (x == CENTER_LABEL_HORIZONTAL) ? ((_vm->_screenWidth - info->width) >> 1) : x; + info->y = (y == CENTER_LABEL_VERTICAL) ? ((_vm->_screenHeight - info->height) >> 1) : y; + + _gfx->setBackground(kBackgroundSlide, info); } void Parallaction_ns::runPendingZones() { @@ -286,16 +277,19 @@ void Parallaction_ns::runPendingZones() { void Parallaction_ns::changeLocation(char *location) { debugC(1, kDebugExec, "changeLocation(%s)", location); + MouseTriState oldMouseState = _input->getMouseState(); + _input->setMouseState(MOUSE_DISABLED); + _soundMan->playLocationMusic(location); - _gfx->setFloatingLabel(0); + _input->stopHovering(); _gfx->freeLabels(); - _input->stopHovering(); - if (_engineFlags & kEngineBlockInput) { - setArrowCursor(); - } + _zoneTrap = nullZonePtr; + setArrowCursor(); + + _gfx->showGfxObj(_char._ani->gfxobj, false); _location._animations.remove(_char._ani); freeLocation(); @@ -307,7 +301,9 @@ void Parallaction_ns::changeLocation(char *location) { showSlide(locname.slide()); uint id = _gfx->createLabel(_menuFont, _location._slideText[0], 1); _gfx->showLabel(id, CENTER_LABEL_HORIZONTAL, 14); - _input->waitUntilLeftClick(); + _gfx->updateScreen(); + + _input->waitForButtonEvent(kMouseLeftUp); _gfx->freeLabels(); freeBackground(); } @@ -317,6 +313,7 @@ void Parallaction_ns::changeLocation(char *location) { } _location._animations.push_front(_char._ani); + _gfx->showGfxObj(_char._ani->gfxobj, true); strcpy(_saveData1, locname.location()); parseLocation(_saveData1); @@ -341,19 +338,18 @@ void Parallaction_ns::changeLocation(char *location) { // and acommands are executed, so that it can be set again if needed. _engineFlags &= ~kEngineChangeLocation; - runCommands(_location._commands); + _cmdExec->run(_location._commands); doLocationEnterTransition(); - runCommands(_location._aCommands); + _cmdExec->run(_location._aCommands); if (_location._hasSound) _soundMan->playSfx(_location._soundFile, 0, true); - debugC(1, kDebugExec, "changeLocation() done"); - - return; + _input->setMouseState(oldMouseState); + debugC(1, kDebugExec, "changeLocation() done"); } @@ -401,6 +397,8 @@ void Parallaction_ns::changeCharacter(const char *name) { Common::String oldArchive = _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); _char._ani->gfxobj = _gfx->loadAnim(_char.getFullName()); + _char._ani->gfxobj->setFlags(kGfxObjCharacter); + _char._ani->gfxobj->clearFlags(kGfxObjNormal); if (!_char.dummy()) { if (getPlatform() == Common::kPlatformAmiga) { diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp index f9de6eb4af..6de0a7d7f5 100644 --- a/engines/parallaction/parser.cpp +++ b/engines/parallaction/parser.cpp @@ -30,8 +30,7 @@ namespace Parallaction { char _tokens[20][MAX_TOKEN_LEN]; -Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) { -} +Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {} Script::~Script() { if (_disposeSource) diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h index d488cf9b58..79e6cf6640 100644 --- a/engines/parallaction/parser.h +++ b/engines/parallaction/parser.h @@ -128,6 +128,7 @@ protected: // BRA specific int numZones; + BackgroundInfo *info; char *bgName; char *maskName; char *pathName; @@ -171,7 +172,7 @@ protected: DECLARE_UNQUALIFIED_COMMAND_PARSER(animation); DECLARE_UNQUALIFIED_COMMAND_PARSER(zone); DECLARE_UNQUALIFIED_COMMAND_PARSER(location); - DECLARE_UNQUALIFIED_COMMAND_PARSER(drop); + DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject); DECLARE_UNQUALIFIED_COMMAND_PARSER(call); DECLARE_UNQUALIFIED_COMMAND_PARSER(simple); DECLARE_UNQUALIFIED_COMMAND_PARSER(move); @@ -192,8 +193,8 @@ protected: Question *parseQuestion(); void parseZone(ZoneList &list, char *name); - void parseZoneTypeBlock(ZonePtr z); - void parseWalkNodes(WalkNodeList &list); + virtual void parseZoneTypeBlock(ZonePtr z); + void parsePointList(PointList &list); void parseAnimation(AnimationList &list, char *name); void parseCommands(CommandList&); void parseCommandFlags(); @@ -221,13 +222,14 @@ public: virtual void init(); virtual ~LocationParser_ns() { + delete _parser; delete _commandsNames; delete _locationStmt; + delete _locationZoneStmt; + delete _locationAnimStmt; delete _zoneTypeNames; delete _zoneFlagNames; - delete _parser; - clearSet(_commandParsers); clearSet(_locationAnimParsers); clearSet(_locationZoneParsers); @@ -283,6 +285,9 @@ protected: DECLARE_UNQUALIFIED_ANIM_PARSER(moveto); DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation); + virtual void parseZoneTypeBlock(ZonePtr z); + void parsePathData(ZonePtr z); + public: LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm) { } @@ -306,6 +311,7 @@ protected: Parser *_parser; Parallaction_ns *_vm; + Script *_script; ProgramPtr _program; @@ -356,7 +362,9 @@ public: virtual void init(); virtual ~ProgramParser_ns() { + delete _parser; delete _instructionNames; + clearSet(_instructionParsers); } diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp index 51da7eb396..3b446805d7 100644 --- a/engines/parallaction/parser_br.cpp +++ b/engines/parallaction/parser_br.cpp @@ -390,7 +390,7 @@ DECLARE_LOCATION_PARSER(flags) { if ((_vm->getLocationFlags() & kFlagsVisited) == 0) { // only for 1st visit - _vm->clearLocationFlags(kFlagsAll); + _vm->clearLocationFlags((uint32)kFlagsAll); int _si = 1; do { @@ -442,7 +442,7 @@ DECLARE_LOCATION_PARSER(redundant) { DECLARE_LOCATION_PARSER(character) { debugC(7, kDebugParser, "LOCATION_PARSER(character) "); - ctxt.characterName = strdup(_tokens[0]); + ctxt.characterName = strdup(_tokens[1]); } @@ -464,9 +464,9 @@ DECLARE_LOCATION_PARSER(mask) { debugC(7, kDebugParser, "LOCATION_PARSER(mask) "); ctxt.maskName = strdup(_tokens[1]); - _vm->_gfx->_backgroundInfo.layers[0] = atoi(_tokens[2]); - _vm->_gfx->_backgroundInfo.layers[1] = atoi(_tokens[3]); - _vm->_gfx->_backgroundInfo.layers[2] = atoi(_tokens[4]); + ctxt.info->layers[0] = atoi(_tokens[2]); + ctxt.info->layers[1] = atoi(_tokens[3]); + ctxt.info->layers[2] = atoi(_tokens[4]); } @@ -750,6 +750,67 @@ DECLARE_ZONE_PARSER(type) { _parser->popTables(); } +void LocationParser_br::parsePathData(ZonePtr z) { + + PathData *data = new PathData; + + do { + + if (!scumm_stricmp("zone", _tokens[0])) { + int id = atoi(_tokens[1]); + parsePointList(data->_lists[id]); + data->_numLists++; + } + + _script->readLineToken(true); + } while (scumm_stricmp("endzone", _tokens[0])); + + z->u.path = data; +} + +void LocationParser_br::parseZoneTypeBlock(ZonePtr z) { + debugC(7, kDebugParser, "parseZoneTypeBlock(name: %s, type: %x)", z->_name, z->_type); + + switch (z->_type & 0xFFFF) { + case kZoneExamine: // examine Zone alloc + parseExamineData(z); + break; + + case kZoneDoor: // door Zone alloc + parseDoorData(z); + break; + + case kZoneGet: // get Zone alloc + parseGetData(z); + break; + + case kZoneMerge: // merge Zone alloc + parseMergeData(z); + break; + + case kZoneHear: // hear Zone alloc + parseHearData(z); + break; + + case kZoneSpeak: // speak Zone alloc + parseSpeakData(z); + break; + + // BRA specific zone + case kZonePath: + parsePathData(z); + break; + + default: + // eats up 'ENDZONE' line for unprocessed zone types + _script->readLineToken(true); + break; + } + + debugC(7, kDebugParser, "parseZoneTypeBlock() done"); + + return; +} DECLARE_ANIM_PARSER(file) { debugC(7, kDebugParser, "ANIM_PARSER(file) "); @@ -983,7 +1044,7 @@ void LocationParser_br::init() { COMMAND_PARSER(zone); // off COMMAND_PARSER(call); COMMAND_PARSER(flags); // toggle - COMMAND_PARSER(drop); + COMMAND_PARSER(invObject); // drop COMMAND_PARSER(simple); // quit COMMAND_PARSER(move); COMMAND_PARSER(zone); // stop @@ -991,7 +1052,7 @@ void LocationParser_br::init() { COMMAND_PARSER(string); // followme COMMAND_PARSER(simple); // onmouse COMMAND_PARSER(simple); // offmouse - COMMAND_PARSER(drop); // add + COMMAND_PARSER(invObject); // add COMMAND_PARSER(zone); // leave COMMAND_PARSER(math); // inc COMMAND_PARSER(math); // dec @@ -1114,11 +1175,14 @@ void LocationParser_br::parse(Script *script) { ctxt.maskName = 0; ctxt.pathName = 0; ctxt.characterName = 0; + ctxt.info = new BackgroundInfo; LocationParser_ns::parse(script); - _vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName); - _vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path; + _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName); + _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info); + _vm->_pathBuffer = &ctxt.info->path; + if (ctxt.characterName) { _vm->changeCharacter(ctxt.characterName); diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp index 2c4601c938..ad0f714fdc 100644 --- a/engines/parallaction/parser_ns.cpp +++ b/engines/parallaction/parser_ns.cpp @@ -299,6 +299,7 @@ void LocationParser_ns::parseAnimation(AnimationList &list, char *name) { AnimationPtr a(new Animation); strncpy(a->_name, name, ZONENAME_LENGTH); + a->_flags |= kFlagsIsAnimation; list.push_front(AnimationPtr(a)); @@ -658,7 +659,7 @@ DECLARE_COMMAND_PARSER(location) { } -DECLARE_COMMAND_PARSER(drop) { +DECLARE_COMMAND_PARSER(invObject) { debugC(7, kDebugParser, "COMMAND_PARSER(drop) "); createCommand(_parser->_lookup); @@ -1011,7 +1012,7 @@ DECLARE_LOCATION_PARSER(disk) { DECLARE_LOCATION_PARSER(nodes) { debugC(7, kDebugParser, "LOCATION_PARSER(nodes) "); - parseWalkNodes(_vm->_location._walkNodes); + parsePointList(_vm->_location._walkPoints); } @@ -1059,7 +1060,7 @@ DECLARE_LOCATION_PARSER(flags) { if ((_vm->getLocationFlags() & kFlagsVisited) == 0) { // only for 1st visit - _vm->clearLocationFlags(kFlagsAll); + _vm->clearLocationFlags((uint32)kFlagsAll); int _si = 1; do { @@ -1124,26 +1125,20 @@ void LocationParser_ns::parse(Script *script) { resolveCommandForwards(); } -void LocationParser_ns::parseWalkNodes(WalkNodeList &list) { - debugC(5, kDebugParser, "parseWalkNodes()"); +void LocationParser_ns::parsePointList(PointList &list) { + debugC(5, kDebugParser, "parsePointList()"); _script->readLineToken(true); while (scumm_stricmp(_tokens[0], "ENDNODES")) { if (!scumm_stricmp(_tokens[0], "COORD")) { - - WalkNodePtr v4(new WalkNode( - atoi(_tokens[1]), - atoi(_tokens[2]) - )); - - list.push_front(v4); + list.push_front(Common::Point(atoi(_tokens[1]), atoi(_tokens[2]))); } _script->readLineToken(true); } - debugC(5, kDebugParser, "parseWalkNodes() done"); + debugC(5, kDebugParser, "parsePointList() done"); return; } @@ -1203,7 +1198,7 @@ void LocationParser_ns::init() { COMMAND_PARSER(zone); // off COMMAND_PARSER(call); // call COMMAND_PARSER(flags); // toggle - COMMAND_PARSER(drop); // drop + COMMAND_PARSER(invObject); // drop COMMAND_PARSER(simple); // quit COMMAND_PARSER(move); // move COMMAND_PARSER(zone); // stop diff --git a/engines/parallaction/sound.cpp b/engines/parallaction/sound.cpp index dd74e8f7aa..df6867a90c 100644 --- a/engines/parallaction/sound.cpp +++ b/engines/parallaction/sound.cpp @@ -175,6 +175,7 @@ void MidiPlayer::close() { _mutex.lock(); _driver->setTimerCallback(NULL, NULL); _driver->close(); + delete _driver; _driver = 0; _parser->setMidiDriver(NULL); delete _parser; @@ -249,6 +250,9 @@ void DosSoundMan::stopMusic() { } void DosSoundMan::playCharacterMusic(const char *character) { + if (character == NULL) { + return; + } if (!scumm_stricmp(_vm->_location._name, "night") || !scumm_stricmp(_vm->_location._name, "intsushi")) { diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp index 0a8ded9e29..bf8f423fd5 100644 --- a/engines/parallaction/walk.cpp +++ b/engines/parallaction/walk.cpp @@ -27,33 +27,43 @@ namespace Parallaction { -static uint16 _doorData1 = 1000; -static ZonePtr _zoneTrap; -static uint16 walkData1 = 0; -static uint16 walkData2 = 0; // next walk frame +#define IS_PATH_CLEAR(x,y) _vm->_pathBuffer->getValue((x), (y)) inline byte PathBuffer::getValue(uint16 x, uint16 y) { byte m = data[(x >> 3) + y * internalWidth]; - uint n = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7)); - return ((1 << n) & m) >> n; + uint bit = 0; + switch (_vm->getGameType()) { + case GType_Nippon: + bit = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7)); + break; + + case GType_BRA: + // Amiga and PC versions pack the path bits the same way in BRA + bit = 7 - (x & 7); + break; + + default: + error("path mask not yet implemented for this game type"); + } + return ((1 << bit) & m) >> bit; } // adjusts position towards nearest walkable point // -void PathBuilder::correctPathPoint(Common::Point &to) { +void PathBuilder_NS::correctPathPoint(Common::Point &to) { - if (_vm->_pathBuffer->getValue(to.x, to.y)) return; + if (IS_PATH_CLEAR(to.x, to.y)) return; int16 right = to.x; int16 left = to.x; do { right++; - } while ((_vm->_pathBuffer->getValue(right, to.y) == 0) && (right < _vm->_pathBuffer->w)); + } while (!IS_PATH_CLEAR(right, to.y) && (right < _vm->_pathBuffer->w)); do { left--; - } while ((_vm->_pathBuffer->getValue(left, to.y) == 0) && (left > 0)); + } while (!IS_PATH_CLEAR(left, to.y) && (left > 0)); right = (right == _vm->_pathBuffer->w) ? 1000 : right - to.x; left = (left == 0) ? 1000 : to.x - left; @@ -62,10 +72,10 @@ void PathBuilder::correctPathPoint(Common::Point &to) { int16 bottom = to.y; do { top--; - } while ((_vm->_pathBuffer->getValue(to.x, top) == 0) && (top > 0)); + } while (!IS_PATH_CLEAR(to.x, top) && (top > 0)); do { bottom++; - } while ((_vm->_pathBuffer->getValue(to.x, bottom) == 0) && (bottom < _vm->_pathBuffer->h)); + } while (!IS_PATH_CLEAR(to.x, bottom) && (bottom < _vm->_pathBuffer->h)); top = (top == 0) ? 1000 : to.y - top; bottom = (bottom == _vm->_pathBuffer->h) ? 1000 : bottom - to.y; @@ -90,7 +100,7 @@ void PathBuilder::correctPathPoint(Common::Point &to) { } -uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& stop) { +uint32 PathBuilder_NS::buildSubPath(const Common::Point& pos, const Common::Point& stop) { uint32 v28 = 0; uint32 v2C = 0; @@ -103,16 +113,15 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& while (true) { - WalkNodeList::iterator nearest = _vm->_location._walkNodes.end(); - WalkNodeList::iterator locNode = _vm->_location._walkNodes.begin(); + PointList::iterator nearest = _vm->_location._walkPoints.end(); + PointList::iterator locNode = _vm->_location._walkPoints.begin(); // scans location path nodes searching for the nearest Node // which can't be farther than the target position // otherwise no _closest_node is selected - while (locNode != _vm->_location._walkNodes.end()) { + while (locNode != _vm->_location._walkPoints.end()) { - Common::Point v8; - (*locNode)->getPoint(v8); + Common::Point v8 = *locNode; v2C = v8.sqrDist(stop); v28 = v8.sqrDist(v20); @@ -124,80 +133,59 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& locNode++; } - if (nearest == _vm->_location._walkNodes.end()) break; + if (nearest == _vm->_location._walkPoints.end()) break; - (*nearest)->getPoint(v20); + v20 = *nearest; v34 = v30 = v20.sqrDist(stop); - _subPath.push_back(WalkNodePtr(new WalkNode(**nearest))); + _subPath.push_back(*nearest); } return v34; } -#if 0 -void printNodes(WalkNodeList *list, const char* text) { - printf("%s\n-------------------\n", text); - for (WalkNodeList::iterator it = list->begin(); it != list->end(); it++) - printf("node [%p] (%i, %i)\n", *it, (*it)->_x, (*it)->_y); - return; -} -#endif // // x, y: mouse click (foot) coordinates // -WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) { +void PathBuilder_NS::buildPath(uint16 x, uint16 y) { debugC(1, kDebugWalk, "PathBuilder::buildPath to (%i, %i)", x, y); + _ch->_walkPath.clear(); + Common::Point to(x, y); correctPathPoint(to); debugC(1, kDebugWalk, "found closest path point at (%i, %i)", to.x, to.y); - WalkNodePtr v48(new WalkNode(to.x, to.y)); - WalkNodePtr v44 = v48; + Common::Point v48(to); + Common::Point v44(to); - uint16 v38 = walkFunc1(to.x, to.y, v44); + uint16 v38 = walkFunc1(to, v44); if (v38 == 1) { // destination directly reachable debugC(1, kDebugWalk, "direct move to (%i, %i)", to.x, to.y); - - _list = new WalkNodeList; - _list->push_back(v48); - return _list; + _ch->_walkPath.push_back(v48); + return; } // path is obstructed: look for alternative - _list = new WalkNodeList; - _list->push_back(v48); -#if 0 - printNodes(_list, "start"); -#endif - - Common::Point stop(v48->_x, v48->_y); + _ch->_walkPath.push_back(v48); Common::Point pos; - _vm->_char.getFoot(pos); + _ch->getFoot(pos); - uint32 v34 = buildSubPath(pos, stop); + uint32 v34 = buildSubPath(pos, v48); if (v38 != 0 && v34 > v38) { // no alternative path (gap?) - _list->clear(); - _list->push_back(v44); - return _list; + _ch->_walkPath.clear(); + _ch->_walkPath.push_back(v44); + return; } - _list->insert(_list->begin(), _subPath.begin(), _subPath.end()); -#if 0 - printNodes(_list, "first segment"); -#endif + _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end()); - (*_list->begin())->getPoint(stop); - buildSubPath(pos, stop); - _list->insert(_list->begin(), _subPath.begin(), _subPath.end()); -#if 0 - printNodes(_list, "complete"); -#endif + buildSubPath(pos, *_ch->_walkPath.begin()); + _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end()); - return _list; + return; } @@ -208,23 +196,23 @@ WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) { // 1 : Point reachable in a straight line // other values: square distance to target (point not reachable in a straight line) // -uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { +uint16 PathBuilder_NS::walkFunc1(const Common::Point &to, Common::Point& node) { - Common::Point arg(x, y); + Common::Point arg(to); - Common::Point v4(0, 0); + Common::Point v4; Common::Point foot; - _vm->_char.getFoot(foot); + _ch->getFoot(foot); Common::Point v8(foot); while (foot != arg) { - if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) != 0) foot.x++; - if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) != 0) foot.x--; - if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) != 0) foot.y++; - if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) != 0) foot.y--; + if (foot.x < to.x && IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++; + if (foot.x > to.x && IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--; + if (foot.y < to.y && IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++; + if (foot.y > to.y && IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--; if (foot == v8 && foot != arg) { @@ -234,10 +222,10 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { while (foot != arg) { - if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) == 0) foot.x++; - if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) == 0) foot.x--; - if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) == 0) foot.y++; - if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) == 0) foot.y--; + if (foot.x < to.x && !IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++; + if (foot.x > to.x && !IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--; + if (foot.y < to.y && !IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++; + if (foot.y > to.y && !IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--; if (foot == v8 && foot != arg) return 0; @@ -245,10 +233,8 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { v8 = foot; } - Node->_x = v4.x; - Node->_y = v4.y; - - return (x - v4.x) * (x - v4.x) + (y - v4.y) * (y - v4.y); + node = v4; + return v4.sqrDist(to); } v8 = foot; @@ -259,190 +245,390 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) { return 1; } -void Parallaction::clipMove(Common::Point& pos, const WalkNodePtr from) { +void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) { - if ((pos.x < from->_x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) { - pos.x = (pos.x + 2 < from->_x) ? pos.x + 2 : from->_x; + if ((pos.x < to.x) && (pos.x < _vm->_pathBuffer->w) && IS_PATH_CLEAR(pos.x + 2, pos.y)) { + pos.x = (pos.x + 2 < to.x) ? pos.x + 2 : to.x; } - if ((pos.x > from->_x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) { - pos.x = (pos.x - 2 > from->_x) ? pos.x - 2 : from->_x; + if ((pos.x > to.x) && (pos.x > 0) && IS_PATH_CLEAR(pos.x - 2, pos.y)) { + pos.x = (pos.x - 2 > to.x) ? pos.x - 2 : to.x; } - if ((pos.y < from->_y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) { - pos.y = (pos.y + 2 <= from->_y) ? pos.y + 2 : from->_y; + if ((pos.y < to.y) && (pos.y < _vm->_pathBuffer->h) && IS_PATH_CLEAR(pos.x, pos.y + 2)) { + pos.y = (pos.y + 2 <= to.y) ? pos.y + 2 : to.y; } - if ((pos.y > from->_y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) { - pos.y = (pos.y - 2 >= from->_y) ? pos.y - 2 :from->_y; + if ((pos.y > to.y) && (pos.y > 0) && IS_PATH_CLEAR(pos.x, pos.y - 2)) { + pos.y = (pos.y - 2 >= to.y) ? pos.y - 2 : to.y; } return; } -int16 Parallaction::selectWalkFrame(const Common::Point& pos, const WalkNodePtr from) { - Common::Point dist(from->_x - pos.x, from->_y - pos.y); +void PathWalker_NS::checkDoor(const Common::Point &foot) { - if (dist.x < 0) - dist.x = -dist.x; - if (dist.y < 0) - dist.y = -dist.y; + ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); + if (z) { + if ((z->_flags & kFlagsClosed) == 0) { + _vm->_location._startPosition = z->u.door->_startPos; + _vm->_location._startFrame = z->u.door->_startFrame; + _vm->scheduleLocationSwitch(z->u.door->_location); + _vm->_zoneTrap = nullZonePtr; + } else { + _vm->_cmdExec->run(z->_commands, z); + } + } - walkData1++; + z = _vm->hitZone(kZoneTrap, foot.x, foot.y); + if (z) { + _vm->setLocationFlags(kFlagsEnter); + _vm->_cmdExec->run(z->_commands, z); + _vm->clearLocationFlags(kFlagsEnter); + _vm->_zoneTrap = z; + } else + if (_vm->_zoneTrap) { + _vm->setLocationFlags(kFlagsExit); + _vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap); + _vm->clearLocationFlags(kFlagsExit); + _vm->_zoneTrap = nullZonePtr; + } - // walk frame selection - int16 v16; - if (_char._ani->getFrameNum() == 20) { +} - if (dist.x > dist.y) { - walkData2 = (from->_x > pos.x) ? 0 : 7; - walkData1 %= 12; - v16 = walkData1 / 2; - } else { - walkData2 = (from->_y > pos.y) ? 14 : 17; - walkData1 %= 8; - v16 = walkData1 / 4; + +void PathWalker_NS::finalizeWalk() { + _engineFlags &= ~kEngineWalking; + + Common::Point foot; + _ch->getFoot(foot); + checkDoor(foot); + + _ch->_walkPath.clear(); +} + +void PathWalker_NS::walk() { + if ((_engineFlags & kEngineWalking) == 0) { + return; + } + + Common::Point curPos; + _ch->getFoot(curPos); + + // update target, if previous was reached + PointList::iterator it = _ch->_walkPath.begin(); + if (it != _ch->_walkPath.end()) { + if (*it == curPos) { + debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it).x, (*it).y); + it = _ch->_walkPath.erase(it); } + } + // advance character towards the target + Common::Point targetPos; + if (it == _ch->_walkPath.end()) { + debugC(1, kDebugWalk, "walk reached last node"); + finalizeWalk(); + targetPos = curPos; } else { + // targetPos is saved to help setting character direction + targetPos = *it; - if (dist.x > dist.y) { - walkData2 = (from->_x > pos.x) ? 0 : 9; - walkData1 %= 16; - v16 = walkData1 / 2; - } else { - walkData2 = (from->_y > pos.y) ? 18 : 21; - walkData1 %= 8; - v16 = walkData1 / 4; - } + Common::Point newPos(curPos); + clipMove(newPos, targetPos); + _ch->setFoot(newPos); + if (newPos == curPos) { + debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle"); + finalizeWalk(); + targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected + } } - return v16; + // targetPos is used to select the direction (and the walkFrame) of a character, + // since it doesn't cause the sudden changes in orientation that newPos would. + // Since newPos is 'adjusted' according to walkable areas, an imaginary line drawn + // from curPos to newPos is prone to abrutply change in direction, thus making the + // code select 'too different' frames when walking diagonally against obstacles, + // and yielding an annoying shaking effect in the character. + _ch->updateDirection(curPos, targetPos); } -uint16 Parallaction::checkDoor() { -// printf("checkDoor()..."); - if (_currentLocationIndex != _doorData1) { - _doorData1 = _currentLocationIndex; - _zoneTrap = nullZonePtr; - } - _engineFlags &= ~kEngineWalking; +PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) { +} - Common::Point foot; - _char.getFoot(foot); - ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y); +bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) { - if (z) { + Common::Point copy(from); + Common::Point p(copy); - if ((z->_flags & kFlagsClosed) == 0) { - _location._startPosition = z->u.door->_startPos; - _location._startFrame = z->u.door->_startFrame; + while (p != to) { - scheduleLocationSwitch(z->u.door->_location); - _zoneTrap = nullZonePtr; + if (p.x < to.x && IS_PATH_CLEAR(p.x + 1, p.y)) p.x++; + if (p.x > to.x && IS_PATH_CLEAR(p.x - 1, p.y)) p.x--; + if (p.y < to.y && IS_PATH_CLEAR(p.x, p.y + 1)) p.y++; + if (p.y > to.y && IS_PATH_CLEAR(p.x, p.y - 1)) p.y--; - } else { - runCommands(z->_commands, z); + if (p == copy && p != to) { + return false; } + + copy = p; } - _char.getFoot(foot); - z = hitZone(kZoneTrap, foot.x, foot.y); + return true; +} - if (z) { - setLocationFlags(kFlagsEnter); - runCommands(z->_commands, z); - clearLocationFlags(kFlagsEnter); - _zoneTrap = z; - } else - if (_zoneTrap) { - setLocationFlags(kFlagsExit); - runCommands(_zoneTrap->_commands, _zoneTrap); - clearLocationFlags(kFlagsExit); - _zoneTrap = nullZonePtr; +void PathBuilder_BR::buildPath(uint16 x, uint16 y) { + Common::Point foot; + _ch->getFoot(foot); + + debugC(1, kDebugWalk, "buildPath: from (%i, %i) to (%i, %i)", foot.x, foot.y, x, y); + _ch->_walkPath.clear(); + + // look for easy path first + Common::Point dest(x, y); + if (directPathExists(foot, dest)) { + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: direct path found"); + return; + } + + // look for short circuit cases + ZonePtr z0 = _vm->hitZone(kZonePath, x, y); + if (!z0) { + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: corner case 0"); + return; + } + ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y); + if (!z1 || z1 == z0) { + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: corner case 1"); + return; + } + + // build complex path + int id = atoi(z0->_name); + + if (z1->u.path->_lists[id].empty()) { + _ch->_walkPath.clear(); + debugC(3, kDebugWalk, "buildPath: no path"); + return; } -// printf("done\n"); + PointList::iterator b = z1->u.path->_lists[id].begin(); + PointList::iterator e = z1->u.path->_lists[id].end(); + for ( ; b != e; b++) { + _ch->_walkPath.push_front(*b); + } + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: complex path"); - _char._ani->_frame = walkData2; - return _char._ani->_frame; + return; } +PathBuilder_BR::PathBuilder_BR(Character *ch) : PathBuilder(ch) { +} + +void PathWalker_BR::finalizeWalk() { + _engineFlags &= ~kEngineWalking; + _first = true; + _fieldC = 1; + + Common::Point foot; + _ch->getFoot(foot); + + ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); + if (z && ((z->_flags & kFlagsClosed) == 0)) { + _vm->_location._startPosition = z->u.door->_startPos; // foot pos + _vm->_location._startFrame = z->u.door->_startFrame; + +#if 0 + // TODO: implement working follower. Must find out a location in which the code is + // used and which is stable enough. + _followerFootInit.x = -1; + if (_follower && z->u.door->startPos2.x != -1) { + _followerFootInit.x = z->u.door->startPos2.x; // foot pos + _followerFootInit.y = z->u.door->startPos2.y; // foot pos + } + _followerFootInit.z = -1; + if (_follower && z->u.door->startPos2.z != -1) { + _followerFootInit.z = z->u.door->startPos2.z; // foot pos + } +#endif + + _vm->scheduleLocationSwitch(z->u.door->_location); + _vm->_cmdExec->run(z->_commands, z); + } + +#if 0 + // TODO: Input::walkTo must be extended to support destination frame in addition to coordinates + // TODO: the frame argument must be passed to PathWalker through PathBuilder, so probably + // a merge between the two Path managers is the right solution + if (_engineFlags & FINAL_WALK_FRAME) { // this flag is set in readInput() + _engineFlags &= ~FINAL_WALK_FRAME; + _char.ani->_frame = _moveToF; // from readInput()... + } else { + _char.ani->_frame = _dirFrame; // from walk() + } + _char.setFoot(foot); +#endif + + _ch->_ani->_frame = _dirFrame; // temporary solution + +#if 0 + // TODO: support scrolling ;) + if (foot.x > _gfx->hscroll + 600) _gfx->scrollRight(78); + if (foot.x < _gfx->hscroll + 40) _gfx->scrollLeft(78); + if (foot.y > 350) _gfx->scrollDown(100); + if (foot.y < 80) _gfx->scrollUp(100); +#endif -void Parallaction::finalizeWalk(WalkNodeList *list) { - checkDoor(); - delete list; + return; } -void Parallaction_ns::walk() { + +void PathWalker_BR::walk() { if ((_engineFlags & kEngineWalking) == 0) { return; } - WalkNodeList *list = _char._walkPath; +#if 0 + // TODO: support delays in walking. This requires extending Input::walkIo(). + if (ch._walkDelay > 0) { + ch._walkDelay--; + if (ch._walkDelay == 0 && _ch._ani->_scriptName) { + // stop script and reset + _ch._ani->_flags &= ~kFlagsActing; + Script *script = findScript(_ch._ani->_scriptName); + script->_nextCommand = script->firstCommand; + } + return; + } +#endif - _char._ani->_oldPos.x = _char._ani->_left; - _char._ani->_oldPos.y = _char._ani->_top; + GfxObj *obj = _ch->_ani->gfxobj; - Common::Point pos; - _char.getFoot(pos); + Common::Rect rect; + obj->getRect(_ch->_ani->_frame, rect); - WalkNodeList::iterator it = list->begin(); + uint scale; + if (rect.bottom > _vm->_location._zeta0) { + scale = 100; + } else + if (rect.bottom < _vm->_location._zeta1) { + scale = _vm->_location._zeta2; + } else { + scale = _vm->_location._zeta2 + ((rect.bottom - _vm->_location._zeta1) * (100 - _vm->_location._zeta2)) / (_vm->_location._zeta0 - _vm->_location._zeta1); + } + int xStep = (scale * 16) / 100 + 1; + int yStep = (scale * 10) / 100 + 1; + + debugC(9, kDebugWalk, "calculated step: (%i, %i)\n", xStep, yStep); + + if (_fieldC == 0) { + _ch->_walkPath.erase(_ch->_walkPath.begin()); - if (it != list->end()) { - if ((*it)->_x == pos.x && (*it)->_y == pos.y) { - debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y); - it = list->erase(it); + if (_ch->_walkPath.empty()) { + finalizeWalk(); + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 0\n"); + return; + } else { + debugC(3, kDebugWalk, "PathWalker_BR::walk, moving to next node\n"); } } - if (it == list->end()) { - debugC(1, kDebugWalk, "walk reached last node"); -// j->_finished = 1; - finalizeWalk(list); - return; - } - _char._walkPath = list; - // selectWalkFrame must be performed before position is changed by clipMove - int16 v16 = selectWalkFrame(pos, *it); - clipMove(pos, *it); + _ch->getFoot(_startFoot); - _char.setFoot(pos); + _fieldC = 0; + _step++; + _step %= 8; - Common::Point newpos(_char._ani->_left, _char._ani->_top); + int walkFrame = _step; + _dirFrame = 0; + Common::Point newpos(_startFoot), delta; - if (newpos == _char._ani->_oldPos) { - debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle"); -// j->_finished = 1; - finalizeWalk(list); - } else { - _char._ani->_frame = v16 + walkData2 + 1; + Common::Point p(*_ch->_walkPath.begin()); + + if (_startFoot.y < p.y && _startFoot.y < 400 && IS_PATH_CLEAR(_startFoot.x, yStep + _startFoot.y)) { + if (yStep + _startFoot.y <= p.y) { + _fieldC = 1; + delta.y = yStep; + newpos.y = yStep + _startFoot.y; + } else { + delta.y = p.y - _startFoot.y; + newpos.y = p.y; + } + _dirFrame = 9; + } else + if (_startFoot.y > p.y && _startFoot.y > 0 && IS_PATH_CLEAR(_startFoot.x, _startFoot.y - yStep)) { + if (_startFoot.y - yStep >= p.y) { + _fieldC = 1; + delta.y = yStep; + newpos.y = _startFoot.y - yStep; + } else { + delta.y = _startFoot.y - p.y; + newpos.y = p.y; + } + _dirFrame = 0; } - return; -} + if (_startFoot.x < p.x && _startFoot.x < 640 && IS_PATH_CLEAR(_startFoot.x + xStep, _startFoot.y)) { + if (_startFoot.x + xStep <= p.x) { + _fieldC = 1; + delta.x = xStep; + newpos.x = xStep + _startFoot.x; + } else { + delta.x = p.x - _startFoot.x; + newpos.x = p.x; + } + if (delta.y < delta.x) { + _dirFrame = 18; // right + } + } else + if (_startFoot.x > p.x && _startFoot.x > 0 && IS_PATH_CLEAR(_startFoot.x - xStep, _startFoot.y)) { + if (_startFoot.x - xStep >= p.x) { + _fieldC = 1; + delta.x = xStep; + newpos.x = _startFoot.x - xStep; + } else { + delta.x = _startFoot.x - p.x; + newpos.x = p.x; + } + if (delta.y < delta.x) { + _dirFrame = 27; // left + } + } + debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i \n", _startFoot.x, _startFoot.y, p.x, p.y, delta.x, delta.y); -WalkNode::WalkNode() : _x(0), _y(0) { -} + if (_fieldC) { + debugC(9, kDebugWalk, "PathWalker_BR::walk, foot moved from (%i, %i) to (%i, %i)\n", _startFoot.x, _startFoot.y, newpos.x, newpos.y); + _ch->_ani->_frame = walkFrame + _dirFrame + 1; + _startFoot.x = newpos.x; + _startFoot.y = newpos.y; + _ch->setFoot(_startFoot); + _ch->_ani->_z = newpos.y; + } -WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) { -} + if (_fieldC || !_ch->_walkPath.empty()) { +// checkTrap(); + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n"); + return; + } -WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) { + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n"); + finalizeWalk(); + return; } -void WalkNode::getPoint(Common::Point &p) const { - p.x = _x; - p.y = _y; -} +PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) { -PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) { } diff --git a/engines/parallaction/walk.h b/engines/parallaction/walk.h index 788a6e1375..8d21e5ebbd 100644 --- a/engines/parallaction/walk.h +++ b/engines/parallaction/walk.h @@ -29,43 +29,89 @@ #include "common/ptr.h" #include "common/list.h" +#include "parallaction/objects.h" + + namespace Parallaction { -struct Animation; +struct Character; + +class PathBuilder { -struct WalkNode { - int16 _x; - int16 _y; +protected: + Character *_ch; public: - WalkNode(); - WalkNode(int16 x, int16 y); - WalkNode(const WalkNode& w); + PathBuilder(Character *ch) : _ch(ch) { } + virtual ~PathBuilder() { } - void getPoint(Common::Point &p) const; + virtual void buildPath(uint16 x, uint16 y) = 0; }; -typedef Common::SharedPtr<WalkNode> WalkNodePtr; -typedef Common::List<WalkNodePtr> WalkNodeList; +class PathBuilder_NS : public PathBuilder { -class PathBuilder { - - AnimationPtr _anim; - - WalkNodeList *_list; - WalkNodeList _subPath; + PointList *_list; + PointList _subPath; void correctPathPoint(Common::Point &to); uint32 buildSubPath(const Common::Point& pos, const Common::Point& stop); - uint16 walkFunc1(int16 x, int16 y, WalkNodePtr Node); + uint16 walkFunc1(const Common::Point &to, Common::Point& node); public: - PathBuilder(AnimationPtr anim); - WalkNodeList* buildPath(uint16 x, uint16 y); + PathBuilder_NS(Character *ch); + void buildPath(uint16 x, uint16 y); +}; + +class PathBuilder_BR : public PathBuilder { + + bool directPathExists(const Common::Point &from, const Common::Point &to); + +public: + PathBuilder_BR(Character *ch); + void buildPath(uint16 x, uint16 y); +}; + +class PathWalker { +protected: + Character *_ch; +public: + PathWalker(Character *ch) : _ch(ch) { } + virtual ~PathWalker() { } + virtual void walk() = 0; }; +class PathWalker_NS : public PathWalker { + + + void finalizeWalk(); + void clipMove(Common::Point& pos, const Common::Point& to); + void checkDoor(const Common::Point &foot); + +public: + PathWalker_NS(Character *ch) : PathWalker(ch) { } + void walk(); +}; + + +class PathWalker_BR : public PathWalker { + + + int _walkDelay; + int _fieldC; + Common::Point _startFoot; + bool _first; + int _step; + + int _dirFrame; + + void finalizeWalk(); + +public: + PathWalker_BR(Character *ch); + void walk(); +}; } diff --git a/engines/queen/graphics.cpp b/engines/queen/graphics.cpp index f863f7663c..6d0a11ccfe 100644 --- a/engines/queen/graphics.cpp +++ b/engines/queen/graphics.cpp @@ -1175,15 +1175,8 @@ BamScene::BamScene(QueenEngine *vm) } void BamScene::playSfx() { - // Don't try to play all the sounds. This is only necessary for the - // fight bam, in which the number of 'sfx bam frames' is too much - // important / too much closer. The original game does not have - // this problem since its playSfx() function returns immediately - // if a sound is already being played. - if (_lastSoundIndex == 0 || _index - _lastSoundIndex >= SFX_SKIP) { - _vm->sound()->playSfx(_vm->logic()->currentRoomSfx()); - _lastSoundIndex = _index; - } + _vm->sound()->playSfx(_vm->logic()->currentRoomSfx()); + _lastSoundIndex = _index; } void BamScene::prepareAnimation() { diff --git a/engines/queen/graphics.h b/engines/queen/graphics.h index 6f00111635..7eadf9a191 100644 --- a/engines/queen/graphics.h +++ b/engines/queen/graphics.h @@ -248,10 +248,6 @@ public: F_REQ_STOP = 2 }; - enum { - SFX_SKIP = 8 - }; - uint16 _flag, _index; private: diff --git a/engines/queen/input.cpp b/engines/queen/input.cpp index 146e95bcef..9f03c341c9 100644 --- a/engines/queen/input.cpp +++ b/engines/queen/input.cpp @@ -27,6 +27,7 @@ #include "common/events.h" #include "common/system.h" +#include "queen/queen.h" #include "queen/input.h" namespace Queen { @@ -51,12 +52,12 @@ const Verb Input::_verbKeys[] = { VERB_USE }; -Input::Input(Common::Language language, OSystem *system) : +Input::Input(Common::Language language, OSystem *system, QueenEngine *vm) : _system(system), _eventMan(system->getEventManager()), _fastMode(false), _keyVerb(VERB_NONE), _cutawayRunning(false), _canQuit(false), _cutawayQuit(false), _dialogueRunning(false), _talkQuit(false), _quickSave(false), _quickLoad(false), _debugger(false), _inKey(Common::KEYCODE_INVALID), - _mouseButton(0), _idleTime(0) { + _mouseButton(0), _idleTime(0) , _vm(vm) { switch (language) { case Common::EN_ANY: @@ -119,8 +120,8 @@ void Input::delay(uint amount) { break; case Common::EVENT_QUIT: - _system->quit(); - break; + _vm->quitGame(); + return; default: break; diff --git a/engines/queen/input.h b/engines/queen/input.h index 86092aeed6..43a57729c6 100644 --- a/engines/queen/input.h +++ b/engines/queen/input.h @@ -49,7 +49,7 @@ public: MOUSE_RBUTTON = 2 }; - Input(Common::Language language, OSystem *system); + Input(Common::Language language, OSystem *system, QueenEngine *vm); void delay(uint amount); @@ -99,6 +99,8 @@ private: Common::EventManager *_eventMan; + QueenEngine *_vm; + //! some cutaways require update() run faster bool _fastMode; diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp index bfbcfa4e59..0327fb74b8 100644 --- a/engines/queen/journal.cpp +++ b/engines/queen/journal.cpp @@ -85,8 +85,8 @@ void Journal::use() { handleMouseWheel(1); break; case Common::EVENT_QUIT: - _system->quit(); - break; + _vm->quitGame(); + return; default: break; } diff --git a/engines/queen/queen.cpp b/engines/queen/queen.cpp index d1a1247c46..c95e44b477 100644 --- a/engines/queen/queen.cpp +++ b/engines/queen/queen.cpp @@ -418,7 +418,7 @@ int QueenEngine::init() { _display = new Display(this, _system); _graphics = new Graphics(this); _grid = new Grid(this); - _input = new Input(_resource->getLanguage(), _system); + _input = new Input(_resource->getLanguage(), _system, this); if (_resource->isDemo()) { _logic = new LogicDemo(this); diff --git a/engines/queen/resource.cpp b/engines/queen/resource.cpp index 5a8db74e3b..b3bd663baf 100644 --- a/engines/queen/resource.cpp +++ b/engines/queen/resource.cpp @@ -106,7 +106,7 @@ ResourceEntry *Resource::resourceEntry(const char *filename) const { re = &_resourceTable[cur]; break; } - } while (cur++ < _resourceEntries); + } while (++cur < _resourceEntries); #endif return re; } diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp index d70fe7209d..27cf1bf6a2 100644 --- a/engines/queen/sound.cpp +++ b/engines/queen/sound.cpp @@ -35,6 +35,7 @@ #include "queen/queen.h" #include "queen/resource.h" +#include "sound/audiostream.h" #include "sound/flac.h" #include "sound/mididrv.h" #include "sound/mp3.h" @@ -45,6 +46,42 @@ namespace Queen { +// The sounds in the PC versions are all played at 11840 Hz. Unfortunately, we +// did not know that at the time, so there are plenty of compressed versions +// which claim that they should be played at 11025 Hz. This "wrapper" class +// works around that. + +class AudioStreamWrapper : public Audio::AudioStream { +protected: + Audio::AudioStream *_stream; + +public: + AudioStreamWrapper(Audio::AudioStream *stream) { + _stream = stream; + } + ~AudioStreamWrapper() { + delete _stream; + } + int readBuffer(int16 *buffer, const int numSamples) { + return _stream->readBuffer(buffer, numSamples); + } + bool isStereo() const { + return _stream->isStereo(); + } + bool endOfData() const { + return _stream->endOfData(); + } + bool endOfStream() { + return _stream->endOfStream(); + } + int getRate() const { + return 11840; + } + int32 getTotalPlayTime() { + return _stream->getTotalPlayTime(); + } +}; + class SilentSound : public PCSound { public: SilentSound(Audio::Mixer *mixer, QueenEngine *vm) : PCSound(mixer, vm) {} @@ -69,7 +106,7 @@ protected: void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) { Common::MemoryReadStream *tmp = f->readStream(size); assert(tmp); - _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeMP3Stream(tmp, true)); + _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeMP3Stream(tmp, true))); } }; #endif @@ -82,7 +119,7 @@ protected: void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) { Common::MemoryReadStream *tmp = f->readStream(size); assert(tmp); - _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeVorbisStream(tmp, true)); + _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeVorbisStream(tmp, true))); } }; #endif @@ -95,7 +132,7 @@ protected: void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) { Common::MemoryReadStream *tmp = f->readStream(size); assert(tmp); - _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeFlacStream(tmp, true)); + _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeFlacStream(tmp, true))); } }; #endif // #ifdef USE_FLAC @@ -229,11 +266,6 @@ void PCSound::setVolume(int vol) { _music->setVolume(vol); } -void PCSound::waitFinished(bool isSpeech) { - while (_mixer->isSoundHandleActive(isSpeech ? _speechHandle : _sfxHandle)) - _vm->input()->delay(10); -} - void PCSound::playSound(const char *base, bool isSpeech) { char name[13]; strcpy(name, base); @@ -243,7 +275,13 @@ void PCSound::playSound(const char *base, bool isSpeech) { name[i] = '0'; } strcat(name, ".SB"); - waitFinished(isSpeech); + if (isSpeech) { + while (_mixer->isSoundHandleActive(_speechHandle)) { + _vm->input()->delay(10); + } + } else { + _mixer->stopHandle(_sfxHandle); + } uint32 size; Common::File *f = _vm->resource()->findSound(name, &size); if (f) { @@ -255,6 +293,8 @@ void PCSound::playSound(const char *base, bool isSpeech) { } void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) { + // In order to simplify the code, we don't parse the .sb header but hard-code the + // values. Refer to tracker item #1876741 for details on the format/fields. int headerSize; f->seek(2, SEEK_CUR); uint16 version = f->readUint16LE(); @@ -276,7 +316,7 @@ void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *so if (sound) { f->read(sound, size); byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE; - _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11025, flags); + _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11840, flags); } } diff --git a/engines/queen/sound.h b/engines/queen/sound.h index c2c1481cc6..331034f746 100644 --- a/engines/queen/sound.h +++ b/engines/queen/sound.h @@ -143,7 +143,6 @@ public: void setVolume(int vol); protected: - void waitFinished(bool isSpeech); void playSound(const char *base, bool isSpeech); virtual void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) = 0; diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp index 3a1e510529..9fffb0f8bf 100644 --- a/engines/saga/animation.cpp +++ b/engines/saga/animation.cpp @@ -55,6 +55,7 @@ Anim::Anim(SagaEngine *vm) : _vm(vm) { Anim::~Anim(void) { reset(); + freeCutawayList(); } void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) { diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp index e936117894..482b3a4c82 100644 --- a/engines/saga/font.cpp +++ b/engines/saga/font.cpp @@ -63,6 +63,8 @@ Font::~Font(void) { free(_fonts[i]); } + + free(_fonts); } @@ -238,6 +240,13 @@ void Font::createOutline(FontData *font) { } } +int Font::translateChar(int charId) { + if (charId <= 127) + return charId; // normal character + else + return _charMap[charId - 128]; // extended character +} + // Returns the horizontal length in pixels of the graphical representation // of at most 'count' characters of the string 'text', taking // into account any formatting options specified by 'flags'. @@ -257,7 +266,7 @@ int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffe for (ct = count; *txt && (!count || ct > 0); txt++, ct--) { ch = *txt & 0xFFU; // Translate character - ch = _charMap[ch]; + ch = translateChar(ch); assert(ch < FONT_CHARCOUNT); width += font->normal.fontCharEntry[ch].tracking; } @@ -336,11 +345,11 @@ void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, siz // Don't do any special font mapping for the Italian fan // translation of ITE if (_vm->getLanguage() != Common::IT_ITA) - c_code = _charMap[c_code]; + c_code = translateChar(c_code); } } else if (_fontMapping == 1) { // Force font mapping - c_code = _charMap[c_code]; + c_code = translateChar(c_code); } else { // In all other cases, ignore font mapping } diff --git a/engines/saga/font.h b/engines/saga/font.h index 6b930ddca0..76c0f06725 100644 --- a/engines/saga/font.h +++ b/engines/saga/font.h @@ -158,6 +158,7 @@ class Font { }; Font::FontId knownFont2FontIdx(KnownFont font); + int translateChar(int charId); int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags); int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags); @@ -196,7 +197,7 @@ class Font { return byteLength; } - static const int _charMap[256]; + static const int _charMap[128]; SagaEngine *_vm; bool _initialized; diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp index 6246cb71da..6abaeea151 100644 --- a/engines/saga/font_map.cpp +++ b/engines/saga/font_map.cpp @@ -32,135 +32,8 @@ namespace Saga { -const int Font::_charMap[256] = { - 0, // 0 - 1, // 1 - 2, // 2 - 3, // 3 - 4, // 4 - 5, // 5 - 6, // 6 - 7, // 7 - 8, // 8 - 9, // 9 - 10, // 10 - 11, // 11 - 12, // 12 - 13, // 13 - 14, // 14 - 15, // 15 - 16, // 16 - 17, // 17 - 18, // 18 - 19, // 19 - 20, // 20 - 21, // 21 - 22, // 22 - 23, // 23 - 24, // 24 - 25, // 25 - 26, // 26 - 27, // 27 - 28, // 28 - 29, // 29 - 30, // 30 - 31, // 31 - 32, // 32 - 33, // 33 - 34, // 34 - 35, // 35 - 36, // 36 - 37, // 37 - 38, // 38 - 39, // 39 - 40, // 40 - 41, // 41 - 42, // 42 - 43, // 43 - 44, // 44 - 45, // 45 - 46, // 46 - 47, // 47 - 48, // 48 - 49, // 49 - 50, // 50 - 51, // 51 - 52, // 52 - 53, // 53 - 54, // 54 - 55, // 55 - 56, // 56 - 57, // 57 - 58, // 58 - 59, // 59 - 60, // 60 - 61, // 61 - 62, // 62 - 63, // 63 - 64, // 64 - 65, // 65 - 66, // 66 - 67, // 67 - 68, // 68 - 69, // 69 - 70, // 70 - 71, // 71 - 72, // 72 - 73, // 73 - 74, // 74 - 75, // 75 - 76, // 76 - 77, // 77 - 78, // 78 - 79, // 79 - 80, // 80 - 81, // 81 - 82, // 82 - 83, // 83 - 84, // 84 - 85, // 85 - 86, // 86 - 87, // 87 - 88, // 88 - 89, // 89 - 90, // 90 - 91, // 91 - 92, // 92 - 93, // 93 - 94, // 94 - 95, // 95 - 96, // 96 - 97, // 97 - 98, // 98 - 99, // 99 - 100, // 100 - 101, // 101 - 102, // 102 - 103, // 103 - 104, // 104 - 105, // 105 - 106, // 106 - 107, // 107 - 108, // 108 - 109, // 109 - 110, // 110 - 111, // 111 - 112, // 112 - 113, // 113 - 114, // 114 - 115, // 115 - 116, // 116 - 117, // 117 - 118, // 118 - 119, // 119 - 120, // 120 - 121, // 121 - 122, // 122 - 123, // 123 - 124, // 124 - 125, // 125 - 126, // 126 - 127, // 127 +const int Font::_charMap[128] = { + // Characters 0 - 127 are mapped directly to ISO 8859-1 199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA 252, // 129 LATIN SMALL LETTER U WITH DIAERESIS 233, // 130 LATIN SMALL LETTER E WITH ACUTE diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp index 7380570a99..1d048baaad 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -334,7 +334,21 @@ Interface::Interface(SagaEngine *vm) : _vm(vm) { Interface::~Interface(void) { free(_inventory); + free(_mainPanel.image); + free(_conversePanel.image); + free(_optionPanel.image); + free(_quitPanel.image); + free(_loadPanel.image); + free(_savePanel.image); + _mainPanel.sprites.freeMem(); + _conversePanel.sprites.freeMem(); + _optionPanel.sprites.freeMem(); + _quitPanel.sprites.freeMem(); + _loadPanel.sprites.freeMem(); + _savePanel.sprites.freeMem(); + _protectPanel.sprites.freeMem(); + _defPortraits.freeMem(); _scenePortraits.freeMem(); } diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp index 5f1d0157d5..6614f4098f 100644 --- a/engines/saga/introproc_ihnm.cpp +++ b/engines/saga/introproc_ihnm.cpp @@ -132,6 +132,8 @@ void Scene::IHNMLoadCutaways() { // Load the cutaways for the title screens _vm->_anim->loadCutawayList(resourcePointer, resourceLength); + + free(resourcePointer); } bool Scene::checkKey() { diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp index b7d4f4f1bd..e150caeca5 100644 --- a/engines/saga/rscfile.cpp +++ b/engines/saga/rscfile.cpp @@ -769,6 +769,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) { _vm->_sprite->_mainSprites.freeMem(); _vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites); + _vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID); _vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength); @@ -806,6 +807,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) { // The IHNM demo has a fixed music track and doesn't load a song table _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1); _vm->_music->play(3, MUSIC_LOOP); + free(resourcePointer); } int voiceLUTResourceID = 0; diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 40eb32b276..fafbd02cec 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -79,6 +79,7 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc) _scene = NULL; _isoMap = NULL; _gfx = NULL; + _driver = NULL; _console = NULL; _render = NULL; _music = NULL; @@ -133,6 +134,7 @@ SagaEngine::~SagaEngine() { delete _render; delete _music; delete _sound; + delete _driver; delete _gfx; delete _console; @@ -188,11 +190,11 @@ int SagaEngine::init() { bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); bool adlib = (midiDriver == MD_ADLIB); - MidiDriver *driver = MidiDriver::createMidi(midiDriver); + _driver = MidiDriver::createMidi(midiDriver); if (native_mt32) - driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - _music = new Music(this, _mixer, driver, _musicVolume); + _music = new Music(this, _mixer, _driver, _musicVolume); _music->setNativeMT32(native_mt32); _music->setAdlib(adlib); diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 4a5fae7ddb..6b6eb6b3fb 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -29,6 +29,7 @@ #include "engines/engine.h" #include "common/stream.h" +#include "sound/mididrv.h" #include "saga/gfx.h" #include "saga/list.h" @@ -531,6 +532,7 @@ public: SndRes *_sndRes; Sound *_sound; Music *_music; + MidiDriver *_driver; Anim *_anim; Render *_render; IsoMap *_isoMap; diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp index 7664af314f..088be34c72 100644 --- a/engines/saga/script.cpp +++ b/engines/saga/script.cpp @@ -150,6 +150,7 @@ Script::~Script() { debug(8, "Shutting down scripting subsystem."); _mainStrings.freeMem(); + _globalVoiceLUT.freeMem(); freeModules(); free(_modules); diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp index e9d002819c..be4f2a423d 100644 --- a/engines/saga/sprite.cpp +++ b/engines/saga/sprite.cpp @@ -74,6 +74,9 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) { Sprite::~Sprite(void) { debug(8, "Shutting down sprite subsystem..."); _mainSprites.freeMem(); + _inventorySprites.freeMem(); + _arrowSprites.freeMem(); + _saveReminderSprites.freeMem(); free(_decodeBuf); } diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp index 8f3175f098..5a45fb7da9 100644 --- a/engines/scumm/charset.cpp +++ b/engines/scumm/charset.cpp @@ -56,7 +56,7 @@ void ScummEngine::loadCJKFont() { _2byteWidth = 16; _2byteHeight = 16; // use FM-TOWNS font rom, since game files don't have kanji font resources - if (fp.open("fmt_fnt.rom", Common::File::kFileReadMode)) { + if (fp.open("fmt_fnt.rom")) { _useCJKMode = true; debug(2, "Loading FM-TOWNS Kanji rom"); _2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar]; diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp index 9f9115e207..23af1f9672 100644 --- a/engines/scumm/debugger.cpp +++ b/engines/scumm/debugger.cpp @@ -298,7 +298,7 @@ bool ScummDebugger::Cmd_ImportRes(int argc, const char** argv) { // FIXME add bounds check if (!strncmp(argv[1], "scr", 3)) { - file.open(argv[2], Common::File::kFileReadMode); + file.open(argv[2]); if (file.isOpen() == false) { DebugPrintf("Could not open file %s\n", argv[2]); return true; diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index 9359c6610c..68d3010199 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -492,7 +492,7 @@ static bool testGame(const GameSettings *g, const DescMap &fileMD5Map, const Com // Note that GF_OLD_BUNDLE is true if and only if GF_OLD256 is false. // Candidates: maniac enhanced, zak enhanced, indy3ega, loom - if (g->version != 2 && g->version != 3 || (g->features & GF_OLD256)) + if ((g->version != 2 && g->version != 3) || (g->features & GF_OLD256)) return false; /* We distinguish the games by the presence/absence of @@ -949,7 +949,7 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const { sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; - for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++) { + for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { // Obtain the last 2 digits of the filename, since they correspond to the save slot int slotNum = atoi(file->c_str() + file->size() - 2); diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp index 6d1cf1bbd8..e4e2b2b620 100644 --- a/engines/scumm/dialogs.cpp +++ b/engines/scumm/dialogs.cpp @@ -297,7 +297,7 @@ void SaveLoadChooser::handleCommand(CommandSender *sender, uint32 cmd, uint32 da break; case GUI::kListSelectionChangedCmd: { if (_gfxWidget) { - updateInfos(); + updateInfos(true); } if (_saveMode) { @@ -350,7 +350,7 @@ void SaveLoadChooser::reflowLayout() { _fillR = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillR"); _fillG = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillG"); _fillB = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillB"); - updateInfos(); + updateInfos(false); } else { _container->setFlags(GUI::WIDGET_INVISIBLE); _gfxWidget->setFlags(GUI::WIDGET_INVISIBLE); @@ -362,7 +362,7 @@ void SaveLoadChooser::reflowLayout() { Dialog::reflowLayout(); } -void SaveLoadChooser::updateInfos() { +void SaveLoadChooser::updateInfos(bool redraw) { int selItem = _list->getSelected(); Graphics::Surface *thumb; thumb = _vm->loadThumbnailFromSlot(_saveMode ? selItem + 1 : selItem); @@ -376,7 +376,8 @@ void SaveLoadChooser::updateInfos() { } delete thumb; - _gfxWidget->draw(); + if (redraw) + _gfxWidget->draw(); InfoStuff infos; memset(&infos, 0, sizeof(InfoStuff)); @@ -386,12 +387,14 @@ void SaveLoadChooser::updateInfos() { (infos.date >> 24) & 0xFF, (infos.date >> 16) & 0xFF, infos.date & 0xFFFF); _date->setLabel(buffer); - _date->draw(); + if (redraw) + _date->draw(); snprintf(buffer, 32, "Time: %.2d:%.2d", (infos.time >> 8) & 0xFF, infos.time & 0xFF); _time->setLabel(buffer); - _time->draw(); + if (redraw) + _time->draw(); int minutes = infos.playtime / 60; int hours = minutes / 60; @@ -400,19 +403,23 @@ void SaveLoadChooser::updateInfos() { snprintf(buffer, 32, "Playtime: %.2d:%.2d", hours & 0xFF, minutes & 0xFF); _playtime->setLabel(buffer); - _playtime->draw(); + if (redraw) + _playtime->draw(); } else { snprintf(buffer, 32, "No date saved"); _date->setLabel(buffer); - _date->draw(); + if (redraw) + _date->draw(); snprintf(buffer, 32, "No time saved"); _time->setLabel(buffer); - _time->draw(); + if (redraw) + _time->draw(); snprintf(buffer, 32, "No playtime saved"); _playtime->setLabel(buffer); - _playtime->draw(); + if (redraw) + _playtime->draw(); } } diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h index 7c99a0ebcc..0d04d8faea 100644 --- a/engines/scumm/dialogs.h +++ b/engines/scumm/dialogs.h @@ -69,7 +69,7 @@ protected: uint8 _fillR, _fillG, _fillB; - void updateInfos(); + void updateInfos(bool redraw); public: SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode, ScummEngine *engine); ~SaveLoadChooser(); diff --git a/engines/scumm/file.cpp b/engines/scumm/file.cpp index bc5fc38225..bf13308a0c 100644 --- a/engines/scumm/file.cpp +++ b/engines/scumm/file.cpp @@ -58,8 +58,8 @@ void ScummFile::resetSubfile() { seek(0, SEEK_SET); } -bool ScummFile::open(const Common::String &filename, AccessMode mode) { - if (File::open(filename, mode)) { +bool ScummFile::open(const Common::String &filename) { + if (File::open(filename)) { resetSubfile(); return true; } else { @@ -187,11 +187,6 @@ uint32 ScummFile::read(void *dataPtr, uint32 dataSize) { return realLen; } -uint32 ScummFile::write(const void *, uint32) { - error("ScummFile does not support writing!"); - return 0; -} - #pragma mark - #pragma mark --- ScummDiskImage --- #pragma mark - @@ -250,11 +245,6 @@ ScummDiskImage::ScummDiskImage(const char *disk1, const char *disk2, GameSetting } } -uint32 ScummDiskImage::write(const void *, uint32) { - error("ScummDiskImage does not support writing!"); - return 0; -} - void ScummDiskImage::setEnc(byte enc) { _stream->setEnc(enc); } @@ -300,7 +290,7 @@ bool ScummDiskImage::openDisk(char num) { return true; } -bool ScummDiskImage::open(const Common::String &filename, AccessMode mode) { +bool ScummDiskImage::open(const Common::String &filename) { uint16 signature; // check signature diff --git a/engines/scumm/file.h b/engines/scumm/file.h index 7064654f89..a2695cac59 100644 --- a/engines/scumm/file.h +++ b/engines/scumm/file.h @@ -36,7 +36,7 @@ class BaseScummFile : public Common::File { public: virtual void setEnc(byte value) = 0; - virtual bool open(const Common::String &filename, AccessMode mode = kFileReadMode) = 0; + virtual bool open(const Common::String &filename) = 0; virtual bool openSubFile(const Common::String &filename) = 0; virtual bool eof() = 0; @@ -44,7 +44,6 @@ public: virtual uint32 size() = 0; virtual void seek(int32 offs, int whence = SEEK_SET) = 0; virtual uint32 read(void *dataPtr, uint32 dataSize) = 0; - virtual uint32 write(const void *dataPtr, uint32 dataSize) = 0; }; class ScummFile : public BaseScummFile { @@ -59,7 +58,7 @@ public: void setSubfileRange(uint32 start, uint32 len); void resetSubfile(); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); bool openSubFile(const Common::String &filename); bool eof(); @@ -67,7 +66,6 @@ public: uint32 size(); void seek(int32 offs, int whence = SEEK_SET); uint32 read(void *dataPtr, uint32 dataSize); - uint32 write(const void *dataPtr, uint32 dataSize); }; class ScummDiskImage : public BaseScummFile { @@ -104,7 +102,7 @@ public: ScummDiskImage(const char *disk1, const char *disk2, GameSettings game); void setEnc(byte value); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); bool openSubFile(const Common::String &filename); void close(); @@ -113,7 +111,6 @@ public: uint32 size() { return _stream->size(); } void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); } uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); } - uint32 write(const void *dataPtr, uint32 dataSize); }; } // End of namespace Scumm diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp index 95f5eec4ea..8325436f87 100644 --- a/engines/scumm/file_nes.cpp +++ b/engines/scumm/file_nes.cpp @@ -62,11 +62,6 @@ struct ScummNESFile::Resource { ScummNESFile::ScummNESFile() : _stream(0), _buf(0), _ROMset(kROMsetNum) { } -uint32 ScummNESFile::write(const void *, uint32) { - error("ScummNESFile does not support writing!"); - return 0; -} - void ScummNESFile::setEnc(byte enc) { _stream->setEnc(enc); } @@ -1234,7 +1229,7 @@ bool ScummNESFile::generateIndex() { return true; } -bool ScummNESFile::open(const Common::String &filename, AccessMode mode) { +bool ScummNESFile::open(const Common::String &filename) { if (_ROMset == kROMsetNum) { char md5str[32+1]; @@ -1267,9 +1262,8 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) { } } - if (File::open(filename, mode)) { - if (_stream) - delete _stream; + if (File::open(filename)) { + delete _stream; _stream = 0; free(_buf); @@ -1282,8 +1276,7 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) { } void ScummNESFile::close() { - if (_stream) - delete _stream; + delete _stream; _stream = 0; free(_buf); diff --git a/engines/scumm/file_nes.h b/engines/scumm/file_nes.h index d601c2c496..4d2d6de275 100644 --- a/engines/scumm/file_nes.h +++ b/engines/scumm/file_nes.h @@ -64,7 +64,7 @@ public: ScummNESFile(); void setEnc(byte value); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); bool openSubFile(const Common::String &filename); void close(); @@ -73,7 +73,6 @@ public: uint32 size() { return _stream->size(); } void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); } uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); } - uint32 write(const void *dataPtr, uint32 dataSize); }; } // End of namespace Scumm diff --git a/engines/scumm/gfxARM.s b/engines/scumm/gfxARM.s index cd3e5c7dad..83aaa78927 100644 --- a/engines/scumm/gfxARM.s +++ b/engines/scumm/gfxARM.s @@ -59,7 +59,7 @@ asmDrawStripToScreen: CMP r1,#4 @ If width<4 BLT end @ return - @ Width &= ~4 ? What's that about then? Width &= ~3 I could have + @ Width &= ~4 ? What''s that about then? Width &= ~3 I could have @ understood... BIC r1,r1,#4 diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp index 33e6748860..f8fb1efca2 100644 --- a/engines/scumm/he/resource_he.cpp +++ b/engines/scumm/he/resource_he.cpp @@ -522,12 +522,13 @@ int Win32ResExtractor::do_resources_recurs(WinLibrary *fi, WinResource *base, /* get a list of all resources at this level */ wr = list_resources(fi, base, &rescnt); - if (wr == NULL) + if (wr == NULL) { if (size != 0) return size; else return 0; - + } + /* process each resource listed */ for (c = 0 ; c < rescnt ; c++) { /* (over)write the corresponding WinResource holder with the current */ diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp index 4d5ec668a0..9429f8d086 100644 --- a/engines/scumm/he/script_v60he.cpp +++ b/engines/scumm/he/script_v60he.cpp @@ -1012,7 +1012,7 @@ void ScummEngine_v60he::o60_openFile() { _hInFileTable[slot] = _saveFileMan->openForLoading(filename); if (_hInFileTable[slot] == 0) { Common::File *f = new Common::File(); - f->open(filename, Common::File::kFileReadMode); + f->open(filename); if (!f->isOpen()) delete f; else diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp index df3d857642..6c3d0023d8 100644 --- a/engines/scumm/he/script_v72he.cpp +++ b/engines/scumm/he/script_v72he.cpp @@ -1692,7 +1692,7 @@ void ScummEngine_v72he::o72_openFile() { _hInFileTable[slot] = _saveFileMan->openForLoading(filename); if (_hInFileTable[slot] == 0) { Common::File *f = new Common::File(); - f->open(filename, Common::File::kFileReadMode); + f->open(filename); if (!f->isOpen()) delete f; else diff --git a/engines/scumm/he/script_v80he.cpp b/engines/scumm/he/script_v80he.cpp index 393e1d3a8f..39ec715d94 100644 --- a/engines/scumm/he/script_v80he.cpp +++ b/engines/scumm/he/script_v80he.cpp @@ -409,7 +409,7 @@ void ScummEngine_v80he::o80_getFileSize() { Common::SeekableReadStream *f = _saveFileMan->openForLoading((const char *)filename); if (!f) { Common::File *file = new Common::File(); - file->open((const char *)filename, Common::File::kFileReadMode); + file->open((const char *)filename); if (!file->isOpen()) delete f; else diff --git a/engines/scumm/he/wiz_he.cpp b/engines/scumm/he/wiz_he.cpp index df472307eb..f514449bff 100644 --- a/engines/scumm/he/wiz_he.cpp +++ b/engines/scumm/he/wiz_he.cpp @@ -1881,7 +1881,7 @@ void Wiz::processWizImage(const WizParameters *params) { memcpy(filename, params->filename, 260); _vm->convertFilePath(filename); - if (f.open((const char *)filename, Common::File::kFileReadMode)) { + if (f.open((const char *)filename)) { uint32 id = f.readUint32BE(); if (id == MKID_BE('AWIZ') || id == MKID_BE('MULT')) { uint32 size = f.readUint32BE(); @@ -1911,7 +1911,7 @@ void Wiz::processWizImage(const WizParameters *params) { break; case 4: if (params->processFlags & kWPFUseFile) { - Common::File f; + Common::DumpFile f; switch (params->fileWriteMode) { case 2: @@ -1924,7 +1924,7 @@ void Wiz::processWizImage(const WizParameters *params) { memcpy(filename, params->filename, 260); _vm->convertFilePath(filename); - if (!f.open((const char *)filename, Common::File::kFileWriteMode)) { + if (!f.open((const char *)filename)) { debug(0, "Unable to open for write '%s'", filename); _vm->VAR(119) = -3; } else { diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp index fa50eca604..d3359fa33e 100644 --- a/engines/scumm/imuse_digi/dimuse.cpp +++ b/engines/scumm/imuse_digi/dimuse.cpp @@ -57,8 +57,8 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps) for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) { _track[l] = new Track; assert(_track[l]); + memset(_track[l], 0, sizeof(Track)); _track[l]->trackId = l; - _track[l]->used = false; } _vm->_timer->installTimerProc(timer_handler, 1000000 / _callbackFps, this); diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp index 1511b9aefc..b18b0ba70f 100644 --- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp +++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp @@ -102,10 +102,10 @@ void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::File *file, SoundDesc *sound, int32 version = file->readUint32BE(); if (version != 3) { if (version == 2) { - warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2."); - warning("Suggested to recompress with latest tool from daily builds."); + warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2"); + warning("Suggested to recompress with latest tool from daily builds"); } else - error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d.", version); + error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d", version); } sound->bits = file->readUint32BE(); sound->freq = file->readUint32BE(); diff --git a/engines/scumm/imuse_digi/dimuse_track.h b/engines/scumm/imuse_digi/dimuse_track.h index 33147128cb..2d4c673cf6 100644 --- a/engines/scumm/imuse_digi/dimuse_track.h +++ b/engines/scumm/imuse_digi/dimuse_track.h @@ -85,13 +85,15 @@ struct Track { int getPan() const { return (pan != 64) ? 2 * pan - 127 : 0; } int getVol() const { return vol / 1000; } Audio::Mixer::SoundType getType() const { - Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType; + Audio::Mixer::SoundType type; if (volGroupId == 1) type = Audio::Mixer::kSpeechSoundType; else if (volGroupId == 2) type = Audio::Mixer::kSFXSoundType; else if (volGroupId == 3) type = Audio::Mixer::kMusicSoundType; + else + error("Track::getType(): invalid sound type"); return type; } }; diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp index acdc2bc222..6bd62c1761 100644 --- a/engines/scumm/resource.cpp +++ b/engines/scumm/resource.cpp @@ -1299,7 +1299,7 @@ void ScummEngine::allocateArrays() { void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int length) { char buf[256]; - Common::File out; + Common::DumpFile out; uint32 size; if (length >= 0) @@ -1313,7 +1313,7 @@ void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int le sprintf(buf, "dumps/%s%d.dmp", tag, idx); - out.open(buf, Common::File::kFileWriteMode); + out.open(buf); if (out.isOpen() == false) return; out.write(ptr, size); diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 36b82519e9..f9e4eb415c 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -411,15 +411,15 @@ void ScummEngine::listSavegames(bool *marks, int num) { char prefix[256]; char slot[3]; int slotNum; - Common::StringList filenames; + Common::StringList files; makeSavegameName(prefix, 99, false); prefix[strlen(prefix)-2] = '*'; prefix[strlen(prefix)-1] = 0; memset(marks, false, num * sizeof(bool)); //assume no savegames for this title - filenames = _saveFileMan->listSavefiles(prefix); + files = _saveFileMan->listSavefiles(prefix); - for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++){ + for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) { //Obtain the last 2 digits of the filename, since they correspond to the save slot slot[0] = file->c_str()[file->size()-2]; slot[1] = file->c_str()[file->size()-1]; diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 62d777aa33..ce8f0a4d9a 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Mon Jun 02 08:37:50 2008 + This file was generated by the md5table tool on Mon Jul 28 00:13:01 2008 DO NOT EDIT MANUALLY! */ @@ -75,7 +75,7 @@ static const MD5Table md5table[] = { { "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformWindows }, { "179879b6e35c1ead0d93aab26db0951b", "fbear", "HE 70", "", 13381, Common::EN_ANY, Common::kPlatformWindows }, { "17b5d5e6af4ae89d62631641d66d5a05", "indy3", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformPC }, - { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES }, + { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES }, { "17fa250eb72dae2dad511ba79c0b6b0a", "tentacle", "", "Demo", -1, Common::FR_FRA, Common::kPlatformPC }, { "182344899c2e2998fca0bebcd82aa81a", "atlantis", "", "CD", 12035, Common::EN_ANY, Common::kPlatformPC }, { "183d7464902d40d00800e8ee1f04117c", "maniac", "V2", "V2", 1988, Common::DE_DEU, Common::kPlatformPC }, @@ -149,7 +149,7 @@ static const MD5Table md5table[] = { { "37ff1b308999c4cca7319edfcc1280a0", "puttputt", "HE 70", "Demo", 8269, Common::EN_ANY, Common::kPlatformWindows }, { "3824e60cdf639d22f6df92a03dc4b131", "fbear", "HE 61", "", 7732, Common::EN_ANY, Common::kPlatformPC }, { "387a544b8b10b26912d8413bab63a853", "monkey2", "", "Demo", -1, Common::EN_ANY, Common::kPlatformPC }, - { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES }, + { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES }, { "3938ee1aa4433fca9d9308c9891172b1", "zak", "FM-TOWNS", "Demo", -1, Common::EN_ANY, Common::kPlatformFMTowns }, { "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformPC }, { "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::FR_FRA, Common::kPlatformAmiga }, @@ -357,7 +357,7 @@ static const MD5Table md5table[] = { { "90e2f0af4f779629695c6394a65bb702", "spyfox2", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown }, { "910e31cffb28226bd68c569668a0d6b4", "monkey", "EGA", "EGA", -1, Common::ES_ESP, Common::kPlatformPC }, { "91469353f7be1b122fa88d23480a1320", "zak", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformAmiga }, - { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES }, + { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES }, { "927a764615c7fcdd72f591355e089d8c", "monkey", "No Adlib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST }, { "92b078d9d6d9d751da9c26b8b3075779", "tentacle", "", "Floppy", -1, Common::FR_FRA, Common::kPlatformPC }, { "92e7727e67f5cd979d8a1070e4eb8cb3", "puttzoo", "HE 98.5", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown }, @@ -503,7 +503,7 @@ static const MD5Table md5table[] = { { "d7b247c26bf1f01f8f7daf142be84de3", "balloon", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows }, { "d831f7c048574dd9d5d85db2a1468099", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 }, { "d8323015ecb8b10bf53474f6e6b0ae33", "dig", "", "", 16304, Common::UNK_LANG, Common::kPlatformUnknown }, - { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES }, + { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES }, { "d917f311a448e3cc7239c31bddb00dd2", "samnmax", "", "CD", 9080, Common::EN_ANY, Common::kPlatformUnknown }, { "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformPC }, { "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns }, diff --git a/engines/scumm/smush/codec47ARM.s b/engines/scumm/smush/codec47ARM.s index 81bfdb2d22..73341c117f 100644 --- a/engines/scumm/smush/codec47ARM.s +++ b/engines/scumm/smush/codec47ARM.s @@ -80,7 +80,7 @@ level1codeFD: LDRB r9,[r8,#384] @ r9 = l = tmp_ptr[384] LDRB r6,[r1],#1 @ r6 = val = *_d_src++ ADD r12,r8,#384 @ r12= &tmp_ptr[384] - @ I don't really believe the next 2 lines are necessary, but... + @ I don''t really believe the next 2 lines are necessary, but... CMP r9,#0 BEQ level1codeFD_over1 level1codeFD_loop1: @@ -94,7 +94,7 @@ level1codeFD_over1: LDRB r9,[r12,#1] @ r9 = l = tmp_ptr[385] LDRB r6,[r1],#1 @ r6 = val = *_d_src++ SUB r12,r12,#256 @ r12= &tmp_ptr[128] (256 = 384-128) - @ I don't really believe the next 2 lines are necessary, but... + @ I don''t really believe the next 2 lines are necessary, but... CMP r9,#0 BEQ level1codeFD_over2 level1codeFD_loop2: @@ -219,7 +219,7 @@ level2codeFD: LDRB r9,[r8,#96] @ r9 = l = tmp_ptr[96] LDRB r6,[r1],#1 @ r6 = val = *_d_src++ ADD r12,r8,#32 @ r12 = tmp_ptr + 32 - @ I don't really believe the next 2 lines are necessary, but... + @ I don''t really believe the next 2 lines are necessary, but... CMP r9,#0 BEQ level2codeFD_over1 level2codeFD_loop1: @@ -232,7 +232,7 @@ level2codeFD_loop1: level2codeFD_over1: LDRB r9,[r12,#65] @ r9 = l = tmp_ptr[97] (65 = 97-32) LDRB r6,[r1],#1 @ r6 = val = *_d_src++ - @ I don't really believe the next 2 lines are necessary, but... + @ I don''t really believe the next 2 lines are necessary, but... CMP r9,#0 MOVEQ PC,R14 level2codeFD_loop2: diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index fdd0598378..7500b16c87 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -89,6 +89,7 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer) Sound::~Sound() { stopCDTimer(); + AudioCD.destroy(); delete _sfxFile; } diff --git a/engines/sky/disk.cpp b/engines/sky/disk.cpp index a2f7d57cb0..a30276f8be 100644 --- a/engines/sky/disk.cpp +++ b/engines/sky/disk.cpp @@ -326,14 +326,14 @@ void Disk::fnFlushBuffers(void) { void Disk::dumpFile(uint16 fileNr) { char buf[128]; - Common::File out; + Common::DumpFile out; byte* filePtr; filePtr = loadFile(fileNr); sprintf(buf, "dumps/file-%d.dmp", fileNr); if (!Common::File::exists(buf)) { - if (out.open(buf, Common::File::kFileWriteMode)) + if (out.open(buf)) out.write(filePtr, _lastLoadedFileSize); } free(filePtr); diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp index 7c2b262d82..4434f4cd68 100644 --- a/engines/sky/music/adlibmusic.cpp +++ b/engines/sky/music/adlibmusic.cpp @@ -47,6 +47,7 @@ AdlibMusic::AdlibMusic(Audio::Mixer *pMixer, Disk *pDisk) AdlibMusic::~AdlibMusic(void) { + OPLDestroy(_opl); _mixer->stopHandle(_soundHandle); } diff --git a/engines/sword1/resman.cpp b/engines/sword1/resman.cpp index d54e290b09..adb84eee83 100644 --- a/engines/sword1/resman.cpp +++ b/engines/sword1/resman.cpp @@ -212,8 +212,8 @@ void *ResMan::openFetchRes(uint32 id) { void ResMan::dumpRes(uint32 id) { char outn[30]; sprintf(outn, "DUMP%08X.BIN", id); - Common::File outf; - if (outf.open(outn, Common::File::kFileWriteMode)) { + Common::DumpFile outf; + if (outf.open(outn)) { resOpen(id); MemHandle *memHandle = resHandle(id); outf.write(memHandle->data, memHandle->size); diff --git a/engines/sword2/music.cpp b/engines/sword2/music.cpp index fd72ba8d52..3b5a09578b 100644 --- a/engines/sword2/music.cpp +++ b/engines/sword2/music.cpp @@ -52,9 +52,11 @@ namespace Sword2 { static Audio::AudioStream *makeCLUStream(Common::File *fp, int size); static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) { - debug(3, "Playing %s from CD %d", base, cd); + bool alreadyOpen; if (!fh->file.isOpen()) { + alreadyOpen = false; + struct { const char *ext; int mode; @@ -75,16 +77,14 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, char filename[20]; for (int i = 0; i < ARRAYSIZE(file_types); i++) { - Common::File f; - sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext); - if (f.open(filename)) { + if (Common::File::exists(filename)) { soundMode = file_types[i].mode; break; } sprintf(filename, "%s.%s", base, file_types[i].ext); - if (f.open(filename)) { + if (Common::File::exists(filename)) { soundMode = file_types[i].mode; break; } @@ -105,7 +105,8 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, fh->idxTab = NULL; } } - } + } else + alreadyOpen = true; uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3; @@ -134,7 +135,13 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, *numSamples = len; if (!pos || !len) { - fh->file.close(); + // We couldn't find the sound. Possibly as a result of a bad + // installation (e.g. using the music file from CD 2 as the + // first music file). Don't close the file if it was already + // open though, because something is playing from it. + warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id); + if (!alreadyOpen) + fh->file.close(); return NULL; } diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp index d6b8025cda..8cddddff78 100644 --- a/engines/sword2/resman.cpp +++ b/engines/sword2/resman.cpp @@ -234,7 +234,6 @@ bool ResourceManager::init() { /** * Returns the address of a resource. Loads if not in memory. Retains a count. */ - byte *ResourceManager::openResource(uint32 res, bool dump) { assert(res < _totalResFiles); @@ -287,7 +286,6 @@ byte *ResourceManager::openResource(uint32 res, bool dump) { if (dump) { char buf[256]; const char *tag; - Common::File out; switch (fetchType(_resList[res].ptr)) { case ANIMATION_FILE: @@ -337,7 +335,8 @@ byte *ResourceManager::openResource(uint32 res, bool dump) { sprintf(buf, "dumps/%s-%d.dmp", tag, res); if (!Common::File::exists(buf)) { - if (out.open(buf, Common::File::kFileWriteMode)) + Common::DumpFile out; + if (out.open(buf)) out.write(_resList[res].ptr, len); } } diff --git a/engines/sword2/sound.h b/engines/sword2/sound.h index 70bae6f851..b89ef8f12b 100644 --- a/engines/sword2/sound.h +++ b/engines/sword2/sound.h @@ -106,7 +106,7 @@ private: void refill(); inline bool eosIntern() const { - return _pos >= _bufferEnd; + return !_file->isOpen() || _pos >= _bufferEnd; } public: diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp new file mode 100644 index 0000000000..c2f01added --- /dev/null +++ b/engines/tinsel/actors.cpp @@ -0,0 +1,897 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles things to do with actors, delegates much moving actor stuff. + */ + +#include "tinsel/actors.h" +#include "tinsel/events.h" +#include "tinsel/film.h" // for FREEL +#include "tinsel/handle.h" +#include "tinsel/inventory.h" // INV_NOICON +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" // for POBJECT +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** actor struct - one per actor */ +struct ACTOR_STRUC { + int32 masking; //!< type of actor masking + SCNHANDLE hActorId; //!< handle actor ID string index + SCNHANDLE hActorCode; //!< handle to actor script +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeadActorId = 0; // The lead actor + +static int NumActors = 0; // The total number of actors in the game + +struct ACTORINFO { + bool alive; // TRUE == alive + bool hidden; // TRUE == hidden + bool completed; // TRUE == script played out + + int x, y, z; + + int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS + SCNHANDLE actorCode; // The actor's script + + const FREEL *presReel; // the present reel + int presRnum; // the present reel number + SCNHANDLE presFilm; // the film that reel belongs to + OBJECT *presObj; // reference for position information + int presX, presY; + + bool tagged; // actor tagged? + SCNHANDLE hTag; // handle to tag text + int tType; // e.g. TAG_Q1TO3 + + bool escOn; + int escEv; + + COLORREF tColour; // Text colour + + SCNHANDLE playFilm; // revert to this after talks + SCNHANDLE talkFilm; // this be deleted in the future! + SCNHANDLE latestFilm; // the last film ordered + bool talking; + + int steps; + +}; + +static ACTORINFO *actorInfo = 0; + +static COLORREF defaultColour = 0; // Text colour + +static bool bActorsOn = false; + +static int ti = 0; + +/** + * Called once at start-up time, and again at restart time. + * Registers the total number of actors in the game. + * @param num Chunk Id + */ +void RegisterActors(int num) { + if (actorInfo == NULL) { + // Store the total number of actors in the game + NumActors = num; + + // Check we can save so many + assert(NumActors <= MAX_SAVED_ALIVES); + + // Allocate RAM for actorInfo + // FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks, + // as this makes the save/load code simpler + actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO)); + + // make sure memory allocated + if (actorInfo == NULL) { + error("Cannot allocate memory for actors"); + } + } else { + // Check the total number of actors is still the same + assert(num == NumActors); + + memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO)); + } + + // All actors start off alive. + while (num--) + actorInfo[num].alive = true; +} + +void FreeActors() { + if (actorInfo) { + free(actorInfo); + actorInfo = NULL; + } +} + +/** + * Called from dec_lead(), i.e. normally once at start of master script. + * @param leadID Lead Id + */ +void setleadid(int leadID) { + LeadActorId = leadID; + actorInfo[leadID-1].mtype = ACT_MASK; +} + +/** + * No comment. + */ +int LeadId(void) { + return LeadActorId; +} + +struct ATP_INIT { + int id; // Actor number + USER_EVENT event; // Event + BUTEVENT bev; // Causal mouse event +}; + +/** + * Runs actor's glitter code. + */ +static void ActorTinselProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + ATP_INIT *atp = (ATP_INIT *)param; + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click + + // Run the Glitter code + assert(actorInfo[atp->id - 1].actorCode); // no code to run + + _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // If it gets here, actor's code has run to completion + actorInfo[atp->id - 1].completed = true; + + CORO_END_CODE; +} + + +//--------------------------------------------------------------------------- + +struct RATP_INIT { + INT_CONTEXT *pic; + int id; // Actor number +}; + +static void ActorRestoredProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + RATP_INIT *r = (RATP_INIT *)param; + + CORO_BEGIN_CODE(_ctx); + + _ctx->pic = RestoreInterpretContext(r->pic); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // If it gets here, actor's code has run to completion + actorInfo[r->id - 1].completed = true; + + CORO_END_CODE; +} + +void RestoreActorProcess(int id, INT_CONTEXT *pic) { + RATP_INIT r = { pic, id }; + + g_scheduler->createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r)); +} + +/** + * Starts up process to runs actor's glitter code. + * @param ano Actor Id + * @param event Event structure + * @param be ButEvent + */ +void actorEvent(int ano, USER_EVENT event, BUTEVENT be) { + ATP_INIT atp; + + // Only if there is Glitter code associated with this actor. + if (actorInfo[ano - 1].actorCode) { + atp.id = ano; + atp.event = event; + atp.bev = be; + g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp)); + } +} + +/** + * Called at the start of each scene for each actor with a code block. + * @param as Actor structure + * @param bRunScript Flag for whether to run actor's script for the scene + */ +void StartActor(const ACTOR_STRUC *as, bool bRunScript) { + SCNHANDLE hActorId = FROM_LE_32(as->hActorId); + + // Zero-out many things + actorInfo[hActorId - 1].hidden = false; + actorInfo[hActorId - 1].completed = false; + actorInfo[hActorId - 1].x = 0; + actorInfo[hActorId - 1].y = 0; + actorInfo[hActorId - 1].presReel = NULL; + actorInfo[hActorId - 1].presFilm = 0; + actorInfo[hActorId - 1].presObj = NULL; + + // Store current scene's parameters for this actor + actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking); + actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode); + + // Run actor's script for this scene + if (bRunScript) { + if (bActorsOn) + actorInfo[hActorId - 1].alive = true; + + if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode)) + actorEvent(hActorId, STARTUP, BE_NONE); + } +} + +/** + * Called at the start of each scene. Start each actor with a code block. + * @param ah Scene handle + * @param numActors Number of actors + * @param bRunScript Flag for whether to run actor scene scripts + */ +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) { + int i; + + // Only actors with code blocks got (x, y) re-initialised, so... + for (i = 0; i < NumActors; i++) { + actorInfo[i].x = actorInfo[i].y = 0; + actorInfo[i].mtype = 0; + } + + const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah); + for (i = 0; i < numActors; i++, as++) { + StartActor(as, bRunScript); + } +} + +/** + * Called between scenes, zeroises all actors. + */ +void DropActors(void) { + for (int i = 0; i < NumActors; i++) { + actorInfo[i].actorCode = 0; // No script + actorInfo[i].presReel = NULL; // No reel running + actorInfo[i].presFilm = 0; // ditto + actorInfo[i].presObj = NULL; // No object + actorInfo[i].x = 0; // No position + actorInfo[i].y = 0; // ditto + + actorInfo[i].talkFilm = 0; + actorInfo[i].latestFilm = 0; + actorInfo[i].playFilm = 0; + actorInfo[i].talking = false; + } +} + +/** + * Kill actors. + * @param ano Actor Id + */ +void DisableActor(int ano) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].alive = false; // Record as dead + actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0; + + // Kill off moving actor properly + pActor = GetMover(ano); + if (pActor) + KillMActor(pActor); +} + +/** + * Enable actors. + * @param ano Actor Id + */ +void EnableActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + // Re-incarnate only if it's dead, or it's script ran to completion + if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) { + actorInfo[ano - 1].alive = true; + actorInfo[ano - 1].hidden = false; + actorInfo[ano - 1].completed = false; + + // Re-run actor's script for this scene + if (actorInfo[ano-1].actorCode) + actorEvent(ano, STARTUP, BE_NONE); + } +} + +/** + * Returns the aliveness (to coin a word) of the actor. + * @param ano Actor Id + */ +bool actorAlive(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].alive; +} + +/** + * Define an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano-1].tagged = true; + actorInfo[ano-1].hTag = tagtext; + actorInfo[ano-1].tType = tp; +} + +/** + * Undefine an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void UnTagActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano-1].tagged = false; +} + +/** + * Redefine an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void ReTagActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (actorInfo[ano-1].hTag) + actorInfo[ano-1].tagged = true; +} + +/** + * Returns a tagged actor's tag type. e.g. TAG_Q1TO3 + * @param ano Actor Id + */ +int TagType(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano-1].tType; +} + +/** + * Returns handle to tagged actor's tag text + * @param ano Actor Id + */ +SCNHANDLE GetActorTag(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].hTag; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +void FirstTaggedActor(void) { + ti = 0; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +int NextTaggedActor(void) { + PMACTOR pActor; + bool hid; + + do { + if (actorInfo[ti].tagged) { + pActor = GetMover(ti+1); + if (pActor) + hid = getMActorHideState(pActor); + else + hid = actorInfo[ti].hidden; + + if (!hid) { + return ++ti; + } + } + } while (++ti < NumActors); + + return 0; +} + +/** + * Returns the masking type of the actor. + * @param ano Actor Id + */ +int32 actorMaskType(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].mtype; +} + +/** + * Store/Return the currently stored co-ordinates of the actor. + * Delegate the task for moving actors. + * @param ano Actor Id + * @param x X position + * @param y Y position + */ +void storeActorPos(int ano, int x, int y) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].x = x; + actorInfo[ano - 1].y = y; +} + +void storeActorSteps(int ano, int steps) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].steps = steps; +} + +int getActorSteps(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].steps; +} + +void storeActorZpos(int ano, int z) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].z = z; +} + + +void GetActorPos(int ano, int *x, int *y) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + + pActor = GetMover(ano); + + if (pActor) + GetMActorPosition(pActor, x, y); + else { + *x = actorInfo[ano - 1].x; + *y = actorInfo[ano - 1].y; + } +} + +/** + * Returns the position of the mid-top of the actor. + * Delegate the task for moving actors. + * @param ano Actor Id + * @param x Output x + * @param y Output y + */ +void GetActorMidTop(int ano, int *x, int *y) { + // Not used in JAPAN version + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + + pActor = GetMover(ano); + + if (pActor) + GetMActorMidTopPosition(pActor, x, y); + else if (actorInfo[ano - 1].presObj) { + *x = (MultiLeftmost(actorInfo[ano - 1].presObj) + + MultiRightmost(actorInfo[ano - 1].presObj)) / 2; + *y = MultiHighest(actorInfo[ano - 1].presObj); + } else + GetActorPos(ano, x, y); // The best we can do! +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorLeft(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiLeftmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorRight(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiRightmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorTop(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiHighest(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + */ +int GetActorBottom(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiLowest(actorInfo[ano - 1].presObj); +} + +/** + * Set actor hidden status to true. + * For a moving actor, actually hide it. + * @param ano Actor Id + */ +void HideActor(int ano) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + if (pActor) + hideMActor(pActor, 0); + else + actorInfo[ano - 1].hidden = true; +} + +/** + * Hide an actor if it's a moving actor. + * @param ano Actor Id + * @param sf sf + */ +bool HideMovingActor(int ano, int sf) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + if (pActor) { + hideMActor(pActor, sf); + return true; + } else { + if (actorInfo[ano - 1].presObj != NULL) + MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object + return false; + } +} + +/** + * Unhide an actor if it's a moving actor. + * @param ano Actor Id + */ +void unHideMovingActor(int ano) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + assert(pActor); // not a moving actor + + unhideMActor(pActor); +} + +/** + * Called after a moving actor had been replaced by an splay(). + * Moves the actor to where the splay() left it, and continues the + * actor's walk (if any) from the new co-ordinates. + */ +void restoreMovement(int ano) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + // Get moving actor involved + pActor = GetMover(ano); + + assert(pActor); // not a moving actor + + if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y) + return; + + pActor->objx = actorInfo[ano - 1].x; + pActor->objy = actorInfo[ano - 1].y; + + if (pActor->actorObj) + SSetActorDest(pActor); +} + +/** + * More properly should be called: + * 'store_actor_reel_and/or_film_and/or_object()' + */ +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + pActor = GetMover(ano); + + // Only store the reel and film for a moving actor if NOT called from MActorProcess() + // (MActorProcess() calls with reel=film=NULL, pobj not NULL) + if (!pActor + || !(reel == NULL && film == 0 && pobj != NULL)) { + actorInfo[ano - 1].presReel = reel; // Store reel + actorInfo[ano - 1].presRnum = reelnum; // Store reel number + actorInfo[ano - 1].presFilm = film; // Store film + actorInfo[ano - 1].presX = x; + actorInfo[ano - 1].presY = y; + } + + // Only store the object for a moving actor if called from MActorProcess() + if (!pActor) { + actorInfo[ano - 1].presObj = pobj; // Store object + } else if (reel == NULL && film == 0 && pobj != NULL) { + actorInfo[ano - 1].presObj = pobj; // Store object + } +} + +/** + * Return the present reel/film of the actor. + */ +const FREEL *actorReel(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].presReel; // the present reel +} + +/***************************************************************************/ + +void setActorPlayFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].playFilm = film; +} + +SCNHANDLE getActorPlayFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].playFilm; +} + +void setActorTalkFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].talkFilm = film; +} + +SCNHANDLE getActorTalkFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].talkFilm; +} + +void setActorTalking(int ano, bool tf) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].talking = tf;; +} + +bool isActorTalking(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].talking; +} + +void setActorLatestFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].latestFilm = film; + actorInfo[ano - 1].steps = 0; +} + +SCNHANDLE getActorLatestFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].latestFilm; +} + +/***************************************************************************/ + +void updateActorEsc(int ano, bool escOn, int escEvent) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].escOn = escOn; + actorInfo[ano - 1].escEv = escEvent; +} + +bool actorEsc(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].escOn; +} + +int actorEev(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].escEv; +} + +/** + * Guess what these do. + */ +int AsetZPos(OBJECT *pObj, int y, int32 z) { + int zPos; + + z += z ? -1 : 0; + + zPos = y + (z << 10); + MultiSetZPosition(pObj, zPos); + return zPos; +} + +/** + * Guess what these do. + */ +void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) { + if (!pActor->aHidden) + AsetZPos(pActor->actorObj, y, zFactor); +} + +/** + * Stores actor's attributes. + * Currently only the speech colours. + */ +void storeActorAttr(int ano, int r1, int g1, int b1) { + assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + + if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure + if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits + if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // } + + if (ano == -1) + defaultColour = RGB(r1, g1, b1); + else + actorInfo[ano - 1].tColour = RGB(r1, g1, b1); +} + +/** + * Get the actor's stored speech colour. + * @param ano Actor Id + */ +COLORREF getActorTcol(int ano) { + // Not used in JAPAN version + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (actorInfo[ano - 1].tColour) + return actorInfo[ano - 1].tColour; + else + return defaultColour; +} + +/** + * Store relevant information pertaining to currently existing actors. + */ +int SaveActors(SAVED_ACTOR *sActorInfo) { + int i, j; + + for (i = 0, j = 0; i < NumActors; i++) { + if (actorInfo[i].presObj != NULL) { + assert(j < MAX_SAVED_ACTORS); // Saving too many actors + +// sActorInfo[j].hidden = actorInfo[i].hidden; + sActorInfo[j].bAlive = actorInfo[i].alive; +// sActorInfo[j].x = (short)actorInfo[i].x; +// sActorInfo[j].y = (short)actorInfo[i].y; + sActorInfo[j].z = (short)actorInfo[i].z; +// sActorInfo[j].presReel = actorInfo[i].presReel; + sActorInfo[j].presRnum = (short)actorInfo[i].presRnum; + sActorInfo[j].presFilm = actorInfo[i].presFilm; + sActorInfo[j].presX = (short)actorInfo[i].presX; + sActorInfo[j].presY = (short)actorInfo[i].presY; + sActorInfo[j].actorID = (short)(i+1); + j++; + } + } + + return j; +} + +void setactorson(void) { + bActorsOn = true; +} + +void ActorsLife(int ano, bool bAlive) { + assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + + actorInfo[ano-1].alive = bAlive; +} + + +void syncAllActorsAlive(Serializer &s) { + for (int i = 0; i < MAX_SAVED_ALIVES; i++) { + s.syncAsByte(actorInfo[i].alive); + s.syncAsByte(actorInfo[i].tagged); + s.syncAsByte(actorInfo[i].tType); + s.syncAsUint32LE(actorInfo[i].hTag); + } +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h new file mode 100644 index 0000000000..91f54519d5 --- /dev/null +++ b/engines/tinsel/actors.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Prototypes of actor functions + */ + +#ifndef TINSEL_ACTOR_H // prevent multiple includes +#define TINSEL_ACTOR_H + + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/events.h" // for USER_EVENT +#include "tinsel/palette.h" // for COLORREF + +namespace Tinsel { + +struct FREEL; +struct INT_CONTEXT; +struct MACTOR; +struct OBJECT; + + +/*----------------------------------------------------------------------*/ + +void RegisterActors(int num); +void FreeActors(void); +void setleadid(int rid); +int LeadId(void); +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript); +void DropActors(void); // No actor reels running +void DisableActor(int actor); +void EnableActor(int actor); +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp); +void UnTagActor(int ano); +void ReTagActor(int ano); +int TagType(int ano); +bool actorAlive(int ano); +int32 actorMaskType(int ano); +void GetActorPos(int ano, int *x, int *y); +void SetActorPos(int ano, int x, int y); +void GetActorMidTop(int ano, int *x, int *y); +int GetActorLeft(int ano); +int GetActorRight(int ano); +int GetActorTop(int ano); +int GetActorBottom(int ano); +void HideActor(int ano); +bool HideMovingActor(int id, int sf); +void unHideMovingActor(int id); +void restoreMovement(int id); +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y); +const FREEL *actorReel(int ano); +SCNHANDLE actorFilm(int ano); + +void setActorPlayFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorPlayFilm(int ano); +void setActorTalkFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorTalkFilm(int ano); +void setActorTalking(int ano, bool tf); +bool isActorTalking(int ano); +void setActorLatestFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorLatestFilm(int ano); + +void updateActorEsc(int ano, bool escOn, int escEv); +bool actorEsc(int ano); +int actorEev(int ano); +void storeActorPos(int ano, int x, int y); +void storeActorSteps(int ano, int steps); +int getActorSteps(int ano); +void storeActorZpos(int ano, int z); +SCNHANDLE GetActorTag(int ano); +void FirstTaggedActor(void); +int NextTaggedActor(void); +int AsetZPos(OBJECT *pObj, int y, int32 zFactor); +void MAsetZPos(MACTOR *pActor, int y, int32 zFactor); +void actorEvent(int ano, USER_EVENT event, BUTEVENT be); + +void storeActorAttr(int ano, int r1, int g1, int b1); +COLORREF getActorTcol(int ano); + +void setactorson(void); + +void ActorsLife(int id, bool bAlive); + +/*----------------------------------------------------------------------*/ + +struct SAVED_ACTOR { + short actorID; + short z; + bool bAlive; + SCNHANDLE presFilm; //!< the film that reel belongs to + short presRnum; //!< the present reel number + short presX, presY; +}; + +int SaveActors(SAVED_ACTOR *sActorInfo); + + +void RestoreActorProcess(int id, INT_CONTEXT *pic); + + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_ACTOR_H */ diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp new file mode 100644 index 0000000000..95d834d88a --- /dev/null +++ b/engines/tinsel/anim.cpp @@ -0,0 +1,404 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle object animation. + */ + +#include "tinsel/anim.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + +/** Animation script commands */ +enum { + ANI_END = 0, //!< end of animation script + ANI_JUMP = 1, //!< animation script jump + ANI_HFLIP = 2, //!< flip animated object horizontally + ANI_VFLIP = 3, //!< flip animated object vertically + ANI_HVFLIP = 4, //!< flip animated object in both directions + ANI_ADJUSTX = 5, //!< adjust animated object x animation point + ANI_ADJUSTY = 6, //!< adjust animated object y animation point + ANI_ADJUSTXY = 7, //!< adjust animated object x & y animation points + ANI_NOSLEEP = 8, //!< do not sleep for this frame + ANI_CALL = 9, //!< call routine + ANI_HIDE = 10 //!< hide animated object +}; + +/** animation script command possibilities */ +union ANI_SCRIPT { + int32 op; //!< treat as an opcode or operand + uint32 hFrame; //!< treat as a animation frame handle +}; + +/** + * Advance to next frame routine. + * @param pAnim Animation data structure + */ +SCRIPTSTATE DoNextFrame(ANIM *pAnim) { + // get a pointer to the script + const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + + while (1) { // repeat until a real image + + switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { + case ANI_END: // end of animation script + + // move to next opcode + pAnim->scriptIndex++; + + // indicate script has finished + return ScriptFinished; + + case ANI_JUMP: // do animation jump + + // move to jump address + pAnim->scriptIndex++; + + // jump to new frame position + pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // go fetch a real image + break; + + case ANI_HFLIP: // flip animated object horizontally + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_VFLIP: // flip animated object vertically + + // next opcode + pAnim->scriptIndex++; + + MultiVerticalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_HVFLIP: // flip animated object in both directions + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + MultiVerticalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_ADJUSTX: // adjust animated object x animation point + + // move to x adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_ADJUSTY: // adjust animated object y animation point + + // move to y adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_ADJUSTXY: // adjust animated object x & y animation points + { + int x, y; + + // move to x adjustment operand + pAnim->scriptIndex++; + x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // move to y adjustment operand + pAnim->scriptIndex++; + y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + MultiAdjustXY(pAnim->pObject, x, y); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + } + + case ANI_NOSLEEP: // do not sleep for this frame + + // next opcode + pAnim->scriptIndex++; + + // indicate not to sleep + return ScriptNoSleep; + + case ANI_CALL: // call routine + + // move to function address + pAnim->scriptIndex++; + + // make function call + + // REMOVED BUGGY CODE + // pFunc is a function pointer that's part of a union and is assumed to be 32-bits. + // There is no known place where a function pointer is stored inside the animation + // scripts, something which wouldn't have worked anyway. Having played through the + // entire game, there hasn't been any occurence of this case, so just error out here + // in case we missed something (highly unlikely though) + error("ANI_CALL opcode encountered! Please report this error to the ScummVM team"); + //(*pAni[pAnim->scriptIndex].pFunc)(pAnim); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_HIDE: // hide animated object + + MultiHideObject(pAnim->pObject); + + // next opcode + pAnim->scriptIndex++; + + // dont skip a sleep + return ScriptSleep; + + default: // must be an actual animation frame handle + + // set objects new animation frame + pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + + // re-shape the object + MultiReshape(pAnim->pObject); + + // next opcode + pAnim->scriptIndex++; + + // dont skip a sleep + return ScriptSleep; + } + } +} + +/** + * Init a ANIM structure for single stepping through a animation script. + * @param pAnim Animation data structure + * @param pAniObj Object to animate + * @param hNewScript Script of multipart frames + * @param aniSpeed Sets speed of animation in frames + */ +void InitStepAnimScript(ANIM *pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) { + OBJECT *pObj; // multi-object list iterator + + pAnim->aniDelta = 1; // will animate on next call to NextAnimRate + pAnim->pObject = pAniObj; // set object to animate + pAnim->hScript = hNewScript; // set animation script + pAnim->scriptIndex = 0; // start of script + pAnim->aniRate = aniSpeed; // set speed of animation + + // reset flip flags for the object - let the script do the flipping + for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) { + AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV), + pObj->hImg); + } +} + +/** + * Execute the next command in a animation script. + * @param pAnim Animation data structure + */ +SCRIPTSTATE StepAnimScript(ANIM *pAnim) { + SCRIPTSTATE state; + + if (--pAnim->aniDelta == 0) { + // re-init animation delta counter + pAnim->aniDelta = pAnim->aniRate; + + // move to next frame + while ((state = DoNextFrame(pAnim)) == ScriptNoSleep) + ; + + return state; + } + + // indicate calling task should sleep + return ScriptSleep; +} + +/** + * Skip the specified number of frames. + * @param pAnim Animation data structure + * @param numFrames Number of frames to skip + */ +void SkipFrames(ANIM *pAnim, int numFrames) { + // get a pointer to the script + const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + + if (numFrames <= 0) + // do nothing + return; + + while (1) { // repeat until a real image + + switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { + case ANI_END: // end of animation script + // going off the end is probably a error + error("SkipFrames(): formally 'assert(0)!'"); + break; + + case ANI_JUMP: // do animation jump + + // move to jump address + pAnim->scriptIndex++; + + // jump to new frame position + pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + break; + + case ANI_HFLIP: // flip animated object horizontally + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + break; + + case ANI_VFLIP: // flip animated object vertically + + // next opcode + pAnim->scriptIndex++; + + MultiVerticalFlip(pAnim->pObject); + break; + + case ANI_HVFLIP: // flip animated object in both directions + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + MultiVerticalFlip(pAnim->pObject); + break; + + case ANI_ADJUSTX: // adjust animated object x animation point + + // move to x adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_ADJUSTY: // adjust animated object y animation point + + // move to y adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_ADJUSTXY: // adjust animated object x & y animation points + { + int x, y; + + // move to x adjustment operand + pAnim->scriptIndex++; + x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // move to y adjustment operand + pAnim->scriptIndex++; + y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + MultiAdjustXY(pAnim->pObject, x, y); + + // next opcode + pAnim->scriptIndex++; + + break; + } + + case ANI_NOSLEEP: // do not sleep for this frame + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_CALL: // call routine + + // skip function address + pAnim->scriptIndex += 2; + break; + + case ANI_HIDE: // hide animated object + + // next opcode + pAnim->scriptIndex++; + break; + + default: // must be an actual animation frame handle + + // one less frame + if (numFrames-- > 0) { + // next opcode + pAnim->scriptIndex++; + } else { + // set objects new animation frame + pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + + // re-shape the object + MultiReshape(pAnim->pObject); + + // we have skipped to the correct place + return; + } + break; + } + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h new file mode 100644 index 0000000000..5b25292a6b --- /dev/null +++ b/engines/tinsel/anim.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Object animation definitions + */ + +#ifndef TINSEL_ANIM_H // prevent multiple includes +#define TINSEL_ANIM_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +struct OBJECT; + +/** animation structure */ +struct ANIM { + int aniRate; //!< animation speed + int aniDelta; //!< animation speed delta counter + OBJECT *pObject; //!< object to animate (assumed to be multi-part) + uint32 hScript; //!< animation script handle + int scriptIndex; //!< current position in animation script +}; + + +/*----------------------------------------------------------------------*\ +|* Anim Function Prototypes *| +\*----------------------------------------------------------------------*/ + +/** states for DoNextFrame */ +enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep}; + +SCRIPTSTATE DoNextFrame( // Execute the next animation frame of a animation script + ANIM *pAnim); // animation data structure + +void InitStepAnimScript( // Init a ANIM struct for single stepping through a animation script + ANIM *pAnim, // animation data structure + OBJECT *pAniObj, // object to animate + SCNHANDLE hNewScript, // handle to script of multipart frames + int aniSpeed); // sets speed of animation in frames + +SCRIPTSTATE StepAnimScript( // Execute the next command in a animation script + ANIM *pAnim); // animation data structure + +void SkipFrames( // Skip the specified number of frames + ANIM *pAnim, // animation data structure + int numFrames); // number of frames to skip + +} // end of namespace Tinsel + +#endif // TINSEL_ANIM_H diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp new file mode 100644 index 0000000000..91d21b4e0b --- /dev/null +++ b/engines/tinsel/background.cpp @@ -0,0 +1,232 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Background handling code. + */ + +#include "tinsel/background.h" +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" +#include "tinsel/sched.h" // process sheduler defs +#include "tinsel/object.h" +#include "tinsel/pid.h" // process identifiers +#include "tinsel/tinsel.h" + +namespace Tinsel { + +// current background +BACKGND *pCurBgnd = NULL; + +/** + * Called to initialise a background. + * @param pBgnd Pointer to data struct for current background + */ + +void InitBackground(BACKGND *pBgnd) { + int i; // playfield counter + PLAYFIELD *pPlayfield; // pointer to current playfield + + // set current background + pCurBgnd = pBgnd; + + // init background sky colour + SetBgndColour(pBgnd->rgbSkyColour); + + // start of playfield array + pPlayfield = pBgnd->fieldArray; + + // for each background playfield + for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) { + // init playfield pos + pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x); + pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y); + + // no scrolling + pPlayfield->fieldXvel = intToFrac(0); + pPlayfield->fieldYvel = intToFrac(0); + + // clear playfield display list + pPlayfield->pDispList = NULL; + + // clear playfield moved flag + pPlayfield->bMoved = false; + } +} + +/** + * Sets the xy position of the specified playfield in the current background. + * @param which Which playfield + * @param newXpos New x position + * @param newYpos New y position + */ + +void PlayfieldSetPos(int which, int newXpos, int newYpos) { + PLAYFIELD *pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // set new integer position + pPlayfield->fieldX = intToFrac(newXpos); + pPlayfield->fieldY = intToFrac(newYpos); + + // set moved flag + pPlayfield->bMoved = true; +} + +/** + * Returns the xy position of the specified playfield in the current background. + * @param which Which playfield + * @param pXpos Returns current x position + * @param pYpos Returns current y position + */ + +void PlayfieldGetPos(int which, int *pXpos, int *pYpos) { + PLAYFIELD *pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // get current integer position + *pXpos = fracToInt(pPlayfield->fieldX); + *pYpos = fracToInt(pPlayfield->fieldY); +} + +/** + * Returns the display list for the specified playfield. + * @param which Which playfield + */ + +OBJECT *GetPlayfieldList(int which) { + PLAYFIELD *pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // return the display list pointer for this playfield + return (OBJECT *)&pPlayfield->pDispList; +} + +/** + * Draws all the playfield object lists for the current background. + * The playfield velocity is added to the playfield position in order + * to scroll each playfield before it is drawn. + */ + +void DrawBackgnd(void) { + int i; // playfield counter + PLAYFIELD *pPlay; // playfield pointer + int prevX, prevY; // save interger part of position + Common::Point ptWin; // window top left + + if (pCurBgnd == NULL) + return; // no current background + + // scroll each background playfield + for (i = 0; i < pCurBgnd->numPlayfields; i++) { + // get pointer to correct playfield + pPlay = pCurBgnd->fieldArray + i; + + // save integer part of position + prevX = fracToInt(pPlay->fieldX); + prevY = fracToInt(pPlay->fieldY); + + // update scrolling + pPlay->fieldX += pPlay->fieldXvel; + pPlay->fieldY += pPlay->fieldYvel; + + // convert fixed point window pos to a int + ptWin.x = fracToInt(pPlay->fieldX); + ptWin.y = fracToInt(pPlay->fieldY); + + // set the moved flag if the playfield has moved + if (prevX != ptWin.x || prevY != ptWin.y) + pPlay->bMoved = true; + + // sort the display list for this background - just in case somebody has changed object Z positions + SortObjectList((OBJECT *)&pPlay->pDispList); + + // generate clipping rects for all objects that have moved etc. + FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin, + &pPlay->rcClip, false, pPlay->bMoved); + + // clear playfield moved flag + pPlay->bMoved = false; + } + + // merge the clipping rectangles + MergeClipRect(); + + // redraw all playfields within the clipping rectangles + const RectList &clipRects = GetClipRects(); + for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { + // clear the clip rectangle on the virtual screen + // for each background playfield + for (i = 0; i < pCurBgnd->numPlayfields; i++) { + Common::Rect rcPlayClip; // clip rect for this playfield + + // get pointer to correct playfield + pPlay = pCurBgnd->fieldArray + i; + + // convert fixed point window pos to a int + ptWin.x = fracToInt(pPlay->fieldX); + ptWin.y = fracToInt(pPlay->fieldY); + + if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r)) + // redraw all objects within this clipping rect + UpdateClipRect((OBJECT *)&pPlay->pDispList, + &ptWin, &rcPlayClip); + } + } + + // transfer any new palettes to the video DAC + PalettesToVideoDAC(); + + // update the screen within the clipping rectangles + for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { + UpdateScreenRect(*r); + } + + // delete all the clipping rectangles + ResetClipRect(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h new file mode 100644 index 0000000000..7b8d099446 --- /dev/null +++ b/engines/tinsel/background.h @@ -0,0 +1,107 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Data structures used for handling backgrounds + */ + +#ifndef TINSEL_BACKGND_H // prevent multiple includes +#define TINSEL_BACKGND_H + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/palette.h" // palette definitions +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + + +/** Scrolling padding. Needed because scroll process does not normally run on every frame */ +enum { + SCROLLX_PAD = 64, + SCROLLY_PAD = 64 +}; + +/** When module BLK_INFO list is this long, switch from a binary to linear search */ +#define LINEAR_SEARCH 5 + +/** background playfield structure - a playfield is a container for modules */ +struct PLAYFIELD { + OBJECT *pDispList; //!< object display list for this playfield + frac_t fieldX; //!< current world x position of playfield + frac_t fieldY; //!< current world y position of playfield + frac_t fieldXvel; //!< current x velocity of playfield + frac_t fieldYvel; //!< current y velocity of playfield + Common::Rect rcClip; //!< clip rectangle for this playfield + bool bMoved; //!< set when playfield has moved +}; + +/** multi-playfield background structure - a backgnd is a container of playfields */ +struct BACKGND { + COLORREF rgbSkyColour; //!< background sky colour + Common::Point ptInitWorld; //!< initial world position + Common::Rect rcScrollLimits; //!< scroll limits + int refreshRate; //!< background update process refresh rate + frac_t *pXscrollTable; //!< pointer to x direction scroll table for this background + frac_t *pYscrollTable; //!< pointer to y direction scroll table for this background + int numPlayfields; //!< number of playfields for this background + PLAYFIELD *fieldArray; //!< pointer to array of all playfields for this background + bool bAutoErase; //!< when set - screen is cleared before anything is plotted (unused) +}; + + +/*----------------------------------------------------------------------*\ +|* Background Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void InitBackground( // called to initialise a background + BACKGND *pBgnd); // pointer to data struct for current background + +void StopBgndScrolling(void); // Stops all background playfields from scrolling + +void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background + int which, // which playfield + int newXpos, // new x position + int newYpos); // new y position + +void PlayfieldGetPos( // Returns the xy position of the specified playfield in the current background + int which, // which playfield + int *pXpos, // returns current x position + int *pYpos); // returns current y position + +OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield + int which); // which playfield + +void KillPlayfieldList( // Kills all the objects on the display list for the specified playfield + int which); // which playfield + +void DrawBackgnd(void); // Draws all playfields for the current background + +void RedrawBackgnd(void); // Completely redraws all the playfield object lists for the current background + +SCNHANDLE BackPal(void); + +} // end of namespace Tinsel + +#endif // TINSEL_BACKGND_H diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp new file mode 100644 index 0000000000..9c1e5f1540 --- /dev/null +++ b/engines/tinsel/bg.cpp @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Plays the background film of a scene. + */ + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" // CONTROL_STARTOFF +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" // For control() + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static SCNHANDLE BackPalette = 0; // Background's palette +static OBJECT *pBG = 0; // The main picture's object. +static int BGspeed = 0; +static SCNHANDLE BgroundHandle = 0; // Current scene handle - stored in case of Save_Scene() +static bool DoFadeIn = false; +static ANIM thisAnim; // used by BGmainProcess() + +/** + * BackPal + */ +SCNHANDLE BackPal(void) { + return BackPalette; +} + +/** + * SetDoFadeIn +*/ +void SetDoFadeIn(bool tf) { + DoFadeIn = tf; +} + +/** + * Called before scene change. + */ +void DropBackground(void) { + pBG = NULL; // No background + BackPalette = 0; // No background palette +} + +/** + * Return the width of the current background. + */ +int BackgroundWidth(void) { + assert(pBG); + return MultiRightmost(pBG) + 1; +} + +/** + * Return the height of the current background. + */ +int BackgroundHeight(void) { + assert(pBG); + return MultiLowest(pBG) + 1; +} + +/** + * Run main animation that comprises the scene background. + */ +static void BGmainProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FREEL *pfr; + const MULTI_INIT *pmi; + + // get the stuff copied to process when it was created + pfr = (const FREEL *)param; + + if (pBG == NULL) { + /*** At start of scene ***/ + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); + + // Initialise and insert the object, and initialise its script. + pBG = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG); + InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); + + if (DoFadeIn) { + FadeInFast(NULL); + DoFadeIn = false; + } + + while (StepAnimScript(&thisAnim) != ScriptFinished) + CORO_SLEEP(1); + + error("Background animation has finished!"); + } else { + // New background during scene + + // Just re-initialise the script. + InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); + StepAnimScript(&thisAnim); + } + + CORO_END_CODE; +} + +/** + * setBackPal() + */ +void setBackPal(SCNHANDLE hPal) { + BackPalette = hPal; + + fettleFontPal(BackPalette); + CreateTranslucentPalette(BackPalette); +} + +void ChangePalette(SCNHANDLE hPal) { + SwapPalette(FindPalette(BackPalette), hPal); + + setBackPal(hPal); +} + +/** + * Given the scene background film, extracts the palette handle for + * everything else's use, then starts a display process for each reel + * in the film. + * @param bfilm Scene background film + */ +void startupBackground(SCNHANDLE bfilm) { + const FILM *pfilm; + IMAGE *pim; + + BgroundHandle = bfilm; // Save handle in case of Save_Scene() + + pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm); + setBackPal(FROM_LE_32(pim->hImgPal)); + + // Extract the film speed + BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate); + + if (pBG == NULL) + control(CONTROL_STARTOFF); // New feature - start scene with control off + + // Start display process for each reel in the film + assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn + g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL)); +} + +/** + * Return the current scene handle. + */ +SCNHANDLE GetBgroundHandle(void) { + return BgroundHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp new file mode 100644 index 0000000000..b67ae7b17f --- /dev/null +++ b/engines/tinsel/cliprect.cpp @@ -0,0 +1,313 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the clipping rectangle code. + */ + +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" // normal object drawing +#include "tinsel/object.h" +#include "tinsel/palette.h" + +namespace Tinsel { + +/** list of all clip rectangles */ +static RectList s_rectList; + +/** + * Resets the clipping rectangle allocator. + */ +void ResetClipRect(void) { + s_rectList.clear(); +} + +/** + * Allocate a clipping rectangle from the free list. + * @param pClip clip rectangle dimensions to allocate + */ +void AddClipRect(const Common::Rect &pClip) { + s_rectList.push_back(pClip); +} + +const RectList &GetClipRects() { + return s_rectList; +} + +/** + * Creates the intersection of two rectangles. + * Returns True if there is a intersection. + * @param pDest Pointer to destination rectangle that is to receive the intersection + * @param pSrc1 Pointer to a source rectangle + * @param pSrc2 Pointer to a source rectangle + */ +bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + pDest.left = MAX(pSrc1.left, pSrc2.left); + pDest.top = MAX(pSrc1.top, pSrc2.top); + pDest.right = MIN(pSrc1.right, pSrc2.right); + pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom); + + return !pDest.isEmpty(); +} + +/** + * Creates the union of two rectangles. + * Returns True if there is a union. + * @param pDest destination rectangle that is to receive the new union + * @param pSrc1 a source rectangle + * @param pSrc2 a source rectangle + */ +bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + pDest.left = MIN(pSrc1.left, pSrc2.left); + pDest.top = MIN(pSrc1.top, pSrc2.top); + pDest.right = MAX(pSrc1.right, pSrc2.right); + pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom); + + return !pDest.isEmpty(); +} + +/** + * Check if the two rectangles are next to each other. + * @param pSrc1 a source rectangle + * @param pSrc2 a source rectangle + */ +static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + Common::Rect pDest; + + pDest.left = MAX(pSrc1.left, pSrc2.left); + pDest.top = MAX(pSrc1.top, pSrc2.top); + pDest.right = MIN(pSrc1.right, pSrc2.right); + pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom); + + return pDest.isValidRect(); +} + +/** + * Adds velocities and creates clipping rectangles for all the + * objects that have moved on the specified object list. + * @param pObjList Playfield display list to draw + * @param pWin Playfield window top left position + * @param pClip Playfield clipping rectangle + * @param bNoVelocity When reset, objects pos is updated with velocity + * @param bScrolled) When set, playfield has scrolled + */ +void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) { + OBJECT *pObj; // object list traversal pointer + + for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { + if (!bNoVelocity) { + // we want to add velocities to objects position + + if (bScrolled) { + // this playfield has scrolled + + // indicate change + pObj->flags |= DMA_CHANGED; + } + } + + if ((pObj->flags & DMA_CHANGED) || // object changed + HasPalMoved(pObj->pPal)) { // or palette moved + // object has changed in some way + + Common::Rect rcClip; // objects clipped bounding rectangle + Common::Rect rcObj; // objects bounding rectangle + + // calc intersection of objects previous bounding rectangle + // NOTE: previous position is in screen co-ords + if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) { + // previous position is within clipping rect + AddClipRect(rcClip); + } + + // calc objects current bounding rectangle + if (pObj->flags & DMA_ABS) { + // object position is absolute + rcObj.left = fracToInt(pObj->xPos); + rcObj.top = fracToInt(pObj->yPos); + } else { + // object position is relative to window + rcObj.left = fracToInt(pObj->xPos) - pWin->x; + rcObj.top = fracToInt(pObj->yPos) - pWin->y; + } + rcObj.right = rcObj.left + pObj->width; + rcObj.bottom = rcObj.top + pObj->height; + + // calc intersection of object with clipping rect + if (IntersectRectangle(rcClip, rcObj, *pClip)) { + // current position is within clipping rect + AddClipRect(rcClip); + + // update previous position + pObj->rcPrev = rcClip; + } else { + // clear previous position + pObj->rcPrev = Common::Rect(); + } + + // clear changed flag + pObj->flags &= ~DMA_CHANGED; + } + } +} + +/** + * Merges any clipping rectangles that overlap to try and reduce + * the total number of clip rectangles. + */ +void MergeClipRect() { + if (s_rectList.size() <= 1) + return; + + RectList::iterator rOuter, rInner; + + for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) { + rInner = rOuter; + while (++rInner != s_rectList.end()) { + + if (LooseIntersectRectangle(*rOuter, *rInner)) { + // these two rectangles overlap or + // are next to each other - merge them + + UnionRectangle(*rOuter, *rOuter, *rInner); + + // remove the inner rect from the list + s_rectList.erase(rInner); + + // move back to beginning of list + rInner = rOuter; + } + } + } +} + +/** + * Redraws all objects within this clipping rectangle. + * @param pObjList Object list to draw + * @param pWin Window top left position + * @param pClip Pointer to clip rectangle + */ +void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) { + int x, y, right, bottom; // object corners + int hclip, vclip; // total size of object clipping + DRAWOBJECT currentObj; // filled in to draw the current object in list + OBJECT *pObj; // object list iterator + + // Initialise the fields of the drawing object to empty + memset(¤tObj, 0, sizeof(DRAWOBJECT)); + + for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { + if (pObj->flags & DMA_ABS) { + // object position is absolute + x = fracToInt(pObj->xPos); + y = fracToInt(pObj->yPos); + } else { + // object position is relative to window + x = fracToInt(pObj->xPos) - pWin->x; + y = fracToInt(pObj->yPos) - pWin->y; + } + + // calc object right + right = x + pObj->width; + if (right < 0) + // totally clipped if negative + continue; + + // calc object bottom + bottom = y + pObj->height; + if (bottom < 0) + // totally clipped if negative + continue; + + // bottom clip = low right y - clip low right y + currentObj.botClip = bottom - pClip->bottom; + if (currentObj.botClip < 0) { + // negative - object is not clipped + currentObj.botClip = 0; + } + + // right clip = low right x - clip low right x + currentObj.rightClip = right - pClip->right; + if (currentObj.rightClip < 0) { + // negative - object is not clipped + currentObj.rightClip = 0; + } + + // top clip = clip top left y - top left y + currentObj.topClip = pClip->top - y; + if (currentObj.topClip < 0) { + // negative - object is not clipped + currentObj.topClip = 0; + } else { // clipped - adjust start position to top of clip rect + y = pClip->top; + } + + // left clip = clip top left x - top left x + currentObj.leftClip = pClip->left - x; + if (currentObj.leftClip < 0) { + // negative - object is not clipped + currentObj.leftClip = 0; + } + else + // NOTE: This else statement is disabled in tinsel v1 + { // clipped - adjust start position to left of clip rect + x = pClip->left; + } + + // calc object total horizontal clipping + hclip = currentObj.leftClip + currentObj.rightClip; + + // calc object total vertical clipping + vclip = currentObj.topClip + currentObj.botClip; + + if (hclip + vclip != 0) { + // object is clipped in some way + + if (pObj->width <= hclip) + // object totally clipped horizontally - ignore + continue; + + if (pObj->height <= vclip) + // object totally clipped vertically - ignore + continue; + + // set clip bit in objects flags + currentObj.flags = pObj->flags | DMA_CLIP; + } else { // object is not clipped - copy flags + currentObj.flags = pObj->flags; + } + + // copy objects properties to local object + currentObj.width = pObj->width; + currentObj.height = pObj->height; + currentObj.xPos = (short)x; + currentObj.yPos = (short)y; + currentObj.pPal = pObj->pPal; + currentObj.constant = pObj->constant; + currentObj.hBits = pObj->hBits; + + // draw the object + DrawObject(¤tObj); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h new file mode 100644 index 0000000000..28a66c312c --- /dev/null +++ b/engines/tinsel/cliprect.h @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CLIPRECT_H // prevent multiple includes +#define TINSEL_CLIPRECT_H + +#include "common/list.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + +typedef Common::List<Common::Rect> RectList; + +/*----------------------------------------------------------------------*\ +|* Clip Rect Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ResetClipRect(); // Resets the clipping rectangle allocator + +void AddClipRect( // Allocate a clipping rectangle from the free list + const Common::Rect &pClip); // clip rectangle dimensions to allocate + +const RectList &GetClipRects(); + +bool IntersectRectangle( // Creates the intersection of two rectangles + Common::Rect &pDest, // pointer to destination rectangle that is to receive the intersection + const Common::Rect &pSrc1, // pointer to a source rectangle + const Common::Rect &pSrc2); // pointer to a source rectangle + +bool UnionRectangle( // Creates the union of two rectangles + Common::Rect &pDest, // destination rectangle that is to receive the new union + const Common::Rect &pSrc1, // a source rectangle + const Common::Rect &pSrc2); // a source rectangle + +void FindMovingObjects( // Creates clipping rectangles for all the objects that have moved on the specified object list + OBJECT *pObjList, // playfield display list to draw + Common::Point *pWin, // playfield window top left position + Common::Rect *pClip, // playfield clipping rectangle + bool bVelocity, // when set, objects pos is updated with velocity + bool bScrolled); // when set, playfield has scrolled + +void MergeClipRect(); // Merges any clipping rectangles that overlap + +void UpdateClipRect( // Redraws all objects within this clipping rectangle + OBJECT *pObjList, // object list to draw + Common::Point *pWin, // window top left position + Common::Rect *pClip); // pointer to clip rectangle + +} // end of namespace Tinsel + +#endif // TINSEL_CLIPRECT_H diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp new file mode 100644 index 0000000000..4c143f1b8d --- /dev/null +++ b/engines/tinsel/config.cpp @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains configuration functionality + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +#include "common/file.h" +#include "common/config-manager.h" + +#include "sound/mixer.h" + +namespace Tinsel { + +//----------------- GLOBAL GLOBAL DATA -------------------- + +int dclickSpeed = DOUBLE_CLICK_TIME; +int volMidi = MAXMIDIVOL; +int volSound = MAXSAMPVOL; +int volVoice = MAXSAMPVOL; +int speedText = DEFTEXTSPEED; +int bSubtitles = false; +int bSwapButtons = 0; +LANGUAGE language = TXT_ENGLISH; +int bAmerica = 0; + + +// Shouldn't really be here, but time is short... +bool bNoBlocking; + +/** + * WriteConfig() + */ + +void WriteConfig(void) { + ConfMan.setInt("dclick_speed", dclickSpeed); + ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL); + ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); + ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); + ConfMan.setInt("talkspeed", (speedText * 255) / 100); + ConfMan.setBool("subtitles", bSubtitles); + //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0); + //ConfigData.language = language; // not necessary, as language has been set in the launcher + //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB +} + +/*---------------------------------------------------------------------*\ +| ReadConfig() | +|-----------------------------------------------------------------------| +| +\*---------------------------------------------------------------------*/ +void ReadConfig(void) { + if (ConfMan.hasKey("dclick_speed")) + dclickSpeed = ConfMan.getInt("dclick_speed"); + + volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume; + volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; + volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; + + if (ConfMan.hasKey("talkspeed")) + speedText = (ConfMan.getInt("talkspeed") * 100) / 255; + if (ConfMan.hasKey("subtitles")) + bSubtitles = ConfMan.getBool("subtitles"); + + // FIXME: If JAPAN is set, subtitles are forced OFF in the original engine + + //bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false; + //ConfigData.language = language; // not necessary, as language has been set in the launcher + //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB + +// The flags here control how many country flags are displayed in one of the option dialogs. +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + language = ConfigData.language; + #ifdef USE_3FLAGS + if (language == TXT_ENGLISH || language == TXT_ITALIAN) { + language = TXT_GERMAN; + bSubtitles = true; + } + #endif + #ifdef USE_4FLAGS + if (language == TXT_ENGLISH) { + language = TXT_GERMAN; + bSubtitles = true; + } + #endif +#else + language = TXT_ENGLISH; +#endif +} + +bool isJapanMode() { +#ifdef JAPAN + return true; +#else + return false; +#endif +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h new file mode 100644 index 0000000000..73cc411cb6 --- /dev/null +++ b/engines/tinsel/config.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_CONFIG_H +#define TINSEL_CONFIG_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// None of these defined -> 1 language, in ENGLISH.TXT +//#define USE_5FLAGS 1 // All 5 flags +//#define USE_4FLAGS 1 // French, German, Italian, Spanish +//#define USE_3FLAGS 1 // French, German, Spanish + +// The Hebrew version appears to the software as being English +// but it needs to have subtitles on... +//#define HEBREW 1 + +//#define JAPAN 1 + + +// double click timer initial value +#define DOUBLE_CLICK_TIME 6 // 6 @ 18Hz = .33 sec + +#define DEFTEXTSPEED 0 + + +extern int dclickSpeed; +extern int volMidi; +extern int volSound; +extern int volVoice; +extern int speedText; +extern int bSubtitles; +extern int bSwapButtons; +extern LANGUAGE language; +extern int bAmerica; + +void WriteConfig(void); +void ReadConfig(void); + +extern bool isJapanMode(); + + +// Shouldn't really be here, but time is short... +extern bool bNoBlocking; + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h new file mode 100644 index 0000000000..e0292735bb --- /dev/null +++ b/engines/tinsel/coroutine.h @@ -0,0 +1,147 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_COROUTINE_H +#define TINSEL_COROUTINE_H + +#include "common/scummsys.h" + +namespace Tinsel { + +/* + * The following is loosely based on an article by Simon Tatham: + * <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>. + * However, many improvements and tweaks have been made, in particular + * by taking advantage of C++ features not available in C. + * + * Why is this code here? Well, the Tinsel engine apparently used + * setjmp/longjmp based coroutines as a core tool from the start, and + * so they are deeply ingrained into the whole code base. When we + * started to get Tinsel ready for ScummVM, we had to deal with that. + * It soon got clear that we could not simply rewrite the code to work + * without some form of coroutines. While possible in principle, it + * would have meant a major restructuring of the entire code base, a + * rather daunting task. Also, it would have very likely introduced + * tons of regressons. + * + * So instead of getting rid of the coroutines, we chose to implement + * them in an alternate way, using Simon Tatham's trick as described + * above. While the trick is dirty, the result seems to be clear enough, + * we hope; plus, it allowed us to stay relatively close to the + * original structure of the code, which made it easier to avoid + * regressions, and will be helpful in the future when comparing things + * against the original code base. + */ + + +/** + * The core of any coroutine context which captures the 'state' of a coroutine. + * Private use only + */ +struct CoroBaseContext { + int _line; + int _sleep; + CoroBaseContext *_subctx; + CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {} + ~CoroBaseContext() { delete _subctx; } +}; + +typedef CoroBaseContext *CoroContext; + + +/** + * Wrapper class which holds a pointer to a pointer to a CoroBaseContext. + * The interesting part is the destructor, which kills the context being held, + * but ONLY if the _sleep val of that context is zero. This way, a coroutine + * can just 'return' w/o having to worry about freeing the allocated context + * (in Simon Tatham's original code, one had to use a special macro to + * return from a coroutine). + */ +class CoroContextHolder { + CoroContext &_ctx; +public: + CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {} + ~CoroContextHolder() { + if (_ctx && _ctx->_sleep == 0) { + delete _ctx; + _ctx = 0; + } + } +}; + + +#define CORO_PARAM CoroContext &coroParam + +#define CORO_SUBCTX coroParam->_subctx + + +#define CORO_BEGIN_CONTEXT struct CoroContextTag : CoroBaseContext { int DUMMY +#define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam + +#define CORO_BEGIN_CODE(x) \ + if (!x) {coroParam = x = new CoroContextTag();}\ + assert(coroParam);\ + assert(coroParam->_sleep >= 0);\ + coroParam->_sleep = 0;\ + CoroContextHolder tmpHolder(coroParam);\ + switch(coroParam->_line) { case 0:; + +#define CORO_END_CODE \ + } + +#define CORO_SLEEP(delay) \ + do {\ + coroParam->_line = __LINE__;\ + coroParam->_sleep = delay;\ + return; case __LINE__:;\ + } while (0) + +/** Stop the currently running coroutine */ +#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0) + +/** Invoke another coroutine */ +#define CORO_INVOKE_ARGS(subCoro, ARGS) \ + do {\ + coroParam->_line = __LINE__;\ + coroParam->_subctx = 0;\ + do {\ + subCoro ARGS;\ + if (!coroParam->_subctx) break;\ + coroParam->_sleep = coroParam->_subctx->_sleep;\ + return; case __LINE__:;\ + } while(1);\ + } while (0) + +#define CORO_INVOKE_0(subCoroutine) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX)) +#define CORO_INVOKE_1(subCoroutine, a0) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0)) +#define CORO_INVOKE_2(subCoroutine, a0,a1) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1)) + + +} // end of namespace Tinsel + +#endif // TINSEL_COROUTINE_H diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp new file mode 100644 index 0000000000..b95662cbfe --- /dev/null +++ b/engines/tinsel/cursor.cpp @@ -0,0 +1,647 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Cursor and cursor trails. + */ + +#include "tinsel/cursor.h" + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" // For EventsManager class +#include "tinsel/film.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" // resetidletime() +#include "tinsel/tinsel.h" // For engine access + + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define ITERATION_BASE FRAC_ONE +#define ITER_ACCELLERATION (10L << (FRAC_BITS - 4)) + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static OBJECT *McurObj = 0; // Main cursor object +static OBJECT *AcurObj = 0; // Auxiliary cursor object + +static ANIM McurAnim = {0,0,0,0,0}; // Main cursor animation structure +static ANIM AcurAnim = {0,0,0,0,0}; // Auxiliary cursor animation structure + +static bool bHiddenCursor = false; // Set when cursor is hidden +static bool bTempNoTrailers = false; // Set when cursor trails are hidden + +static bool bFrozenCursor = false; // Set when cursor position is frozen + +static frac_t IterationSize = 0; + +static SCNHANDLE CursorHandle = 0; // Handle to cursor reel data + +static int numTrails = 0; +static int nextTrail = 0; + +static bool bWhoa = false; // Set by DropCursor() at the end of a scene + // - causes cursor processes to do nothing + // Reset when main cursor has re-initialised + +static bool restart = false; // When main cursor has been bWhoa-ed, it waits + // for this to be set to true. + +static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets + + + +#define MAX_TRAILERS 10 + +static struct { + + ANIM trailAnim; // Animation structure + OBJECT *trailObj; // This trailer's object + +} ntrailData [MAX_TRAILERS]; + +static int lastCursorX = 0, lastCursorY = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void MoveCursor(void); + +/** + * Initialise and insert a cursor trail object, set its Z-pos, and hide + * it. Also initialise its animation script. + */ +static void InitCurTrailObj(int i, int x, int y) { + const FREEL *pfr; // pointer to reel + IMAGE *pim; // pointer to image + const MULTI_INIT *pmi; // MULTI_INIT structure + + const FILM *pfilm; + + if (!numTrails) + return; + + // Get rid of old object + if (ntrailData[i].trailObj != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + + pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // No background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Initialise and insert the object, set its Z-pos, and hide it + ntrailData[i].trailObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL); + MultiSetAniXY(ntrailData[i].trailObj, x, y); + + // Initialise the animation script + InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + StepAnimScript(&ntrailData[i].trailAnim); +} + +/** + * Get the cursor position from the mouse driver. + */ +static bool GetDriverPosition(int *x, int *y) { + Common::Point ptMouse = _vm->getMousePosition(); + *x = ptMouse.x; + *y = ptMouse.y; + + return(*x >= 0 && *x <= SCREEN_WIDTH-1 && + *y >= 0 && *y <= SCREEN_HEIGHT-1); +} + +/** + * Move the cursor relative to current position. + */ +void AdjustCursorXY(int deltaX, int deltaY) { + int x, y; + + if (deltaX || deltaY) { + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY)); + } + MoveCursor(); +} + +/** + * Move the cursor to an absolute position. + */ +void SetCursorXY(int newx, int newy) { + int x, y; + int Loffset, Toffset; // Screen offset + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + newx -= Loffset; + newy -= Toffset; + + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(newx, newy)); + MoveCursor(); +} + +/** + * Move the cursor to a screen position. + */ +void SetCursorScreenXY(int newx, int newy) { + int x, y; + + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(newx, newy)); + MoveCursor(); +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position in (x,y). + * Returns false if there is no cursor object. + */ +bool GetCursorXYNoWait(int *x, int *y, bool absolute) { + if (McurObj == NULL) { + *x = *y = 0; + return false; + } + + GetAniPosition(McurObj, x, y); + + if (absolute) { + int Loffset, Toffset; // Screen offset + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + *x += Loffset; + *y += Toffset; + } + + return true; +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position. + * If called while there is no cursor object, the calling process ends + * up waiting until there is. + */ +void GetCursorXY(int *x, int *y, bool absolute) { + //while (McurObj == NULL) + // ProcessSleepSelf(); + assert(McurObj); + GetCursorXYNoWait(x, y, absolute); +} + +/** + * Re-initialise the main cursor to use the main cursor reel. + * Called from TINLIB.C to restore cursor after hiding it. + * Called from INVENTRY.C to restore cursor after customising it. + */ +void RestoreMainCursor(void) { + const FILM *pfilm; + + if (McurObj != NULL) { + pfilm = (const FILM *)LockMem(CursorHandle); + + InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + StepAnimScript(&McurAnim); + } + bHiddenCursor = false; + bFrozenCursor = false; +} + +/** + * Called from INVENTRY.C to customise the main cursor. + */ +void SetTempCursor(SCNHANDLE pScript) { + if (McurObj != NULL) + InitStepAnimScript(&McurAnim, McurObj, pScript, 2); +} + +/** + * Hide the cursor. + */ +void DwHideCursor(void) { + int i; + + bHiddenCursor = true; + + if (McurObj) + MultiHideObject(McurObj); + if (AcurObj) + MultiHideObject(AcurObj); + + for (i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * Unhide the cursor. + */ +void UnHideCursor(void) { + bHiddenCursor = false; +} + +/** + * Freeze the cursor. + */ +void FreezeCursor(void) { + bFrozenCursor = true; +} + +/** + * HideCursorTrails + */ +void HideCursorTrails(void) { + int i; + + bTempNoTrailers = true; + + for (i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * UnHideCursorTrails + */ +void UnHideCursorTrails(void) { + bTempNoTrailers = false; +} + +/** + * Get pointer to image from a film reel. And the rest. + */ +IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) { + const MULTI_INIT *pmi; + const FRAME *pFrame; + + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); + if (ppmi) + *ppmi = pmi; + + pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + + // get pointer to image + return (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); +} + +/** + * Get pointer to image from a film. And the rest. + */ +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) { + const FILM *pfilm; + const FREEL *pfr; + + pfilm = (const FILM *)LockMem(hFilm); + if (ppfilm) + *ppfilm = pfilm; + + pfr = &pfilm->reels[reel]; + if (ppfr) + *ppfr = pfr; + + return GetImageFromReel(pfr, ppmi); +} + +/** + * Delete auxillary cursor. Restore animation offsets in the image. + */ +void DelAuxCursor(void) { + if (AcurObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + AcurObj = NULL; + } +} + +/** + * Set auxillary cursor. + * Save animation offsets from the image if required. + */ +void SetAuxCursor(SCNHANDLE hFilm) { + IMAGE *pim; // Pointer to auxillary cursor's image + const FREEL *pfr; + const MULTI_INIT *pmi; + const FILM *pfilm; + int x, y; // Cursor position + + DelAuxCursor(); // Get rid of previous + + GetCursorXY(&x, &y, false); // Note: also waits for cursor to appear + + pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // no background palette + pim->hImgPal = TO_LE_32(BackPal()); // Poke in the background palette + + ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - ((int16) FROM_LE_16(pim->anioffX))); + ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - ((int16) FROM_LE_16(pim->anioffY))); + + // Initialise and insert the auxillary cursor object + AcurObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + + // Initialise the animation and set its position + InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + MultiSetAniXY(AcurObj, x - ACoX, y - ACoY); + MultiSetZPosition(AcurObj, Z_ACURSOR); + + if (bHiddenCursor) + MultiHideObject(AcurObj); +} + +/** + * MoveCursor + */ +static void MoveCursor(void) { + int startX, startY; + Common::Point ptMouse; + frac_t newX, newY; + unsigned dir; + + // get cursors start animation position + GetCursorXYNoWait(&startX, &startY, false); + + // get mouse drivers current position + ptMouse = _vm->getMousePosition(); + + // convert to fixed point + newX = intToFrac(ptMouse.x); + newY = intToFrac(ptMouse.y); + + // modify mouse driver position depending on cursor keys + if ((dir = _vm->getKeyDirection()) != 0) { + if (dir & MSK_LEFT) + newX -= IterationSize; + + if (dir & MSK_RIGHT) + newX += IterationSize; + + if (dir & MSK_UP) + newY -= IterationSize; + + if (dir & MSK_DOWN) + newY += IterationSize; + + IterationSize += ITER_ACCELLERATION; + + // set new mouse driver position + _vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY))); + } else + + IterationSize = ITERATION_BASE; + + // get new mouse driver position - could have been modified + ptMouse = _vm->getMousePosition(); + + if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) { + resetUserEventTime(); + + if (!bTempNoTrailers && !bHiddenCursor) { + InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY); + if (nextTrail == numTrails) + nextTrail = 0; + } + } + + // adjust cursor to new mouse position + if (McurObj) + MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y); + if (AcurObj != NULL) + MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY); + + if (InventoryActive() && McurObj) { + // Notify the inventory + Xmovement(ptMouse.x - startX); + Ymovement(ptMouse.y - startY); + } + + lastCursorX = ptMouse.x; + lastCursorY = ptMouse.y; +} + +/** + * Initialise cursor object. + */ +static void InitCurObj(void) { + const FILM *pfilm; + const FREEL *pfr; + const MULTI_INIT *pmi; + IMAGE *pim; + + pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // no background palette + pim->hImgPal = TO_LE_32(BackPal()); +//--- + + AcurObj = NULL; // No auxillary cursor + + McurObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj); + + InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +} + +/** + * Initialise the cursor position. + */ +static void InitCurPos(void) { + Common::Point ptMouse = _vm->getMousePosition(); + lastCursorX = ptMouse.x; + lastCursorY = ptMouse.y; + + MultiSetZPosition(McurObj, Z_CURSOR); + MoveCursor(); + MultiHideObject(McurObj); + + IterationSize = ITERATION_BASE; +} + +/** + * CursorStoppedCheck + */ +static void CursorStoppedCheck(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // If scene is closing down + if (bWhoa) { + // ...wait for next scene start-up + while (!restart) + CORO_SLEEP(1); + + // Re-initialise + InitCurObj(); + InitCurPos(); + InventoryIconCursor(); // May be holding something + + // Re-start the cursor trails + restart = false; // set all bits + bWhoa = false; + } + CORO_END_CODE; +} + +/** + * The main cursor process. + */ +void CursorProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (!CursorHandle || !BackPal()) + CORO_SLEEP(1); + + InitCurObj(); + InitCurPos(); + InventoryIconCursor(); // May be holding something + + bWhoa = false; + restart = false; + + while (1) { + // allow rescheduling + CORO_SLEEP(1); + + // Stop/start between scenes + CORO_INVOKE_0(CursorStoppedCheck); + + // Step the animation script(s) + StepAnimScript(&McurAnim); + if (AcurObj != NULL) + StepAnimScript(&AcurAnim); + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } + } + + // Move the cursor as appropriate + if (!bFrozenCursor) + MoveCursor(); + + // If the cursor should be hidden... + if (bHiddenCursor) { + // ...hide the cursor object(s) + MultiHideObject(McurObj); + if (AcurObj) + MultiHideObject(AcurObj); + + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) + MultiHideObject(ntrailData[i].trailObj); + } + + // Wait 'til cursor is again required. + while (bHiddenCursor) { + CORO_SLEEP(1); + + // Stop/start between scenes + CORO_INVOKE_0(CursorStoppedCheck); + } + } + } + CORO_END_CODE; +} + +/** + * Called from dec_cursor() Glitter function. + * Register the handle to cursor reel data. + */ +void DwInitCursor(SCNHANDLE bfilm) { + const FILM *pfilm; + + CursorHandle = bfilm; + + pfilm = (const FILM *)LockMem(CursorHandle); + numTrails = FROM_LE_32(pfilm->numreels) - 1; + + assert(numTrails <= MAX_TRAILERS); +} + +/** + * DropCursor is called when a scene is closing down. + */ +void DropCursor(void) { + AcurObj = NULL; // No auxillary cursor + McurObj = NULL; // No cursor object (imminently deleted elsewhere) + bHiddenCursor = false; // Not hidden in next scene + bTempNoTrailers = false; // Trailers not hidden in next scene + bWhoa = true; // Suspend cursor processes + + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * RestartCursor is called when a new scene is starting up. + */ +void RestartCursor(void) { + restart = true; // Get the main cursor to re-initialise +} + +/** + * Called when restarting the game, ensures correct re-start with NULL + * pointers etc. + */ +void RebootCursor(void) { + McurObj = AcurObj = NULL; + for (int i = 0; i < MAX_TRAILERS; i++) + ntrailData[i].trailObj = NULL; + + bHiddenCursor = bTempNoTrailers = bFrozenCursor = false; + + CursorHandle = 0; + + bWhoa = false; + restart = false; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h new file mode 100644 index 0000000000..15349dda26 --- /dev/null +++ b/engines/tinsel/cursor.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CURSOR_H // prevent multiple includes +#define TINSEL_CURSOR_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +void AdjustCursorXY(int deltaX, int deltaY); +void SetCursorXY(int x, int y); +void SetCursorScreenXY(int newx, int newy); +void GetCursorXY(int *x, int *y, bool absolute); +bool GetCursorXYNoWait(int *x, int *y, bool absolute); + +void RestoreMainCursor(void); +void SetTempCursor(SCNHANDLE pScript); +void DwHideCursor(void); +void UnHideCursor(void); +void FreezeCursor(void); +void HideCursorTrails(void); +void UnHideCursorTrails(void); +void DelAuxCursor(void); +void SetAuxCursor(SCNHANDLE hFilm); +void DwInitCursor(SCNHANDLE bfilm); +void DropCursor(void); +void RestartCursor(void); +void RebootCursor(void); + +} // end of namespace Tinsel + +#endif // TINSEL_CURSOR_H diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp new file mode 100644 index 0000000000..dc37e6a9a1 --- /dev/null +++ b/engines/tinsel/debugger.cpp @@ -0,0 +1,162 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "tinsel/tinsel.h" +#include "tinsel/debugger.h" +#include "tinsel/inventory.h" +#include "tinsel/pcode.h" +#include "tinsel/scene.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" +#include "tinsel/font.h" +#include "tinsel/strres.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In PDISPLAY.CPP +extern void TogglePathDisplay(void); +// In tinsel.cpp +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +// In scene.cpp +extern SCNHANDLE GetSceneHandle(void); + +//----------------- SUPPORT FUNCTIONS --------------------- + +//static +int strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp; + sscanf(s, "%xh", &tmp); + return (int)tmp; +} + +//----------------- CONSOLE CLASS --------------------- + +Console::Console() : GUI::Debugger() { + DCmd_Register("item", WRAP_METHOD(Console, cmd_item)); + DCmd_Register("scene", WRAP_METHOD(Console, cmd_scene)); + DCmd_Register("music", WRAP_METHOD(Console, cmd_music)); + DCmd_Register("sound", WRAP_METHOD(Console, cmd_sound)); + DCmd_Register("string", WRAP_METHOD(Console, cmd_string)); +} + +Console::~Console() { +} + +bool Console::cmd_item(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s item_number\n", argv[0]); + DebugPrintf("Sets the currently active 'held' item\n"); + return true; + } + + HoldItem(INV_NOICON); + HoldItem(strToInt(argv[1])); + return false; +} + +bool Console::cmd_scene(int argc, const char **argv) { + if (argc < 1 || argc > 3) { + DebugPrintf("%s [scene_number [entry number]]\n", argv[0]); + DebugPrintf("If no parameters are given, prints the current scene.\n"); + DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n"); + return true; + } + + if (argc == 1) { + DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT); + return true; + } + + uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT; + int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1; + + SetNewScene(sceneNumber, entryNumber, TRANS_CUT); + return false; +} + +bool Console::cmd_music(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]); + DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n"); + DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n"); + return true; + } + + int param = strToInt(argv[1]); + if (param == 0) { + DebugPrintf("Track number/offset can't be 0!\n", argv[0]); + } else if (param > 0) { + // Track provided + PlayMidiSequence(GetTrackOffset(param - 1), false); + } else if (param < 0) { + // Offset provided + param = param * -1; + PlayMidiSequence(param, false); + } + return true; +} + +bool Console::cmd_sound(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s id\n", argv[0]); + DebugPrintf("Plays the sound with the given ID\n"); + return true; + } + + int id = strToInt(argv[1]); + if (_vm->_sound->sampleExists(id)) + _vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType); + else + DebugPrintf("Sample %d does not exist!\n", id); + + return true; +} + +bool Console::cmd_string(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s id\n", argv[0]); + DebugPrintf("Prints the string with the given ID\n"); + return true; + } + + char tmp[TBUFSZ]; + int id = strToInt(argv[1]); + LoadStringRes(id, tmp, TBUFSZ); + DebugPrintf("%s\n", tmp); + + return true; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h new file mode 100644 index 0000000000..219bc71224 --- /dev/null +++ b/engines/tinsel/debugger.h @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DEBUGGER_H +#define TINSEL_DEBUGGER_H + +#include "gui/debugger.h" + +namespace Tinsel { + +class TinselEngine; + +class Console: public GUI::Debugger { +protected: + bool cmd_item(int argc, const char **argv); + bool cmd_scene(int argc, const char **argv); + bool cmd_music(int argc, const char **argv); + bool cmd_sound(int argc, const char **argv); + bool cmd_string(int argc, const char **argv); +public: + Console(); + virtual ~Console(void); +}; + +} // End of namespace Tinsel + +#endif diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp new file mode 100644 index 0000000000..a638dde2c5 --- /dev/null +++ b/engines/tinsel/detection.cpp @@ -0,0 +1,278 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" + +#include "common/advancedDetector.h" +#include "common/file.h" + +#include "tinsel/tinsel.h" + + +namespace Tinsel { + +struct TinselGameDescription { + Common::ADGameDescription desc; + + int gameID; + int gameType; + uint32 features; + uint16 version; +}; + +uint32 TinselEngine::getGameID() const { + return _gameDescription->gameID; +} + +uint32 TinselEngine::getFeatures() const { + return _gameDescription->features; +} + +Common::Language TinselEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +Common::Platform TinselEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +uint16 TinselEngine::getVersion() const { + return _gameDescription->version; +} + +} + +static const PlainGameDescriptor tinselGames[] = { + {"tinsel", "Tinsel engine game"}, + {"dw", "Discworld"}, + {"dw2", "Discworld 2: Mortality Bytes!"}, + {0, 0} +}; + + +namespace Tinsel { + +static const TinselGameDescription gameDescriptions[] = { + + // Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas + // versions with *.scn files tinsel v2 (7/5/1995) + // Update: this is not entirely true, there were some versions released + // with *.gra files and used tinsel v2 + + { + { // This version has *.gra files but uses tinsel v2 + "dw", + "Floppy", + AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_FLOPPY, + TINSEL_V2, + }, + + { // English CD v1. This version has *.gra files but uses tinsel v2 + { + "dw", + "CD", + { + {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD, + TINSEL_V2, + }, + + { // English CD v2 + { + "dw", + "CD", + { + {"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + +#if 0 + { // English Saturn CD + { + "dw", + "CD", + { + {"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD, + TINSEL_V2, + }, +#endif + + { // Demo from http://www.adventure-treff.de/specials/dl_demos.php + { + "dw", + "Demo", + AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192), + //AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GID_DW1, + 0, + GF_DEMO, + TINSEL_V1, + }, + + { // German CD re-release "Neon Edition" + // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT + { + "dw", + "CD", + AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556), + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + + { AD_TABLE_END_MARKER, 0, 0, 0, 0 } +}; + +/** + * The fallback game descriptor used by the Tinsel engine's fallbackDetector. + * Contents of this struct are to be overwritten by the fallbackDetector. + */ +static TinselGameDescription g_fallbackDesc = { + { + "", + "", + AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor + Common::UNK_LANG, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + 0, + 0, + 0, + 0, +}; + +} // End of namespace Tinsel + +static const Common::ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)Tinsel::gameDescriptions, + // Size of that superset structure + sizeof(Tinsel::TinselGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + tinselGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "tinsel", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0 +}; + +class TinselMetaEngine : public Common::AdvancedMetaEngine { +public: + TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "Tinsel Engine"; + } + + virtual const char *getCopyright() const { + return "Tinsel Engine"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; + + const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const; + +}; + +bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { + const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc; + if (gd) { + *engine = new Tinsel::TinselEngine(syst, gd); + } + return gd != 0; +} + +const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const { + // Set the default values for the fallback descriptor's ADGameDescription part. + Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG; + Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC; + Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS; + + // Set default values for the fallback descriptor's TinselGameDescription part. + Tinsel::g_fallbackDesc.gameID = 0; + Tinsel::g_fallbackDesc.features = 0; + Tinsel::g_fallbackDesc.version = 0; + + //return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc; + return NULL; +} + +#if PLUGIN_ENABLED_DYNAMIC(TINSEL) + REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#else + REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#endif diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h new file mode 100644 index 0000000000..d14dd43fa2 --- /dev/null +++ b/engines/tinsel/dw.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DW_H +#define TINSEL_DW_H + +#include "common/scummsys.h" +#include "common/endian.h" + +namespace Tinsel { + +/** scene handle data type */ +typedef uint32 SCNHANDLE; + +/** polygon handle */ +typedef int HPOLYGON; + + +#define EOS_CHAR '\0' // string terminator +#define LF_CHAR '\x0a' // line feed + +// file names +#define SAMPLE_FILE "english.smp" // all samples +#define SAMPLE_INDEX "english.idx" // sample index filename +#define MIDI_FILE "midi.dat" // all MIDI sequences +#define INDEX_FILENAME "index" // name of index file + +#define SCNHANDLE_SHIFT 23 // amount to shift scene handles by +#define NO_SCNHANDLES 300 // number of memory handles for scenes +#define MASTER_SCNHANDLE (0 << SCNHANDLE_SHIFT) // master scene memory handle + +// the minimum value a integer number can have +#define MIN_INT (1 << (8*sizeof(int) - 1)) +#define MIN_INT16 (-32767) + +// the maximum value a integer number can have +#define MAX_INT (~MIN_INT) + +// TODO: v1->v2 scene files +#ifdef FILE_SPLIT +// each scene is split into 2 files +#define INV_OBJ_SCNHANDLE (2 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects) +#else +#define INV_OBJ_SCNHANDLE (1 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects) +#endif + + +#define FIELD_WORLD 0 +#define FIELD_STATUS 1 + + + + +// We don't set the Z position for print and talk text +// i.e. it gets a Z position of 0 + +#define Z_INV_BRECT 10 // Inventory background rectangle +#define Z_INV_MFRAME 15 // Inventory window frame +#define Z_INV_HTEXT 15 // Inventory heading text +#define Z_INV_ICONS 16 // Icons in inventory +#define Z_INV_ITEXT 995 // Icon text + +#define Z_INV_RFRAME 22 // Re-sizing frame + +#define Z_CURSOR 1000 // Cursor +#define Z_CURSORTRAIL 999 // Cursor trails +#define Z_ACURSOR 990 // Auxillary cursor + +#define Z_TAG_TEXT 995 // In front of auxillary cursor + +#define Z_MDGROOVE 20 +#define Z_MDSLIDER 21 + +#define Z_TOPPLAY 100 + +#define Z_TOPW_TEXT Z_TAG_TEXT + +// Started a collection of assorted maximum numbers here: +#define MAX_MOVERS 6 // Moving actors using path system +#define MAX_SAVED_ACTORS 32 // Saved 'Normal' actors +#define MAX_SAVED_ALIVES 512 // Saves actors'lives + +// Legal non-existant entrance number for LoadScene() +#define NO_ENTRY_NUM (-3458) // Magic unlikely number + + +#define SAMPLETIMEOUT (15*ONE_SECOND) + +// Language for the resource strings +enum LANGUAGE { + TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN, + TXT_ITALIAN, TXT_SPANISH +}; + +} // end of namespace Tinsel + +#endif // TINSEL_DW_H diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp new file mode 100644 index 0000000000..91645da71b --- /dev/null +++ b/engines/tinsel/effect.cpp @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Handles effect polygons. +// +// EffectPolyProcess() monitors triggering of effect code (i.e. a moving +// actor entering an effect polygon). +// EffectProcess() runs the appropriate effect code. +// +// NOTE: Currently will only run one effect process at a time, i.e. +// effect polygons will not currently nest. It won't be very difficult +// to fix this if required. + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/pid.h" +#include "tinsel/pcode.h" // LEAD_ACTOR +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" + + +namespace Tinsel { + +struct EP_INIT { + HPOLYGON hEpoly; + PMACTOR pActor; + int index; +}; + +/** + * Runs an effect polygon's Glitter code with ENTER event, waits for the + * actor to leave that polygon. Then runs the polygon's Glitter code + * with LEAVE event. + */ +static void EffectProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + EP_INIT *to = (EP_INIT *)param; // get the stuff copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + int x, y; // Lead actor position + + // Run effect poly enter script + effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID); + + do { + CORO_SLEEP(1); + GetMActorPosition(to->pActor, &x, &y); + } while (InPolygon(x, y, EFFECT) == to->hEpoly); + + // Run effect poly leave script + effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID); + + SetMAinEffectPoly(to->index, false); + + CORO_END_CODE; +} + +/** + * If the actor was not already in an effect polygon, checks to see if + * it has just entered one. If it has, a process is started up to run + * the polygon's Glitter code. + */ +static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) { + HPOLYGON hPoly; + EP_INIT epi; + + // If just entered an effect polygon, the effect should be triggered. + if (!IsMAinEffectPoly(index)) { + hPoly = InPolygon(x, y, EFFECT); + if (hPoly != NOPOLY) { + //Just entered effect polygon + SetMAinEffectPoly(index, true); + + epi.hEpoly = hPoly; + epi.pActor = pActor; + epi.index = index; + g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi)); + } + } +} + +/** + * Just calls FettleEffectPolys() every clock tick. + */ +void EffectPolyProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + while (1) { + for (int i = 0; i < MAX_MOVERS; i++) { + PMACTOR pActor = GetLiveMover(i); + if (pActor != NULL) { + int x, y; + GetMActorPosition(pActor, &x, &y); + FettleEffectPolys(x, y, i, pActor); + } + } + + CORO_SLEEP(1); // allow re-scheduling + } + CORO_END_CODE; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp new file mode 100644 index 0000000000..bf9f428fd4 --- /dev/null +++ b/engines/tinsel/events.cpp @@ -0,0 +1,439 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Main purpose is to process user events. + * Also provides a couple of utility functions. + */ + +#include "tinsel/actors.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/handle.h" // For LockMem() +#include "tinsel/inventory.h" +#include "tinsel/move.h" // For walking lead actor +#include "tinsel/pcode.h" // For Interpret() +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" // For walking lead actor +#include "tinsel/sched.h" +#include "tinsel/scroll.h" // For DontScrollCursor() +#include "tinsel/timers.h" // DwGetCurrentTime() +#include "tinsel/tinlib.h" // For control() +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in PDISPLAY.C +extern int GetTaggedActor(void); +extern HPOLYGON GetTaggedPoly(void); + + +//----------------- EXTERNAL GLOBAL DATA --------------------- + +extern bool bEnableF1; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int userEvents = 0; // Whenever a button or a key comes in +static uint32 lastUserEvent = 0; // Time it hapenned +static int butEvents = 0; // Single or double, left or right. Or escape key. +static int escEvents = 0; // Escape key + + +static int eCount = 0; + +/** + * Gets called before each schedule, only 1 user action per schedule + * is allowed. + */ +void ResetEcount(void) { + eCount = 0; +} + + +void IncUserEvents(void) { + userEvents++; + lastUserEvent = DwGetCurrentTime(); +} + +/** + * If this is a single click, wait to check it's not the first half of a + * double click. + * If this is a double click, the process from the waiting single click + * gets killed. + */ +void AllowDclick(CORO_PARAM, BUTEVENT be) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + if (be == BE_SLEFT) { + GetToken(TOKEN_LEFT_BUT); + CORO_SLEEP(dclickSpeed+1); + FreeToken(TOKEN_LEFT_BUT); + + // Prevent activation of 2 events on the same tick + if (++eCount != 1) + CORO_KILL_SELF(); + + break; + + } else if (be == BE_DLEFT) { + GetToken(TOKEN_LEFT_BUT); + FreeToken(TOKEN_LEFT_BUT); + } + CORO_END_CODE; +} + +/** + * Take control from player, if the player has it. + * Return TRUE if control taken, FALSE if not. + */ + +bool GetControl(int param) { + if (TestToken(TOKEN_CONTROL)) { + control(param); + return true; + } else + return false; +} + +struct TP_INIT { + HPOLYGON hPoly; // Polygon + USER_EVENT event; // Trigerring event + BUTEVENT bev; // To allow for double clicks + bool take_control; // Set if control should be taken + // while code is running. + int actor; +}; + +/** + * Runs glitter code associated with a polygon. + */ +static void PolyTinselProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + bool took_control; // Set if this function takes control + CORO_END_CONTEXT(_ctx); + + TP_INIT *to = (TP_INIT *)param; // get the stuff copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click + + // Control may have gone off during AllowDclick() + if (!TestToken(TOKEN_CONTROL) + && (to->event == WALKTO || to->event == ACTION || to->event == LOOK)) + CORO_KILL_SELF(); + + // Take control, if requested + if (to->take_control) + _ctx->took_control = GetControl(CONTROL_OFF); + else + _ctx->took_control = false; + + // Hide conversation if appropriate + if (to->event == CONVERSE) + convHide(true); + + // Run the code + _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // Free control if we took it + if (_ctx->took_control) + control(CONTROL_ON); + + // Restore conv window if applicable + if (to->event == CONVERSE) + convHide(false); + + CORO_END_CODE; +} + +/** + * Runs glitter code associated with a polygon. + */ +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) { + TP_INIT to = { hPoly, event, be, tc, 0 }; + + g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) { + TP_INIT to = { hPoly, event, BE_NONE, false, actor }; + + g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +//----------------------------------------------------------------------- + +struct WP_INIT { + int x; // } Where to walk to + int y; // } +}; + +/** + * Perform a walk directly initiated by a click. + */ +static void WalkProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PMACTOR pActor; + CORO_END_CONTEXT(_ctx); + + WP_INIT *to = (WP_INIT *)param; // get the co-ordinates - copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + _ctx->pActor = GetMover(LEAD_ACTOR); + if (_ctx->pActor->MActorState == NORM_MACTOR) { + assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path + + GetToken(TOKEN_LEAD); + SetActorDest(_ctx->pActor, to->x, to->y, false, 0); + DontScrollCursor(); + + while (MAmoving(_ctx->pActor)) + CORO_SLEEP(1); + + FreeToken(TOKEN_LEAD); + } + + CORO_END_CODE; +} + +void walkto(int x, int y) { + WP_INIT to = { x, y }; + + g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to)); +} + +/** + * Run appropriate actor or polygon glitter code. + * If none, and it's a WALKTO event, do a walk. + */ +static void User_Event(USER_EVENT uEvent, BUTEVENT be) { + int actor; + int aniX, aniY; + HPOLYGON hPoly; + + // Prevent activation of 2 events on the same tick + if (++eCount != 1) + return; + + if ((actor = GetTaggedActor()) != 0) + actorEvent(actor, uEvent, be); + else if ((hPoly = GetTaggedPoly()) != NOPOLY) + RunPolyTinselCode(hPoly, uEvent, be, false); + else { + GetCursorXY(&aniX, &aniY, true); + + // There could be a poly involved which has no tag. + if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY + || (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) { + RunPolyTinselCode(hPoly, uEvent, be, false); + } else if (uEvent == WALKTO) + walkto(aniX, aniY); + } +} + + +/** + * ProcessButEvent + */ +void ProcessButEvent(BUTEVENT be) { + IncUserEvents(); + + if (bSwapButtons) { + switch (be) { + case BE_SLEFT: + be = BE_SRIGHT; + break; + case BE_DLEFT: + be = BE_DRIGHT; + break; + case BE_SRIGHT: + be = BE_SLEFT; + break; + case BE_DRIGHT: + be = BE_DLEFT; + break; + case BE_LDSTART: + be = BE_RDSTART; + break; + case BE_LDEND: + be = BE_RDEND; + break; + case BE_RDSTART: + be = BE_LDSTART; + break; + case BE_RDEND: + be = BE_LDEND; + break; + default: + break; + } + } + +// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT) + if (be == BE_SLEFT || be == BE_SRIGHT) + butEvents++; + + if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND) + return; + + if (InventoryActive()) { + ButtonToInventory(be); + } else { + switch (be) { + case BE_SLEFT: + User_Event(WALKTO, BE_SLEFT); + break; + + case BE_DLEFT: + User_Event(ACTION, BE_DLEFT); + break; + + case BE_SRIGHT: + User_Event(LOOK, BE_SRIGHT); + break; + + default: + break; + } + } +} + +/** + * ProcessKeyEvent + */ + +void ProcessKeyEvent(KEYEVENT ke) { + // This stuff to allow F1 key during startup. + if (bEnableF1 && ke == OPTION_KEY) + control(CONTROL_ON); + else + IncUserEvents(); + + if (ke == ESC_KEY) { + escEvents++; + butEvents++; // Yes, I do mean this + } + + // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning + if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND) + return; + + switch (ke) { + case QUIT_KEY: + PopUpConf(QUIT); + break; + + case OPTION_KEY: + PopUpConf(OPTION); + break; + + case SAVE_KEY: + PopUpConf(SAVE); + break; + + case LOAD_KEY: + PopUpConf(LOAD); + break; + + case WALKTO_KEY: + if (InventoryActive()) + ButtonToInventory(BE_SLEFT); + else + User_Event(WALKTO, BE_NONE); + break; + + case ACTION_KEY: + if (InventoryActive()) + ButtonToInventory(BE_DLEFT); + else + User_Event(ACTION, BE_NONE); + break; + + case LOOK_KEY: + if (InventoryActive()) + ButtonToInventory(BE_SRIGHT); + else + User_Event(LOOK, BE_NONE); + break; + + case ESC_KEY: + case PGUP_KEY: + case PGDN_KEY: + case HOME_KEY: + case END_KEY: + if (InventoryActive()) + KeyToInventory(ke); + break; + + default: + break; + } +} + +/** + * For ESCapable Glitter sequences + */ + +int GetEscEvents(void) { + return escEvents; +} + +/** + * For cutting short talk()s etc. + */ + +int GetLeftEvents(void) { + return butEvents; +} + +/** + * For waitkey() Glitter function + */ + +int getUserEvents(void) { + return userEvents; +} + +uint32 getUserEventTime(void) { + return DwGetCurrentTime() - lastUserEvent; +} + +void resetUserEventTime(void) { + lastUserEvent = DwGetCurrentTime(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h new file mode 100644 index 0000000000..bc49d68717 --- /dev/null +++ b/engines/tinsel/events.h @@ -0,0 +1,84 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * User events processing and utility functions + */ + +#ifndef TINSEL_EVENTS_H +#define TINSEL_EVENTS_H + +#include "tinsel/dw.h" +#include "tinsel/coroutine.h" + +namespace Tinsel { + +enum BUTEVENT { + BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT, + BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND, + BE_UNKNOWN +}; + + +enum KEYEVENT { + ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY, + PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY, + WALKTO_KEY, ACTION_KEY, LOOK_KEY, + NOEVENT_KEY +}; + + +/** + * Reasons for running Glitter code. + * Do not re-order these as equivalent CONSTs are defined in the master + * scene Glitter source file for testing against the event() library function. + */ +enum USER_EVENT { + POINTED, WALKTO, ACTION, LOOK, + ENTER, LEAVE, STARTUP, CONVERSE, + UNPOINT, PUTDOWN, + NOEVENT +}; + + +void AllowDclick(CORO_PARAM, BUTEVENT be); +bool GetControl(int param); + +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc); +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor); + +void ProcessButEvent(BUTEVENT be); +void ProcessKeyEvent(KEYEVENT ke); + + +int GetEscEvents(void); +int GetLeftEvents(void); +int getUserEvents(void); + +uint32 getUserEventTime(void); +void resetUserEventTime(void); + +void ResetEcount(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_EVENTS_H */ diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp new file mode 100644 index 0000000000..0018727ccb --- /dev/null +++ b/engines/tinsel/faders.cpp @@ -0,0 +1,175 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Palette Fader and Flasher processes. + */ + +#include "tinsel/pid.h" // list of all process IDs +#include "tinsel/sched.h" // scheduler defs +#include "tinsel/faders.h" // fader defs +#include "tinsel/handle.h" +#include "tinsel/palette.h" // Palette Manager defs + +namespace Tinsel { + +/** structure used by the "FadeProcess" process */ +struct FADE { + const long *pColourMultTable; // list of fixed point colour multipliers - terminated with negative entry + PALQ *pPalQ; // palette queue entry to fade +}; + +// fixed point fade multiplier tables +//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1}; +const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1}; +//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1}; +const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1}; + +/** + * Scale 'colour' by the fixed point colour multiplier 'colourMult' + * @param colour Colour to scale + * @param colourMult Fixed point multiplier + */ +static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) { + // apply multiplier to RGB components + uint32 red = ((GetRValue(colour) * colourMult) << 8) >> 24; + uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24; + uint32 blue = ((GetBValue(colour) * colourMult) << 8) >> 24; + + // return new colour + return RGB(red, green, blue); +} + +/** + * Applies the fixed point multiplier 'mult' to all colours in + * 'pOrig' to produce 'pNew'. Each colour in the palette will be + * multiplied by 'mult'. + * @param pNew Pointer to new palette + * @param pOrig Pointer to original palette + * @param numColours Number of colours in the above palettes + * @param mult Fixed point multiplier + */ +static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) { + for (int i = 0; i < numColours; i++, pNew++, pOrig++) { + // apply multiplier to RGB components + *pNew = ScaleColour(*pOrig, mult); + } +} + +/** + * Process to fade one palette. + * A pointer to a 'FADE' structure must be passed to this process when + * it is created. + */ +static void FadeProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + COLORREF fadeRGB[MAX_COLOURS]; // local copy of palette + const long *pColMult; // pointer to colour multiplier table + PALETTE *pPalette; // pointer to palette + CORO_END_CONTEXT(_ctx); + + // get the fade data structure - copied to process when it was created + FADE *pFade = (FADE *)param; + + CORO_BEGIN_CODE(_ctx); + + // get pointer to palette - reduce pointer indirection a bit + _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal); + + for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) { + // go through all multipliers in table - until a negative entry + + // fade palette using next multiplier + FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB, + FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult); + + // send new palette to video DAC + UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB); + + // allow time for video DAC to be updated + CORO_SLEEP(1); + } + + CORO_END_CODE; +} + +/** + * Generic palette fader/unfader. Creates a 'FadeProcess' process + * for each palette that is to fade. + * @param multTable Fixed point colour multiplier table + * @param noFadeTable List of palettes not to fade + */ +static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) { + PALQ *pPal; // palette manager iterator + + // create a process for each palette in the palette queue + for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) { + bool bFade = true; + // assume we want to fade this palette + + // is palette in the list of palettes not to fade + if (noFadeTable != NULL) { + // there is a list of palettes not to fade + for (int i = 0; noFadeTable[i] != 0; i++) { + if (pPal->hPal == noFadeTable[i]) { + // palette is in the list - dont fade it + bFade = false; + + // leave loop prematurely + break; + } + } + } + + if (bFade) { + FADE fade; + + // fill in FADE struct + fade.pColourMultTable = multTable; + fade.pPalQ = pPal; + + // create a fader process for this palette + g_scheduler->createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE)); + } + } +} + +/** + * Fades a list of palettes down to black. + * @param noFadeTable A NULL terminated list of palettes not to fade. + */ +void FadeOutFast(SCNHANDLE noFadeTable[]) { + // call generic fader + Fader(fadeout, noFadeTable); +} + +/** + * Fades a list of palettes from black to their current colours. + * @param noFadeTable A NULL terminated list of palettes not to fade. + */ +void FadeInFast(SCNHANDLE noFadeTable[]) { + // call generic fader + Fader(fadein, noFadeTable); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h new file mode 100644 index 0000000000..1e9336fae8 --- /dev/null +++ b/engines/tinsel/faders.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Data structures used by the fader and flasher processes + */ + +#ifndef TINSEL_FADERS_H // prevent multiple includes +#define TINSEL_FADERS_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +enum { + /** + * Number of iterations in a fade out. + * Must match which FadeOut() is in use. + */ + COUNTOUT_COUNT = 6 +}; + +/*----------------------------------------------------------------------*\ +|* Fader Function Prototypes *| +\*----------------------------------------------------------------------*/ + +// usefull palette faders - they all need a list of palettes that +// should not be faded. This parameter can be +// NULL - fade all palettes. + +void FadeOutFast(SCNHANDLE noFadeTable[]); +void FadeInFast(SCNHANDLE noFadeTable[]); + +} // end of namespace Tinsel + +#endif // TINSEL_FADERS_H diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h new file mode 100644 index 0000000000..c8bf4604bc --- /dev/null +++ b/engines/tinsel/film.h @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FILM_H // prevent multiple includes +#define TINSEL_FILM_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +#include "common/pack-start.h" // START STRUCT PACKING + +struct FREEL { + SCNHANDLE mobj; + SCNHANDLE script; +} PACKED_STRUCT; + +struct FILM { + int32 frate; + int32 numreels; + FREEL reels[1]; +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp new file mode 100644 index 0000000000..620298867e --- /dev/null +++ b/engines/tinsel/font.cpp @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/dw.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static char tBuffer[TBUFSZ]; + +static SCNHANDLE hTagFont = 0, hTalkFont = 0; + + +/** + * Return address of tBuffer + */ +char *tBufferAddr() { + return tBuffer; +} + +/** + * Return hTagFont handle. + */ +SCNHANDLE hTagFontHandle() { + return hTagFont; +} + +/** + * Return hTalkFont handle. + */ +SCNHANDLE hTalkFontHandle() { + return hTalkFont; +} + +/** + * Called from dec_tagfont() Glitter function. Store the tag font handle. + */ +void TagFontHandle(SCNHANDLE hf) { + hTagFont = hf; // Store the font handle +} + +/** + * Called from dec_talkfont() Glitter function. + * Store the talk font handle. + */ +void TalkFontHandle(SCNHANDLE hf) { + hTalkFont = hf; // Store the font handle +} + +/** + * Poke the background palette into character 0's images. + */ +void fettleFontPal(SCNHANDLE fontPal) { + const FONT *pFont; + IMAGE *pImg; + + assert(fontPal); + assert(hTagFont); // Tag font not declared + assert(hTalkFont); // Talk font not declared + + pFont = (const FONT *)LockMem(hTagFont); + pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 + pImg->hImgPal = TO_LE_32(fontPal); + + pFont = (const FONT *)LockMem(hTalkFont); + pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 + pImg->hImgPal = TO_LE_32(fontPal); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h new file mode 100644 index 0000000000..b75c36191c --- /dev/null +++ b/engines/tinsel/font.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FONT_H // prevent multiple includes +#define TINSEL_FONT_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// A temporary buffer for extracting text into is defined in font.c +// Accessed using tBufferAddr(), this is how big it is: +#define TBUFSZ 512 + + +char *tBufferAddr(void); +SCNHANDLE hTagFontHandle(void); +SCNHANDLE hTalkFontHandle(void); + +void TagFontHandle(SCNHANDLE hf); +void TalkFontHandle(SCNHANDLE hf); +void fettleFontPal(SCNHANDLE fontPal); + +} // end of namespace Tinsel + +#endif // TINSEL_FONT_H diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp new file mode 100644 index 0000000000..cd0937d944 --- /dev/null +++ b/engines/tinsel/graphics.cpp @@ -0,0 +1,440 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#include "tinsel/graphics.h" +#include "tinsel/handle.h" // LockMem() +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +// Defines used in graphic drawing +#define CHARPTR_OFFSET 16 +#define CHAR_WIDTH 4 +#define CHAR_HEIGHT 4 + +extern uint8 transPalette[MAX_COLOURS]; + +//----------------- SUPPORT FUNCTIONS --------------------- + +/** + * Straight rendering with transparency support + */ + +static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + // Set up the offset between destination blocks + int rightClip = applyClipping ? pObj->rightClip : 0; + Common::Rect boxBounds; + + if (applyClipping) { + // Adjust the height down to skip any bottom clipping + pObj->height -= pObj->botClip; + + // Make adjustment for the top clipping row + srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2); + pObj->height -= pObj->topClip; + pObj->topClip %= 4; + } + + // Vertical loop + while (pObj->height > 0) { + // Get the start of the next line output + uint8 *tempDest = destP; + + // Get the line width, and figure out which row range within the 4 row high blocks + // will be displayed if clipping is to be taken into account + int width = pObj->width; + + if (!applyClipping) { + // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks + boxBounds.top = 0; + boxBounds.bottom = 3; + boxBounds.left = 0; + } else { + // Handle any possible clipping at the top of the char block. + // We already handled topClip partially at the beginning of this function. + // Hence the only non-zero values it can assume at this point are 1,2,3, + // and that only during the very first iteration (i.e. when the top char + // block is drawn only partially). In particular, we set topClip to zero, + // as all following blocks are not to be top clipped. + boxBounds.top = pObj->topClip; + pObj->topClip = 0; + + boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3); + + // Handle any possible clipping at the start of the line + boxBounds.left = pObj->leftClip; + if (boxBounds.left >= 4) { + srcP += sizeof(uint16) * (boxBounds.left >> 2); + width -= boxBounds.left & 0xfffc; + boxBounds.left %= 4; + } + + width -= boxBounds.left; + } + + // Horizontal loop + while (width > rightClip) { + boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3); + assert(boxBounds.bottom >= boxBounds.top); + assert(boxBounds.right >= boxBounds.left); + + int16 indexVal = READ_LE_UINT16(srcP); + srcP += sizeof(uint16); + + if (indexVal >= 0) { + // Draw a 4x4 block based on the opcode as in index into the block list + const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4); + p += boxBounds.top * sizeof(uint32); + for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) { + Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top))); + } + + } else { + // Draw a 4x4 block with transparency support + indexVal &= 0x7fff; + + // If index is zero, then skip drawing the block completely + if (indexVal > 0) { + // Use the index along with the object's translation offset + const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4); + + // Loop through each pixel - only draw a pixel if it's non-zero + p += boxBounds.top * sizeof(uint32); + for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) { + p += boxBounds.left; + for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) { + if (*p) + *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p; + } + p += 3 - boxBounds.right; + } + } + } + + tempDest += boxBounds.right - boxBounds.left + 1; + width -= 3 - boxBounds.left + 1; + + // None of the remaining horizontal blocks should be left clipped + boxBounds.left = 0; + } + + // If there is any width remaining, there must be a right edge clipping + if (width >= 0) + srcP += sizeof(uint16) * ((width + 3) >> 2); + + // Move to next line line + pObj->height -= boxBounds.bottom - boxBounds.top + 1; + destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH; + } +} + +/** + * Fill the destination area with a constant colour + */ + +static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { + if (applyClipping) { + pObj->height -= pObj->topClip + pObj->botClip; + pObj->width -= pObj->leftClip + pObj->rightClip; + + if (pObj->width <= 0) + return; + } + + // Loop through any remaining lines + while (pObj->height > 0) { + Common::set_to(destP, destP + pObj->width, pObj->constant); + + --pObj->height; + destP += SCREEN_WIDTH; + } +} + +/** + * Translates the destination surface within the object's bounds using the transparency + * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp) + */ + +static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { + if (applyClipping) { + pObj->height -= pObj->topClip + pObj->botClip; + pObj->width -= pObj->leftClip + pObj->rightClip; + + if (pObj->width <= 0) + return; + } + + // Set up the offset between destination lines + int lineOffset = SCREEN_WIDTH - pObj->width; + + // Loop through any remaining lines + while (pObj->height > 0) { + for (int i = 0; i < pObj->width; ++i, ++destP) + *destP = transPalette[*destP]; + + --pObj->height; + destP += lineOffset; + } +} + + +#if 0 +// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method +// from the v1 source. It may be needed to be included later on to support v1 gfx files + +/** + * Straight rendering with transparency support + * Possibly only used in the Discworld Demo + */ + +static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + // FIXME: If this method is used for the demo, it still needs to be made Endian safe + + // Set up the offset between destination lines + pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0); + + // Top clipped line handling + while (applyClipping && (pObj->topClip > 0)) { + // Loop through discarding the data for the line + int width = pObj->width; + while (width > 0) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Dump the data + srcP += ((opcodeOrLen + 3) / 4) * 4; + width -= opcodeOrLen; + } else { + // Dump the run-length opcode + width -= -opcodeOrLen; + } + } + + --pObj->height; + --pObj->topClip; + } + + // Loop for the required number of rows + while (pObj->height > 0) { + + int width = pObj->width; + + // Handling for left edge clipping - this basically involves dumping data until we reach + // the part of the line to be displayed + int clipLeft = pObj->leftClip; + while (applyClipping && (clipLeft > 0)) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Copy a specified number of bytes + // Make adjustments for past the clipping width + int remainder = 4 - (opcodeOrLen % 4); + srcP += MIN(clipLeft, opcodeOrLen); + opcodeOrLen -= MIN(clipLeft, opcodeOrLen); + clipLeft -= MIN(clipLeft, opcodeOrLen); + width -= opcodeOrLen; + + + // Handle any right edge clipping (if run length covers entire width) + if (width < pObj->rightClip) { + remainder += (pObj->rightClip - width); + opcodeOrLen -= (pObj->rightClip - width); + } + + if (opcodeOrLen > 0) + Common::copy(srcP, srcP + opcodeOrLen, destP); + + } else { + // Output a run length number of bytes + // Get data for byte value and run length + opcodeOrLen = -opcodeOrLen; + int runLength = opcodeOrLen & 0xff; + uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + + // Make adjustments for past the clipping width + runLength -= MIN(clipLeft, runLength); + clipLeft -= MIN(clipLeft, runLength); + width -= runLength; + + // Handle any right edge clipping (if run length covers entire width) + if (width < pObj->rightClip) + runLength -= (pObj->rightClip - width); + + if (runLength > 0) { + // Displayable part starts partway through the slice + if (colourVal != 0) + Common::set_to(destP, destP + runLength, colourVal); + destP += runLength; + } + } + + if (width < pObj->rightClip) + width = 0; + } + + // Handling for the visible part of the line + int endWidth = applyClipping ? pObj->rightClip : 0; + while (width > endWidth) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Copy the specified number of bytes + int remainder = 4 - (opcodeOrLen % 4); + + if (width < endWidth) { + // Shorten run length by right clipping + remainder += (pObj->rightClip - width); + opcodeOrLen -= (pObj->rightClip - width); + } + + Common::copy(srcP, srcP + opcodeOrLen, destP); + srcP += opcodeOrLen + remainder; + destP += opcodeOrLen; + width -= opcodeOrLen; + + } else { + // Handle a given run length + opcodeOrLen = -opcodeOrLen; + int runLength = opcodeOrLen & 0xff; + uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + + if (width < endWidth) + // Shorten run length by right clipping + runLength -= (pObj->rightClip - width); + + // Only set pixels if colourVal non-zero (0 signifies transparency) + if (colourVal != 0) + // Fill out a run length of a specified colour + Common::set_to(destP, destP + runLength, colourVal); + + destP += runLength; + width -= runLength; + } + } + + // If right edge clipping is being applied, then width may still be non-zero - in + // that case all remaining line data until the end of the line must be ignored + while (width > 0) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Dump the data + srcP += ((opcodeOrLen + 3) / 4) * 4; + width -= opcodeOrLen; + } else { + // Dump the run-length opcode + width -= -opcodeOrLen; + } + } + + --pObj->height; + destP += pObj->lineoffset; + } +} +#endif + +//----------------- MAIN FUNCTIONS --------------------- + +/** + * Clears both the screen surface buffer and screen to the specified value + */ +void ClearScreen() { + void *pDest = _vm->screen().getBasePtr(0, 0); + memset(pDest, 0, SCREEN_WIDTH * SCREEN_HEIGHT); + g_system->clearScreen(); + g_system->updateScreen(); +} + +/** + * Updates the screen surface within the following rectangle + */ +void UpdateScreenRect(const Common::Rect &pClip) { + byte *pDest = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top); + g_system->copyRectToScreen(pDest, _vm->screen().pitch, pClip.left, pClip.top, pClip.width(), pClip.height()); + g_system->updateScreen(); +} + +/** + * Draws the specified object onto the screen surface buffer + */ +void DrawObject(DRAWOBJECT *pObj) { + uint8 *srcPtr = NULL; + uint8 *destPtr; + + if ((pObj->width <= 0) || (pObj->height <= 0)) + // Empty image, so return immediately + return; + + // If writing constant data, don't bother locking the data pointer and reading src details + if ((pObj->flags & DMA_CONST) == 0) { + byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000); + + srcPtr = p + (pObj->hBits & 0x7FFFFF); + pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10); + pObj->transOffset = READ_LE_UINT32(p + 0x14); + } + + // Get destination starting point + destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos); + + // Handle various draw types + uint8 typeId = pObj->flags & 0xff; + switch (typeId) { + case 0x01: + case 0x08: + case 0x41: + case 0x48: + WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); + break; + + case 0x04: + case 0x44: + // ClpWrtConst with/without clipping + WrtConst(pObj,destPtr, typeId == 0x44); + break; + + case 0x84: + case 0xC4: + // WrtTrans with/without clipping + WrtTrans(pObj, destPtr, typeId == 0xC4); + break; + + default: + // NoOp + error("Unknown drawing type %d", typeId); + break; + } +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h new file mode 100644 index 0000000000..85299d4873 --- /dev/null +++ b/engines/tinsel/graphics.h @@ -0,0 +1,78 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#ifndef TINSEL_GRAPHICS_H // prevent multiple includes +#define TINSEL_GRAPHICS_H + +#include "tinsel/dw.h" + +#include "common/rect.h" +#include "common/system.h" +#include "graphics/surface.h" + +namespace Tinsel { + +struct PALQ; + + +#define SCREEN_WIDTH 320 // PC screen dimensions +#define SCREEN_HEIGHT 200 +#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x +#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y + +/** draw object structure - only used when drawing objects */ +struct DRAWOBJECT { + char *charBase; // character set base address + int transOffset; // transparent character offset + int flags; // object flags - see above for list + PALQ *pPal; // objects palette Q position + int constant; // which colour in palette for monochrome objects + int width; // width of object + int height; // height of object + SCNHANDLE hBits; // image bitmap handle + int lineoffset; // offset to next line + int leftClip; // amount to clip off object left + int rightClip; // amount to clip off object right + int topClip; // amount to clip off object top + int botClip; // amount to clip off object bottom + short xPos; // x position of object + short yPos; // y position of object +}; + + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ClearScreen(); +void DrawObject(DRAWOBJECT *pObj); + +// called to update a rectangle on the video screen from a video page +void UpdateScreenRect(const Common::Rect &pClip); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp new file mode 100644 index 0000000000..11623516ec --- /dev/null +++ b/engines/tinsel/handle.cpp @@ -0,0 +1,366 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code + */ + +#define BODGE + +#include "common/file.h" + +#include "tinsel/dw.h" +#include "tinsel/scn.h" // name of "index" file +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" // heap memory manager + + +// these are included only so the relevant structs can be used in convertLEStructToNative() +#include "tinsel/anim.h" +#include "tinsel/multiobj.h" +#include "tinsel/film.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/text.h" +#include "tinsel/scene.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +bool bLockedScene = 0; +#endif + + +//----------------- LOCAL DEFINES -------------------- + +struct MEMHANDLE { + char szName[12]; //!< 00 - file name of graphics file + int32 filesize; //!< 12 - file size and flags + MEM_NODE *pNode; //!< 16 - memory node for the graphics +}; + + +/** memory allocation flags - stored in the top bits of the filesize field */ +enum { + fPreload = 0x01000000L, //!< preload memory + fDiscard = 0x02000000L, //!< discard memory + fSound = 0x04000000L, //!< sound data + fGraphic = 0x08000000L, //!< graphic data + fCompressed = 0x10000000L, //!< compressed data + fLoaded = 0x20000000L //!< set when file data has been loaded +}; +#define FSIZE_MASK 0x00FFFFFFL //!< mask to isolate the filesize +#define MALLOC_MASK 0xFF000000L //!< mask to isolate the memory allocation flags +#define OFFSETMASK 0x007fffffL //!< get offset of address +//#define HANDLEMASK 0xFF800000L //!< get handle of address + +//----------------- LOCAL GLOBAL DATA -------------------- + +// handle table gets loaded from index file at runtime +static MEMHANDLE *handleTable = 0; + +// number of handles in the handle table +static uint numHandles = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a file + + +/** + * Loads the graphics handle table index file and preloads all the + * permanent graphics etc. + */ +void SetupHandleTable(void) { + enum { RECORD_SIZE = 20 }; + + int len; + uint i; + MEMHANDLE *pH; + Common::File f; + + if (f.open(INDEX_FILENAME)) { + // get size of index file + len = f.size(); + + if (len > 0) { + if ((len % RECORD_SIZE) != 0) { + // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + + // calc number of handles + numHandles = len / RECORD_SIZE; + + // allocate memory for the index file + handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE)); + + // make sure memory allocated + assert(handleTable); + + // load data + for (i = 0; i < numHandles; i++) { + f.read(handleTable[i].szName, 12); + handleTable[i].filesize = f.readUint32LE(); + // The pointer should always be NULL. We don't + // need to read that from the file. + handleTable[i].pNode = NULL; + f.seek(4, SEEK_CUR); + } + + if (f.ioFailed()) { + // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + + // close the file + f.close(); + } else { // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + } else { // cannot find the index file + error("Cannot find file %s", INDEX_FILENAME); + } + + // allocate memory nodes and load all permanent graphics + for (i = 0, pH = handleTable; i < numHandles; i++, pH++) { + if (pH->filesize & fPreload) { + // allocate a fixed memory node for permanent files + pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK); + + // make sure memory allocated + assert(pH->pNode); + + // load the data + LoadFile(pH, true); + } +#ifdef BODGE + else if ((pH->filesize & FSIZE_MASK) == 8) { + pH->pNode = NULL; + } +#endif + else { + // allocate a discarded memory node for other files + pH->pNode = MemoryAlloc( + DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC, + pH->filesize & FSIZE_MASK); + + // make sure memory allocated + assert(pH->pNode); + } + } +} + +void FreeHandleTable(void) { + if (handleTable) { + free(handleTable); + handleTable = NULL; + } +} + +/** + * Loads a memory block as a file. + * @param pH Memory block pointer + * @param bWarn If set, treat warnings as errors + */ +void LoadFile(MEMHANDLE *pH, bool bWarn) { + Common::File f; + char szFilename[sizeof(pH->szName) + 1]; + + if (pH->filesize & fCompressed) { + error("Compression handling has been removed!"); + } + + // extract and zero terminate the filename + strncpy(szFilename, pH->szName, sizeof(pH->szName)); + szFilename[sizeof(pH->szName)] = 0; + + if (f.open(szFilename)) { + // read the data + int bytes; + uint8 *addr; + + if (pH->filesize & fPreload) + // preload - no need to lock the memory + addr = (uint8 *)pH->pNode; + else { + // discardable - lock the memory + addr = (uint8 *)MemoryLock(pH->pNode); + } +#ifdef DEBUG + if (addr == NULL) { + if (pH->filesize & fPreload) + // preload - no need to lock the memory + addr = (uint8 *)pH->pNode; + else { + // discardable - lock the memory + addr = (uint8 *)MemoryLock(pH->pNode); + } + } +#endif + + // make sure address is valid + assert(addr); + + bytes = f.read(addr, pH->filesize & FSIZE_MASK); + + // close the file + f.close(); + + if ((pH->filesize & fPreload) == 0) { + // discardable - unlock the memory + MemoryUnlock(pH->pNode); + } + + // set the loaded flag + pH->filesize |= fLoaded; + + if (bytes == (pH->filesize & FSIZE_MASK)) { + return; + } + + if (bWarn) + // file is corrupt + error("File %s is corrupt", szFilename); + } + + if (bWarn) + // cannot find file + error("Cannot find file %s", szFilename); +} + +/** + * Returns the address of a image, given its memory handle. + * @param offset Handle and offset to data + */ +uint8 *LockMem(SCNHANDLE offset) { + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + if (pH->filesize & fPreload) { + // permanent files are already loaded + return (uint8 *)pH->pNode + (offset & OFFSETMASK); + } else { + if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded)) + // already allocated and loaded + return pH->pNode->pBaseAddr + (offset & OFFSETMASK); + + if (pH->pNode->pBaseAddr == NULL) + // must have been discarded - reallocate the memory + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, + DWM_MOVEABLE | DWM_DISCARDABLE); + + if (pH->pNode->pBaseAddr == NULL) + error("Out of memory"); + + LoadFile(pH, true); + + // make sure address is valid + assert(pH->pNode->pBaseAddr); + + return pH->pNode->pBaseAddr + (offset & OFFSETMASK); + } +} + +/** + * Called to make the current scene non-discardable. + * @param offset Handle and offset to data + */ +void LockScene(SCNHANDLE offset) { + + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + +#ifdef DEBUG + assert(!bLockedScene); // Trying to lock more than one scene +#endif + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + // compact the heap to avoid fragmentation while scene is non-discardable + HeapCompact(MAX_INT, false); + + if ((pH->filesize & fPreload) == 0) { + // change the flags for the node + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE); +#ifdef DEBUG + bLockedScene = true; +#endif + } +} + +/** + * Called to make the current scene discardable again. + * @param offset Handle and offset to data + */ +void UnlockScene(SCNHANDLE offset) { + + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + if ((pH->filesize & fPreload) == 0) { + // change the flags for the node + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE); +#ifdef DEBUG + bLockedScene = false; +#endif + } +} + +/*----------------------------------------------------------------------*/ + +#ifdef BODGE + +/** + * Validates that a specified handle pointer is valid + * @param offset Handle and offset to data + */ +bool ValidHandle(SCNHANDLE offset) { + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + return (pH->filesize & FSIZE_MASK) != 8; +} +#endif + +} // end of namespace Tinsel diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h new file mode 100644 index 0000000000..2cb1638d9d --- /dev/null +++ b/engines/tinsel/handle.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Graphics Memory Manager data structures + * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed + */ + +#ifndef TINSEL_HANDLE_H // prevent multiple includes +#define TINSEL_HANDLE_H + +#include "tinsel/dw.h" // new data types + +namespace Tinsel { + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void SetupHandleTable(void); // Loads the graphics handle table index file and preloads all the permanent graphics etc. +void FreeHandleTable(void); + +uint8 *LockMem( // returns the addr of a image, given its memory handle + SCNHANDLE offset); // handle and offset to data + +void LockScene( // Called to make the current scene non-discardable + SCNHANDLE offset); // handle and offset to data + +void UnlockScene( // Called to make the current scene discardable again + SCNHANDLE offset); // handle and offset to data + +} // end of namespace Tinsel + +#endif // TINSEL_HANDLE_H diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp new file mode 100644 index 0000000000..aff085d003 --- /dev/null +++ b/engines/tinsel/heapmem.cpp @@ -0,0 +1,594 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code. + */ + +#include "tinsel/heapmem.h" +#include "tinsel/timers.h" // For DwGetCurrentTime + +namespace Tinsel { + +// minimum memory required for MS-DOS version of game +#define MIN_MEM 2506752L + +// list of all memory nodes +MEM_NODE mnodeList[NUM_MNODES]; + +// pointer to the linked list of free mnodes +static MEM_NODE *pFreeMemNodes; + +#ifdef DEBUG +// diagnostic mnode counters +static int numNodes; +static int maxNodes; +#endif + +// the mnode heap sentinel +static MEM_NODE heapSentinel; + +// +static MEM_NODE *AllocMemNode(void); + + +/** + * Initialises the memory manager. + */ +void MemoryInit(void) { + MEM_NODE *pNode; + +#ifdef DEBUG + // clear number of nodes in use + numNodes = 0; +#endif + + // place first node on free list + pFreeMemNodes = mnodeList; + + // link all other objects after first + for (int i = 1; i < NUM_MNODES; i++) { + mnodeList[i - 1].pNext = mnodeList + i; + } + + // null the last mnode + mnodeList[NUM_MNODES - 1].pNext = NULL; + + // allocatea big chunk of memory + const uint32 size = 2*MIN_MEM+655360L; + uint8 *mem = (uint8 *)malloc(size); + assert(mem); + + // allocate a mnode for this memory + pNode = AllocMemNode(); + + // make sure mnode was allocated + assert(pNode); + + // convert segment to memory address + pNode->pBaseAddr = mem; + + // set size of the memory heap + pNode->size = size; + + // clear the memory + memset(pNode->pBaseAddr, 0, size); + + // set cyclic links to the sentinel + heapSentinel.pPrev = pNode; + heapSentinel.pNext = pNode; + pNode->pPrev = &heapSentinel; + pNode->pNext = &heapSentinel; + + // flag sentinel as locked + heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of mnodes used at once. + */ + +void MemoryStats(void) { + printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES); +} +#endif + +/** + * Allocate a mnode from the free list. + */ +static MEM_NODE *AllocMemNode(void) { + // get the first free mnode + MEM_NODE *pMemNode = pFreeMemNodes; + + // make sure a mnode is available + assert(pMemNode); // Out of memory nodes + + // the next free mnode + pFreeMemNodes = pMemNode->pNext; + + // wipe out the mnode + memset(pMemNode, 0, sizeof(MEM_NODE)); + +#ifdef DEBUG + // one more mnode in use + if (++numNodes > maxNodes) + maxNodes = numNodes; +#endif + + // return new mnode + return pMemNode; +} + +/** + * Return a mnode back to the free list. + * @param pMemNode Node of the memory object + */ +void FreeMemNode(MEM_NODE *pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +#ifdef DEBUG + // one less mnode in use + --numNodes; + assert(numNodes >= 0); +#endif + + // place free list in mnode next + pMemNode->pNext = pFreeMemNodes; + + // add mnode to top of free list + pFreeMemNodes = pMemNode; +} + + +/** + * Tries to make space for the specified number of bytes on the specified heap. + * @param size Number of bytes to free up + * @param bDiscard When set - will discard blocks to fullfill the request + */ +bool HeapCompact(long size, bool bDiscard) { + MEM_NODE *pHeap = &heapSentinel; + MEM_NODE *pPrev, *pCur, *pOldest; + long largest; // size of largest free block + uint32 oldest; // time of the oldest discardable block + + while (true) { + bool bChanged; + + do { + bChanged = false; + for (pPrev = pHeap->pNext, pCur = pPrev->pNext; + pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) { + if (pPrev->flags == 0 && pCur->flags == 0) { + // set the changed flag + bChanged = true; + + // both blocks are free - merge them + pPrev->size += pCur->size; + + // unlink the current mnode + pPrev->pNext = pCur->pNext; + pCur->pNext->pPrev = pPrev; + + // free the current mnode + FreeMemNode(pCur); + + // leave the loop + break; + } else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE + && pCur->flags == 0) { + // a free block after a moveable block - swap them + + // set the changed flag + bChanged = true; + + // move the unlocked blocks data up (can overlap) + memmove(pPrev->pBaseAddr + pCur->size, + pPrev->pBaseAddr, pPrev->size); + + // swap the order in the linked list + pPrev->pPrev->pNext = pCur; + pCur->pNext->pPrev = pPrev; + + pCur->pPrev = pPrev->pPrev; + pPrev->pPrev = pCur; + + pPrev->pNext = pCur->pNext; + pCur->pNext = pPrev; + + pCur->pBaseAddr = pPrev->pBaseAddr; + pPrev->pBaseAddr += pCur->size; + + // leave the loop + break; + } + } + } while (bChanged); + + // find the largest free block + for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { + if (pCur->flags == 0 && pCur->size > largest) + largest = pCur->size; + } + + if (largest >= size) + // we have freed enough memory + return true; + + if (!bDiscard) + // we cannot free enough without discarding blocks + return false; + + // find the oldest discardable block + oldest = DwGetCurrentTime(); + pOldest = NULL; + for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { + if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED)) + == DWM_DISCARDABLE) { + // found a non-discarded discardable block + if (pCur->lruTime < oldest) { + oldest = pCur->lruTime; + pOldest = pCur; + } + } + } + + if (pOldest) + // discard the oldest block + MemoryDiscard(pOldest); + else + // cannot discard any blocks + return false; + } +} + +/** + * Allocates the specified number of bytes from the heap. + * @param flags Allocation attributes + * @param size Number of bytes to allocate + */ +MEM_NODE *MemoryAlloc(int flags, long size) { + MEM_NODE *pHeap = &heapSentinel; + MEM_NODE *pNode; + bool bCompacted = true; // set when heap has been compacted + + // compact the heap if we are allocating fixed memory + if (flags & DWM_FIXED) + HeapCompact(MAX_INT, false); + + while ((flags & DWM_NOALLOC) == 0 && bCompacted) { + // search the heap for a free block + + for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) { + if (pNode->flags == 0 && pNode->size >= size) { + // a free block of the required size + pNode->flags = flags; + + // update the LRU time + pNode->lruTime = DwGetCurrentTime() + 1; + + if (pNode->size == size) { + // an exact fit + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + if (flags & DWM_FIXED) + // lock the memory + return (MEM_NODE *)MemoryLock(pNode); + else + // just return the node + return pNode; + } else { + // allocate a node for the remainder of the free block + MEM_NODE *pTemp = AllocMemNode(); + + // calc size of the free block + long freeSize = pNode->size - size; + + // set size of free block + pTemp->size = freeSize; + + // set size of node + pNode->size = size; + + if (flags & DWM_FIXED) { + // place the free node after pNode + pTemp->pBaseAddr = pNode->pBaseAddr + size; + pTemp->pNext = pNode->pNext; + pTemp->pPrev = pNode; + pNode->pNext->pPrev = pTemp; + pNode->pNext = pTemp; + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + return (MEM_NODE *)MemoryLock(pNode); + } else { + // place the free node before pNode + pTemp->pBaseAddr = pNode->pBaseAddr; + pNode->pBaseAddr += freeSize; + pTemp->pNext = pNode; + pTemp->pPrev = pNode->pPrev; + pNode->pPrev->pNext = pTemp; + pNode->pPrev = pTemp; + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + return pNode; + } + } + } + } + // compact the heap if we get to here + bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true); + } + + // not allocated a block if we get to here + if (flags & DWM_DISCARDABLE) { + // chain a discarded node onto the end of the heap + pNode = AllocMemNode(); + pNode->flags = flags | DWM_DISCARDED; + + // set mnode at the end of the list + pNode->pPrev = pHeap->pPrev; + pNode->pNext = pHeap; + + // fix links to this mnode + pHeap->pPrev->pNext = pNode; + pHeap->pPrev = pNode; + + // return the discarded node + return pNode; + } + + // could not allocate a block + return NULL; +} + +/** + * Discards the specified memory object. + * @param pMemNode Node of the memory object + */ +void MemoryDiscard(MEM_NODE *pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // object must be discardable + assert(pMemNode->flags & DWM_DISCARDABLE); + + // object cannot be locked + assert((pMemNode->flags & DWM_LOCKED) == 0); + + if ((pMemNode->flags & DWM_DISCARDED) == 0) { + // allocate a free node to replace this node + MEM_NODE *pTemp = AllocMemNode(); + + // copy node data + memcpy(pTemp, pMemNode, sizeof(MEM_NODE)); + + // flag as a free block + pTemp->flags = 0; + + // link in the free node + pTemp->pPrev->pNext = pTemp; + pTemp->pNext->pPrev = pTemp; + + // discard the node + pMemNode->flags |= DWM_DISCARDED; + pMemNode->pBaseAddr = NULL; + pMemNode->size = 0; + + // and place it at the end of the heap + while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL) + pTemp = pTemp->pNext; + + // pTemp now points to the heap sentinel + // set mnode at the end of the list + pMemNode->pPrev = pTemp->pPrev; + pMemNode->pNext = pTemp; + + // fix links to this mnode + pTemp->pPrev->pNext = pMemNode; + pTemp->pPrev = pMemNode; + } +} + +/** + * Frees the specified memory object and invalidates its node. + * @param pMemNode Node of the memory object + */ +void MemoryFree(MEM_NODE *pMemNode) { + MEM_NODE *pPrev, *pNext; + + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // get pointer to the next mnode + pNext = pMemNode->pNext; + + // get pointer to the previous mnode + pPrev = pMemNode->pPrev; + + if (pPrev->flags == 0) { + // there is a previous free mnode + pPrev->size += pMemNode->size; + + // unlink this mnode + pPrev->pNext = pNext; // previous to next + pNext->pPrev = pPrev; // next to previous + + // free this mnode + FreeMemNode(pMemNode); + + pMemNode = pPrev; + } + if (pNext->flags == 0) { + // the next mnode is free + pMemNode->size += pNext->size; + + // flag as a free block + pMemNode->flags = 0; + + // unlink the next mnode + pMemNode->pNext = pNext->pNext; + pNext->pNext->pPrev = pMemNode; + + // free the next mnode + FreeMemNode(pNext); + } +} + +/** + * Locks a memory object and returns a pointer to the first byte + * of the objects memory block. + * @param pMemNode Node of the memory object + */ +void *MemoryLock(MEM_NODE *pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // make sure memory object is not already locked + assert((pMemNode->flags & DWM_LOCKED) == 0); + + // check for a discarded or null memory object + if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0) + return NULL; + + // set the lock flag + pMemNode->flags |= DWM_LOCKED; + + // return memory objects base address + return pMemNode->pBaseAddr; +} + +/** + * Changes the size or attributes of a specified memory object. + * @param pMemNode Node of the memory object + * @param size New size of block + * @param flags How to reallocate the object + */ +MEM_NODE *MemoryReAlloc(MEM_NODE *pMemNode, long size, int flags) { + MEM_NODE *pNew; + + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // validate the flags + // cannot be fixed and moveable + assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE)); + + // cannot be fixed and discardable + assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE)); + + // must be fixed or moveable + assert(flags & (DWM_FIXED | DWM_MOVEABLE)); + + // align the size to machine boundary requirements + size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1); + + // validate the size + assert(size); + + // make sure we want the node on the same heap + assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC))); + + if (size == pMemNode->size) { + // must be just a change in flags + + // update the nodes flags + pMemNode->flags = flags; + } else { + // unlink the mnode from the current heap + pMemNode->pNext->pPrev = pMemNode->pPrev; + pMemNode->pPrev->pNext = pMemNode->pNext; + + // allocate a new node + pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size); + + // make sure memory allocated + assert(pNew != NULL); + + // update the nodes flags + pNew->flags = flags; + + // copy the node to the current node + memcpy(pMemNode, pNew, sizeof(MEM_NODE)); + + // relink the mnode into the list + pMemNode->pPrev->pNext = pMemNode; + pMemNode->pNext->pPrev = pMemNode; + + // free the new node + FreeMemNode(pNew); + } + + if (flags & DWM_FIXED) + // lock the memory + return (MEM_NODE *)MemoryLock(pMemNode); + else + // just return the node + return pMemNode; +} + +/** + * Unlocks a memory object. + * @param pMemNode Node of the memory object + */ +void MemoryUnlock(MEM_NODE *pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // make sure memory object is already locked + assert(pMemNode->flags & DWM_LOCKED); + + // clear the lock flag + pMemNode->flags &= ~DWM_LOCKED; + + // update the LRU time + pMemNode->lruTime = DwGetCurrentTime(); +} + +/** + * Retrieves the mnode associated with the specified pointer to a memory object. + * @param pMem Address of memory object + */ +MEM_NODE *MemoryHandle(void *pMem) { + MEM_NODE *pNode; + // search the DOS heap + for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) { + if (pNode->pBaseAddr == pMem) + // found it + return pNode; + } + + // not found if we get to here + return NULL; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h new file mode 100644 index 0000000000..7fb85985a9 --- /dev/null +++ b/engines/tinsel/heapmem.h @@ -0,0 +1,109 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager defines + */ + +#ifndef TINSEL_HEAPMEM_H +#define TINSEL_HEAPMEM_H + +#include "tinsel/dw.h" // new data types + +namespace Tinsel { + +#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192) + +struct MEM_NODE { + MEM_NODE *pNext; // link to the next node in the list + MEM_NODE *pPrev; // link to the previous node in the list + uint8 *pBaseAddr; // base address of the memory object + long size; // size of the memory object + uint32 lruTime; // time when memory object was last accessed + int flags; // allocation attributes +}; + +// allocation flags for the MemoryAlloc function +#define DWM_FIXED 0x0001 // allocates fixed memory +#define DWM_MOVEABLE 0x0002 // allocates movable memory +#define DWM_DISCARDABLE 0x0004 // allocates discardable memory +#define DWM_NOALLOC 0x0008 // when used with discardable memory - allocates a discarded block +#define DWM_NOCOMPACT 0x0010 // does not discard memory to satisfy the allocation request +#define DWM_ZEROINIT 0x0020 // initialises memory contents to zero +#define DWM_SOUND 0x0040 // allocate from the sound pool +#define DWM_GRAPHIC 0x0080 // allocate from the graphics pool + +// return value from the MemoryFlags function +#define DWM_DISCARDED 0x0100 // the objects memory block has been discarded + +// internal allocation flags +#define DWM_LOCKED 0x0200 // the objects memory block is locked +#define DWM_SENTINEL 0x0400 // the objects memory block is a sentinel + + +/*----------------------------------------------------------------------*\ +|* Memory Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void MemoryInit(void); // initialises the memory manager + +#ifdef DEBUG +void MemoryStats(void); // Shows the maximum number of mnodes used at once +#endif + +MEM_NODE *MemoryAlloc( // allocates the specified number of bytes from the heap + int flags, // allocation attributes + long size); // number of bytes to allocate + +void MemoryDiscard( // discards the specified memory object + MEM_NODE *pMemNode); // node of the memory object + +int MemoryFlags( // returns information about the specified memory object + MEM_NODE *pMemNode); // node of the memory object + +void MemoryFree( // frees the specified memory object and invalidates its node + MEM_NODE *pMemNode); // node of the memory object + +MEM_NODE *MemoryHandle( // Retrieves the mnode associated with the specified pointer to a memory object + void *pMem); // address of memory object + +void *MemoryLock( // locks a memory object and returns a pointer to the first byte of the objects memory block + MEM_NODE *pMemNode); // node of the memory object + +MEM_NODE *MemoryReAlloc( // changes the size or attributes of a specified memory object + MEM_NODE *pMemNode, // node of the memory object + long size, // new size of block + int flags); // how to reallocate the object + +long MemorySize( // returns the size, in bytes, of the specified memory object + MEM_NODE *pMemNode); // node of the memory object + +void MemoryUnlock( // unlocks a memory object + MEM_NODE *pMemNode); // node of the memory object + +bool HeapCompact( // Allocates the specified number of bytes from the specified heap + long size, // number of bytes to free up + bool bDiscard); // when set - will discard blocks to fullfill the request + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp new file mode 100644 index 0000000000..2a0f3695c0 --- /dev/null +++ b/engines/tinsel/inventory.cpp @@ -0,0 +1,4535 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles the inventory and conversation windows. + * + * And the save/load game windows. Some of this will be platform + * specific - I'll try to separate this ASAP. + * + * And there's still a bit of tidying and commenting to do yet. + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/polygons.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinsel.h" // For engine access +#include "tinsel/token.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart; // restart flag - set to restart the game + +#ifdef MAC_OPTIONS +// In MAC_SOUND.C +extern int volMaster; +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// Tag functions in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +//#define ALL_CURSORS + +#define INV_PICKUP BE_SLEFT // Local names +#define INV_LOOK BE_SRIGHT // for button events +#define INV_ACTION BE_DLEFT // + + +// For SlideSlider() and similar +enum SSFN { + S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN +}; + +/** attribute values - may become bit field if further attributes are added */ +enum { + IO_ONLYINV1 = 0x01, + IO_ONLYINV2 = 0x02, + IO_DROPCODE = 0x04 +}; + +//----------------------- +// Moveable window translucent rectangle position limits +enum { + MAXLEFT = 315, // + MINRIGHT = 3, // These values keep 2 pixcells + MINTOP = -13, // of header on the screen. + MAXTOP = 195 // +}; + +//----------------------- +// Indices into winPartsf's reels +#define IX_SLIDE 0 // Slider +#define IX_V26 1 +#define IX_V52 2 +#define IX_V78 3 +#define IX_V104 4 +#define IX_V130 5 +#define IX_H26 6 +#define IX_H52 7 +#define IX_H78 8 +#define IX_H104 9 +#define IX_H130 10 +#define IX_H156 11 +#define IX_H182 12 +#define IX_H208 13 +#define IX_H234 14 +#define IX_TL 15 // Top left corner +#define IX_TR 16 // Top right corner +#define IX_BL 17 // Bottom left corner +#define IX_BR 18 // Bottom right corner +#define IX_H25 19 +#define IX_V11 20 +#define IX_RTL 21 // Re-sizing top left corner +#define IX_RTR 22 // Re-sizing top right corner +#define IX_RBR 23 // Re-sizing bottom right corner +#define IX_CURLR 24 // } +#define IX_CURUD 25 // } +#define IX_CURDU 26 // } Custom cursors +#define IX_CURDD 27 // } +#define IX_CURUP 28 // } +#define IX_CURDOWN 29 // } +#define IX_MDGROOVE 30 // 'Mixing desk' slider background +#define IX_MDSLIDER 34 // 'Mixing desk' slider + +#define IX_BLANK1 35 // +#define IX_BLANK2 36 // +#define IX_BLANK3 37 // +#define IX_CIRCLE1 38 // +#define IX_CIRCLE2 39 // +#define IX_CROSS1 40 // +#define IX_CROSS2 41 // +#define IX_CROSS3 42 // +#define IX_QUIT1 43 // +#define IX_QUIT2 44 // +#define IX_QUIT3 45 // +#define IX_TICK1 46 // +#define IX_TICK2 47 // +#define IX_TICK3 48 // +#define IX_NTR 49 // New top right corner +#define HOPEDFORREELS 50 + +#define NORMGRAPH 0 +#define DOWNGRAPH 1 +#define HIGRAPH 2 +//----------------------- +#define FIX_UK 0 +#define FIX_FR 1 +#define FIX_GR 2 +#define FIX_IT 3 +#define FIX_SP 4 +#define FIX_USA 5 +#define HOPEDFORFREELS 6 // Expected flag reels +//----------------------- + +#define MAX_ININV 150 // Max in an inventory +#define MAX_CONVBASIC 10 // Max permanent conversation icons + +#define MAXHICONS 10 // Max dimensions of +#define MAXVICONS 6 // an inventory window + +#define ITEM_WIDTH 25 // Dimensions of an icon +#define ITEM_HEIGHT 25 // + +// Number of objects that makes up an empty window +#define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides + // + Bground + title + slider + // + more Needed for save game window + +#define MAX_ICONS MAXHICONS*MAXVICONS + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +//----- Permanent data (compiled in) ----- + +// Save game name editing cursor + +#define CURSOR_CHAR '_' +char sCursor[2] = { CURSOR_CHAR, 0 }; +static const int hFillers[MAXHICONS] = { + IX_H26, // 2 icons wide + IX_H52, // 3 + IX_H78, // 4 + IX_H104, // 5 + IX_H130, // 6 + IX_H156, // 7 + IX_H182, // 8 + IX_H208, // 9 + IX_H234 // 10 icons wide +}; +static const int vFillers[MAXVICONS] = { + IX_V26, // 2 icons high + IX_V52, // 3 + IX_V78, // 4 + IX_V104, // 5 + IX_V130 // 6 icons high +}; + + +//----- Permanent data (set once) ----- + +static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data +static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data +static SCNHANDLE configStrings[20]; + +static INV_OBJECT *pio = 0; // Inventory objects' data +static int numObjects = 0; // Number of inventory objects + + +//----- Permanent data (updated, valid while inventory closed) ----- + +static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState; + +static int HeldItem = INV_NOICON; // Current held item + +struct INV_DEF { + + int MinHicons; // } + int MinVicons; // } Dimension limits + int MaxHicons; // } + int MaxVicons; // } + + int NoofHicons; // } + int NoofVicons; // } Current dimentsions + + int ItemOrder[MAX_ININV]; // Contained items + int NoofItems; // Current number of held items + + int FirstDisp; // Index to first item currently displayed + + int inventoryX; // } Display position + int inventoryY; // } + int otherX; // } Display position + int otherY; // } + + int MaxInvObj; // Max. allowed contents + + SCNHANDLE hInvTitle; // Window heading + + bool resizable; // Re-sizable window? + bool moveable; // Moveable window? + + int sNoofHicons; // } + int sNoofVicons; // } Current dimensions + + bool bMax; // Maximised last time open? + +}; + +static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ... + + +// Permanent contents of conversation inventory +static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents +static int Num0Order = 0; // - copy to conv. inventory at pop-up time + + + +//----- Data pertinant to current active inventory ----- + +static int ino = 0; // Which inventory is currently active + +static bool InventoryHidden = false; +static bool InventoryMaximised = false; + +static enum { ID_NONE, ID_MOVE, ID_SLIDE, + ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT, + ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT, + ID_CSLIDE, ID_MDCONT } InvDragging; + +static int SuppH = 0; // 'Linear' element of +static int SuppV = 0; // dimensions during re-sizing + +static int Ychange = 0; // +static int Ycompensate = 0; // All to do with re-sizing. +static int Xchange = 0; // +static int Xcompensate = 0; // + +static bool ItemsChanged = 0; // When set, causes items to be re-drawn + +static bool bOpenConf = 0; + +static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction +static int TLwidth = 0, TLheight = 0; // +static int TRwidth = 0; // +static int BLheight = 0; // + + + +static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window) +static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons) +static ANIM iconAnims[MAX_ICONS]; +static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window) + +static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference + // objects are in objArray. + +static int slideY = 0; // For positioning the slider +static int slideYmax = 0, slideYmin = 0; // + +// Also to do with the slider +static struct { int n; int y; } slideStuff[MAX_ININV+1]; + +#define MAXSLIDES 4 +struct MDSLIDES { + int num; + OBJECT *obj; + int min, max; +}; +static MDSLIDES mdSlides[MAXSLIDES]; +static int numMdSlides = 0; + +static int GlitterIndex = 0; + +static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block +static int thisConvIcon = 0; // Passed to polygon code via convIcon() +static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call +static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug' +static int sX = 0; // used by SlideMSlider() - current x-coordinate +static int lX = 0; // used by SlideMSlider() - last x-coordinate + +//----- Data pertinant to configure (incl. load/save game) ----- + +#define COL_MAINBOX TBLUE1 // Base blue colour +#define COL_BOX TBLUE1 +#define COL_HILIGHT TBLUE4 + +#ifdef JAPAN +#define BOX_HEIGHT 17 +#define EDIT_BOX1_WIDTH 149 +#else +#define BOX_HEIGHT 13 +#define EDIT_BOX1_WIDTH 145 +#endif +#define EDIT_BOX2_WIDTH 166 + +// RGROUP Radio button group - 1 is selectable at a time. Action on double click +// ARSBUT Action if a radio button is selected +// AABUT Action always +// AATBUT Action always, text box +// AAGBUT Action always, graphic button +// SLIDER Not a button at all +enum BTYPE { + RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER, + TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING +}; + +enum BFUNC { + NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN, + OPENLOAD, OPENSAVE, OPENREST, + OPENSOUND, OPENCONT, +#ifndef JAPAN + OPENSUBT, +#endif + OPENQUIT, + INITGAME, MIDIVOL, + CLANG, RLANG +#ifdef MAC_OPTIONS + , MASTERVOL, SAMPVOL +#endif +}; + +struct CONFBOX { + BTYPE boxType; + BFUNC boxFunc; + char *boxText; + int ixText; + int xpos; + int ypos; + int w; // Doubles as max value for SLIDERs + int h; // Doubles as iteration size for SLIDERs + int *ival; + int bi; // Base index for AAGBUTs +}; + + +#define NO_HEADING (-1) +#define USE_POINTER (-1) +#define SIX_LOAD_OPTION 0 +#define SIX_SAVE_OPTION 1 +#define SIX_RESTART_OPTION 2 +#define SIX_SOUND_OPTION 3 +#define SIX_CONTROL_OPTION 4 +#ifndef JAPAN +#define SIX_SUBTITLES_OPTION 5 +#endif +#define SIX_QUIT_OPTION 6 +#define SIX_RESUME_OPTION 7 +#define SIX_LOAD_HEADING 8 +#define SIX_SAVE_HEADING 9 +#define SIX_RESTART_HEADING 10 +#define SIX_MVOL_SLIDER 11 +#define SIX_SVOL_SLIDER 12 +#define SIX_VVOL_SLIDER 13 +#define SIX_DCLICK_SLIDER 14 +#define SIX_DCLICK_TEST 15 +#define SIX_SWAP_TOGGLE 16 +#define SIX_TSPEED_SLIDER 17 +#define SIX_STITLE_TOGGLE 18 +#define SIX_QUIT_HEADING 19 + + +/*-------------------------------------------------------------*\ +| This is the main menu (that comes up when you hit F1 on a PC) | +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define FBY 11 // y-offset of first button +#define FBX 13 // x-offset of first button +#else +#define FBY 20 // y-offset of first button +#define FBX 15 // x-offset of first button +#endif + +CONFBOX optionBox[] = { + + { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifdef JAPAN +// TODO: If in JAPAN mode, simply disable the subtitles button? + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#else + { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#endif + +}; + +/*-------------------------------------------------------------*\ +| These are the load and save game menus. | +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define NUM_SL_RGROUP 7 // number of visible slots +#define SY 32 // y-position of first slot +#else +#define NUM_SL_RGROUP 9 // number of visible slots +#define SY 31 // y-position of first slot +#endif + +CONFBOX loadBox[NUM_SL_RGROUP+2] = { + + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } + +}; + +CONFBOX saveBox[NUM_SL_RGROUP+2] = { + + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } + +}; + + +/*-------------------------------------------------------------*\ +| This is the restart confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX restartBox[] = { + +#ifdef JAPAN + { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } +#endif + +}; + + +/*-------------------------------------------------------------*\ +| This is the sound control 'menu'. | +\*-------------------------------------------------------------*/ + +#ifdef MAC_OPTIONS + CONFBOX soundBox[] = { + { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 }, + { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 }, + { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 }, + { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 } + }; +#else +CONFBOX soundBox[] = { + { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, MAXMIDIVOL, 2, &volMidi, 0 }, + { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, MAXSAMPVOL, 2, &volSound, 0 }, + { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, MAXSAMPVOL, 2, &volVoice, 0 } +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the (mouse) control 'menu'. | +\*-------------------------------------------------------------*/ + +int bFlipped; // looks like this is just so the code has something to alter! + + +#ifdef MAC_OPTIONS +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 } + +}; +#else +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }, +#ifdef JAPAN + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 } +#else + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the subtitles 'menu'. | +\*-------------------------------------------------------------*/ + +#ifndef JAPAN +CONFBOX subtitlesBox[] = { + +#ifdef USE_5FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_4FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_3FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP }, +#endif + + { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the quit confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX quitBox[] = { +#ifdef JAPAN + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } +#endif +}; + + +CONFBOX topwinBox[] = { + { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 } +}; + + + +struct CONFINIT { + int h; + int v; + int x; + int y; + bool bExtraWin; + CONFBOX *Box; + int NumBoxes; + int ixHeading; +}; + +CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, ARRAYSIZE(optionBox), NO_HEADING }; + +CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, ARRAYSIZE(loadBox), SIX_LOAD_HEADING }; +CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, ARRAYSIZE(saveBox), SIX_SAVE_HEADING }; +#ifdef JAPAN +CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; +#else +CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING }; +#endif +CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, ARRAYSIZE(soundBox), NO_HEADING }; +#ifdef MAC_OPTIONS + CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; +#else + CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING }; +#endif +#ifndef JAPAN +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +CONFINIT ciSubtitles = { 10, 6, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING }; +#else +CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING }; +#endif +#endif +CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, ARRAYSIZE(quitBox), SIX_QUIT_HEADING }; + +CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING }; + +#define NOBOX (-1) + +// Conf window globals +static struct { + CONFBOX *Box; + int NumBoxes; + bool bExtraWin; + int ixHeading; + bool editableRgroup; + + int selBox; + int pointBox; // Box pointed to on last call + int saveModifier; + int fileBase; + int numSaved; +} cd = { + NULL, 0, false, 0, false, + NOBOX, NOBOX, 0, 0, 0 +}; + +// For editing save game names +char sedit[SG_DESC_LEN+2]; + +#define HL1 0 // Hilight that moves with the cursor +#define HL2 1 // Hilight on selected RGROUP box +#define HL3 2 // Text on selected RGROUP box +#define NUMHL 3 + + +// Data for button press/toggle effects +static struct { + bool bButAnim; + CONFBOX *box; + bool press; // true = button press; false = button toggle +} g_buttonEffect = { false, 0, false }; + + +//----- LOCAL FORWARD REFERENCES ----- + +enum { + IB_NONE = -1, // + IB_UP = -2, // negative numbers returned + IB_DOWN = -3, // by WhichInvBox() + IB_SLIDE = -4, // + IB_SLIDE_UP = -5, // + IB_SLIDE_DOWN = -6 // +}; + +enum { + HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit + IS_LEFT = HI_BIT, + IS_SLIDER = (IS_LEFT >> 1), + IS_RIGHT = (IS_SLIDER >> 1), + IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT) +}; + +static int WhichInvBox(int curX, int curY, bool bSlides); +static void SlideMSlider(int x, SSFN fn); +static OBJECT *AddObject(const FREEL *pfreel, int num); +static void AddBoxes(bool posnSlide); + +static void ConfActionSpecial(int i); + + +/*-------------------------------------------------------------------------*/ +/*** Magic numbers ***/ + +#define M_SW 5 // Side width +#define M_TH 5 // Top height +#ifdef JAPAN +#define M_TOFF 6 // Title text Y offset from top +#define M_TBB 20 // Title box bottom Y offset +#else +#define M_TOFF 4 // Title text Y offset from top +#define M_TBB 14 // Title box bottom Y offset +#endif +#define M_SBL 26 // Scroll bar left X offset +#define M_SH 5 // Slider height (*) +#define M_SW 5 // Slider width (*) +#define M_SXOFF 9 // Slider X offset from right-hand side +#ifdef JAPAN +#define M_IUT 22 // Y offset of top of up arrow +#define M_IUB 30 // Y offset of bottom of up arrow +#else +#define M_IUT 16 // Y offset of top of up arrow +#define M_IUB 24 // Y offset of bottom of up arrow +#endif +#define M_IDT 10 // Y offset (from bottom) of top of down arrow +#define M_IDB 3 // Y offset (from bottom) of bottom of down arrow +#define M_IAL 12 // X offset (from right) of left of scroll arrows +#define M_IAR 3 // X offset (from right) of right of scroll arrows + +#define START_ICONX (M_SW+1) // } Relative offset of first icon +#define START_ICONY (M_TBB+M_TH+1) // } within the inventory window + +/*-------------------------------------------------------------------------*/ + + + +#ifndef JAPAN +bool LanguageChange(void) { + LANGUAGE nLang; + +#ifdef USE_3FLAGS + // VERY quick dodgy bodge + if (cd.selBox == 0) + nLang = TXT_FRENCH; + else if (cd.selBox == 1) + nLang = TXT_GERMAN; + else + nLang = TXT_SPANISH; + if (nLang != language) { +#elif defined(USE_4FLAGS) + nLang = (LANGUAGE)(cd.selBox + 1); + if (nLang != language) { +#else + if (cd.selBox != language) { + nLang = (LANGUAGE)cd.selBox; +#endif + KillInventory(); + ChangeLanguage(nLang); + language = nLang; + return true; + } + else + return false; +} +#endif + +/**************************************************************************/ +/******************** Some miscellaneous functions ************************/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +| DumpIconArray()/DumpDobjArray()/DumpObjArray() | +|-----------------------------------------------------------------------| +| Delete all the objects in iconArray[]/DobjArray[]/objArray[] | +\*---------------------------------------------------------------------*/ +static void DumpIconArray(void){ + for (int i = 0; i < MAX_ICONS; i++) { + if (iconArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]); + iconArray[i] = NULL; + } + } +} + +/** + * Delete all the objects in DobjArray[] + */ + +static void DumpDobjArray(void) { + for (int i = 0; i < MAX_WCOMP; i++) { + if (DobjArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]); + DobjArray[i] = NULL; + } + } +} + +/** + * Delete all the objects in objArray[] + */ + +static void DumpObjArray(void) { + for (int i = 0; i < MAX_WCOMP; i++) { + if (objArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]); + objArray[i] = NULL; + } + } +} + +/** + * Convert item ID number to pointer to item's compiled data + * i.e. Image data and Glitter code. + */ +INV_OBJECT *findInvObject(int num) { + INV_OBJECT *retval = pio; + + for (int i = 0; i < numObjects; i++, retval++) { + if (retval->id == num) + return retval; + } + + error("Trying to manipulate undefined inventory icon"); +} + +/** + * Returns position of an item in one of the inventories. + * The actual position is not important for the uses that this is put to. + */ + +int InventoryPos(int num) { + int i; + + for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory + if (InvD[INV_1].ItemOrder[i] == num) + return i; + + for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory + if (InvD[INV_2].ItemOrder[i] == num) + return i; + + if (HeldItem == num) + return INV_HELDNOTIN; // Held, but not in either inventory + + return INV_NOICON; // Not held, not in either inventory +} + +bool IsInInventory(int object, int invnum) { + assert(invnum == INV_1 || invnum == INV_2); + + for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory + if (InvD[invnum].ItemOrder[i] == object) + return true; + + return false; +} + +/** + * Returns which item is held (INV_NOICON (-1) if none) + */ + +int WhichItemHeld(void) { + return HeldItem; +} + +/** + * Called from the cursor module when it re-initialises (at the start of + * a new scene). For if we are holding something at scene-change time. + */ + +void InventoryIconCursor(void) { + INV_OBJECT *invObj; + + if (HeldItem != INV_NOICON) { + invObj = findInvObject(HeldItem); + SetAuxCursor(invObj->hFilm); + } +} + +/** + * Returns TRUE if the inventory is active. + */ + +bool InventoryActive(void) { + return (InventoryState == ACTIVE_INV); +} + +int WhichInventoryOpen(void) { + if (InventoryState != ACTIVE_INV) + return 0; + else + return ino; +} + + +/**************************************************************************/ +/************** Running inventory item's Glitter code *********************/ +/**************************************************************************/ + +struct ITP_INIT { + INV_OBJECT *pinvo; + USER_EVENT event; + BUTEVENT bev; +}; + +/** + * Run inventory item's Glitter code + */ +static void InvTinselProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + int ThisPointedWait; // Fix the 'repeated pressing bug' + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + ITP_INIT *to = (ITP_INIT *)param; + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, to->bev); + + _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo); + CORO_INVOKE_1(Interpret, _ctx->pic); + + if (to->event == POINTED) { + _ctx->ThisPointedWait = ++PointedWaitCount; + while (1) { + CORO_SLEEP(1); + int x, y; + GetCursorXY(&x, &y, false); + if (InvItemId(x, y) != to->pinvo->id) + break; + + // Fix the 'repeated pressing bug' + if (_ctx->ThisPointedWait != PointedWaitCount) + CORO_KILL_SELF(); + } + + _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo); + CORO_INVOKE_1(Interpret, _ctx->pic); + } + + CORO_END_CODE; +} + +/** + * Run inventory item's Glitter code + */ +void RunInvTinselCode(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) { + ITP_INIT to = { pinvo, event, be }; + + if (InventoryHidden) + return; + + GlitterIndex = index; + g_scheduler->createProcess(PID_TCODE, InvTinselProcess, &to, sizeof(to)); +} + +/**************************************************************************/ +/****************** Load/Save game specific functions *********************/ +/**************************************************************************/ + +/** + * Set first load/save file entry displayed. + * Point Box[] text pointers to appropriate file descriptions. + */ + +void firstFile(int first) { + int i, j; + + i = getList(); + + cd.numSaved = i; + + if (first < 0) + first = 0; + else if (first > MAX_SFILES-NUM_SL_RGROUP) + first = MAX_SFILES-NUM_SL_RGROUP; + + if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) { + // Blank first entry for new save + cd.Box[0].boxText = NULL; + cd.saveModifier = j = 1; + } else { + cd.saveModifier = j = 0; + } + + for (i = first; j < NUM_SL_RGROUP; j++, i++) { + cd.Box[j].boxText = ListEntry(i, LE_DESC); + } + + cd.fileBase = first; +} + +/** + * Save the game using filename from selected slot & current description. + */ + +void InvSaveGame(void) { + if (cd.selBox != NOBOX) { +#ifndef JAPAN + sedit[strlen(sedit)-1] = 0; // Don't include the cursor! +#endif + SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit); + } +} + +/** + * Load the selected saved game. + */ +void InvLoadGame(void) { + int rGame; + + if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) { + rGame = cd.selBox; + cd.selBox = NOBOX; + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + if (iconArray[HL2] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + iconArray[HL2] = NULL; + } + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + RestoreGame(rGame+cd.fileBase); + } +} + +/** + * Edit the string in sedit[] + * Returns TRUE if the string was altered. + */ +#ifndef JAPAN +bool UpdateString(const Common::KeyState &kbd) { + int cpos; + + if (!cd.editableRgroup) + return false; + + cpos = strlen(sedit)-1; + + if (kbd.keycode == Common::KEYCODE_BACKSPACE) { + if (!cpos) + return false; + sedit[cpos] = 0; + cpos--; + sedit[cpos] = CURSOR_CHAR; + return true; +// } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) { + } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) { + if (cpos == SG_DESC_LEN) + return false; + sedit[cpos] = kbd.ascii; + cpos++; + sedit[cpos] = CURSOR_CHAR; + sedit[cpos+1] = 0; + return true; + } + return false; +} +#endif + +/** + * Keystrokes get sent here when load/save screen is up. + */ +bool InvKeyIn(const Common::KeyState &kbd) { + if (kbd.keycode == Common::KEYCODE_PAGEUP || + kbd.keycode == Common::KEYCODE_PAGEDOWN || + kbd.keycode == Common::KEYCODE_HOME || + kbd.keycode == Common::KEYCODE_END) + return true; // Key needs processing + + if (kbd.keycode == 0 && kbd.ascii == 0) { + ; + } else if (kbd.keycode == Common::KEYCODE_RETURN) { + return true; // Key needs processing + } else if (kbd.keycode == Common::KEYCODE_ESCAPE) { + return true; // Key needs processing + } else { +#ifndef JAPAN + if (UpdateString(kbd)) { + /* + * Delete display of text currently being edited, + * and replace it with freshly edited text. + */ + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, + hTagFontHandle(), 0); + if (MultiRightmost(iconArray[HL3]) > 213) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE)); + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, + hTagFontHandle(), 0); + } + MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); + } +#endif + } + return false; +} + +/*---------------------------------------------------------------------*\ +| Select() | +|-----------------------------------------------------------------------| +| Highlights selected box. | +| If it's editable (save game), copy existing description and add a | +| cursor. | +\*---------------------------------------------------------------------*/ +void Select(int i, bool force) { +#ifdef JAPAN + time_t secs_now; + struct tm *time_now; +#endif + + i &= ~IS_MASK; + + if (cd.selBox == i && !force) + return; + + cd.selBox = i; + + // Clear previous selected highlight and text + if (iconArray[HL2] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + iconArray[HL2] = NULL; + } + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + + // New highlight box + switch (cd.Box[i].boxType) { + case RGROUP: + iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + MultiSetAniXY(iconArray[HL2], + InvD[ino].inventoryX + cd.Box[i].xpos, + InvD[ino].inventoryY + cd.Box[i].ypos); + + // Z-position of box, and add edit text if appropriate + if (cd.editableRgroup) { + MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1); + + assert(cd.Box[i].ixText == USE_POINTER); +#ifdef JAPAN + // Current date and time + time(&secs_now); + time_now = localtime(&secs_now); + strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now); +#else + // Current description with cursor appended + if (cd.Box[i].boxText != NULL) { + strcpy(sedit, cd.Box[i].boxText); + strcat(sedit, sCursor); + } else { + strcpy(sedit, sCursor); + } +#endif + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[i].xpos + 2, +#ifdef JAPAN + InvD[ino].inventoryY + cd.Box[i].ypos + 2, +#else + InvD[ino].inventoryY + cd.Box[i].ypos, +#endif + hTagFontHandle(), 0); + MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); + } else { + MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1); + } + + _vm->divertKeyInput(InvKeyIn); + + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + MultiSetAniXY(iconArray[HL2], + InvD[ino].inventoryX + cd.Box[i].xpos - 2, + InvD[ino].inventoryY + cd.Box[i].ypos - 2); + MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1); + + break; +#endif + default: + break; + } +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * If the item is not already held, hold it. + */ + +void HoldItem(int item) { + INV_OBJECT *invObj; + + if (HeldItem != item) { + if (item == INV_NOICON && HeldItem != INV_NOICON) + DelAuxCursor(); // no longer aux cursor + + if (item != INV_NOICON) { + invObj = findInvObject(item); + SetAuxCursor(invObj->hFilm); // and is aux. cursor + } + + HeldItem = item; // Item held + } + + // Redraw contents - held item not displayed as a content. + ItemsChanged = true; +} + +/** + * Stop holding an item. + */ + +void DropItem(int item) { + if (HeldItem == item) { + HeldItem = INV_NOICON; // Item not held + DelAuxCursor(); // no longer aux cursor + } + + // Redraw contents - held item was not displayed as a content. + ItemsChanged = true; +} + +/** + * Stick the item into an inventory list (ItemOrder[]), and hold the + * item if requested. + */ + +void AddToInventory(int invno, int icon, bool hold) { + int i; + bool bOpen; +#ifdef DEBUG + INV_OBJECT *invObj; +#endif + + assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory + + if (invno == INV_OPEN) { + assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open + invno = ino; + bOpen = true; + + // Make sure it doesn't get in both! + RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon); + } else + bOpen = false; + +#ifdef DEBUG + invObj = findInvObject(icon); + if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1) + || (invObj->attribute & IO_ONLYINV2 && invno != INV_2)) + error("Trying to add resticted object to wrong inventory"); +#endif + + if (invno == INV_1) + RemFromInventory(INV_2, icon); + else if (invno == INV_2) + RemFromInventory(INV_1, icon); + + // See if it's already there + for (i = 0; i < InvD[invno].NoofItems; i++) { + if (InvD[invno].ItemOrder[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == InvD[invno].NoofItems) { + if (!bOpen) { + if (invno == INV_CONV) { + // For conversation, insert before last icon + // which will always be the goodbye icon + InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1]; + InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon; + InvD[invno].NoofItems++; + } else { + InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon; + } + ItemsChanged = true; + } else { + // It could be that the index is beyond what you'd expect + // as delinv may well have been called + if (GlitterIndex < InvD[invno].NoofItems) { + memmove(&InvD[invno].ItemOrder[GlitterIndex + 1], + &InvD[invno].ItemOrder[GlitterIndex], + (InvD[invno].NoofItems-GlitterIndex)*sizeof(int)); + InvD[invno].ItemOrder[GlitterIndex] = icon; + } else { + InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon; + } + InvD[invno].NoofItems++; + } + } + + // Hold it if requested + if (hold) + HoldItem(icon); +} + +/** + * Take the item from the inventory list (ItemOrder[]). + * Return FALSE if item wasn't present, true if it was. + */ + +bool RemFromInventory(int invno, int icon) { + int i; + + assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory + + // See if it's there + for (i = 0; i < InvD[invno].NoofItems; i++) { + if (InvD[invno].ItemOrder[i] == icon) + break; + } + + if (i == InvD[invno].NoofItems) + return false; // Item wasn't there + else { + memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int)); + InvD[invno].NoofItems--; + ItemsChanged = true; + return true; // Item removed + } +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +| InvArea() | +|-----------------------------------------------------------------------| +| Work out which area of the inventory window the cursor is in. | +|-----------------------------------------------------------------------| +| This used to be worked out with appropriately defined magic numbers. | +| Then the graphic changed and I got it right again. Then the graphic | +| changed and I got fed up of faffing about. It's probably easier just | +| to rework all this. | +\*---------------------------------------------------------------------*/ +enum { I_NOTIN, I_MOVE, I_BODY, + I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT, + I_TOP, I_BOTTOM, I_LEFT, I_RIGHT, + I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN, + I_ENDCHANGE +}; + +#define EXTRA 1 // This was introduced when we decided to increase + // the active area of the borders for re-sizing. + +/*---------------------------------*/ +#define LeftX InvD[ino].inventoryX +#define TopY InvD[ino].inventoryY +/*---------------------------------*/ + +int InvArea(int x, int y) { + int RightX = MultiRightmost(RectObject) + 1; + int BottomY = MultiLowest(RectObject) + 1; + +// Outside the whole rectangle? + if (x <= LeftX - EXTRA || x > RightX + EXTRA + || y <= TopY - EXTRA || y > BottomY + EXTRA) + return I_NOTIN; + +// The bottom line + if (y > BottomY - 2 - EXTRA) { // Below top of bottom line? + if (x <= LeftX + 2 + EXTRA) + return I_BLEFT; // Bottom left corner + else if (x > RightX - 2 - EXTRA) + return I_BRIGHT; // Bottom right corner + else + return I_BOTTOM; // Just plain bottom + } + +// The top line + if (y <= TopY + 2 + EXTRA) { // Above bottom of top line? + if (x <= LeftX + 2 + EXTRA) + return I_TLEFT; // Top left corner + else if (x > RightX - 2 - EXTRA) + return I_TRIGHT; // Top right corner + else + return I_TOP; // Just plain top + } + +// Sides + if (x <= LeftX + 2 + EXTRA) // Left of right of left side? + return I_LEFT; + else if (x > RightX - 2 - EXTRA) // Right of left of right side? + return I_RIGHT; + +// From here down still needs fixing up properly +/* +* In the move area? +*/ + if (ino != INV_CONF + && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 && + y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2) + return I_MOVE; + +/* +* Scroll bits +*/ + if (ino == INV_CONF && cd.bExtraWin) { + } else { + if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) { + if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1) + return I_UP; + if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1) + return I_DOWN; + + if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) { + if (y < TopY + slideY) + return I_SLIDE_UP; + if (y < TopY + slideY + M_SH) + return I_SLIDE; + else + return I_SLIDE_DOWN; + } + } + } + + return I_BODY; +} + +/** + * Returns the id of the icon displayed under the given position. + * Also return co-ordinates of items tag display position, if requested. + */ + +int InvItem(int *x, int *y, bool update) { + int itop, ileft; + int row, col; + int item; + int IconsX; + + itop = InvD[ino].inventoryY + START_ICONY; + + IconsX = InvD[ino].inventoryX + START_ICONX; + + for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { + ileft = IconsX; + + for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { + if (*x >= ileft && *x < ileft + ITEM_WIDTH && + *y >= itop && *y < itop + ITEM_HEIGHT) { + if (update) { + *x = ileft + ITEM_WIDTH/2; + *y = itop /*+ ITEM_HEIGHT/4*/; + } + return item; + } + + ileft += ITEM_WIDTH + 1; + } + itop += ITEM_HEIGHT + 1; + } + return INV_NOICON; +} + +/** + * Returns the id of the icon displayed under the given position. + */ + +int InvItemId(int x, int y) { + int itop, ileft; + int row, col; + int item; + + if (InventoryHidden || InventoryState == IDLE_INV) + return INV_NOICON; + + itop = InvD[ino].inventoryY + START_ICONY; + + int IconsX = InvD[ino].inventoryX + START_ICONX; + + for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { + ileft = IconsX; + + for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { + if (x >= ileft && x < ileft + ITEM_WIDTH && + y >= itop && y < itop + ITEM_HEIGHT) { + return InvD[ino].ItemOrder[item]; + } + + ileft += ITEM_WIDTH + 1; + } + itop += ITEM_HEIGHT + 1; + } + return INV_NOICON; +} + +/*---------------------------------------------------------------------*\ +| WhichInvBox() | +|-----------------------------------------------------------------------| +| Finds which box the cursor is in. | +\*---------------------------------------------------------------------*/ +#define MD_YSLIDTOP 7 +#define MD_YSLIDBOT 18 +#define MD_YBUTTOP 9 +#define MD_YBUTBOT 16 +#define MD_XLBUTL 1 +#define MD_XLBUTR 10 +#define MD_XRBUTL 105 +#define MD_XRBUTR 114 + +static int WhichInvBox(int curX, int curY, bool bSlides) { + if (bSlides) { + for (int i = 0; i < numMdSlides; i++) { + if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj) + && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj)) + return mdSlides[i].num | IS_SLIDER; + } + } + + curX -= InvD[ino].inventoryX; + curY -= InvD[ino].inventoryY; + + for (int i = 0; i < cd.NumBoxes; i++) { + switch (cd.Box[i].boxType) { + case SLIDER: + if (bSlides) { + if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) { + if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR) + return i | IS_LEFT; + if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR) + return i | IS_RIGHT; + } + } + break; + + case AAGBUT: + case ARSGBUT: + case TOGGLE: + case FLIP: + if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h + && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) + return i; + break; + + default: + // 'Normal' box + if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h + && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) + return i; + break; + } + } + + if (cd.bExtraWin) { + if (curX > 20 + 181 && curX < 20 + 181 + 8 && + curY > 24 + 2 && curY < 24 + 139 + 5) { + + if (curY < 24 + 2 + 5) { + return IB_UP; + } else if (curY > 24 + 139) { + return IB_DOWN; + } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) { + return IB_SLIDE; + } else if (curY+InvD[ino].inventoryY < slideY) { + return IB_SLIDE_UP; + } else if (curY+InvD[ino].inventoryY >= slideY + 5) { + return IB_SLIDE_DOWN; + } + } + } + + return IB_NONE; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * InBoxes + */ +void InvBoxes(bool InBody, int curX, int curY) { + int index; // Box pointed to on this call + const FILM *pfilm; + + // Find out which icon is currently pointed to + if (!InBody) + index = -1; + else { + index = WhichInvBox(curX, curY, false); + } + + // If no icon pointed to, or points to (logical position of) + // currently held icon, then no icon is pointed to! + if (index < 0) { + // unhigh-light box (if one was) + cd.pointBox = NOBOX; + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + } else if (index != cd.pointBox) { + cd.pointBox = index; + // A new box is pointed to - high-light it + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) || +///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP || + cd.Box[cd.pointBox].boxType == AATBUT || + cd.Box[cd.pointBox].boxType == AABUT) { + iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, + InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + } + else if (cd.Box[cd.pointBox].boxType == AAGBUT || + cd.Box[cd.pointBox].boxType == ARSGBUT || + cd.Box[cd.pointBox].boxType == TOGGLE) { + pfilm = (const FILM *)LockMem(winPartsf); + + iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, + InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + } + } +} + +static void ButtonPress(CORO_PARAM, CONFBOX *box) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FILM *pfilm; + + assert(box->boxType == AAGBUT || box->boxType == ARSGBUT); + + // Replace highlight image with normal image + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + pfilm = (const FILM *)LockMem(winPartsf); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // Replace normal image with depresses image + pfilm = (const FILM *)LockMem(winPartsf); + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold depressed image for 2 frames + CORO_SLEEP(2); + if (iconArray[HL1] == NULL) + return; + + // Replace depressed image with normal image + pfilm = (const FILM *)LockMem(winPartsf); + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + CORO_SLEEP(1); + + CORO_END_CODE; +} + +static void ButtonToggle(CORO_PARAM, CONFBOX *box) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FILM *pfilm; + + assert(box->boxType == TOGGLE); + + // Remove hilight image + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (InventoryState != ACTIVE_INV) + return; + + // Add depressed image + pfilm = (const FILM *)LockMem(winPartsf); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold depressed image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // Toggle state + (*box->ival) = *(box->ival) ^ 1; // XOR with true + box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1; + AddBoxes(false); + // Keep highlight (e.g. flag) + if (cd.selBox != NOBOX) + Select(cd.selBox, true); + + // New state, depressed image + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold new depressed image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // New state, normal + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (InventoryState != ACTIVE_INV) + return; + + // New state, highlighted + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + CORO_END_CODE; +} + +/** + * Monitors for POINTED event for inventory icons. + */ + +void InvLabels(bool InBody, int aniX, int aniY) { + int index; // Icon pointed to on this call + INV_OBJECT *invObj; + + // Find out which icon is currently pointed to + if (!InBody) + index = INV_NOICON; + else { + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (index >= InvD[ino].NoofItems) + index = INV_NOICON; + else + index = InvD[ino].ItemOrder[index]; + } + } + + // If no icon pointed to, or points to (logical position of) + // currently held icon, then no icon is pointed to! + if (index == INV_NOICON || index == HeldItem) { + pointedIcon = INV_NOICON; + } else if (index != pointedIcon) { + // A new icon is pointed to - run its script with POINTED event + invObj = findInvObject(index); + if (invObj->hScript) + RunInvTinselCode(invObj, POINTED, BE_NONE, index); + pointedIcon = index; + } +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * All to do with the slider. + * I can't remember how it works - or, indeed, what it does. + * It seems to set up slideStuff[], an array of possible first-displayed + * icons set against the matching y-positions of the slider. + */ + +void AdjustTop(void) { + int tMissing, bMissing, nMissing; + int nslideY; + int rowsWanted; + int slideRange; + int n, i; + + // Only do this if there's a slider + if (!SlideObject) + return; + + rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons; + + while (rowsWanted < InvD[ino].NoofVicons) { + if (InvD[ino].FirstDisp) { + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + rowsWanted++; + } else + break; + } + tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0; + bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0; + + nMissing = tMissing + bMissing; + slideRange = slideYmax - slideYmin; + + if (!tMissing) + nslideY = slideYmin; + else if (!bMissing) + nslideY = slideYmax; + else { + nslideY = tMissing*slideRange/nMissing; + nslideY += slideYmin; + } + + if (nMissing) { + n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons; + for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) { + slideStuff[i].n = n; + slideStuff[i].y = (i*slideRange/nMissing) + slideYmin; + } + if (slideStuff[0].n < 0) + slideStuff[0].n = 0; + assert(i < MAX_ININV + 1); + slideStuff[i].n = -1; + } else { + slideStuff[0].n = 0; + slideStuff[0].y = slideYmin; + slideStuff[1].n = -1; + } + + if (nslideY != slideY) { + MultiMoveRelXY(SlideObject, 0, nslideY - slideY); + slideY = nslideY; + } +} + +/** + * Insert an inventory icon object onto the display list. + */ + +OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) { + INV_OBJECT *invObj; // Icon data + const MULTI_INIT *pmi; // Its INIT structure - from the reel + IMAGE *pim; // ... you get the picture + OBJECT *pPlayObj; // The object we insert + + invObj = findInvObject(num); + + // Get pointer to image + pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Set up the multi-object + pPlayObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + + return pPlayObj; +} + +/** + * Create display objects for the displayed icons in an inventory window. + */ + +void FillInInventory(void) { + int Index; // Index into ItemOrder[] + int n = 0; // index into iconArray[] + int xpos, ypos; + int row, col; + const FREEL *pfr; + const FILM *pfilm; + + DumpIconArray(); + + if (InvDragging != ID_SLIDE) + AdjustTop(); // Set up slideStuff[] + + Index = InvD[ino].FirstDisp; // Start from first displayed object + n = 0; + ypos = START_ICONY; // Y-offset of first display row + + for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) { + xpos = START_ICONX; // X-offset of first display column + + for (col = 0; col < InvD[ino].NoofHicons; col++) { + if (Index >= InvD[ino].NoofItems) + break; + else if (InvD[ino].ItemOrder[Index] != HeldItem) { + // Create a display object and position it + iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm); + MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos); + MultiSetZPosition(iconArray[n], Z_INV_ICONS); + + InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + + n++; + } + Index++; + xpos += ITEM_WIDTH + 1; // X-offset of next display column + } + } +} + +/** + * Set up a rectangle as the background to the inventory window. + * Additionally, sticks the window title up. + */ + +enum {FROM_HANDLE, FROM_STRING}; + +void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) { + // Why not 2 ???? + int width = TLwidth + extraH + TRwidth - 3; + int height = TLheight + extraV + BLheight - 3; + + // Create a rectangle object + RectObject = *rect = TranslucentObject(width, height); + + // add it to display list and position it + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect); + MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1); + MultiSetZPosition(*rect, Z_INV_BRECT); + + // Create text object using title string + if (textFrom == FROM_HANDLE) { + LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ); + *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + hTagFontHandle(), TXT_CENTRE); + assert(*title); // Inventory title string produced NULL text + MultiSetZPosition(*title, Z_INV_HTEXT); + } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) { + LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ); + *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + hTagFontHandle(), TXT_CENTRE); + assert(*title); // Inventory title string produced NULL text + MultiSetZPosition(*title, Z_INV_HTEXT); + } +} + +/** + * Insert a part of the inventory window frame onto the display list. + */ + +static OBJECT *AddObject(const FREEL *pfreel, int num) { + const MULTI_INIT *pmi; // Get the MULTI_INIT structure + IMAGE *pim; + OBJECT *pPlayObj; + + // Get pointer to image + pim = GetImageFromReel(pfreel, &pmi); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Horrible bodge involving global variables to save + // width and/or height of some window frame components + if (num == TL) { + TLwidth = FROM_LE_16(pim->imgWidth); + TLheight = FROM_LE_16(pim->imgHeight); + } else if (num == TR) { + TRwidth = FROM_LE_16(pim->imgWidth); + } else if (num == BL) { + BLheight = FROM_LE_16(pim->imgHeight); + } + + // Set up and insert the multi-object + pPlayObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + + return pPlayObj; +} + +/** + * Display the scroll bar slider. + */ + +void AddSlider(OBJECT **slide, const FILM *pfilm) { + SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); + MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY); + MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +enum { + SLIDE_RANGE = 81, + SLIDE_MINX = 8, + SLIDE_MAXX = 8+SLIDE_RANGE, + + MDTEXT_YOFF = 6, + MDTEXT_XOFF = -4 +}; + +/** + * Display a box with some text in it. + */ + +void AddBox(int *pi, int i) { + int x = InvD[ino].inventoryX + cd.Box[i].xpos; + int y = InvD[ino].inventoryY + cd.Box[i].ypos; + int *pival = cd.Box[i].ival; + int xdisp; + const FILM *pfilm; + + switch (cd.Box[i].boxType) { + default: + // Give us a box + iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + if (cd.Box[i].ixText == USE_POINTER) { + if (cd.Box[i].boxText != NULL) { + if (cd.Box[i].boxType == RGROUP) { + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN + x+2, y+2, hTagFontHandle(), 0); +#else + x+2, y, hTagFontHandle(), 0); +#endif + } else { + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN +// Note: it never seems to go here! + x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else + x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif + } + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + } else { + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code! + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +#ifdef JAPAN + x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else + x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + break; + + case AAGBUT: + case ARSGBUT: + pfilm = (const FILM *)LockMem(winPartsf); + + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + assert(flagFilm != 0); // Language flags not declared! + + pfilm = (const FILM *)LockMem(flagFilm); + + if (bAmerica && cd.Box[i].bi == FIX_UK) + cd.Box[i].bi = FIX_USA; + + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2); + *pi += 1; + + break; +#endif + case FLIP: + pfilm = (const FILM *)LockMem(winPartsf); + + if (*(cd.Box[i].ival)) + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); + else + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case TOGGLE: + pfilm = (const FILM *)LockMem(winPartsf); + + cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1; + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case SLIDER: + pfilm = (const FILM *)LockMem(winPartsf); + xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w; + + iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_MDGROOVE); + *pi += 1; + iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1); + MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y); + MultiSetZPosition(iconArray[*pi], Z_MDSLIDER); + assert(numMdSlides < MAXSLIDES); + mdSlides[numMdSlides].num = i; + mdSlides[numMdSlides].min = x+SLIDE_MINX; + mdSlides[numMdSlides].max = x+SLIDE_MAXX; + mdSlides[numMdSlides++].obj = iconArray[*pi]; + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + } +} + +/** + * Display some boxes. + */ +static void AddBoxes(bool posnSlide) { + int oCount = NUMHL; // Object count - allow for HL1, HL2 etc. + + DumpIconArray(); + numMdSlides = 0; + + for (int i = 0; i < cd.NumBoxes; i++) { + AddBox(&oCount, i); + } + + if (cd.bExtraWin) { + if (posnSlide) + slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP); + MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY); + } + + assert(oCount < MAX_ICONS); // added too many icons +} + +/** + * Display the scroll bar slider. + */ + +void AddEWSlider(OBJECT **slide, const FILM *pfilm) { + SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); + MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY); + MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +/** + * AddExtraWindow + */ + +int AddExtraWindow(int x, int y, OBJECT **retObj) { + int n = 0; + const FILM *pfilm; + + // Get the frame's data + pfilm = (const FILM *)LockMem(winPartsf); + + x += 20; + y += 24; + +// Draw the four corners + retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left + MultiSetAniXY(retObj[n], x, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right + MultiSetAniXY(retObj[n], x + 152, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left + MultiSetAniXY(retObj[n], x, y + 124); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right + MultiSetAniXY(retObj[n], x + 152, y + 124); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + +// Draw the edges + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top + MultiSetAniXY(retObj[n], x + 6, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom + MultiSetAniXY(retObj[n], x + 6, y + 143); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left + MultiSetAniXY(retObj[n], x, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1 + MultiSetAniXY(retObj[n], x + 179, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2 + MultiSetAniXY(retObj[n], x + 188, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + + slideY = slideYmin = y + 9; + slideYmax = y + 134; + AddEWSlider(&retObj[n++], pfilm); + + return n; +} + + +enum InventoryType { EMPTY, FULL, CONF }; + +/** + * Construct an inventory window - either a standard one, with + * background, slider and icons, or a re-sizing window. + */ +void ConstructInventory(InventoryType filling) { + int eH, eV; // Extra width and height + int n = 0; // Index into object array + int zpos; // Z-position of frame + int invX = InvD[ino].inventoryX; + int invY = InvD[ino].inventoryY; + OBJECT **retObj; + const FILM *pfilm; + + extern bool RePosition(void); // Forward reference + // Select the object array to use + if (filling == FULL || filling == CONF) { + retObj = objArray; // Standard window + zpos = Z_INV_MFRAME; + } else { + retObj = DobjArray; // Re-sizing window + zpos = Z_INV_RFRAME; + } + + // Dispose of anything it may be replacing + for (int i = 0; i < MAX_WCOMP; i++) { + if (retObj[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]); + retObj[i] = NULL; + } + } + + // Get the frame's data + pfilm = (const FILM *)LockMem(winPartsf); + + // Standard window is of granular dimensions + if (filling == FULL) { + // Round-up/down to nearest number of icons + if (SuppH > ITEM_WIDTH / 2) + InvD[ino].NoofHicons++; + if (SuppV > ITEM_HEIGHT / 2) + InvD[ino].NoofVicons++; + SuppH = SuppV = 0; + } + + // Extra width and height + eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH; + eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV; + + // Which window frame corners to use + if (filling == FULL && ino != INV_CONV) { + TL = IX_TL; + TR = IX_TR; + BL = IX_BL; + BR = IX_BR; + } else { + TL = IX_RTL; + TR = IX_RTR; + BL = IX_BL; + BR = IX_RBR; + } + +// Draw the four corners + retObj[n] = AddObject(&pfilm->reels[TL], TL); + MultiSetAniXY(retObj[n], invX, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[TR], TR); + MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[BL], BL); + MultiSetAniXY(retObj[n], invX, invY + TLheight + eV); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[BR], BR); + MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV); + MultiSetZPosition(retObj[n], zpos); + n++; + +// Draw extra Top and bottom parts + if (InvD[ino].NoofHicons > 1) { + // Top side + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Bottom of header box + if (filling == FULL) { + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Extra bits for conversation - hopefully temporary + if (ino == INV_CONV) { + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + + retObj[n] = AddObject(&pfilm->reels[IX_H52], -1); + MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + } + + // Bottom side + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppH) { + int offx = TLwidth + eH - 26; + if (offx < TLwidth) // Not too far! + offx = TLwidth; + + // Top side extra + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + offx, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Bottom side extra + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + +// Draw extra side parts + if (InvD[ino].NoofVicons > 1) { + // Left side + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Left side of scroll bar + if (filling == FULL && ino != INV_CONV) { + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + } + + // Right side + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppV) { + int offy = TLheight + eV - 26; + if (offy < 5) + offy = 5; + + // Left side extra + retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); + MultiSetAniXY(retObj[n], invX, invY + offy); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Right side extra + retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy); + MultiSetZPosition(retObj[n], zpos); + n++; + } + + OBJECT **rect, **title; + +// Draw background, slider and icons + if (filling == FULL) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_HANDLE); + + if (ino == INV_CONV) + SlideObject = NULL; + else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) { + slideYmin = TLheight - 2; + slideYmax = TLheight + eV + 10; + AddSlider(&retObj[n++], pfilm); + } + + FillInInventory(); + } + else if (filling == CONF) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_STRING); + if (cd.bExtraWin) + n += AddExtraWindow(invX, invY, &retObj[n]); + AddBoxes(true); + } + + assert(n < MAX_WCOMP); // added more parts than we can handle! + + // Reposition returns TRUE if needs to move + if (InvD[ino].moveable && filling == FULL && RePosition()) { + ConstructInventory(FULL); + } +} + + +/** + * Call this when drawing a 'FULL', movable inventory. Checks that the + * position of the Translucent object is within limits. If it isn't, + * adjusts the x/y position of the current inventory and returns TRUE. + */ +bool RePosition(void) { + int p; + bool bMoveitMoveit = false; + + assert(RectObject); // no recangle object! + + // Test for off-screen horizontally + p = MultiLeftmost(RectObject); + if (p > MAXLEFT) { + // Too far to the right + InvD[ino].inventoryX += MAXLEFT - p; + bMoveitMoveit = true; // I like to.... + } else { + // Too far to the left? + p = MultiRightmost(RectObject); + if (p < MINRIGHT) { + InvD[ino].inventoryX += MINRIGHT - p; + bMoveitMoveit = true; // I like to.... + } + } + + // Test for off-screen vertically + p = MultiHighest(RectObject); + if (p < MINTOP) { + // Too high + InvD[ino].inventoryY += MINTOP - p; + bMoveitMoveit = true; // I like to.... + } else if (p > MAXTOP) { + // Too low + InvD[ino].inventoryY += MAXTOP - p; + bMoveitMoveit = true; // I like to.... + } + + return bMoveitMoveit; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * Get the cursor's reel, poke in the background palette, + * and customise the cursor. + */ +void AlterCursor(int num) { + const FREEL *pfreel; + IMAGE *pim; + + // Get pointer to image + pim = GetImageFromFilm(winPartsf, num, &pfreel); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + SetTempCursor(FROM_LE_32(pfreel->script)); +} + +enum InvCursorFN {IC_AREA, IC_DROP}; + +/** + * InvCursor + */ +void InvCursor(InvCursorFN fn, int CurX, int CurY) { + static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR, + IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var + + int area; // The part of the window the cursor is over + bool restoreMain = false; + + // If currently dragging, don't be messing about with the cursor shape + if (InvDragging != ID_NONE) + return; + + switch (fn) { + case IC_DROP: + ICursor = IC_NORMAL; + InvCursor(IC_AREA, CurX, CurY); + break; + + case IC_AREA: + area = InvArea(CurX, CurY); + + // Check for POINTED events + if (ino == INV_CONF) + InvBoxes(area == I_BODY, CurX, CurY); + else + InvLabels(area == I_BODY, CurX, CurY); + + // No cursor trails while within inventory window + if (area == I_NOTIN) + UnHideCursorTrails(); + else + HideCursorTrails(); + + switch (area) { + case I_NOTIN: + restoreMain = true; + break; + + case I_TLEFT: + case I_BRIGHT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_DR) { + AlterCursor(IX_CURDD); + ICursor = IC_DR; + } + break; + + case I_TRIGHT: + case I_BLEFT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_UR) { + AlterCursor(IX_CURDU); + ICursor = IC_UR; + } + break; + + case I_TOP: + case I_BOTTOM: + if (!InvD[ino].resizable) { + restoreMain = true; + break; + } + if (ICursor != IC_TB) { + AlterCursor(IX_CURUD); + ICursor = IC_TB; + } + break; + + case I_LEFT: + case I_RIGHT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_LR) { + AlterCursor(IX_CURLR); + ICursor = IC_LR; + } + break; + + case I_UP: + case I_SLIDE_UP: + case I_DOWN: + case I_SLIDE_DOWN: + case I_SLIDE: + case I_MOVE: + case I_BODY: + restoreMain = true; + break; + } + break; + } + + if (restoreMain && ICursor != IC_NORMAL) { + RestoreMainCursor(); + ICursor = IC_NORMAL; + } +} + + + + +/*-------------------------------------------------------------------------*/ + + +/**************************************************************************/ +/******************** Conversation specific functions *********************/ +/**************************************************************************/ + + +void ConvAction(int index) { + assert(ino == INV_CONV); // not conv. window! + + switch (index) { + case INV_NOICON: + return; + + case INV_CLOSEICON: + thisConvIcon = -1; // Postamble + break; + + case INV_OPENICON: + thisConvIcon = -2; // Preamble + break; + + default: + thisConvIcon = InvD[ino].ItemOrder[index]; + break; + } + + RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true); +} +/*-------------------------------------------------------------------------*/ + +void AddIconToPermanentDefaultList(int icon) { + int i; + + // See if it's already there + for (i = 0; i < Num0Order; i++) { + if (Inv0Order[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == Num0Order) { + Inv0Order[Num0Order++] = icon; + } +} + +/*-------------------------------------------------------------------------*/ + +void convPos(int fn) { + if (fn == CONV_DEF) + InvD[INV_CONV].inventoryY = 8; + else if (fn == CONV_BOTTOM) + InvD[INV_CONV].inventoryY = 150; +} + +void ConvPoly(HPOLYGON hPoly) { + thisConvPoly = hPoly; +} + +int convIcon(void) { + return thisConvIcon; +} + +void CloseDownConv(void) { + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + KillInventory(); + } +} + +void convHide(bool hide) { + int aniX, aniY; + int i; + + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + if (hide) { + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0); + } + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0); + } + InventoryHidden = true; + + InvLabels(false, 0, 0); + } else { + InventoryHidden = false; + + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0); + } + + // Don't flash if items changed. If they have, will be redrawn anyway. + if (!ItemsChanged) { + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0); + } + } + + GetCursorXY(&aniX, &aniY, false); + InvLabels(true, aniX, aniY); + } + } +} + +bool convHid(void) { + return InventoryHidden; +} + + +/**************************************************************************/ +/******************* Open and closing functions ***************************/ +/**************************************************************************/ + +/** + * Start up an inventory window. + */ + +void PopUpInventory(int invno) { + assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory + + if (InventoryState == IDLE_INV) { + bOpenConf = false; // Better safe than sorry... + + DisableTags(); // Tags disabled during inventory + + if (invno == INV_CONV) { // Conversation window? + // Start conversation with permanent contents + memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int)); + memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int)); + InvD[INV_CONV].NoofItems = Num0Order; + thisConvIcon = 0; + } else if (invno == INV_CONF) { // Configuration window? + cd.selBox = NOBOX; + cd.pointBox = NOBOX; + } + + ino = invno; // The open inventory + + ItemsChanged = false; // Nothing changed + InvDragging = ID_NONE; // Not dragging + InventoryState = ACTIVE_INV; // Inventory actiive + InventoryHidden = false; // Not hidden + InventoryMaximised = InvD[ino].bMax; + if (invno != INV_CONF) // Configuration window? + ConstructInventory(FULL); // Draw it up + else { + ConstructInventory(CONF); // Draw it up + } + } +} + +void SetConfGlobals(CONFINIT *ci) { + InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h; + InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v; + InvD[INV_CONF].inventoryX = ci->x; + InvD[INV_CONF].inventoryY = ci->y; + cd.bExtraWin = ci->bExtraWin; + cd.Box = ci->Box; + cd.NumBoxes = ci->NumBoxes; + cd.ixHeading = ci->ixHeading; +} + +/** + * PopupConf + */ + +void PopUpConf(CONFTYPE type) { + int curX, curY; + + if (InventoryState != IDLE_INV) + return; + + InvD[INV_CONF].resizable = false; + InvD[INV_CONF].moveable = false; + + switch (type) { + case SAVE: + case LOAD: + if (type == SAVE) { + SetCursorScreenXY(262, 91); + SetConfGlobals(&ciSave); + cd.editableRgroup = true; + } else { + SetConfGlobals(&ciLoad); + cd.editableRgroup = false; + } + firstFile(0); + break; + + case QUIT: +#ifdef JAPAN + SetCursorScreenXY(180, 106); +#else + SetCursorScreenXY(180, 90); +#endif + SetConfGlobals(&ciQuit); + break; + + case RESTART: +#ifdef JAPAN + SetCursorScreenXY(180, 106); +#else + SetCursorScreenXY(180, 90); +#endif + SetConfGlobals(&ciRestart); + break; + + case OPTION: + SetConfGlobals(&ciOption); + break; + + case CONTROLS: + SetConfGlobals(&ciControl); + break; + + case SOUND: + SetConfGlobals(&ciSound); + break; + +#ifndef JAPAN + case SUBT: + SetConfGlobals(&ciSubtitles); + break; +#endif + + case TOPWIN: + SetConfGlobals(&ciTopWin); + ino = INV_CONF; + ConstructInventory(CONF); // Draw it up + InventoryState = BOGUS_INV; + return; + + default: + return; + } + + if (HeldItem != INV_NOICON) + DelAuxCursor(); // no longer aux cursor + + PopUpInventory(INV_CONF); + + if (type == SAVE || type == LOAD) + Select(0, false); +#ifndef JAPAN +#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS) + else if (type == SUBT) { +#ifdef USE_3FLAGS + // VERY quick dirty bodges + if (language == TXT_FRENCH) + Select(0, false); + else if (language == TXT_GERMAN) + Select(1, false); + else + Select(2, false); +#elif defined(USE_4FLAGS) + Select(language-1, false); +#else + Select(language, false); +#endif + } +#endif +#endif // JAPAN + + GetCursorXY(&curX, &curY, false); + InvCursor(IC_AREA, curX, curY); +} + +/** + * Close down an inventory window. + */ + +void KillInventory(void) { + if (objArray[0] != NULL) { + DumpObjArray(); + DumpDobjArray(); + DumpIconArray(); + } + + if (InventoryState == ACTIVE_INV) { + EnableTags(); + + InvD[ino].bMax = InventoryMaximised; + + UnHideCursorTrails(); + _vm->divertKeyInput(NULL); + } + + InventoryState = IDLE_INV; + + if (bOpenConf) { + bOpenConf = false; + PopUpConf(OPTION); + } else if (ino == INV_CONF) + InventoryIconCursor(); +} + +void CloseInventory(void) { + // If not active, ignore this + if (InventoryState != ACTIVE_INV) + return; + + // If hidden, a conversation action is still underway - ignore this + if (InventoryHidden) + return; + + // If conversation, this is a closeing event + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + + KillInventory(); + + RestoreMainCursor(); +} + + + +/**************************************************************************/ +/************************ The inventory process ***************************/ +/**************************************************************************/ + +/** + * Redraws the icons if appropriate. Also handle button press/toggle effects + */ +void InventoryProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (1) { + CORO_SLEEP(1); // allow scheduling + + if (objArray[0] != NULL) { + if (ItemsChanged && ino != INV_CONF && !InventoryHidden) { + FillInInventory(); + + // Needed when clicking on scroll bar. + int curX, curY; + GetCursorXY(&curX, &curY, false); + InvCursor(IC_AREA, curX, curY); + + ItemsChanged = false; + } + if (ino != INV_CONF) { + for (int i = 0; i < MAX_ICONS; i++) { + if (iconArray[i] != NULL) + StepAnimScript(&iconAnims[i]); + } + } + if (InvDragging == ID_MDCONT) { + // Mixing desk control + int sval, index, *pival; + + index = cd.selBox & ~IS_MASK; + pival = cd.Box[index].ival; + sval = *pival; + + if (cd.selBox & IS_LEFT) { + *pival -= cd.Box[index].h; + if (*pival < 0) + *pival = 0; + } else if (cd.selBox & IS_RIGHT) { + *pival += cd.Box[index].h; + if (*pival > cd.Box[index].w) + *pival = cd.Box[index].w; + } + + if (sval != *pival) { + SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN); + } + } + } + + if (g_buttonEffect.bButAnim) { + assert(g_buttonEffect.box); + if (g_buttonEffect.press) { + if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT) + CORO_INVOKE_1(ButtonPress, g_buttonEffect.box); + switch (g_buttonEffect.box->boxFunc) { + case SAVEGAME: + KillInventory(); + InvSaveGame(); + break; + case LOADGAME: + KillInventory(); + InvLoadGame(); + break; + case IQUITGAME: + _vm->quitFlag = true; + break; + case CLOSEWIN: + KillInventory(); + break; + case OPENLOAD: + KillInventory(); + PopUpConf(LOAD); + break; + case OPENSAVE: + KillInventory(); + PopUpConf(SAVE); + break; + case OPENREST: + KillInventory(); + PopUpConf(RESTART); + break; + case OPENSOUND: + KillInventory(); + PopUpConf(SOUND); + break; + case OPENCONT: + KillInventory(); + PopUpConf(CONTROLS); + break; + #ifndef JAPAN + case OPENSUBT: + KillInventory(); + PopUpConf(SUBT); + break; + #endif + case OPENQUIT: + KillInventory(); + PopUpConf(QUIT); + break; + case INITGAME: + KillInventory(); + bRestart = true; + break; + #if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case CLANG: + if (!LanguageChange()) + KillInventory(); + break; + case RLANG: + KillInventory(); + break; + #endif + default: + break; + } + } else + CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box); + + g_buttonEffect.bButAnim = false; + } + + } + CORO_END_CODE; +} + +/**************************************************************************/ +/*************** Drag stuff - Resizing and moving window ******************/ +/**************************************************************************/ + +/** + * Appears to find the nearest entry in slideStuff[] to the supplied + * y-coordinate. + */ +int NearestSlideY(int fity) { + int nearDist = 1000; + int thisDist; + int nearI = 0; // Index of nearest fit + int i = 0; + + do { + thisDist = ABS(slideStuff[i].y - fity); + if (thisDist < nearDist) { + nearDist = thisDist; + nearI = i; + } + } while (slideStuff[++i].n != -1); + return nearI; +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ +void SlideSlider(int y, SSFN fn) { + static int newY = 0, lasti = 0; // FIXME: local static var + int gotoY, ati; + + // Only do this if there's a slider + if (!SlideObject) + return; + + switch (fn) { + case S_START: // Start of a drag on the slider + newY = slideY; + lasti = NearestSlideY(slideY); + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < slideYmin) + gotoY = slideYmin; // Above top limit + else if (newY > slideYmax) + gotoY = slideYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + // Move slider to new position + MultiMoveRelXY(SlideObject, 0, gotoY - slideY); + slideY = gotoY; + + // Re-draw icons if necessary + ati = NearestSlideY(slideY); + if (ati != lasti) { + InvD[ino].FirstDisp = slideStuff[ati].n; + assert(InvD[ino].FirstDisp >= 0); // negative first displayed + ItemsChanged = true; + lasti = ati; + } + break; + + case S_END: // End of a drag on the slider + // Draw icons from new start icon + ati = NearestSlideY(slideY); + InvD[ino].FirstDisp = slideStuff[ati].n; + ItemsChanged = true; + break; + + default: + break; + } +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ + +void SlideCSlider(int y, SSFN fn) { + static int newY = 0; // FIXME: local static var + int gotoY; + int fc; + + // Only do this if there's a slider + if (!SlideObject) + return; + + switch (fn) { + case S_START: // Start of a drag on the slider + newY = slideY; + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < slideYmin) + gotoY = slideYmin; // Above top limit + else if (newY > slideYmax) + gotoY = slideYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + slideY = gotoY; + + fc = cd.fileBase; + firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin)); + if (fc != cd.fileBase) { + AddBoxes(false); + fc -= cd.fileBase; + cd.selBox += fc; + if (cd.selBox < 0) + cd.selBox = 0; + else if (cd.selBox >= NUM_SL_RGROUP) + cd.selBox = NUM_SL_RGROUP-1; + Select(cd.selBox, true); + } + break; + + case S_END: // End of a drag on the slider + break; + + default: + break; + } +} + +/** + * Gets called at the start and end of a drag on a mixing desk slider, + * and upon x-movement during such a drag. + */ + +static void SlideMSlider(int x, SSFN fn) { + static int newX = 0; // FIXME: local static var + int gotoX; + int index, i; + + if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN) + ; + else if (!(cd.selBox & IS_SLIDER)) + return; + + // Work out the indices + index = cd.selBox & ~IS_MASK; + for (i = 0; i < numMdSlides; i++) + if (mdSlides[i].num == index) + break; + assert(i < numMdSlides); + + switch (fn) { + case S_START: // Start of a drag on the slider + // can use index as a throw-away value + GetAniPosition(mdSlides[i].obj, &newX, &index); + lX = sX = newX; + break; + + case S_SLIDE: // X-movement during drag + if (x == 0) + return; + + newX = newX + x; // New x-position + + if (newX < mdSlides[i].min) + gotoX = mdSlides[i].min; // Below bottom limit + else if (newX > mdSlides[i].max) + gotoX = mdSlides[i].max; // Above top limit + else + gotoX = newX; // Hunky-Dory + + // Move slider to new position + MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0); + sX = gotoX; + + if (lX != sX) { + *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE; + if (cd.Box[index].boxFunc == MIDIVOL) + SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS + if (cd.Box[index].boxFunc == MASTERVOL) + SetSystemVolume(*cd.Box[index].ival); + + if (cd.Box[index].boxFunc == SAMPVOL) + SetSampleVolume(*cd.Box[index].ival); +#endif + lX = sX; + } + break; + + case S_TIMEUP: + case S_TIMEDN: + gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w; + MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX); + + if (cd.Box[index].boxFunc == MIDIVOL) + SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS + if (cd.Box[index].boxFunc == MASTERVOL) + SetSystemVolume(*cd.Box[index].ival); + + if (cd.Box[index].boxFunc == SAMPVOL) + SetSampleVolume(*cd.Box[index].ival); +#endif + break; + + case S_END: // End of a drag on the slider + AddBoxes(false); // Might change position slightly +#ifndef JAPAN + if (ino == INV_CONF && cd.Box == subtitlesBox) + Select(language, false); +#endif + break; + } +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingTaller(void) { + if (SuppV) { + Ychange += SuppV; + if (Ycompensate == 'T') + InvD[ino].inventoryY += SuppV; + SuppV = 0; + } + while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) { + Ychange -= (ITEM_HEIGHT+1); + InvD[ino].NoofVicons++; + if (Ycompensate == 'T') + InvD[ino].inventoryY -= (ITEM_HEIGHT+1); + } + if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) { + SuppV = Ychange; + Ychange = 0; + if (Ycompensate == 'T') + InvD[ino].inventoryY -= SuppV; + } +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingShorter(void) { + int StartNvi = InvD[ino].NoofVicons; + int StartUv = SuppV; + + if (SuppV) { + Ychange += (SuppV - (ITEM_HEIGHT+1)); + InvD[ino].NoofVicons++; + SuppV = 0; + } + while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) { + Ychange += (ITEM_HEIGHT+1); + InvD[ino].NoofVicons--; + } + if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) { + SuppV = (ITEM_HEIGHT+1) + Ychange; + InvD[ino].NoofVicons--; + Ychange = 0; + } + if (Ycompensate == 'T') + InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingWider(void) { + int StartNhi = InvD[ino].NoofHicons; + int StartUh = SuppH; + + if (SuppH) { + Xchange += SuppH; + SuppH = 0; + } + while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) { + Xchange -= (ITEM_WIDTH+1); + InvD[ino].NoofHicons++; + } + if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) { + SuppH = Xchange; + Xchange = 0; + } + if (Xcompensate == 'L') + InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingNarrower(void) { + int StartNhi = InvD[ino].NoofHicons; + int StartUh = SuppH; + + if (SuppH) { + Xchange += (SuppH - (ITEM_WIDTH+1)); + InvD[ino].NoofHicons++; + SuppH = 0; + } + while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) { + Xchange += (ITEM_WIDTH+1); + InvD[ino].NoofHicons--; + } + if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) { + SuppH = (ITEM_WIDTH+1) + Xchange; + InvD[ino].NoofHicons--; + Xchange = 0; + } + if (Xcompensate == 'L') + InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + + +/** + * Called from Xmovement()/Ymovement() during re-sizing. + */ + +void ChangeingSize(void) { + /* Make it taller or shorter if necessary. */ + if (Ychange > 0) + GettingTaller(); + else if (Ychange < 0) + GettingShorter(); + + /* Make it wider or narrower if necessary. */ + if (Xchange > 0) + GettingWider(); + else if (Xchange < 0) + GettingNarrower(); + + ConstructInventory(EMPTY); +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Xmovement(int x) { + int aniX, aniY; + int i; + + if (x && objArray[0] != NULL) { + switch (InvDragging) { + case ID_MOVE: + GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY); + InvD[ino].inventoryX +=x; + MultiSetAniX(objArray[0], InvD[ino].inventoryX); + for (i = 1; objArray[i] && i < MAX_WCOMP; i++) + MultiMoveRelXY(objArray[i], x, 0); + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) + MultiMoveRelXY(iconArray[i], x, 0); + break; + + case ID_LEFT: + case ID_TLEFT: + case ID_BLEFT: + Xchange -= x; + ChangeingSize(); + break; + + case ID_RIGHT: + case ID_TRIGHT: + case ID_BRIGHT: + Xchange += x; + ChangeingSize(); + break; + + case ID_NONE: + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_AREA, aniX, aniY); + break; + + case ID_MDCONT: + SlideMSlider(x, S_SLIDE); + break; + + default: + break; + } + } +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Ymovement(int y) { + int aniX, aniY; + int i; + + if (y && objArray[0] != NULL) { + switch (InvDragging) { + case ID_MOVE: + GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY); + InvD[ino].inventoryY +=y; + MultiSetAniY(objArray[0], InvD[ino].inventoryY); + for (i = 1; objArray[i] && i < MAX_WCOMP; i++) + MultiMoveRelXY(objArray[i], 0, y); + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) + MultiMoveRelXY(iconArray[i], 0, y); + break; + + case ID_SLIDE: + SlideSlider(y, S_SLIDE); + break; + + case ID_CSLIDE: + SlideCSlider(y, S_SLIDE); + break; + + case ID_BOTTOM: + case ID_BLEFT: + case ID_BRIGHT: + Ychange += y; + ChangeingSize(); + break; + + case ID_TOP: + case ID_TLEFT: + case ID_TRIGHT: + Ychange -= y; + ChangeingSize(); + break; + + case ID_NONE: + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_AREA, aniX, aniY); + break; + + default: + break; + } + } +} + +/** + * Called when a drag is commencing. + */ + +void InvDragStart(void) { + int curX, curY; // cursor's animation position + + GetCursorXY(&curX, &curY, false); + +/* +* Do something different for Save/Restore screens +*/ + if (ino == INV_CONF) { + int whichbox; + + whichbox = WhichInvBox(curX, curY, true); + + if (whichbox == IB_SLIDE) { + InvDragging = ID_CSLIDE; + SlideCSlider(0, S_START); + } else if (whichbox > 0 && (whichbox & IS_MASK)) { + InvDragging = ID_MDCONT; // Mixing desk control + cd.selBox = whichbox; + SlideMSlider(0, S_START); + } + return; + } + +/* +* Normal operation +*/ + switch (InvArea(curX, curY)) { + case I_MOVE: + if (InvD[ino].moveable) { + InvDragging = ID_MOVE; + } + break; + + case I_SLIDE: + InvDragging = ID_SLIDE; + SlideSlider(0, S_START); + break; + + case I_BOTTOM: + if (InvD[ino].resizable) { + Ychange = 0; + InvDragging = ID_BOTTOM; + Ycompensate = 'B'; + } + break; + + case I_TOP: + if (InvD[ino].resizable) { + Ychange = 0; + InvDragging = ID_TOP; + Ycompensate = 'T'; + } + break; + + case I_LEFT: + if (InvD[ino].resizable) { + Xchange = 0; + InvDragging = ID_LEFT; + Xcompensate = 'L'; + } + break; + + case I_RIGHT: + if (InvD[ino].resizable) { + Xchange = 0; + InvDragging = ID_RIGHT; + Xcompensate = 'R'; + } + break; + + case I_TLEFT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'T'; + Xchange = 0; + Xcompensate = 'L'; + InvDragging = ID_TLEFT; + } + break; + + case I_TRIGHT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'T'; + Xchange = 0; + Xcompensate = 'R'; + InvDragging = ID_TRIGHT; + } + break; + + case I_BLEFT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'B'; + Xchange = 0; + Xcompensate = 'L'; + InvDragging = ID_BLEFT; + } + break; + + case I_BRIGHT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'B'; + Xchange = 0; + Xcompensate = 'R'; + InvDragging = ID_BRIGHT; + } + break; + } +} + +/** + * Called when a drag is over. + */ + +void InvDragEnd(void) { + int curX, curY; // cursor's animation position + + GetCursorXY(&curX, &curY, false); + + if (InvDragging != ID_NONE) { + if (InvDragging == ID_SLIDE) { + SlideSlider(0, S_END); + } else if (InvDragging == ID_CSLIDE) { + ; // No action + } else if (InvDragging == ID_MDCONT) { + SlideMSlider(0, S_END); + } else if (InvDragging == ID_MOVE) { + ; // No action + } else { + // Were re-sizing. Redraw the whole thing. + DumpDobjArray(); + DumpObjArray(); + ConstructInventory(FULL); + + // If this was the maximised, it no longer is! + if (InventoryMaximised) { + InventoryMaximised = false; + InvD[ino].otherX = InvD[ino].inventoryX; + InvD[ino].otherY = InvD[ino].inventoryY; + } + } + InvDragging = ID_NONE; + } + + // Cursor could well now be inappropriate + InvCursor(IC_AREA, curX, curY); + + Xchange = Ychange = 0; // Probably no need, but does no harm! +} + + +/**************************************************************************/ +/************** Incoming events - further processing **********************/ +/**************************************************************************/ + +/** + * ConfAction + */ +void ConfAction(int i, bool dbl) { + + if (i >= 0) { + switch (cd.Box[i].boxType) { + case FLIP: + if (dbl) { + *(cd.Box[i].ival) ^= 1; // XOR with true + AddBoxes(false); + } + break; + + case TOGGLE: + if (!g_buttonEffect.bButAnim) { + g_buttonEffect.bButAnim = true; + g_buttonEffect.box = &cd.Box[i]; + g_buttonEffect.press = false; + } + break; + + case RGROUP: + if (dbl) { + // Already highlighted + switch (cd.Box[i].boxFunc) { + case SAVEGAME: + KillInventory(); + InvSaveGame(); + break; + case LOADGAME: + KillInventory(); + InvLoadGame(); + break; + default: + break; + } + } else { + Select(i, false); + } + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + if (dbl) { + Select(i, false); + LanguageChange(); + } else { + Select(i, false); + } + break; +#endif + + case AAGBUT: + case ARSGBUT: + case ARSBUT: + case AABUT: + case AATBUT: + if (g_buttonEffect.bButAnim) + break; + + g_buttonEffect.bButAnim = true; + g_buttonEffect.box = &cd.Box[i]; + g_buttonEffect.press = true; + break; + default: + break; + } + } else { + ConfActionSpecial(i); + } +} + +static void ConfActionSpecial(int i) { + switch (i) { + case IB_NONE: + break; + case IB_UP: // Scroll up + if (cd.fileBase > 0) { + firstFile(cd.fileBase-1); + AddBoxes(true); + if (cd.selBox < NUM_SL_RGROUP-1) + cd.selBox += 1; + Select(cd.selBox, true); + } + break; + case IB_DOWN: // Scroll down + if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { + firstFile(cd.fileBase+1); + AddBoxes(true); + if (cd.selBox) + cd.selBox -= 1; + Select(cd.selBox, true); + } + break; + case IB_SLIDE_UP: + if (cd.fileBase > 0) { + firstFile(cd.fileBase-(NUM_SL_RGROUP-1)); + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } + break; + case IB_SLIDE_DOWN: // Scroll down + if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { + firstFile(cd.fileBase+(NUM_SL_RGROUP-1)); + AddBoxes(true); + cd.selBox = NUM_SL_RGROUP-1; + Select(cd.selBox, true); + } + break; + } +} +// SLIDE_UP and SLIDE_DOWN on d click?????? + +void InvPutDown(int index) { + int aniX, aniY; + // index is the drop position + int hiIndex; // Current position of held item (if in) + + // Find where the held item is positioned in this inventory (if it is) + for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++) + if (InvD[ino].ItemOrder[hiIndex] == HeldItem) + break; + + // If drop position would leave a gap, move it up + if (index >= InvD[ino].NoofItems) { + if (hiIndex == InvD[ino].NoofItems) // Not in, add it + index = InvD[ino].NoofItems; + else + index = InvD[ino].NoofItems - 1; + } + + if (hiIndex == InvD[ino].NoofItems) { // Not in, add it + if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) { + InvD[ino].NoofItems++; + + // Don't leave it in the other inventory! + if (InventoryPos(HeldItem) != INV_HELDNOTIN) + RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem); + } else { + // No room at the inn! + return; + } + } + + // Position it in the inventory + if (index < hiIndex) { + memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int)); + InvD[ino].ItemOrder[index] = HeldItem; + } else if (index > hiIndex) { + memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int)); + InvD[ino].ItemOrder[index] = HeldItem; + } else { + InvD[ino].ItemOrder[index] = HeldItem; + } + + HeldItem = INV_NOICON; + ItemsChanged = true; + DelAuxCursor(); + RestoreMainCursor(); + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_DROP, aniX, aniY); +} + +void InvPdProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GetToken(TOKEN_LEFT_BUT); + CORO_SLEEP(dclickSpeed+1); + FreeToken(TOKEN_LEFT_BUT); + + // get the stuff copied to process when it was created + int *pindex = (int *)param; + + InvPutDown(*pindex); + + CORO_END_CODE; +} + +void InvPickup(int index) { + INV_OBJECT *invObj; + + if (index != INV_NOICON) { + if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + // Pick-up + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index); + } else if (HeldItem != INV_NOICON) { // Put icon down + // Put-down + invObj = findInvObject(HeldItem); + + if (invObj->attribute & IO_DROPCODE && invObj->hScript) + RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index); + + else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1) + && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2)) + g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index)); + } + } +} + +/** + * Pick up/put down icon + */ +void InvSLClick(void) { + int i; + int aniX, aniY; // Cursor's animation position + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + KillInventory(); + break; + + case I_SLIDE_UP: + if (InvD[ino].NoofVicons == 1) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + + case I_UP: + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + + case I_SLIDE_DOWN: + if (InvD[ino].NoofVicons == 1) + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) { + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + } + ItemsChanged = true; + break; + + case I_DOWN: + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + ItemsChanged = true; + } + break; + + case I_BODY: + if (ino == INV_CONF) { + if (!InventoryHidden) + ConfAction(WhichInvBox(aniX, aniY, false), false); + } else { + i = InvItem(&aniX, &aniY, false); + + // Special bodge for David, to + // cater for drop in dead space between icons + if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) { + aniX += 1; // 1 to the right + i = InvItem(&aniX, &aniY, false); + if (i == INV_NOICON) { + aniX -= 1; // 1 down + aniY += 1; + i = InvItem(&aniX, &aniY, false); + if (i == INV_NOICON) { + aniX += 1; // 1 down-right + i = InvItem(&aniX, &aniY, false); + } + } + } + + if (ino == INV_CONV) { + ConvAction(i); + } else + InvPickup(i); + } + break; + } +} + +void InvAction(void) { + int index; + INV_OBJECT *invObj; + int aniX, aniY; + int i; + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_BODY: + if (ino == INV_CONF) { + if (!InventoryHidden) + ConfAction(WhichInvBox(aniX, aniY, false), true); + } else if (ino == INV_CONV) { + index = InvItem(&aniX, &aniY, false); + ConvAction(index); + } else { + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, ACTION, INV_ACTION, index); + } + } + } + break; + + case I_MOVE: // Maximise/unmaximise inventory + if (!InvD[ino].resizable) + break; + + if (!InventoryMaximised) { + InvD[ino].sNoofHicons = InvD[ino].NoofHicons; + InvD[ino].sNoofVicons = InvD[ino].NoofVicons; + InvD[ino].NoofHicons = InvD[ino].MaxHicons; + InvD[ino].NoofVicons = InvD[ino].MaxVicons; + InventoryMaximised = true; + + i = InvD[ino].inventoryX; + InvD[ino].inventoryX = InvD[ino].otherX; + InvD[ino].otherX = i; + i = InvD[ino].inventoryY; + InvD[ino].inventoryY = InvD[ino].otherY; + InvD[ino].otherY = i; + } else { + InvD[ino].NoofHicons = InvD[ino].sNoofHicons; + InvD[ino].NoofVicons = InvD[ino].sNoofVicons; + InventoryMaximised = false; + + i = InvD[ino].inventoryX; + InvD[ino].inventoryX = InvD[ino].otherX; + InvD[ino].otherX = i; + i = InvD[ino].inventoryY; + InvD[ino].inventoryY = InvD[ino].otherY; + InvD[ino].otherY = i; + } + + // Delete current, and re-draw + DumpDobjArray(); + DumpObjArray(); + ConstructInventory(FULL); + break; + + case I_UP: + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + case I_DOWN: + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + ItemsChanged = true; + } + break; + } + +} + + +void InvLook(void) { + int index; + INV_OBJECT *invObj; + int aniX, aniY; + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_BODY: + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, LOOK, INV_LOOK, index); + } + } + break; + + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + KillInventory(); + break; + } +} + + +/**************************************************************************/ +/********************* Incoming events ************************************/ +/**************************************************************************/ + + +void ButtonToInventory(BUTEVENT be) { + if (InventoryHidden) + return; + + switch (be) { + case INV_PICKUP: // BE_SLEFT + InvSLClick(); + break; + + case INV_LOOK: // BE_SRIGHT + if (IsConfWindow()) + InvSLClick(); + else + InvLook(); + break; + + case INV_ACTION: // BE_DLEFT + if (InvDragging != ID_MDCONT) + InvDragEnd(); + InvAction(); + break; + + case BE_LDSTART: // Left drag start + InvDragStart(); + break; + + case BE_LDEND: // Left drag end + InvDragEnd(); + break; + +// case BE_DLEFT: // Double click left (also ends left drag) +// ButtonToInventory(LDEND); +// break; + + case BE_RDSTART: + case BE_RDEND: + case BE_UNKNOWN: + break; + default: + break; + } +} + +void KeyToInventory(KEYEVENT ke) { + int i; + + switch (ke) { + case ESC_KEY: + if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox) + bOpenConf = true; + CloseInventory(); + break; + + case PGDN_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + ConfActionSpecial(IB_SLIDE_DOWN); + } else { + // This code is a copy of SLClick on IB_SLIDE_DOWN + // TODO: So share this duplicate code + if (InvD[ino].NoofVicons == 1) + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) { + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + } + ItemsChanged = true; + } + break; + + case PGUP_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + ConfActionSpecial(IB_SLIDE_UP); + } else { + // This code is a copy of SLClick on I_SLIDE_UP + // TODO: So share this duplicate code + if (InvD[ino].NoofVicons == 1) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + case HOME_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + firstFile(0); + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } else { + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + case END_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + firstFile(MAX_SFILES); // Will get reduced to appropriate value + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } else { + InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + default: + error("We're at KeyToInventory(), with default"); + } +} + +/**************************************************************************/ +/************************* Odds and Ends **********************************/ +/**************************************************************************/ + +/** + * Called from Glitter function invdepict() + * Changes (permanently) the animation film for that object. + */ + +void invObjectFilm(int object, SCNHANDLE hFilm) { + INV_OBJECT *invObj; + + invObj = findInvObject(object); + invObj->hFilm = hFilm; + + if (HeldItem != object) + ItemsChanged = true; +} + +/** + * (Un)serialize the inventory data for save/restore game. + */ + +void syncInvInfo(Serializer &s) { + for (int i = 0; i < NUM_INV; i++) { + s.syncAsSint32LE(InvD[i].MinHicons); + s.syncAsSint32LE(InvD[i].MinVicons); + s.syncAsSint32LE(InvD[i].MaxHicons); + s.syncAsSint32LE(InvD[i].MaxVicons); + s.syncAsSint32LE(InvD[i].NoofHicons); + s.syncAsSint32LE(InvD[i].NoofVicons); + for (int j = 0; j < MAX_ININV; j++) { + s.syncAsSint32LE(InvD[i].ItemOrder[j]); + } + s.syncAsSint32LE(InvD[i].NoofItems); + s.syncAsSint32LE(InvD[i].FirstDisp); + s.syncAsSint32LE(InvD[i].inventoryX); + s.syncAsSint32LE(InvD[i].inventoryY); + s.syncAsSint32LE(InvD[i].otherX); + s.syncAsSint32LE(InvD[i].otherY); + s.syncAsSint32LE(InvD[i].MaxInvObj); + s.syncAsSint32LE(InvD[i].hInvTitle); + s.syncAsSint32LE(InvD[i].resizable); + s.syncAsSint32LE(InvD[i].moveable); + s.syncAsSint32LE(InvD[i].sNoofHicons); + s.syncAsSint32LE(InvD[i].sNoofVicons); + s.syncAsSint32LE(InvD[i].bMax); + } +} + +/**************************************************************************/ +/************************ Initialisation stuff ****************************/ +/**************************************************************************/ + +/** + * Called from PlayGame(), stores handle to inventory objects' data - + * its id, animation film and Glitter script. + */ +// Note: the SCHANDLE type here has been changed to a void* +void RegisterIcons(void *cptr, int num) { + numObjects = num; + pio = (INV_OBJECT *) cptr; +} + +/** + * Called from Glitter function 'dec_invw()' - Declare the bits that the + * inventory windows are constructed from, and special cursors. + */ + +void setInvWinParts(SCNHANDLE hf) { +#ifdef DEBUG + const FILM *pfilm; +#endif + + winPartsf = hf; + +#ifdef DEBUG + pfilm = (const FILM *)LockMem(hf); + assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected +#endif +} + +/** + * Called from Glitter function 'dec_flags()' - Declare the language + * flag films + */ + +void setFlagFilms(SCNHANDLE hf) { +#ifdef DEBUG + const FILM *pfilm; +#endif + + flagFilm = hf; + +#ifdef DEBUG + pfilm = (const FILM *)LockMem(hf); + assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected +#endif +} + +void setConfigStrings(SCNHANDLE *tp) { + memcpy(configStrings, tp, sizeof(configStrings)); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv(int num, SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight, + int startx, int starty, bool moveable) { + if (MaxWidth > MAXHICONS) + MaxWidth = MAXHICONS; // Max window width + if (MaxHeight > MAXVICONS) + MaxHeight = MAXVICONS; // Max window height + if (MaxContents > MAX_ININV) + MaxContents = MAX_ININV; // Max contents + + if (StartWidth > MaxWidth) + StartWidth = MaxWidth; + if (StartHeight > MaxHeight) + StartHeight = MaxHeight; + + InventoryState = IDLE_INV; + + InvD[num].MaxHicons = MaxWidth; + InvD[num].MinHicons = MinWidth; + InvD[num].MaxVicons = MaxHeight; + InvD[num].MinVicons = MinHeight; + + InvD[num].NoofHicons = StartWidth; + InvD[num].NoofVicons = StartHeight; + + memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder)); + InvD[num].NoofItems = 0; + + InvD[num].FirstDisp = 0; + + InvD[num].inventoryX = startx; + InvD[num].inventoryY = starty; + InvD[num].otherX = 21; + InvD[num].otherY = 15; + + InvD[num].MaxInvObj = MaxContents; + + InvD[num].hInvTitle = text; + + if (MaxWidth != MinWidth && MaxHeight != MinHeight) + InvD[num].resizable = true; + + InvD[num].moveable = moveable; + + InvD[num].bMax = false; +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_convw(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 20, 8, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv1(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 100, 100, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv2(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 100, 100, true); +} + +int InvGetLimit(int invno) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + + return InvD[invno].MaxInvObj; +} + +void InvSetLimit(int invno, int MaxContents) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents + + if (MaxContents > MAX_ININV) + MaxContents = MAX_ININV; // Max contents + + InvD[invno].MaxInvObj = MaxContents; +} + +void InvSetSize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + + if (StartWidth > MaxWidth) + StartWidth = MaxWidth; + if (StartHeight > MaxHeight) + StartHeight = MaxHeight; + + InvD[invno].MaxHicons = MaxWidth; + InvD[invno].MinHicons = MinWidth; + InvD[invno].MaxVicons = MaxHeight; + InvD[invno].MinVicons = MinHeight; + + InvD[invno].NoofHicons = StartWidth; + InvD[invno].NoofVicons = StartHeight; + + if (MaxWidth != MinWidth && MaxHeight != MinHeight) + InvD[invno].resizable = true; + else + InvD[invno].resizable = false; + + InvD[invno].bMax = false; +} + +/**************************************************************************/ + +bool IsTopWindow(void) { + return (InventoryState == BOGUS_INV); +} + + +bool IsConfWindow(void) { + return (InventoryState == ACTIVE_INV && ino == INV_CONF); +} + + +bool IsConvWindow(void) { + return (InventoryState == ACTIVE_INV && ino == INV_CONV); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h new file mode 100644 index 0000000000..d83439c68f --- /dev/null +++ b/engines/tinsel/inventory.h @@ -0,0 +1,142 @@ + +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Inventory related functions + */ + +#ifndef TINSEL_INVENTORY_H // prevent multiple includes +#define TINSEL_INVENTORY_H + +#include "tinsel/dw.h" +#include "tinsel/events.h" // for KEYEVENT, BUTEVENT + +namespace Tinsel { + +class Serializer; + +enum { + INV_OPEN = -1, + INV_CONV = 0, + INV_1 = 1, + INV_2 = 2, + INV_CONF = 3, + + NUM_INV = 4 +}; + +/** structure of each inventory object */ +struct INV_OBJECT { + int32 id; // inventory objects id + SCNHANDLE hFilm; // inventory objects animation film + SCNHANDLE hScript; // inventory objects event handling script + int32 attribute; // inventory object's attribute +}; + +void PopUpInventory(int invno); + +enum CONFTYPE { + SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN +}; + +void PopUpConf(CONFTYPE type); + + +void Xmovement(int x); +void Ymovement(int y); + +void ButtonToInventory(BUTEVENT be); + +void KeyToInventory(KEYEVENT ke); + + +int WhichItemHeld(void); + +void HoldItem(int item); +void DropItem(int item); +void AddToInventory(int invno, int icon, bool hold); +bool RemFromInventory(int invno, int icon); + + +void RegisterIcons(void *cptr, int num); + +void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +bool InventoryActive(void); + +void AddIconToPermanentDefaultList(int icon); + +void convPos(int bpos); +void ConvPoly(HPOLYGON hp); +int convIcon(void); +void CloseDownConv(void); +void convHide(bool hide); +bool convHid(void); + +enum { + INV_NOICON = -1, + INV_CLOSEICON = -2, + INV_OPENICON = -3, + INV_HELDNOTIN = -4 +}; + +void ConvAction(int index); + +void InventoryIconCursor(void); + +void setInvWinParts(SCNHANDLE hf); +void setFlagFilms(SCNHANDLE hf); +void setConfigStrings(SCNHANDLE *tp); + +int InvItem(int *x, int *y, bool update); +int InvItemId(int x, int y); + +int InventoryPos(int num); + +bool IsInInventory(int object, int invnum); + +void KillInventory(void); + +void invObjectFilm(int object, SCNHANDLE hFilm); + +void syncInvInfo(Serializer &s); + +int InvGetLimit(int invno); +void InvSetLimit(int invno, int n); +void InvSetSize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +int WhichInventoryOpen(void); + +bool IsTopWindow(void); +bool IsConfWindow(void); +bool IsConvWindow(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_INVENTRY_H */ diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp new file mode 100644 index 0000000000..4c64eaf091 --- /dev/null +++ b/engines/tinsel/mareels.cpp @@ -0,0 +1,132 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Functions to set up moving actors' reels. + */ + +#include "tinsel/pcode.h" // For D_UP, D_DOWN +#include "tinsel/rince.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +enum { + NUM_INTERVALS = NUM_MAINSCALES - 1, + + // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth + MAX_SCRENTRIES = NUM_INTERVALS*2*3 +}; + +struct SCIdataStruct { + int actor; + int scale; + int direction; + SCNHANDLE reels[4]; +}; + +static SCIdataStruct SCIdata[MAX_SCRENTRIES]; + +static int scrEntries = 0; + +/** + * Return handle to actor's talk reel at present scale and direction. + */ +SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) { + assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES); + switch (dirn) { + case TF_NONE: + return pActor->TalkReels[pActor->scale-1][pActor->dirn]; + + case TF_UP: + return pActor->TalkReels[pActor->scale-1][AWAY]; + + case TF_DOWN: + return pActor->TalkReels[pActor->scale-1][FORWARD]; + + case TF_LEFT: + return pActor->TalkReels[pActor->scale-1][LEFTREEL]; + + case TF_RIGHT: + return pActor->TalkReels[pActor->scale-1][RIGHTREEL]; + + default: + error("GetMactorTalkReel() - illegal direction!"); + } +} + +/** + * scalingreels + */ +void setscalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { + assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale + assert(!(scale == 1 && direction == D_UP) && + !(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale + + assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached! + + SCIdata[scrEntries].actor = actor; + SCIdata[scrEntries].scale = scale; + SCIdata[scrEntries].direction = direction; + SCIdata[scrEntries].reels[LEFTREEL] = left; + SCIdata[scrEntries].reels[RIGHTREEL] = right; + SCIdata[scrEntries].reels[FORWARD] = forward; + SCIdata[scrEntries].reels[AWAY] = away; + scrEntries++; +} + +/** + * ScalingReel + */ +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) { + int d; // Direction + + // The smaller the number, the bigger the scale + if (scale1 < scale2) + d = D_DOWN; + else + d = D_UP; + + for (int i = 0; i < scrEntries; i++) { + if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) { + if (SCIdata[i].reels[reel] == TF_NONE) + return 0; + else + return SCIdata[i].reels[reel]; + } + } + return 0; +} + +/** + * RebootScalingReels + */ +void RebootScalingReels(void) { + scrEntries = 0; + memset(SCIdata, 0, sizeof(SCIdata)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk new file mode 100644 index 0000000000..b00afcddbc --- /dev/null +++ b/engines/tinsel/module.mk @@ -0,0 +1,52 @@ +MODULE := engines/tinsel + +MODULE_OBJS = \ + actors.o \ + anim.o \ + background.o \ + bg.o \ + cliprect.o \ + config.o \ + cursor.o \ + debugger.o \ + detection.o \ + effect.o \ + events.o \ + faders.o \ + font.o \ + graphics.o \ + handle.o \ + heapmem.o \ + inventory.o \ + mareels.o \ + move.o \ + multiobj.o \ + music.o \ + object.o \ + palette.o \ + pcode.o \ + pdisplay.o \ + play.o \ + polygons.o \ + rince.o \ + saveload.o \ + savescn.o \ + scene.o \ + sched.o \ + scn.o \ + scroll.o \ + sound.o \ + strres.o \ + text.o \ + timers.o \ + tinlib.o \ + tinsel.o \ + token.o + +# This module can be built as a plugin +ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp new file mode 100644 index 0000000000..803bc5fd7b --- /dev/null +++ b/engines/tinsel/move.cpp @@ -0,0 +1,1618 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles walking and use of the path system. + * + * Contains the dodgiest code in the whole system. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/tinlib.h" // For stand() + +namespace Tinsel { + +//----------------- DEVELOPMENT OPTIONS -------------------- + +#define SLOW_RINCE_DOWN 0 + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + +// in POLYGONS.C +// Deliberatley defined here, and not in polygons.h +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta); + +//----------------- LOCAL DEFINES -------------------- + +#define XMDIST 4 +#define XHMDIST 2 +#define YMDIST 2 +#define YHMDIST 2 + +#define XTHERE 1 +#define XRESTRICT 2 +#define YTHERE 4 +#define YRESTRICT 8 +#define STUCK 16 + +#define LEAVING_PATH 0x100 +#define ENTERING_BLOCK 0x200 +#define ENTERING_MBLOCK 0x400 + +#define ALL_SORTED 1 +#define NOT_SORTED 0 + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#if SLOW_RINCE_DOWN +static int Interlude = 0; // For slowing down walking, for testing +static int BogusVar = 0; // For slowing down walking, for testing +#endif + +static int32 DefaultRefer = 0; +static int hSlowVar = 0; // used by MoveActor() + + +//----------------- FORWARD REFERENCES -------------------- + +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, + int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p, + bool bOver, bool bBodge, + PMACTOR pActor, PMACTOR *collisionActor = 0); + + +#if SLOW_RINCE_DOWN +/** + * AddInterlude + */ + +void AddInterlude(int n) { + Interlude += n; + if (Interlude < 0) + Interlude = 0; +} +#endif + +/** + * Given (x, y) of a click within a path polygon, checks that the + * co-ordinates are not within a blocking polygon. If it is not, the + * destination is the click point, otherwise tries to find a legal point + * below or above the click point. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { + int Loffset, Toffset; + int i; + + /*-------------------------------------- + Clicked within a path, + go to where requested unless blocked. + --------------------------------------*/ + if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) { + // Not in a blocking polygon - go to where requested. + *ptgtX = clickX; + *ptgtY = clickY; + } else { + /*------------------------------------------------------ + In a Blocking polygon - try searching down and up. + If still nowhere (for now) give up! + ------------------------------------------------------*/ + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) { + // Don't leave the path system + if (InPolygon(clickX, i, PATH) == NOPOLY) { + i = SCREEN_HEIGHT; + break; + } + if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + } + if (i == SCREEN_HEIGHT) { + for (i = clickY-1; i >= Toffset; i--) { + // Don't leave the path system + if (InPolygon(clickX, i, PATH) == NOPOLY) { + i = -1; + break; + } + if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + } + } + if (i < 0) { + return ALL_SORTED; + } + } + return NOT_SORTED; +} + +/** + * Given (x, y) of a click within a referral polygon, works out the + * destination according to the referral type. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) { + int i; + int end; // Extreme of the scene + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + *ptgtX = *ptgtY = -1; + + switch (PolySubtype(hRefpoly)) { + case REF_POINT: // Go to specified node + getPolyNode(hRefpoly, ptgtX, ptgtY); + assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point + break; + + case REF_DOWN: // Search downwards + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY + && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + break; + + case REF_UP: // Search upwards + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY + && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + break; + + case REF_RIGHT: // Search to the right + end = BackgroundWidth(); + for (i = clickX+1; i < end; i++) + if (InPolygon(i, clickY, PATH) != NOPOLY + && InPolygon(i, clickY, BLOCKING) == NOPOLY) { + *ptgtX = i; + *ptgtY = clickY; + break; + } + break; + + case REF_LEFT: // Search to the left + for (i = clickX-1; i >= 0; i--) + if (InPolygon(i, clickY, PATH) != NOPOLY + && InPolygon(i, clickY, BLOCKING) == NOPOLY) { + *ptgtX = i; + *ptgtY = clickY; + break; + } + break; + } + if (*ptgtX != -1 && *ptgtY != -1) { + return NOT_SORTED; + } else + return ALL_SORTED; +} + +/** + * Given (x, y) of a click, works out the destination according to the + * default referral type. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { + int i; + int end; // Extreme of the scene + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + switch (DefaultRefer) { + case REF_DEFAULT: + // Try searching down and up (onscreen). + for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + for (i = clickY-1; i >= Toffset; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + // Try searching down and up (offscreen). + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_UP: + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_DOWN: + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_LEFT: + for (i = clickX-1; i >= 0; i--) + if (InPolygon(i, clickY, PATH) != NOPOLY) { + return ClickedOnPath(i, clickY, ptgtX, ptgtY); + } + break; + + case REF_RIGHT: + end = BackgroundWidth(); + for (i = clickX + 1; i < end; i++) + if (InPolygon(i, clickY, PATH) != NOPOLY) { + return ClickedOnPath(i, clickY, ptgtX, ptgtY); + } + break; + } + + // Going nowhere! + return ALL_SORTED; +} + +/** + * Given (x, y) of the click, ascertains whether the click is within a + * path, within a referral poly, or niether. The appropriate function + * then gets called to give us a revised destination. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) { + HPOLYGON hPoly; + + /*-------------------------------------- + Clicked within a path? + if not, within a referral poly? + if not, try and sort something out. + ---------------------------------------*/ + if (InPolygon(clickX, clickY, PATH) != NOPOLY) { + return ClickedOnPath(clickX, clickY, ptgtX, ptgtY); + } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) { + return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY); + } else { + return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY); + } +} + +/** + * Work out which reel to adopt for a section of movement. + */ +static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) { + int xchange = 0, ychange = 0; + enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir; + enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir; + + DIRREEL reel = lastreel; // Leave alone if can't decide + + /* + * Determine size and direction of X movement. + * i.e. left, right, none or not allowed. + */ + if (getPolyReelType(hPath) == REEL_VERT) + xdir = X_NO; + else if (tox == -1) + xdir = X_NONE; + else { + xchange = tox - fromx; + if (xchange > 0) + xdir = X_RIGHT; + else if (xchange < 0) { + xchange = -xchange; + xdir = X_LEFT; + } else + xdir = X_NONE; + } + + /* + * Determine size and direction of Y movement. + * i.e. up, down, none or not allowed. + */ + if (getPolyReelType(hPath) == REEL_HORIZ) + ydir = Y_NO; + else if (toy == -1) + ydir = Y_NONE; + else { + ychange = toy - fromy; + if (ychange > 0) + ydir = Y_DOWN; + else if (ychange < 0) { + ychange = -ychange; + ydir = Y_UP; + } else + ydir = Y_NONE; + } + + /* + * Some adjustment to allow for different x and y pixell sizes. + */ + ychange += ychange; // Double y distance to cover + + /* + * Determine which reel to use. + */ + if (xdir == X_NO) { + // Forced to be FORWARD or AWAY + switch (ydir) { + case Y_DOWN: + reel = FORWARD; + break; + case Y_UP: + reel = AWAY; + break; + default: + if (reel != AWAY) // No gratuitous turn + reel = FORWARD; + break; + } + } else if (ydir == Y_NO) { + // Forced to be LEFTREEL or RIGHTREEL + switch (xdir) { + case X_LEFT: + reel = LEFTREEL; + break; + case X_RIGHT: + reel = RIGHTREEL; + break; + default: + if (reel != LEFTREEL) // No gratuitous turn + reel = RIGHTREEL; + break; + } + } else if (xdir != X_NONE || ydir != Y_NONE) { + if (xdir == X_NONE) + reel = (ydir == Y_DOWN) ? FORWARD : AWAY; + else if (ydir == Y_NONE) + reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; + else { + bool DontBother = false; + + if (xchange <= 4 && ychange <= 4) { + switch (reel) { + case LEFTREEL: + if (xdir == X_LEFT) + DontBother = true; + break; + case RIGHTREEL: + if (xdir == X_RIGHT) + DontBother = true; + break; + case FORWARD: + if (ydir == Y_DOWN) + DontBother = true; + break; + case AWAY: + if (ydir == Y_UP) + DontBother = true; + break; + } + } + if (!DontBother) { + if (xchange > ychange) + reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; + else + reel = (ydir == Y_DOWN) ? FORWARD : AWAY; + } + } + } + return reel; +} + +/** + * Haven't moved, look towards the cursor. + */ +static void GotThereWithoutMoving(PMACTOR pActor) { + int curX, curY; + DIRREEL reel; + + if (!pActor->TagReelRunning) { + GetCursorXYNoWait(&curX, &curY, true); + + reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath); + + if (reel != pActor->dirn) + SetMActorWalkReel(pActor, reel, pActor->scale, false); + } +} + +/** + * Arrived at final destination. + */ +static void GotThere(PMACTOR pActor) { + pActor->targetX = pActor->targetY = -1; // 4/1/95 + pActor->ItargetX = pActor->ItargetY = -1; + pActor->UtargetX = pActor->UtargetY = -1; + + // Perhaps we have'nt moved. + if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy) + GotThereWithoutMoving(pActor); + + ReTagActor(pActor->actorID); // Tag allowed while stationary + + SetMActorStanding(pActor); + + pActor->bMoving = false; +} + +enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY }; + +/** + * Can we get straight there? + */ +static cgt CanGetThere(PMACTOR pActor, int tx, int ty) { + int s1, s2; // s2 not used here! + HPOLYGON hS2p; // nor is s2p! + int nextx, nexty; + + int targetX = tx; + int targetY = ty; // Ultimate destination + int x = pActor->objx; + int y = pActor->objy; // Present position + + while (targetX != -1 || targetY != -1) { + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, false, pActor); + + if (s1 == (XTHERE | YTHERE)) { + return GT_OK; // Can get there directly. + } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { + return GT_MAY; // Can't get there directly. + } else if (s1 & STUCK) { + if (s2 == LEAVING_PATH) + return GT_NOTL; // Can't get there. + else + return GT_NOTB; // Can't get there. + } else if (x == nextx && y == nexty) { + return GT_NOT2; // Can't get there. + } + x = nextx; + y = nexty; + } + return GT_MAY; +} + + +/** + * Set final destination. + */ +static void SetMoverUltDest(PMACTOR pActor, int x, int y) { + pActor->UtargetX = x; + pActor->UtargetY = y; + pActor->hUpath = InPolygon(x, y, PATH); + + assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination +} + +/** + * Set intermediate destination. + * + * If in final destination path, go straight to target. + * If in a neighbouring path to the final destination, if the target path + * is a follow nodes path, head for the end node, otherwise head straight + * for the target. + * Otherwise, head towards the pseudo-centre or end node of the first + * en-route path. + */ +static void SetMoverIntDest(PMACTOR pActor, int x, int y) { + HPOLYGON hIpath, hTpath; + int node; + + hTpath = InPolygon(x, y, PATH); // Target path +#ifdef DEBUG + if (!pActor->bIgPath) + assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path +#endif + + if (pActor->hCpath == hTpath || pActor->bIgPath + || IsInPolygon(pActor->objx, pActor->objy, hTpath)) { + // In destination path - head straight for the target. + pActor->ItargetX = x; + pActor->ItargetY = y; + pActor->hIpath = hTpath; + } else if (IsAdjacentPath(pActor->hCpath, hTpath)) { + // In path adjacent to target + if (PolySubtype(hTpath) != NODE) { + // Target path is normal - head for target. + // Added 26/01/95, innroom + if (CanGetThere(pActor, x, y) == GT_NOTL) { + NearestCorner(&x, &y, pActor->hCpath, hTpath); + } + pActor->ItargetX = x; + pActor->ItargetY = y; + } else { + // Target path is node - head for end node. + node = NearestEndNode(hTpath, pActor->objx, pActor->objy); + getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY); + + } + pActor->hIpath = hTpath; + } else { + assert(hTpath != NOPOLY); // Error 701 + hIpath = getPathOnTheWay(pActor->hCpath, hTpath); + + if (hIpath != NOPOLY) { + /* Head for an en-route path */ + if (PolySubtype(hIpath) != NODE) { + /* En-route path is normal - head for pseudo centre. */ + if (CanGetThere(pActor, x, y) == GT_OK) { + pActor->ItargetX = x; + pActor->ItargetY = y; + } else { + pActor->ItargetX = PolyCentreX(hIpath); + pActor->ItargetY = PolyCentreY(hIpath); + } + } else { + /* En-route path is node - head for end node. */ + node = NearestEndNode(hIpath, pActor->objx, pActor->objy); + getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY); + } + pActor->hIpath = hIpath; + } + } + + pActor->InDifficulty = NO_PROB; +} + +/** + * Set short-term destination and adopt the appropriate reel. + */ +static void SetMoverDest(PMACTOR pActor, int x, int y) { + int scale; + DIRREEL reel; + + // Set the co-ordinates requested. + pActor->targetX = x; + pActor->targetY = y; + pActor->InDifficulty = NO_PROB; + + reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath); + scale = GetScale(pActor->hCpath, pActor->objy); + if (scale != pActor->scale || reel != pActor->dirn) { + SetMActorWalkReel(pActor, reel, scale, false); + } +} + +/** + * SetNextDest + */ +static void SetNextDest(PMACTOR pActor) { + int targetX, targetY; // Ultimate destination + int x, y; // Present position + int nextx, nexty; + int s1, lstatus = 0; + int s2; + HPOLYGON hS2p; + int i; + HPOLYGON hNpoly; + HPOLYGON hPath; + int znode; + int nx, ny; + int sx, sy; + HPOLYGON hEb; + + int ss1, ss2; + HPOLYGON shS2p; + PMACTOR collisionActor; +#if 1 + int sTargetX, sTargetY; +#endif + + /* + * Desired destination (Itarget) is already set + */ + x = pActor->objx; // Current position + y = pActor->objy; + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + + /* + * If we're where we're headed, end it all (the moving). + */ +// if (x == targetX && y == targetY) + if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) { + if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) { + // Desired position + GotThere(pActor); + return; + } else { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + } + } + + if (pActor->bNoPath || pActor->bIgPath) { + /* Can get there directly. */ + SetMoverDest(pActor, targetX, targetY); + pActor->over = false; + return; + } + + /*---------------------------------------------------------------------- + | Some work to do here if we're in a follow nodes polygon - basically + | head for the next node. + ----------------------------------------------------------------------*/ + hNpoly = pActor->hFnpath; // The node path we're in (if any) + switch (pActor->npstatus) { + case NOT_IN: + break; + + case ENTERING: + znode = NearestEndNode(hNpoly, x, y); + /* Hang on, we're probably here already! */ + if (znode) { + pActor->npstatus = GOING_DOWN; + pActor->line = znode-1; + getNpathNode(hNpoly, znode - 1, &nx, &ny); + } else { + pActor->npstatus = GOING_UP; + pActor->line = znode; + getNpathNode(hNpoly, 1, &nx, &ny); + } + SetMoverDest(pActor, nx, ny); + + // Test for pseudo-one-node npaths + if (numNodes(hNpoly) == 2 && + ABS(pActor->objx - pActor->targetX) < XMDIST && + ABS(pActor->objy - pActor->targetY) < YMDIST) { + // That's enough, we're leaving + pActor->npstatus = LEAVING; + } else { + // Normal situation + pActor->over = true; + return; + } + // Fall through for LEAVING + + case LEAVING: + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + + case GOING_UP: + i = pActor->line; // The line we're on + + // Is this the final target line? + if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) { + // The final leg of the journey + pActor->line = i+1; + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->over = false; + return; + } else { + // Go to the next node unless we're at the last one + i++; // The node we're at + if (++i < numNodes(hNpoly)) { + getNpathNode(hNpoly, i, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->line = i-1; + if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && + ABS(pActor->UtargetY - pActor->targetY) < YMDIST) + pActor->over = false; + else + pActor->over = true; + return; + } else { + // Last node - we're off + pActor->npstatus = LEAVING; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + } + } + + case GOING_DOWN: + i = pActor->line; // The line we're on and the node we're at + + // Is this the final target line? + if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) { + // The final leg of the journey + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->line = i-1; + pActor->over = false; + return; + } else { + // Go to the next node unless we're at the last one + if (--i >= 0) { + getNpathNode(hNpoly, i, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->line--; /* The next node to head for */ + if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && + ABS(pActor->UtargetY - pActor->targetY) < YMDIST) + pActor->over = false; + else + pActor->over = true; + return; + } else { + // Last node - we're off + pActor->npstatus = LEAVING; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + } + } + } + + + + + /*------------------------------------------------------ + | See if it can get there directly. There may be an + | intermediate destination to head for. + ------------------------------------------------------*/ + + while (targetX != -1 || targetY != -1) { +#if 1 + // 'push' the target + sTargetX = targetX; + sTargetY = targetY; +#endif + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor); + + if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) { + ss1 = s1; + ss2 = s2; + shS2p = hS2p; +#if 1 + // 'pop' the target + targetX = sTargetX; + targetY = sTargetY; +#endif + // Note: this aint right - targetX/Y (may) have been + // nobbled by that last call to NewCoOrdinates() + // Re-instating them (can) leads to oscillation + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor); + + if (x == nextx && y == nexty) { + s1 = ss1; + s2 = ss2; + hS2p = shS2p; + } + } + + if (s1 == (XTHERE | YTHERE)) { + /* Can get there directly. */ + SetMoverDest(pActor, nextx, nexty); + pActor->over = false; + break; + } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT) + || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { + /*------------------------------------------------- + Can't go any further in this direction. | + If it's because of a blocking polygon, try to do | + something about it. | + -------------------------------------------------*/ + if (s2 & ENTERING_BLOCK) { + x = pActor->objx; // Current position + y = pActor->objy; + // Go to the nearest corner of the blocking polygon concerned + BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY); + SetMoverDest(pActor, x, y); + pActor->over = false; + } else if (s2 & ENTERING_MBLOCK) { + if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { + // The best we're going to achieve + SetMoverUltDest(pActor, x, y); + SetMoverDest(pActor, x, y); + } else { + sx = pActor->objx; + sy = pActor->objy; +// pActor->objx = x; +// pActor->objy = y; + + hEb = InitExtraBlock(pActor, collisionActor); + x = pActor->objx; + y = pActor->objy; + BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY); + + pActor->objx = sx; + pActor->objy = sy; + SetMoverDest(pActor, x, y); + pActor->over = false; + } + } else { + /*---------------------------------------- + Currently, this is as far as we can go. | + Definitely room for improvement here! | + ----------------------------------------*/ + hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH); + if (hPath != pActor->hIpath) { + if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath)) + hPath = pActor->hIpath; + } + assert(hPath == pActor->hIpath); + + if (pActor->InDifficulty == NO_PROB) { + x = PolyCentreX(hPath); + y = PolyCentreY(hPath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_CENTRE; + pActor->over = false; + } else if (pActor->InDifficulty == TRY_CENTRE) { + NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_CORNER; + pActor->over = false; + } else if (pActor->InDifficulty == TRY_CORNER) { + NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_NEXTCORNER; + pActor->over = false; + } + } + break; + } + else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT)) + || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) { + /*----------------------------------------------- + A restriction in a direction has been removed. | + Use this as an intermediate destination. | + -----------------------------------------------*/ + SetMoverDest(pActor, nextx, nexty); + pActor->over = false; + break; + } + + x = nextx; + y = nexty; + + /*------------------------- + Change of path polygon? | + -------------------------*/ + hPath = InPolygon(x, y, PATH); + if (pActor->hCpath != hPath && + !IsInPolygon(x, y, pActor->hCpath) && + !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) { + /*---------------------------------------------------------- + If just entering a follow nodes polygon, go to first node.| + Else if just going to pass through, go to pseudo-centre. | + ----------------------------------------------------------*/ + if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) { + int node = NearestEndNode(hPath, x, y); + getNpathNode(hPath, node, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->over = true; + } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) && + !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) { + SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath)); + pActor->over = true; + } else { + SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY); + } + break; + } + + lstatus = s1; + } +} + +/** + * Work out where the next position should be. + * Check that it's in a path and not in a blocking polygon. + */ +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, + int *newx, int *newy, int *s1, int *s2, + HPOLYGON *hS2p, bool bOver, bool bBodge, + PMACTOR pActor, PMACTOR *collisionActor) { + HPOLYGON hPoly; + int sidem, depthm; + int sidesteps, depthsteps; + PMACTOR ma; + + *s1 = *s2 = 0; + + /*------------------------------------------------ + Don't overrun if this is the final destination. | + ------------------------------------------------*/ + if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) || + *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX)) + bOver = false; + + /*---------------------------------------------------- + Decide how big a step to attempt in each direction. | + ----------------------------------------------------*/ + sidesteps = *targetX == -1 ? 0 : *targetX - fromx; + sidesteps = ABS(sidesteps); + + depthsteps = *targetY == -1 ? 0 : *targetY - fromy; + depthsteps = ABS(depthsteps); + + if (sidesteps && depthsteps > sidesteps) { + depthm = YMDIST; + sidem = depthm * sidesteps/depthsteps; + + if (!sidem) + sidem = 1; + } else if (depthsteps && sidesteps > depthsteps) { + sidem = XMDIST; + depthm = sidem * depthsteps/sidesteps; + + if (!depthm) { + if (bBodge) + depthm = 1; + } else if (depthm > YMDIST) + depthm = YMDIST; + } else { + sidem = sidesteps ? XMDIST : 0; + depthm = depthsteps ? YMDIST : 0; + } + + *newx = fromx; + *newy = fromy; + + /*------------------------------------------------------------ + If Left-Right movement is required - then make the move, | + but don't overshoot, and do notice when we're already there | + ------------------------------------------------------------*/ + if (*targetX == -1) + *s1 |= XTHERE; + else { + if (*targetX > fromx) { /* To the right? */ + *newx += sidem; // Move to the right... + if (*newx == *targetX) + *s1 |= XTHERE; + else if (*newx > *targetX) { // ...but don't overshoot + if (!bOver) + *newx = *targetX; + else + *targetX = *newx; + *s1 |= XTHERE; + } + } else if (*targetX < fromx) { /* To the left? */ + *newx -= sidem; // Move to the left... + if (*newx == *targetX) + *s1 |= XTHERE; + else if (*newx < *targetX) { // ...but don't overshoot + if (!bOver) + *newx = *targetX; + else + *targetX = *newx; + *s1 |= XTHERE; + } + } else { + *targetX = -1; // We're already there! + *s1 |= XTHERE; + } + } + + /*-------------------------------------------------------------- + If Up-Down movement is required - then make the move, + but don't overshoot, and do notice when we're already there + --------------------------------------------------------------*/ + if (*targetY == -1) + *s1 |= YTHERE; + else { + if (*targetY > fromy) { /* Downwards? */ + *newy += depthm; // Move down... + if (*newy == *targetY) // ...but don't overshoot + *s1 |= YTHERE; + else if (*newy > *targetY) { // ...but don't overshoot + if (!bOver) + *newy = *targetY; + else + *targetY = *newy; + *s1 |= YTHERE; + } + } else if (*targetY < fromy) { /* Upwards? */ + *newy -= depthm; // Move up... + if (*newy == *targetY) // ...but don't overshoot + *s1 |= YTHERE; + else if (*newy < *targetY) { // ...but don't overshoot + if (!bOver) + *newy = *targetY; + else + *targetY = *newy; + *s1 |= YTHERE; + } + } else { + *targetY = -1; // We're already there! + *s1 |= YTHERE; + } + } + + /* Give over if this is it */ + if (*s1 == (XTHERE | YTHERE)) + return; + + /*------------------------------------------------------ + Have worked out where an optimum step would take us. + Must now check if it's in a legal spot. + ------------------------------------------------------*/ + + if (!pActor->bNoPath && !pActor->bIgPath) { + /*------------------------------ + Must stay in a path polygon. + -------------------------------*/ + hPoly = InPolygon(*newx, *newy, PATH); + if (hPoly == NOPOLY) { + *s2 = LEAVING_PATH; // Trying to leave the path polygons + + if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + return; + } + } + + /*-------------------------------------- + Must stay out of blocking polygons. + --------------------------------------*/ + hPoly = InPolygon(*newx, *newy, BLOCKING); + if (hPoly != NOPOLY) { + *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly + *hS2p = hPoly; + + if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + } + } + /*------------------------------------------------------ + Must stay out of moving actors' blocking polygons. + ------------------------------------------------------*/ + ma = InMActorBlock(pActor, *newx, *newy); + if (ma != NULL) { + // Ignore if already in it (it may have just appeared) + if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) { + *s2 = ENTERING_MBLOCK; // Trying to walk through an actor + + *hS2p = -1; + if (collisionActor) + *collisionActor = ma; + + if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL + && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL + && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + } + } + } + } +} + +/** + * SetOffWithinNodePath + */ +static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath, + int targetX, int targetY) { + int endnode; + HPOLYGON hIpath; + int nx, ny; + int x, y; + + if (StartPath == DestPath) { + if (pActor->line == pActor->Tline) { + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->over = false; + } else if (pActor->line < pActor->Tline) { + getNpathNode(StartPath, pActor->line+1, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_UP; + } else if (pActor->line > pActor->Tline) { + getNpathNode(StartPath, pActor->line, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_DOWN; + } + } else { + /* + * Leaving this path - work out + * which end of this path to head for. + */ + assert(DestPath != NOPOLY); // Error 702 + if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) { + // This should never happen! + // It's the old code that didn't always work. + endnode = NearestEndNode(StartPath, targetX, targetY); + } else { + if (PolySubtype(hIpath) != NODE) { + x = PolyCentreX(hIpath); + y = PolyCentreY(hIpath); + endnode = NearestEndNode(StartPath, x, y); + } else { + endnode = NearEndNode(StartPath, hIpath); + } + } + +#if 1 + if ((pActor->npstatus == LEAVING) && + endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) { + // Leave it be + } else +#endif + { + if (endnode) { + getNpathNode(StartPath, pActor->line+1, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_UP; + } else { + getNpathNode(StartPath, pActor->line, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_DOWN; + } + } + } +} + +/** + * Restore a movement, called from restoreMovement() in ACTORS.CPP + */ +void SSetActorDest(PMACTOR pActor) { + if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { + stand(pActor->actorID, pActor->objx, pActor->objy, 0); + + if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { + SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY, + pActor->bIgPath, 0); + } + } else { + stand(pActor->actorID, pActor->objx, pActor->objy, 0); + } +} + +/** + * Initiate a movement, called from WalkTo_Event() + */ +void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) { + HPOLYGON StartPath, DestPath = 0; + int targetX, targetY; + + if (pActor->actorID == LeadId()) // Now only for lead actor + UnTagActor(pActor->actorID); // Tag not allowed while moving + pActor->ticket++; + pActor->stop = false; + pActor->over = false; + pActor->fromx = pActor->objx; + pActor->fromy = pActor->objy; + pActor->bMoving = true; + pActor->bIgPath = igPath; + + // Use the supplied reel or restore the normal actor. + if (film != 0) + AlterMActor(pActor, film, AR_WALKREEL); + else + AlterMActor(pActor, 0, AR_NORMAL); + + if (igPath) { + targetX = clickX; + targetY = clickY; + } else { + if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) { + GotThere(pActor); + return; + } + assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination! + assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination! + } + + + /***** Now have a destination to aim for. *****/ + + /*---------------------------------- + | Don't move if it's not worth it. + ----------------------------------*/ + if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) { + GotThere(pActor); + return; + } + + /*------------------------------------------------------ + | If the destiation is within a follow nodes polygon, + | set destination as the nearest node. + ------------------------------------------------------*/ + if (!igPath) { + DestPath = InPolygon(targetX, targetY, PATH); + if (PolySubtype(DestPath) == NODE) { + // Find the nearest point on a line, or nearest node + FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline); + } + } + + assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 + SetMoverUltDest(pActor, targetX, targetY); + SetMoverIntDest(pActor, targetX, targetY); + + /*------------------------------------------------------------------- + | If in a follow nodes path, need to set off in the right direction! | + -------------------------------------------------------------------*/ + if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) { + SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY); + } else { + // Set off! + SetNextDest(pActor); + } +} + +/** + * Change scale if appropriate. + */ +static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) { + int scale; + + scale = GetScale(hPath, ypos); + if (scale != pActor->scale) { + SetMActorWalkReel(pActor, pActor->dirn, scale, false); + } +} + +/** + * Not going anywhere - Kick off again if not at final destination. + */ +static void NotMoving(PMACTOR pActor, int x, int y) { + pActor->targetX = pActor->targetY = -1; + +// if (x == pActor->UtargetX && y == pActor->UtargetY) + if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) { + GotThere(pActor); + return; + } + + if (pActor->ItargetX != -1 || pActor->ItargetY != -1) { + SetNextDest(pActor); + } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } +} + +/** + * Does the necessary business when entering a different path polygon. + */ +static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { + int firstnode; // First node to go to + int lastnode; // Last node to go to + HPOLYGON hIpath; + int nx, ny; + int nxl, nyl; + + pActor->hCpath = hPath; // current path + + if (hPath == NOPOLY) { + // Not proved this ever happens, but just in case + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + return; + } + + // Is new path a node path? + if (PolySubtype(hPath) == NODE) { + // Node path - usually go to nearest end node + firstnode = NearestEndNode(hPath, x, y); + lastnode = -1; + + // If this is not the destination path, + // find which end nodfe we wish to leave via + if (hPath != pActor->hUpath) { + if (pActor->bIgPath) { + lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY); + } else { + assert(pActor->hUpath != NOPOLY); // Error 703 + hIpath = getPathOnTheWay(hPath, pActor->hUpath); + assert(hIpath != NOPOLY); // No path on the way + + if (PolySubtype(hIpath) != NODE) { + lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath)); + } else { + lastnode = NearEndNode(hPath, hIpath); + } + } + } + // Test for pseudo-one-node npaths + if (lastnode != -1 && numNodes(hPath) == 2) { + getNpathNode(hPath, firstnode, &nx, &ny); + getNpathNode(hPath, lastnode, &nxl, &nyl); + if (nxl == nx && nyl == ny) + firstnode = lastnode; + } + + // If leaving by same node as entering, don't bother. + if (lastnode == firstnode) { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } else { + // Head for first node + pActor->over = true; + pActor->npstatus = ENTERING; + pActor->hFnpath = hPath; + pActor->line = firstnode ? firstnode - 1 : firstnode; + if (pActor->line == pActor->Tline && hPath == pActor->hUpath) { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + } else { + // This doesn't seem right + getNpathNode(hPath, firstnode, &nx, &ny); + if (ABS(pActor->objx - nx) < XMDIST + && ABS(pActor->objy - ny) < YMDIST) { + pActor->npstatus = ENTERING; + pActor->hFnpath = hPath; + SetNextDest(pActor); + } else { + getNpathNode(hPath, firstnode, &nx, &ny); + SetMoverDest(pActor, nx, ny); + } + } + } + return; + } else { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009 +// Added 26/01/95 + if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY)) + return; + + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } +} + +/** + * Move + */ +void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) { + MultiSetAniXY(pActor->actorObj, newx, newy); + MAsetZPos(pActor, newy, getPolyZfactor(hPath)); + if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) { + // The end of a scale-change reel + // Revert to normal walking reel + pActor->walkReel = false; + pActor->scount = 0; + SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); + } + pActor->objx = newx; + pActor->objy = newy; + + // Synchronised walking reels + if (++pActor->scount >= 6) + pActor->scount = 0; +} + +/** + * Called from MActorProcess() on every tick. + * + * Moves the actor as appropriate. + */ +void MoveActor(PMACTOR pActor) { + int newx, newy; + HPOLYGON hPath; + int status, s2; // s2 not used here! + HPOLYGON hS2p; // nor is s2p! + HPOLYGON hEb; + PMACTOR ma; + int sTargetX, sTargetY; + bool bNewPath = false; + + // Only do anything if the actor needs to move! + if (pActor->targetX == -1 && pActor->targetY == -1) + return; + + if (pActor->stop) { + GotThere(pActor); + pActor->stop = false; + SetMActorStanding(pActor); + return; + } + +#if SLOW_RINCE_DOWN +/**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code +/**/ return; // +/**/ BogusVar = 0; // +#endif + + // During swalk()s, movement while hidden may be slowed down. + if (pActor->aHidden) { + if (++hSlowVar < pActor->SlowFactor) + return; + hSlowVar = 0; + } + + // 'push' the target + sTargetX = pActor->targetX; + sTargetY = pActor->targetY; + + NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, + &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor); + + if (newx == pActor->objx && newy == pActor->objy) { + // 'pop' the target + pActor->targetX = sTargetX; + pActor->targetY = sTargetY; + + NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy, + &status, &s2, &hS2p, pActor->over, true, pActor); + if (newx == pActor->objx && newy == pActor->objy) { + NotMoving(pActor, newx, newy); + return; + } + } + + // Find out which path we're in now + hPath = InPolygon(newx, newy, PATH); + if (hPath == NOPOLY) { + if (pActor->bNoPath) { + Move(pActor, newx, newy, pActor->hCpath); + return; + } else { + // May be marginally outside! + // OR bIgPath may be set. + hPath = pActor->hCpath; + } + } else if (pActor->bNoPath) { + pActor->bNoPath = false; + bNewPath = true; + } else if (hPath != pActor->hCpath) { + if (IsInPolygon(newx, newy, pActor->hCpath)) + hPath = pActor->hCpath; + } + + CheckScale(pActor, hPath, newy); + + /* + * Must stay out of moving actors' blocking polygons. + */ + ma = InMActorBlock(pActor, newx, newy); + if (ma != NULL) { + // Stop if there's no chance of arriving + if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { + GotThere(pActor); + return; + } + + if (InMActorBlock(pActor, pActor->objx, pActor->objy)) + ; + else { + hEb = InitExtraBlock(pActor, ma); + newx = pActor->objx; + newy = pActor->objy; + BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY); + SetMoverDest(pActor, newx, newy); + return; + } + } + + /*-------------------------------------- + This is where it actually gets moved. + --------------------------------------*/ + Move(pActor, newx, newy, hPath); + + // Entering a new path polygon? + if (hPath != pActor->hCpath || bNewPath) + EnteringNewPath(pActor, hPath, newx, newy); +} + +/** + * Store the default refer type for the current scene. + */ +void SetDefaultRefer(int32 defRefer) { + DefaultRefer = defRefer; +} + +/** + * DoMoveActor + */ +void DoMoveActor(PMACTOR pActor) { + int wasx, wasy; + int i; + +#define NUMBER 1 + + wasx = pActor->objx; + wasy = pActor->objy; + + MoveActor(pActor); + + if ((pActor->targetX != -1 || pActor->targetY != -1) + && (wasx == pActor->objx && wasy == pActor->objy)) { + for (i=0; i < NUMBER; i++) { + MoveActor(pActor); + if (wasx != pActor->objx || wasy != pActor->objy) + break; + } +// assert(i<NUMBER); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h new file mode 100644 index 0000000000..2c5f2cfe73 --- /dev/null +++ b/engines/tinsel/move.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_MOVE_H // prevent multiple includes +#define TINSEL_MOVE_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +struct MACTOR; + +void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film); +void SSetActorDest(MACTOR *pActor); +void DoMoveActor(MACTOR *pActor); + +void SetDefaultRefer(int32 defRefer); + +} // end of namespace Tinsel + +#endif /* TINSEL_MOVE_H */ diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp new file mode 100644 index 0000000000..c60592069f --- /dev/null +++ b/engines/tinsel/multiobj.cpp @@ -0,0 +1,533 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle multi-part objects. + */ + +#include "tinsel/multiobj.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" + +namespace Tinsel { + +// from object.c +extern OBJECT *objectList; + +/** + * Initialise a multi-part object using a list of images to init + * each object piece. One object is created for each image in the list. + * All objects are given the same palette as the first image. A pointer + * to the first (master) object created is returned. + * @param pInitTbl Pointer to multi-object initialisation table + */ +OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) { + OBJ_INIT obj_init; // object init table + OBJECT *pFirst, *pObj; // object pointers + FRAME *pFrame; // list of images for the multi-part object + + if (pInitTbl->hMulFrame) { + // we have a frame handle + pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame)); + + obj_init.hObjImg = READ_LE_UINT32(pFrame); // first objects shape + } else { // this must be a animation list for a NULL object + pFrame = NULL; + obj_init.hObjImg = 0; // first objects shape + } + + // init the object init table + obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags); // all objects have same flags + obj_init.objID = (int)FROM_LE_32(pInitTbl->mulID); // all objects have same ID + obj_init.objX = (int)FROM_LE_32(pInitTbl->mulX); // all objects have same X ani pos + obj_init.objY = (int)FROM_LE_32(pInitTbl->mulY); // all objects have same Y ani pos + obj_init.objZ = (int)FROM_LE_32(pInitTbl->mulZ); // all objects have same Z pos + + // create and init the first object + pObj = pFirst = InitObject(&obj_init); + + if (pFrame) { + // if we have any animation frames + + pFrame++; + + while (READ_LE_UINT32(pFrame) != 0) { + // set next objects shape + obj_init.hObjImg = READ_LE_UINT32(pFrame); + + // create next object and link to previous + pObj = pObj->pSlave = InitObject(&obj_init); + + pFrame++; + } + } + + // null end of list for final object + pObj->pSlave = NULL; + + // return master object + return pFirst; +} + +/** + * Inserts the multi-part object onto the specified object list. + * @param pObjList List to insert multi-part object onto +* @param pInsObj Head of multi-part object to insert + + */ + +void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) { + // validate object pointer + assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // add next part to the specified list + InsertObject(pObjList, pInsObj); + + // next obj in list + pInsObj = pInsObj->pSlave; + } while (pInsObj != NULL); +} + +/** + * Deletes all the pieces of a multi-part object from the + * specified object list. + * @param pObjList List to delete multi-part object from + * @param pMultiObj Multi-part object to be deleted + */ + +void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // delete object + DelObject(pObjList, pMultiObj); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + } + while (pMultiObj != NULL); +} + +/** + * Hides a multi-part object by giving each object a "NullImage" + * image pointer. + * @param pMultiObj Multi-part object to be hidden + */ + +void MultiHideObject(OBJECT *pMultiObj) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // set master shape to null animation frame + pMultiObj->hShape = 0; + + // change all objects + MultiReshape(pMultiObj); +} + +/** + * Horizontally flip a multi-part object. + * @param pFlipObj Head of multi-part object to flip + */ + +void MultiHorizontalFlip(OBJECT *pFlipObj) { + // validate object pointer + assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // horizontally flip the next part + AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH, + pFlipObj->hImg); + + // next obj in list + pFlipObj = pFlipObj->pSlave; + } while (pFlipObj != NULL); +} + +/** + * Vertically flip a multi-part object. + * @param pFlipObj Head of multi-part object to flip + */ + +void MultiVerticalFlip(OBJECT *pFlipObj) { + // validate object pointer + assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // vertically flip the next part + AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV, + pFlipObj->hImg); + + // next obj in list + pFlipObj = pFlipObj->pSlave; + } + while (pFlipObj != NULL); +} + +/** + * Adjusts the coordinates of a multi-part object. The adjustments + * take into account the orientation of the object. + * @param pMultiObj Multi-part object to be adjusted + * @param deltaX X adjustment + * @param deltaY Y adjustment + */ + +void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + if (deltaX == 0 && deltaY == 0) + return; // ignore no change + + if (pMultiObj->flags & DMA_FLIPH) { + // image is flipped horizontally - flip the x direction + deltaX = -deltaX; + } + + if (pMultiObj->flags & DMA_FLIPV) { + // image is flipped vertically - flip the y direction + deltaY = -deltaY; + } + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // adjust the x position + pMultiObj->xPos += intToFrac(deltaX); + + // adjust the y position + pMultiObj->yPos += intToFrac(deltaY); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + + } while (pMultiObj != NULL); +} + +/** + * Moves all the pieces of a multi-part object by the specified + * amount. Does not take into account the objects orientation. + * @param pMultiObj Multi-part object to be adjusted + * @param deltaX X movement + * @param deltaY Y movement + */ + +void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + if (deltaX == 0 && deltaY == 0) + return; // ignore no change + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // adjust the x position + pMultiObj->xPos += intToFrac(deltaX); + + // adjust the y position + pMultiObj->yPos += intToFrac(deltaY); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + + } while (pMultiObj != NULL); +} + +/** + * Sets the x & y anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose position is to be changed + * @param newAniX New x animation position + * @param newAniY New y animation position + */ + +void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc difference between current and new positions + newAniX -= curAniX; + newAniY -= curAniY; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, newAniX, newAniY); +} + +/** + * Sets the x anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose x position is to be changed + * @param newAniX New x animation position + */ + +void MultiSetAniX(OBJECT *pMultiObj, int newAniX) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc x difference between current and new positions + newAniX -= curAniX; + curAniY = 0; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, newAniX, curAniY); +} + +/** + * Sets the y anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose x position is to be changed + * @param newAniX New y animation position + */ + +void MultiSetAniY(OBJECT *pMultiObj, int newAniY) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc y difference between current and new positions + curAniX = 0; + newAniY -= curAniY; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, curAniX, newAniY); +} + +/** + * Sets the Z position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object to be adjusted + * @param newZ New Z order + */ + +void MultiSetZPosition(OBJECT *pMultiObj, int newZ) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // set the new z position + pMultiObj->zPos = newZ; + + // next obj in list + pMultiObj = pMultiObj->pSlave; + } + while (pMultiObj != NULL); +} + +/** + * Reshape a multi-part object. + * @param pMultiObj Multi-part object to re-shape + */ + +void MultiReshape(OBJECT *pMultiObj) { + SCNHANDLE hFrame; + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get objects current anim frame + hFrame = pMultiObj->hShape; + + if (hFrame != 0 && hFrame != pMultiObj->hMirror) { + // a valid shape frame which is different from previous + + // get pointer to frame + const FRAME *pFrame = (const FRAME *)LockMem(hFrame); + + // update previous + pMultiObj->hMirror = hFrame; + + while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) { + // a normal image - update the current object with this image + AnimateObject(pMultiObj, READ_LE_UINT32(pFrame)); + + // move to next image for this frame + pFrame++; + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + + // null the remaining object parts + while (pMultiObj != NULL) { + // set a null image for this object part + AnimateObject(pMultiObj, 0); + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + } else if (hFrame == 0) { + // update previous + pMultiObj->hMirror = hFrame; + + // null all the object parts + while (pMultiObj != NULL) { + // set a null image for this object part + AnimateObject(pMultiObj, 0); + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + } +} + +/** + * Returns the left-most point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiLeftmost(OBJECT *pMulti) { + int left; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init leftmost point to first object + left = fracToInt(pMulti->xPos); + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->xPos) < left) + // this object is further left + left = fracToInt(pMulti->xPos); + } + } + + // return left-most point + return left; +} + +/** + * Returns the right-most point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiRightmost(OBJECT *pMulti) { + int right; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init right-most point to first object + right = fracToInt(pMulti->xPos) + pMulti->width; + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->xPos) + pMulti->width > right) + // this object is further right + right = fracToInt(pMulti->xPos) + pMulti->width; + } + } + + // return right-most point + return right - 1; +} + +/** + * Returns the highest point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiHighest(OBJECT *pMulti) { + int highest; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init highest point to first object + highest = fracToInt(pMulti->yPos); + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->yPos) < highest) + // this object is higher + highest = fracToInt(pMulti->yPos); + } + } + + // return highest point + return highest; +} + +/** + * Returns the lowest point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiLowest(OBJECT *pMulti) { + int lowest; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init lowest point to first object + lowest = fracToInt(pMulti->yPos) + pMulti->height; + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->yPos) + pMulti->height > lowest) + // this object is lower + lowest = fracToInt(pMulti->yPos) + pMulti->height; + } + } + + // return lowest point + return lowest - 1; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h new file mode 100644 index 0000000000..6d25600ea2 --- /dev/null +++ b/engines/tinsel/multiobj.h @@ -0,0 +1,124 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Multi-part object definitions + */ + +#ifndef TINSEL_MULTIOBJ_H // prevent multiple includes +#define TINSEL_MULTIOBJ_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +struct OBJECT; + +#include "common/pack-start.h" // START STRUCT PACKING + +/** + * multi-object initialisation structure (parallels OBJ_INIT struct) + */ +struct MULTI_INIT { + SCNHANDLE hMulFrame; //!< multi-objects shape - NULL terminated list of IMAGE structures + int32 mulFlags; //!< multi-objects flags + int32 mulID; //!< multi-objects id + int32 mulX; //!< multi-objects initial x ani position + int32 mulY; //!< multi-objects initial y ani position + int32 mulZ; //!< multi-objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +/*----------------------------------------------------------------------*\ +|* Multi Object Function Prototypes *| +\*----------------------------------------------------------------------*/ + +OBJECT *MultiInitObject( // Initialise a multi-part object + const MULTI_INIT *pInitTbl); // pointer to multi-object initialisation table + +void MultiInsertObject( // Insert a multi-part object onto a object list + OBJECT *pObjList, // list to insert multi-part object onto + OBJECT *pInsObj); // head of multi-part object to insert + +void MultiDeleteObject( // Delete all the pieces of a multi-part object + OBJECT *pObjList, // list to delete multi-part object from + OBJECT *pMultiObj); // multi-part object to be deleted + +void MultiHideObject( // Hide a multi-part object + OBJECT *pMultiObj); // multi-part object to be hidden + +void MultiHorizontalFlip( // Hortizontally flip a multi-part object + OBJECT *pFlipObj); // head of multi-part object to flip + +void MultiVerticalFlip( // Vertically flip a multi-part object + OBJECT *pFlipObj); // head of multi-part object to flip + +void MultiAdjustXY( // Adjust coords of a multi-part object. Takes into account the orientation + OBJECT *pMultiObj, // multi-part object to be adjusted + int deltaX, // x adjustment + int deltaY); // y adjustment + +void MultiMoveRelXY( // Move multi-part object relative. Does not take into account the orientation + OBJECT *pMultiObj, // multi-part object to be moved + int deltaX, // x movement + int deltaY); // y movement + +void MultiSetAniXY( // Set the x & y anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose position is to be changed + int newAniX, // new x animation position + int newAniY); // new y animation position + +void MultiSetAniX( // Set the x anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose x position is to be changed + int newAniX); // new x animation position + +void MultiSetAniY( // Set the y anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose y position is to be adjusted + int newAniY); // new y animation position + +void MultiSetZPosition( // Sets the z position of a multi-part object + OBJECT *pMultiObj, // multi-part object to be adjusted + int newZ); // new Z order + +void MultiMatchAniPoints( // Matches a multi-parts pos and orientation to be the same as a reference object + OBJECT *pMoveObj, // multi-part object to be moved + OBJECT *pRefObj); // multi-part object to match with + +void MultiReshape( // Reshape a multi-part object + OBJECT *pMultiObj); // multi-part object to re-shape + +int MultiLeftmost( // Returns the left-most point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiRightmost( // Returns the right-most point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiHighest( // Returns the highest point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiLowest( // Returns the lowest point of a multi-part object + OBJECT *pMulti); // multi-part object + +} // end of namespace Tinsel + +#endif // TINSEL_MULTIOBJ_H diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp new file mode 100644 index 0000000000..7d4efd8079 --- /dev/null +++ b/engines/tinsel/music.cpp @@ -0,0 +1,551 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// FIXME: This code is taken from MADE and may need more work (e.g. setVolume). + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "sound/audiocd.h" +#include "common/config-manager.h" +#include "common/file.h" + +#include "tinsel/config.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +namespace Tinsel { + +//--------------------------- Midi data ------------------------------------- + +// sound buffer structure used for MIDI data and samples +struct SOUND_BUFFER { + uint8 *pDat; // pointer to actual buffer + uint32 size; // size of the buffer +}; + +// get set when music driver is installed +//static MDI_DRIVER *mDriver; +//static HSEQUENCE mSeqHandle; + +// if non-zero this is the index position of the next MIDI sequence to play +static uint32 dwMidiIndex = 0; + +// MIDI buffer +static SOUND_BUFFER midiBuffer = { 0, 0 }; + +static SCNHANDLE currentMidi = 0; +static bool currentLoop = false; + +const SCNHANDLE midiOffsetsGRAVersion[] = { + 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450, + 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898, + 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762, + 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988, + 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566, + 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174, + 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946, + 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540, + 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680, + 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306, + 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812, + 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832, + 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708, + 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016, + 794630, 796422, 798998 +}; + +const SCNHANDLE midiOffsetsSCNVersion[] = { + 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108, + 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264, + 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638, + 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754, + 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712, + 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478, + 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296, + 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680, + 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716, + 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562, + 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016, + 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982, + 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342, + 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042, + 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066, + 869822, 875436, 877234, 879818 +}; + +// TODO: finish this (currently unmapped tracks are 0) +const int enhancedAudioSCNVersion[] = { + 0, 0, 2, 0, 0, 0, 0, 3, 3, 4, + 4, 0, 0, 0, 0, 0, 0, 10, 3, 11, + 11, 0, 13, 13, 13, 13, 13, 0, 13, 13, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 24, 0, 0, 27, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 55, 56, 56, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 4, 83, 83, 83, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 52, 4, + 0, 0, 0, 0 +}; + +int GetTrackNumber(SCNHANDLE hMidi) { + if (_vm->getFeatures() & GF_SCNFILES) { + for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) { + if (midiOffsetsSCNVersion[i] == hMidi) + return i; + } + } else { + for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) { + if (midiOffsetsGRAVersion[i] == hMidi) + return i; + } + } + + return -1; +} + +SCNHANDLE GetTrackOffset(int trackNumber) { + if (_vm->getFeatures() & GF_SCNFILES) { + assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion)); + return midiOffsetsSCNVersion[trackNumber]; + } else { + assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion)); + return midiOffsetsGRAVersion[trackNumber]; + } +} + +/** + * Plays the specified MIDI sequence through the sound driver. + * @param dwFileOffset File offset of MIDI sequence data + * @param bLoop Whether to loop the sequence + */ +bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { + currentMidi = dwFileOffset; + currentLoop = bLoop; + + if (volMidi != 0) { + SetMidiVolume(volMidi); + // Support for compressed music from the music enhancement project + AudioCD.stop(); + + int trackNumber = GetTrackNumber(dwFileOffset); + if (trackNumber >= 0) { +#if 0 + // TODO: GRA version + int track = enhancedAudioSCNVersion[trackNumber]; + if (track > 0) + AudioCD.play(track, -1, 0, 0); +#endif + } else { + warning("Unknown MIDI offset %d", dwFileOffset); + } + + if (AudioCD.isPlaying()) + return true; + } + + // set file offset for this sequence + dwMidiIndex = dwFileOffset; + + // the index and length of the last tune loaded + static uint32 dwLastMidiIndex; + static uint32 dwLastSeqLen; + + uint32 dwSeqLen = 0; // length of the sequence + + if (dwMidiIndex == 0) + return true; + + if (dwMidiIndex != dwLastMidiIndex) { + Common::File midiStream; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // update index of last tune loaded + dwLastMidiIndex = dwMidiIndex; + + // move to correct position in the file + midiStream.seek(dwMidiIndex, SEEK_SET); + + // read the length of the sequence + dwSeqLen = midiStream.readUint32LE(); + + // make sure buffer is large enough for this sequence + assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size); + + // stop any currently playing tune + _vm->_music->stop(); + + // read the sequence + if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen) + error("File %s is corrupt", MIDI_FILE); + + midiStream.close(); + + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + + // Store the length + dwLastSeqLen = dwSeqLen; + } else { + // dwMidiIndex == dwLastMidiIndex + _vm->_music->stop(); + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + } + + // allow another sequence to play + dwMidiIndex = 0; + + return true; +} + +/** + * Returns TRUE if a Midi tune is currently playing. + */ +bool MidiPlaying(void) { + if (AudioCD.isPlaying()) return true; + return _vm->_music->isPlaying(); +} + +/** + * Stops any currently playing midi. + */ +bool StopMidi(void) { + currentMidi = 0; + currentLoop = false; + + AudioCD.stop(); + _vm->_music->stop(); + return true; +} + + +/** + * Gets the volume of the MIDI music. + */ +int GetMidiVolume() { + return volMidi; +} + +/** + * Sets the volume of the MIDI music. + * @param vol New volume - 0..MAXMIDIVOL + */ +void SetMidiVolume(int vol) { + assert(vol >= 0 && vol <= MAXMIDIVOL); + + if (vol == 0 && volMidi == 0) { + // Nothing to do + } else if (vol == 0 && volMidi != 0) { + // Stop current midi sequence + AudioCD.stop(); + StopMidi(); + } else if (vol != 0 && volMidi == 0) { + // Perhaps restart last midi sequence + if (currentLoop) { + PlayMidiSequence(currentMidi, true); + _vm->_music->setVolume(vol); + } + } else if (vol != 0 && volMidi != 0) { + // Alter current volume + _vm->_music->setVolume(vol); + } + + volMidi = vol; +} + +/** + * Opens and inits all MIDI sequence files. + */ +void OpenMidiFiles(void) { + Common::File midiStream; + + // Demo version has no midi file + if (_vm->getFeatures() & GF_DEMO) + return; + + if (midiBuffer.pDat) + // already allocated + return; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // gen length of the largest sequence + midiBuffer.size = midiStream.readUint32LE(); + if (midiStream.ioFailed()) + error("File %s is corrupt", MIDI_FILE); + + if (midiBuffer.size) { + // allocate a buffer big enough for the largest MIDI sequence + if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) { + // clear out the buffer + memset(midiBuffer.pDat, 0, midiBuffer.size); +// VMM_lock(midiBuffer.pDat, midiBuffer.size); + } else { + //mSeqHandle = NULL; + } + } + + midiStream.close(); +} + +void DeleteMidiBuffer() { + free(midiBuffer.pDat); + midiBuffer.pDat = NULL; +} + +MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) { + memset(_channel, 0, sizeof(_channel)); + _masterVolume = 0; + this->open(); + _xmidiParser = MidiParser::createParser_XMIDI(); +} + +MusicPlayer::~MusicPlayer() { + _driver->setTimerCallback(NULL, NULL); + stop(); + this->close(); + _xmidiParser->setMidiDriver(NULL); + delete _xmidiParser; +} + +void MusicPlayer::setVolume(int volume) { + Common::StackLock lock(_mutex); + + // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range? + volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255); + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + + if (_masterVolume == volume) + return; + + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + _channel[i]->volume(_channelVolume[i] * _masterVolume / 255); + } + } +} + +int MusicPlayer::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void MusicPlayer::close() { + stop(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MusicPlayer::send(uint32 b) { + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + + if (_channel[channel]) { + _channel[channel]->send(b); + + if ((b & 0xFFF0) == 0x0079B0) { + // We've just Reset All Controllers, so we need to + // re-adjust the volume. Otherwise, volume is reset to + // default whenever the music changes. + _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel); + } + } +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: // End of Track + if (_looping) + _parser->jumpToTick(0); + else + stop(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void MusicPlayer::onTimer(void *refCon) { + MusicPlayer *music = (MusicPlayer *)refCon; + Common::StackLock lock(music->_mutex); + + if (music->_isPlaying) + music->_parser->onTimer(); +} + +void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { + if (_isPlaying) + return; + + stop(); + + // It seems like not all music (the main menu music, for instance) set + // all the instruments explicitly. That means the music will sound + // different, depending on which music played before it. This appears + // to be a genuine glitch in the original. For consistency, reset all + // instruments to the default one (piano). + + for (int i = 0; i < 16; i++) { + _driver->send(0xC0 | i, 0, 0); + } + + // Load XMID resource data + + if (_xmidiParser->loadMusic(midiData, size)) { + MidiParser *parser = _xmidiParser; + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + _looping = loop; + _isPlaying = true; + } +} + +void MusicPlayer::stop() { + Common::StackLock lock(_mutex); + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } +} + +void MusicPlayer::pause() { + setVolume(-1); + _isPlaying = false; +} + +void MusicPlayer::resume() { + setVolume(GetMidiVolume()); + _isPlaying = true; +} + +void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) { + *pMidi = currentMidi; + *pLoop = currentLoop; +} + +void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) { + AudioCD.stop(); + StopMidi(); + + currentMidi = Midi; + currentLoop = Loop; + + if (volMidi != 0 && Loop) { + PlayMidiSequence(currentMidi, true); + SetMidiVolume(volMidi); + } +} + +#if 0 +// Dumps all of the game's music in external XMIDI *.xmi files +void dumpMusic() { + Common::File midiFile; + Common::DumpFile outFile; + char outName[20]; + midiFile.open(MIDI_FILE); + int outFileSize = 0; + char buffer[20000]; + + int total = (_vm->getFeatures() & GF_SCNFILES) ? + ARRAYSIZE(midiOffsetsSCNVersion) : + ARRAYSIZE(midiOffsetsGRAVersion); + + for (int i = 0; i < total; i++) { + sprintf(outName, "track%03d.xmi", i + 1); + outFile.open(outName); + + if (_vm->getFeatures() & GF_SCNFILES) { + if (i < total - 1) + outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4; + + midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET); + } else { + if (i < total - 1) + outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4; + + midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET); + } + + assert(outFileSize < 20000); + midiFile.read(buffer, outFileSize); + outFile.write(buffer, outFileSize); + + outFile.close(); + } + + midiFile.close(); +} +#endif + +} // End of namespace Made diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h new file mode 100644 index 0000000000..80456e2a76 --- /dev/null +++ b/engines/tinsel/music.h @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef TINSEL_MUSIC_H +#define TINSEL_MUSIC_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace Tinsel { + +#define MAXMIDIVOL 127 + +bool PlayMidiSequence( // Plays the specified MIDI sequence through the sound driver + uint32 dwFileOffset, // handle of MIDI sequence data + bool bLoop); // Whether to loop the sequence + +bool MidiPlaying(void); // Returns TRUE if a Midi tune is currently playing + +bool StopMidi(void); // Stops any currently playing midi + +void SetMidiVolume( // Sets the volume of the MIDI music. Returns the old volume + int vol); // new volume - 0..MAXMIDIVOL + +int GetMidiVolume(); + +void OpenMidiFiles(); +void DeleteMidiBuffer(); + +void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop); +void RestoreMidiFacts(SCNHANDLE Midi, bool Loop); + +int GetTrackNumber(SCNHANDLE hMidi); +SCNHANDLE GetTrackOffset(int trackNumber); + +void dumpMusic(); + + +class MusicPlayer : public MidiDriver { +public: + MusicPlayer(MidiDriver *driver); + ~MusicPlayer(); + + bool isPlaying() { return _isPlaying; } + void setPlaying(bool playing) { _isPlaying = playing; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + + void playXMIDI(byte *midiData, uint32 size, bool loop); + void stop(); + void pause(); + void resume(); + void setLoop(bool loop) { _looping = loop; } + + //MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + + // The original sets the "sequence timing" to 109 Hz, whatever that + // means. The default is 120. + + uint32 getBaseTempo(void) { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; } + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + MidiParser *_parser; + Common::Mutex _mutex; + +protected: + + static void onTimer(void *data); + + MidiChannel *_channel[16]; + MidiDriver *_driver; + MidiParser *_xmidiParser; + byte _channelVolume[16]; + + bool _isPlaying; + bool _looping; + byte _masterVolume; +}; + +} // End of namespace Made + +#endif diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp new file mode 100644 index 0000000000..709fa4fad9 --- /dev/null +++ b/engines/tinsel/object.cpp @@ -0,0 +1,530 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the Object Manager code. + */ + +#include "tinsel/object.h" +#include "tinsel/background.h" +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" // low level interface +#include "tinsel/handle.h" + +#define OID_EFFECTS 0x2000 // generic special effects object id + +namespace Tinsel { + +/** screen clipping rectangle */ +static const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + +// list of all objects +OBJECT *objectList = 0; + +// pointer to free object list +static OBJECT *pFreeObjects = 0; + +#ifdef DEBUG +// diagnostic object counters +static int numObj = 0; +static int maxObj = 0; +#endif + +void FreeObjectList(void) { + if (objectList) { + free(objectList); + objectList = NULL; + } +} + +/** + * Kills all objects and places them on the free list. + */ + +void KillAllObjects(void) { + int i; + +#ifdef DEBUG + // clear number of objects in use + numObj = 0; +#endif + + if (objectList == NULL) { + // first time - allocate memory for object list + objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT)); + + // make sure memory allocated + if (objectList == NULL) { + error("Cannot allocate memory for object data"); + } + } + + // place first object on free list + pFreeObjects = objectList; + + // link all other objects after first + for (i = 1; i < NUM_OBJECTS; i++) { + objectList[i - 1].pNext = objectList + i; + } + + // null the last object + objectList[NUM_OBJECTS - 1].pNext = NULL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of objects used at once. + */ + +void ObjectStats(void) { + printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS); +} +#endif + +/** + * Allocate a object from the free list. + */ +OBJECT *AllocObject(void) { + OBJECT *pObj = pFreeObjects; // get a free object + + // check for no free objects + assert(pObj != NULL); + + // a free object exists + + // get link to next free object + pFreeObjects = pObj->pNext; + + // clear out object + memset(pObj, 0, sizeof(OBJECT)); + + // set default drawing mode and set changed bit + pObj->flags = DMA_WNZ | DMA_CHANGED; + +#ifdef DEBUG + // one more object in use + if (++numObj > maxObj) + maxObj = numObj; +#endif + + // return new object + return pObj; +} + +/** + * Copy one object to another. + * @param pDest Destination object + * @param pSrc Source object + */ +void CopyObject(OBJECT *pDest, OBJECT *pSrc) { + // save previous dimensions etc. + Common::Rect rcSave = pDest->rcPrev; + + // make a copy + memcpy(pDest, pSrc, sizeof(OBJECT)); + + // restore previous dimensions etc. + pDest->rcPrev = rcSave; + + // set changed flag in destination + pDest->flags |= DMA_CHANGED; + + // null the links + pDest->pNext = pDest->pSlave = NULL; +} + +/** + * Inserts an object onto the specified object list. The object + * lists are sorted in Z Y order. + * @param pObjList List to insert object onto + * @param pInsObj Object to insert + */ + +void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) { + OBJECT *pPrev, *pObj; // object list traversal pointers + + // validate object pointer + assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + + for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + // check Z order + if (pInsObj->zPos < pObj->zPos) { + // object Z is lower than list Z - insert here + break; + } else if (pInsObj->zPos == pObj->zPos) { + // Z values are the same - sort on Y + if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) { + // object Y is lower than or same as list Y - insert here + break; + } + } + } + + // insert obj between pPrev and pObj + pInsObj->pNext = pObj; + pPrev->pNext = pInsObj; +} + + +/** + * Deletes an object from the specified object list and places it + * on the free list. + * @param pObjList List to delete object from + * @param pDelObj Object to delete + */ +void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { + OBJECT *pPrev, *pObj; // object list traversal pointers + + // validate object pointer + assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1); + +#ifdef DEBUG + // one less object in use + --numObj; + assert(numObj >= 0); +#endif + + for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + if (pObj == pDelObj) { + // found object to delete + + if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) { + // allocate a clipping rect for objects previous pos + AddClipRect(pDelObj->rcPrev); + } + + // make PREV next = OBJ next - removes OBJ from list + pPrev->pNext = pObj->pNext; + + // place free list in OBJ next + pObj->pNext = pFreeObjects; + + // add OBJ to top of free list + pFreeObjects = pObj; + + // delete objects palette + if (pObj->pPal) + FreePalette(pObj->pPal); + + // quit + return; + } + } + + // if we get to here - object has not been found on the list + error("DelObject(): formally 'assert(0)!'"); +} + + +/** + * Sort the specified object list in Z Y order. + * @param pObjList List to sort + */ +void SortObjectList(OBJECT *pObjList) { + OBJECT *pPrev, *pObj; // object list traversal pointers + OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT + + // put at head of list + head.pNext = pObjList->pNext; + + // set head of list dummy OBJ Z Y values to lowest possible + head.yPos = intToFrac(MIN_INT16); + head.zPos = MIN_INT; + + for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + // check Z order + if (pObj->zPos < pPrev->zPos) { + // object Z is lower than previous Z + + // remove object from list + pPrev->pNext = pObj->pNext; + + // re-insert object on list + InsertObject(pObjList, pObj); + + // back to beginning of list + pPrev = &head; + pObj = head.pNext; + } else if (pObj->zPos == pPrev->zPos) { + // Z values are the same - sort on Y + if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) { + // object Y is lower than previous Y + + // remove object from list + pPrev->pNext = pObj->pNext; + + // re-insert object on list + InsertObject(pObjList, pObj); + + // back to beginning of list + pPrev = &head; + pObj = head.pNext; + } + } + } +} + +/** + * Returns the animation offsets of a image, dependent on the + * images orientation flags. + * @param hImg Iimage to get animation offset of + * @param flags Images current flags + * @param pAniX Gets set to new X animation offset + * @param pAniY Gets set to new Y animation offset + */ +void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) { + if (hImg) { + const IMAGE *pImg = (const IMAGE *)LockMem(hImg); + + // set ani X + *pAniX = (int16) FROM_LE_16(pImg->anioffX); + + // set ani Y + *pAniY = (int16) FROM_LE_16(pImg->anioffY); + + if (flags & DMA_FLIPH) { + // we are flipped horizontally + + // set ani X = -ani X + width - 1 + *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1; + } + + if (flags & DMA_FLIPV) { + // we are flipped vertically + + // set ani Y = -ani Y + height - 1 + *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1; + } + } else + // null image + *pAniX = *pAniY = 0; +} + + +/** + * Returns the x,y position of an objects animation point. + * @param pObj Pointer to object + * @param pPosX Gets set to objects X animation position + * @param pPosY Gets set to objects Y animation position + */ +void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) { + // validate object pointer + assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1); + + // get the animation offset of the object + GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY); + + // from animation offset and objects position - determine objects animation point + *pPosX += fracToInt(pObj->xPos); + *pPosY += fracToInt(pObj->yPos); +} + +/** + * Initialise a object using a OBJ_INIT structure to supply parameters. + * @param pInitTbl Pointer to object initialisation table + */ +OBJECT *InitObject(const OBJ_INIT *pInitTbl) { + // allocate a new object + OBJECT *pObj = AllocObject(); + + // make sure object created + assert(pObj != NULL); + + // set objects shape + pObj->hImg = pInitTbl->hObjImg; + + // set objects ID + pObj->oid = pInitTbl->objID; + + // set objects flags + pObj->flags = DMA_CHANGED | pInitTbl->objFlags; + + // set objects Z position + pObj->zPos = pInitTbl->objZ; + + // get pointer to image + if (pInitTbl->hObjImg) { + int aniX, aniY; // objects animation offsets + PALQ *pPalQ; // palette queue pointer + const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image + + // allocate a palette for this object + pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); + + // make sure palette allocated + assert(pPalQ != NULL); + + // assign palette to object + pObj->pPal = pPalQ; + + // set objects size + pObj->width = FROM_LE_16(pImg->imgWidth); + pObj->height = FROM_LE_16(pImg->imgHeight); + + // set objects bitmap definition + pObj->hBits = FROM_LE_32(pImg->hImgBits); + + // get animation offset of object + GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY); + + // set objects X position - subtract ani offset + pObj->xPos = intToFrac(pInitTbl->objX - aniX); + + // set objects Y position - subtract ani offset + pObj->yPos = intToFrac(pInitTbl->objY - aniY); + } else { // no image handle - null image + + // set objects X position + pObj->xPos = intToFrac(pInitTbl->objX); + + // set objects Y position + pObj->yPos = intToFrac(pInitTbl->objY); + } + + // return new object + return pObj; +} + +/** + * Give a object a new image and new orientation flags. + * @param pAniObj Object to be updated + * @param newflags Objects new flags + * @param hNewImg Objects new image + */ +void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { + // validate object pointer + assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1); + + if (pAniObj->hImg != hNewImg + || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) { + // something has changed + + int oldAniX, oldAniY; // objects old animation offsets + int newAniX, newAniY; // objects new animation offsets + + // get objects old animation offsets + GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY); + + // get objects new animation offsets + GetAniOffset(hNewImg, newflags, &newAniX, &newAniY); + + if (hNewImg) { + // get pointer to image + const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg); + + // setup new shape + pAniObj->width = FROM_LE_16(pNewImg->imgWidth); + pAniObj->height = FROM_LE_16(pNewImg->imgHeight); + + // set objects bitmap definition + pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits); + } else { // null image + pAniObj->width = 0; + pAniObj->height = 0; + pAniObj->hBits = 0; + } + + // set objects flags and signal a change + pAniObj->flags = newflags | DMA_CHANGED; + + // set objects image + pAniObj->hImg = hNewImg; + + // adjust objects position - subtract new from old for difference + pAniObj->xPos += intToFrac(oldAniX - newAniX); + pAniObj->yPos += intToFrac(oldAniY - newAniY); + } +} + +/** + * Give an object a new image. + * @param pAniObj Object to animate + * @param hNewImg Objects new image + */ +void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) { + // dont change the objects flags + AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg); +} + +/** + * Creates a rectangle object of the given dimensions and returns + * a pointer to the object. + * @param hPal Palette for the rectangle object + * @param colour Which colour offset from the above palette + * @param width Width of rectangle + * @param height Height of rectangle + */ +OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) { + // template for initialising the rectangle object + static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0}; + PALQ *pPalQ; // palette queue pointer + + // allocate and init a new object + OBJECT *pRect = InitObject(&rectObj); + + // allocate a palette for this object + pPalQ = AllocPalette(hPal); + + // make sure palette allocated + assert(pPalQ != NULL); + + // assign palette to object + pRect->pPal = pPalQ; + + // set colour in the palette + pRect->constant = colour; + + // set rectangle width + pRect->width = width; + + // set rectangle height + pRect->height = height; + + // return pointer to rectangle object + return pRect; +} + +/** + * Creates a translucent rectangle object of the given dimensions + * and returns a pointer to the object. + * @param width Width of rectangle + * @param height Height of rectangle + */ +OBJECT *TranslucentObject(int width, int height) { + // template for initialising the rectangle object + static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0}; + + // allocate and init a new object + OBJECT *pRect = InitObject(&rectObj); + + // set rectangle width + pRect->width = width; + + // set rectangle height + pRect->height = height; + + // return pointer to rectangle object + return pRect; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h new file mode 100644 index 0000000000..8b61571a3e --- /dev/null +++ b/engines/tinsel/object.h @@ -0,0 +1,206 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Object Manager data structures + */ + +#ifndef TINSEL_OBJECT_H // prevent multiple includes +#define TINSEL_OBJECT_H + +#include "tinsel/dw.h" +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct PALQ; + +enum { + /** the maximum number of objects */ + NUM_OBJECTS = 256, + + // object flags + DMA_WNZ = 0x0001, //!< write non-zero data + DMA_CNZ = 0x0002, //!< write constant on non-zero data + DMA_CONST = 0x0004, //!< write constant on both zero & non-zero data + DMA_WA = 0x0008, //!< write all data + DMA_FLIPH = 0x0010, //!< flip object horizontally + DMA_FLIPV = 0x0020, //!< flip object vertically + DMA_CLIP = 0x0040, //!< clip object + DMA_TRANS = 0x0084, //!< translucent rectangle object + DMA_ABS = 0x0100, //!< position of object is absolute + DMA_CHANGED = 0x0200, //!< object has changed in some way since the last frame + DMA_USERDEF = 0x0400, //!< user defined flags start here + + /** flags that effect an objects appearance */ + DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS) +}; + +/** structure for image */ +struct IMAGE { + short imgWidth; //!< image width + short imgHeight; //!< image height + short anioffX; //!< image x animation offset + short anioffY; //!< image y animation offset + SCNHANDLE hImgBits; //!< image bitmap handle + SCNHANDLE hImgPal; //!< image palette handle +}; + + +/** a multi-object animation frame is a list of multi-image handles */ +typedef uint32 FRAME; + + +// object structure +struct OBJECT { + OBJECT *pNext; //!< pointer to next object in list + OBJECT *pSlave; //!< pointer to slave object (multi-part objects) +// char *pOnDispList; //!< pointer to display list byte for background objects +// frac_t xVel; //!< x velocity of object +// frac_t yVel; //!< y velocity of object + frac_t xPos; //!< x position of object + frac_t yPos; //!< y position of object + int zPos; //!< z position of object + Common::Rect rcPrev; //!< previous screen coordinates of object bounding rectangle + int flags; //!< object flags - see above for list + PALQ *pPal; //!< objects palette Q position + int constant; //!< which colour in palette for monochrome objects + int width; //!< width of object + int height; //!< height of object + SCNHANDLE hBits; //!< image bitmap handle + SCNHANDLE hImg; //!< handle to object image definition + SCNHANDLE hShape; //!< objects current animation frame + SCNHANDLE hMirror; //!< objects previous animation frame + int oid; //!< object identifier +}; + +#include "common/pack-start.h" // START STRUCT PACKING + +// object initialisation structure +struct OBJ_INIT { + SCNHANDLE hObjImg; // objects shape - handle to IMAGE structure + int32 objFlags; // objects flags + int32 objID; // objects id + int32 objX; // objects initial x position + int32 objY; // objects initial y position + int32 objZ; // objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/*----------------------------------------------------------------------*\ +|* Object Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void KillAllObjects(void); // kill all objects and place them on free list + +void FreeObjectList(void); // free the object list + +#ifdef DEBUG +void ObjectStats(void); // Shows the maximum number of objects used at once +#endif + +OBJECT *AllocObject(void); // allocate a object from the free list + +void FreeObject( // place a object back on the free list + OBJECT *pFreeObj); // object to free + +void CopyObject( // copy one object to another + OBJECT *pDest, // destination object + OBJECT *pSrc); // source object + +void InsertObject( // insert a object onto a sorted object list + OBJECT *pObjList, // list to insert object onto + OBJECT *pInsObj); // object to insert + +void DelObject( // delete a object from a object list and add to free list + OBJECT *pObjList, // list to delete object from + OBJECT *pDelObj); // object to delete + +void SortObjectList( // re-sort an object list + OBJECT *pObjList); // list to sort + +OBJECT *GetNextObject( // object list iterator - returns next obj in list + OBJECT *pObjList, // which object list + OBJECT *pStrtObj); // object to start from - when NULL will start from beginning of list + +OBJECT *FindObject( // Searches the specified obj list for a object matching the specified OID + OBJECT *pObjList, // object list to search + int oidDesired, // object identifer of object to find + int oidMask); // mask to apply to object identifiers before comparison + +void GetAniOffset( // returns the anim offsets of a image, takes into account orientation + SCNHANDLE hImg, // image to get animation offset of + int flags, // images current flags + int *pAniX, // gets set to new X animation offset + int *pAniY); // gets set to new Y animation offset + +void GetAniPosition( // Returns a objects x,y animation point + OBJECT *pObj, // pointer to object + int *pPosX, // gets set to objects X animation position + int *pPosY); // gets set to objects Y animation position + +OBJECT *InitObject( // Init a object using a OBJ_INIT struct + const OBJ_INIT *pInitTbl); // pointer to object initialisation table + +void AnimateObjectFlags( // Give a object a new image and new orientation flags + OBJECT *pAniObj, // object to be updated + int newflags, // objects new flags + SCNHANDLE hNewImg); // objects new image + +void AnimateObject( // give a object a new image + OBJECT *pAniObj, // object to animate + SCNHANDLE hNewImg); // objects new image + +void HideObject( // Hides a object by giving it a "NullImage" image pointer + OBJECT *pObj); // object to be hidden + +OBJECT *RectangleObject( // create a rectangle object of the given dimensions + SCNHANDLE hPal, // palette for the rectangle object + int colour, // which colour offset from the above palette + int width, // width of rectangle + int height); // height of rectangle + +OBJECT *TranslucentObject( // create a translucent rectangle object of the given dimensions + int width, // width of rectangle + int height); // height of rectangle + +void ResizeRectangle( // resizes a rectangle object + OBJECT *pRect, // rectangle object pointer + int width, // new width of rectangle + int height); // new height of rectangle + + +// FIXME: This does not belong here +struct FILM; +struct FREEL; +struct MULTI_INIT; +IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0); +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0, + const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0); + + +} // end of namespace Tinsel + +#endif // TINSEL_OBJECT_H diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp new file mode 100644 index 0000000000..3bc2b514b5 --- /dev/null +++ b/engines/tinsel/palette.cpp @@ -0,0 +1,440 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Palette Allocator for IBM PC. + */ + +#include "tinsel/dw.h" // TBLUE1 definition +#include "tinsel/graphics.h" +#include "tinsel/handle.h" // LockMem definition +#include "tinsel/palette.h" // palette allocator structures etc. +#include "tinsel/tinsel.h" + +#include "common/system.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +/** video DAC transfer Q structure */ +struct VIDEO_DAC_Q { + union { + SCNHANDLE hRGBarray; //!< handle of palette or + COLORREF *pRGBarray; //!< list of palette colours + } pal; + bool bHandle; //!< when set - use handle of palette + int destDACindex; //!< start index of palette in video DAC + int numColours; //!< number of colours in "hRGBarray" +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +/** background colour */ +static COLORREF bgndColour = BLACK; + +/** palette allocator data */ +static PALQ palAllocData[NUM_PALETTES]; + + +/** video DAC transfer Q length */ +#define VDACQLENGTH (NUM_PALETTES+2) + +/** video DAC transfer Q */ +static VIDEO_DAC_Q vidDACdata[VDACQLENGTH]; + +/** video DAC transfer Q head pointer */ +static VIDEO_DAC_Q *pDAChead; + +/** colour index of the 4 colours used for the translucent palette */ +#define COL_HILIGHT TBLUE1 + +/** the translucent palette lookup table */ +uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp + +#ifdef DEBUG +// diagnostic palette counters +static int numPals = 0; +static int maxPals = 0; +static int maxDACQ = 0; +#endif + +/** + * Transfer palettes in the palette Q to Video DAC. + */ +void PalettesToVideoDAC(void) { + PALQ *pPalQ; // palette Q iterator + VIDEO_DAC_Q *pDACtail = vidDACdata; // set tail pointer + bool needUpdate = false; + + // while Q is not empty + while (pDAChead != pDACtail) { + PALETTE *pPalette; // pointer to hardware palette + COLORREF *pColours; // pointer to list of RGB triples + +#ifdef DEBUG + // make sure palette does not overlap + assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS); +#else + // make sure palette does not overlap + if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS) + pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex; +#endif + + if (pDACtail->bHandle) { + // we are using a palette handle + + // get hardware palette pointer + pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray); + + // get RGB pointer + pColours = pPalette->palRGB; + } else { + // we are using a palette pointer + pColours = pDACtail->pal.pRGBarray; + } + + if (pDACtail->numColours > 0) + needUpdate = true; + + // update the system palette + g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours); + + // update tail pointer + pDACtail++; + + } + + // reset video DAC transfer Q head pointer + pDAChead = vidDACdata; + + // clear all palette moved bits + for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++) + pPalQ->posInDAC &= ~PALETTE_MOVED; + + if (needUpdate) + g_system->updateScreen(); +} + +/** + * Commpletely reset the palette allocator. + */ +void ResetPalAllocator(void) { +#ifdef DEBUG + // clear number of palettes in use + numPals = 0; +#endif + + // wipe out the palette allocator data + memset(palAllocData, 0, sizeof(palAllocData)); + + // reset video DAC transfer Q head pointer + pDAChead = vidDACdata; +} + +#ifdef DEBUG +/** + * Shows the maximum number of palettes used at once. + */ +void PaletteStats(void) { + printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES); + printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH); +} +#endif + +/** + * Places a palette in the video DAC queue. + * @param posInDAC Position in video DAC + * @param numColours Number of colours in palette + * @param hPalette Handle to palette + */ +void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) { + // check Q overflow + assert(pDAChead < vidDACdata + VDACQLENGTH); + + pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC + pDAChead->numColours = numColours; // set number of colours + pDAChead->pal.hRGBarray = hPalette; // set handle of palette + pDAChead->bHandle = true; // we are using a palette handle + + // update head pointer + ++pDAChead; + +#ifdef DEBUG + if ((pDAChead-vidDACdata) > maxDACQ) + maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Places a palette in the video DAC queue. + * @param posInDAC Position in video DAC + * @param numColours, Number of colours in palette + * @param pColours List of RGB triples + */ +void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) { + // check Q overflow + assert(pDAChead < vidDACdata + NUM_PALETTES); + + pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC + pDAChead->numColours = numColours; // set number of colours + pDAChead->pal.pRGBarray = pColours; // set addr of palette + pDAChead->bHandle = false; // we are not using a palette handle + + // update head pointer + ++pDAChead; + +#ifdef DEBUG + if ((pDAChead-vidDACdata) > maxDACQ) + maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Allocate a palette. + * @param hNewPal Palette to allocate + */ +PALQ *AllocPalette(SCNHANDLE hNewPal) { + PALQ *pPrev, *p; // walks palAllocData + int iDAC; // colour index in video DAC + PALQ *pNxtPal; // next PALQ struct in palette allocator + PALETTE *pNewPal; + + // get pointer to new palette + pNewPal = (PALETTE *)LockMem(hNewPal); + + // search all structs in palette allocator - see if palette already allocated + for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { + if (p->hPal == hNewPal) { + // found the desired palette in palette allocator + p->objCount++; // update number of objects using palette + return p; // return palette queue position + } + } + + // search all structs in palette allocator - find a free slot + iDAC = FGND_DAC_INDEX; // init DAC index to first available foreground colour + + for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { + if (p->hPal == 0) { + // found a free slot in palette allocator + p->objCount = 1; // init number of objects using palette + p->posInDAC = iDAC; // set palettes start pos in video DAC + p->hPal = hNewPal; // set hardware palette data + p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette + +#ifdef DEBUG + // one more palette in use + if (++numPals > maxPals) + maxPals = numPals; +#endif + + // Q the change to the video DAC + UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal); + + // move all palettes after this one down (if necessary) + for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) { + if (pNxtPal->hPal != 0) { + // palette slot is in use + if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours) + // no need to move palettes down + break; + + // move palette down - indicate change + pNxtPal->posInDAC = pPrev->posInDAC + + pPrev->numColours | PALETTE_MOVED; + + // Q the palette change in position to the video DAC + UpdateDACqueueHandle(pNxtPal->posInDAC, + pNxtPal->numColours, + pNxtPal->hPal); + + // update previous palette to current palette + pPrev = pNxtPal; + } + } + + // return palette pointer + return p; + } + + // set new DAC index + iDAC = p->posInDAC + p->numColours; + } + + // no free palettes + error("AllocPalette(): formally 'assert(0)!'"); +} + +/** + * Free a palette allocated with "AllocPalette". + * @param pFreePal Palette queue entry to free + */ +void FreePalette(PALQ *pFreePal) { + // validate palette Q pointer + assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1); + + // reduce the palettes object reference count + pFreePal->objCount--; + + // make sure palette has not been deallocated too many times + assert(pFreePal->objCount >= 0); + + if (pFreePal->objCount == 0) { + pFreePal->hPal = 0; // palette is no longer in use + +#ifdef DEBUG + // one less palette in use + --numPals; + assert(numPals >= 0); +#endif + } +} + +/** + * Find the specified palette. + * @param hSrchPal Hardware palette to search for + */ +PALQ *FindPalette(SCNHANDLE hSrchPal) { + PALQ *pPal; // palette allocator iterator + + // search all structs in palette allocator + for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) { + if (pPal->hPal == hSrchPal) + // found palette in palette allocator + return pPal; + } + + // palette not found + return NULL; +} + +/** + * Swaps the palettes at the specified palette queue position. + * @param pPalQ Palette queue position + * @param hNewPal New palette + */ +void SwapPalette(PALQ *pPalQ, SCNHANDLE hNewPal) { + // convert handle to palette pointer + PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal); + + // validate palette Q pointer + assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1); + + if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) { + // new palette will fit the slot + + // install new palette + pPalQ->hPal = hNewPal; + + // Q the change to the video DAC + UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal); + } else { + // # colours are different - will have to update all following palette entries + + PALQ *pNxtPalQ; // next palette queue position + + for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) { + if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours) + // no need to move palettes down + break; + + // move palette down + pNxtPalQ->posInDAC = pPalQ->posInDAC + + pPalQ->numColours | PALETTE_MOVED; + + // Q the palette change in position to the video DAC + UpdateDACqueueHandle(pNxtPalQ->posInDAC, + pNxtPalQ->numColours, + pNxtPalQ->hPal); + + // update previous palette to current palette + pPalQ = pNxtPalQ; + } + } +} + +/** + * Statless palette iterator. Returns the next palette in the list + * @param pStrtPal Palette to start from - when NULL will start from beginning of list + */ +PALQ *GetNextPalette(PALQ *pStrtPal) { + if (pStrtPal == NULL) { + // start of palette iteration - return 1st palette + return (palAllocData[0].objCount) ? palAllocData : NULL; + } + + // validate palette Q pointer + assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1); + + // return next active palette in list + while (++pStrtPal < palAllocData + NUM_PALETTES) { + if (pStrtPal->objCount) + // active palette found + return pStrtPal; + } + + // non found + return NULL; +} + +/** + * Sets the current background colour. + * @param colour Colour to set the background to + */ +void SetBgndColour(COLORREF colour) { + // update background colour struct + bgndColour = colour; + + // Q the change to the video DAC + UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour); +} + +/** + * Builds the translucent palette from the current backgrounds palette. + * @param hPalette Handle to current background palette + */ +void CreateTranslucentPalette(SCNHANDLE hPalette) { + // get a pointer to the palette + PALETTE *pPal = (PALETTE *)LockMem(hPalette); + + // leave background colour alone + transPalette[0] = 0; + + for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) { + // get the RGB colour model values + uint8 red = GetRValue(pPal->palRGB[i]); + uint8 green = GetGValue(pPal->palRGB[i]); + uint8 blue = GetBValue(pPal->palRGB[i]); + + // calculate the Value field of the HSV colour model + unsigned val = (red > green) ? red : green; + val = (val > blue) ? val : blue; + + // map the Value field to one of the 4 colours reserved for the translucent palette + val /= 63; + transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h new file mode 100644 index 0000000000..fdc4826dbd --- /dev/null +++ b/engines/tinsel/palette.h @@ -0,0 +1,144 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Palette Allocator Definitions + */ + +#ifndef TINSEL_PALETTE_H // prevent multiple includes +#define TINSEL_PALETTE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +typedef uint32 COLORREF; + +#define RGB(r,g,b) ((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16))) + +#define GetRValue(rgb) ((uint8)(FROM_LE_32(rgb))) +#define GetGValue(rgb) ((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8)) +#define GetBValue(rgb) ((uint8)((FROM_LE_32(rgb))>>16)) + +enum { + MAX_COLOURS = 256, //!< maximum number of colours - for VGA 256 + BITS_PER_PIXEL = 8, //!< number of bits per pixel for VGA 256 + MAX_INTENSITY = 255, //!< the biggest value R, G or B can have + NUM_PALETTES = 3, //!< number of palettes + + // Discworld has some fixed apportioned bits in the palette. + BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC + FGND_DAC_INDEX = 1, //!< index of first foreground colour in Video DAC + TBLUE1 = 228, //!< Blue used in translucent rectangles + TBLUE2 = 229, //!< Blue used in translucent rectangles + TBLUE3 = 230, //!< Blue used in translucent rectangles + TBLUE4 = 231, //!< Blue used in translucent rectangles + TALKFONT_COL = 233 +}; + +// some common colours + +#define BLACK (RGB(0, 0, 0)) +#define WHITE (RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY)) +#define RED (RGB(MAX_INTENSITY, 0, 0)) +#define GREEN (RGB(0, MAX_INTENSITY, 0)) +#define BLUE (RGB(0, 0, MAX_INTENSITY)) +#define YELLOW (RGB(MAX_INTENSITY, MAX_INTENSITY, 0)) +#define MAGENTA (RGB(MAX_INTENSITY, 0, MAX_INTENSITY)) +#define CYAN (RGB(0, MAX_INTENSITY, MAX_INTENSITY)) + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** hardware palette structure */ +struct PALETTE { + int32 numColours; //!< number of colours in the palette + COLORREF palRGB[MAX_COLOURS]; //!< actual palette colours +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/** palette queue structure */ +struct PALQ { + SCNHANDLE hPal; //!< handle to palette data struct + int objCount; //!< number of objects using this palette + int posInDAC; //!< palette position in the video DAC + int numColours; //!< number of colours in the palette +}; + + +#define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC" + // field - the palette entry has moved + +// Translucent objects have NULL pPal +#define HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED)) + + +/*----------------------------------------------------------------------*\ +|* Palette Manager Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ResetPalAllocator(void); // wipe out all palettes + +#ifdef DEBUG +void PaletteStats(void); // Shows the maximum number of palettes used at once +#endif + +void PalettesToVideoDAC(void); // Update the video DAC with palettes currently the the DAC queue + +void UpdateDACqueueHandle( + int posInDAC, // position in video DAC + int numColours, // number of colours in palette + SCNHANDLE hPalette); // handle to palette + +void UpdateDACqueue( // places a palette in the video DAC queue + int posInDAC, // position in video DAC + int numColours, // number of colours in palette + COLORREF *pColours); // list of RGB tripples + +PALQ *AllocPalette( // allocate a new palette + SCNHANDLE hNewPal); // palette to allocate + +void FreePalette( // free a palette allocated with "AllocPalette" + PALQ *pFreePal); // palette queue entry to free + +PALQ *FindPalette( // find a palette in the palette queue + SCNHANDLE hSrchPal); // palette to search for + +void SwapPalette( // swaps palettes at the specified palette queue position + PALQ *pPalQ, // palette queue position + SCNHANDLE hNewPal); // new palette + +PALQ *GetNextPalette( // returns the next palette in the queue + PALQ *pStrtPal); // queue position to start from - when NULL will start from beginning of queue + +COLORREF GetBgndColour(void); // returns current background colour + +void SetBgndColour( // sets current background colour + COLORREF colour); // colour to set the background to + +void CreateTranslucentPalette(SCNHANDLE BackPal); + +} // end of namespace Tinsel + +#endif // TINSEL_PALETTE_H diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp new file mode 100644 index 0000000000..023417fe3c --- /dev/null +++ b/engines/tinsel/pcode.cpp @@ -0,0 +1,593 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Virtual processor. + */ + +#include "tinsel/dw.h" +#include "tinsel/events.h" // 'POINTED' etc. +#include "tinsel/handle.h" // LockMem() +#include "tinsel/inventory.h" // for inventory id's +#include "tinsel/pcode.h" // opcodes etc. +#include "tinsel/scn.h" // FindChunk() +#include "tinsel/serializer.h" +#include "tinsel/tinlib.h" // Library routines + +#include "common/util.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState); + +//----------------- LOCAL DEFINES -------------------- + +/** list of all opcodes */ +enum OPCODE { + OP_HALT = 0, //!< end of program + OP_IMM = 1, //!< loads signed immediate onto stack + OP_ZERO = 2, //!< loads zero onto stack + OP_ONE = 3, //!< loads one onto stack + OP_MINUSONE = 4, //!< loads minus one onto stack + OP_STR = 5, //!< loads string offset onto stack + OP_FILM = 6, //!< loads film offset onto stack + OP_FONT = 7, //!< loads font offset onto stack + OP_PAL = 8, //!< loads palette offset onto stack + OP_LOAD = 9, //!< loads local variable onto stack + OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable + OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable + OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable + OP_CALL = 13, //!< procedure call + OP_LIBCALL = 14, //!< library procedure call - long offset to procedure + OP_RET = 15, //!< procedure return + OP_ALLOC = 16, //!< allocate storage on stack + OP_JUMP = 17, //!< unconditional jump - signed word offset + OP_JMPFALSE = 18, //!< conditional jump - signed word offset + OP_JMPTRUE = 19, //!< conditional jump - signed word offset + OP_EQUAL = 20, //!< tests top two items on stack for equality + OP_LESS, //!< tests top two items on stack + OP_LEQUAL, //!< tests top two items on stack + OP_NEQUAL, //!< tests top two items on stack + OP_GEQUAL, //!< tests top two items on stack + OP_GREAT = 25, //!< tests top two items on stack + OP_PLUS, //!< adds top two items on stack and replaces with result + OP_MINUS, //!< subs top two items on stack and replaces with result + OP_LOR, //!< logical or of top two items on stack and replaces with result + OP_MULT, //!< multiplies top two items on stack and replaces with result + OP_DIV = 30, //!< divides top two items on stack and replaces with result + OP_MOD, //!< divides top two items on stack and replaces with modulus + OP_AND, //!< bitwise ands top two items on stack and replaces with result + OP_OR, //!< bitwise ors top two items on stack and replaces with result + OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result + OP_LAND = 35, //!< logical ands top two items on stack and replaces with result + OP_NOT, //!< logical nots top item on stack + OP_COMP, //!< complements top item on stack + OP_NEG, //!< negates top item on stack + OP_DUP, //!< duplicates top item on stack + OP_ESCON = 40, //!< start of escapable sequence + OP_ESCOFF = 41, //!< end of escapable sequence + OP_CIMM, //!< loads signed immediate onto stack (special to case statements) + OP_CDFILM //!< loads film offset onto stack but not in current scene +}; + +// modifiers for the above opcodes +#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits +#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits + +#define OPMASK 0x3F //!< mask to isolate the opcode + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int32 *pGlobals = 0; // global vars + +static int numGlobals = 0; // How many global variables to save/restore + +static INT_CONTEXT *icList = 0; + +/** + * Keeps the code array pointer up to date. + */ +void LockCode(INT_CONTEXT *ic) { + if (ic->GSort == GS_MASTER) + ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE); + else + ic->code = (byte *)LockMem(ic->hCode); +} + +/** + * Find a free interpret context and allocate it to the calling process. + */ +static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) { + INT_CONTEXT *pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort == GS_NONE) { + pic->pProc = g_scheduler->getCurrentProcess(); + pic->GSort = gsort; + return pic; + } +#ifdef DEBUG + else { + if (pic->pProc == g_scheduler->getCurrentProcess()) + error("Found unreleased interpret context"); + } +#endif + } + + error("Out of interpret contexts"); +} + +/** + * Normal release of an interpret context. + * Called from the end of Interpret(). + */ +static void FreeInterpretContextPi(INT_CONTEXT *pic) { + pic->GSort = GS_NONE; +} + +/** + * Free interpret context owned by a dying process. + * Ensures that interpret contexts don't get lost when an Interpret() + * call doesn't complete. + */ +void FreeInterpretContextPr(PROCESS *pProc) { + INT_CONTEXT *pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort != GS_NONE && pic->pProc == pProc) { + pic->GSort = GS_NONE; + break; + } + } +} + +/** + * Free all interpret contexts except for the master script's + */ +void FreeMostInterpretContexts(void) { + INT_CONTEXT *pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort != GS_MASTER) { + pic->GSort = GS_NONE; + } + } +} + +/** + * Free the master script's interpret context. + */ +void FreeMasterInterpretContext(void) { + INT_CONTEXT *pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort == GS_MASTER) { + pic->GSort = GS_NONE; + return; + } + } +} + +/** + * Allocate and initialise an interpret context. + * Called from a process prior to Interpret(). + * @param gsort which sort of code + * @param hCode Handle to code to execute + * @param event Causal event + * @param hpoly Associated polygon (if any) + * @param actorId Associated actor (if any) + * @param pinvo Associated inventory object + */ +INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event, + HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo) { + INT_CONTEXT *ic; + + ic = AllocateInterpretContext(gsort); + + // Previously parameters to Interpret() + ic->hCode = hCode; + LockCode(ic); + ic->event = event; + ic->hpoly = hpoly; + ic->actorid = actorid; + ic->pinvo = pinvo; + + // Previously local variables in Interpret() + ic->bHalt = false; // set to exit interpeter + ic->escOn = false; + ic->myescEvent = 0; // only initialised to prevent compiler warning! + ic->sp = 0; + ic->bp = ic->sp + 1; + ic->ip = 0; // start of code + + ic->resumeState = RES_NOT; + + return ic; +} + +/** + * Allocate and initialise an interpret context with restored data. + */ +INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) { + INT_CONTEXT *ic; + + ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden + + memcpy(ic, ric, sizeof(INT_CONTEXT)); + ic->pProc = g_scheduler->getCurrentProcess(); + ic->resumeState = RES_1; + + LockCode(ic); + + return ic; +} + +/** + * Allocates enough RAM to hold the global Glitter variables. + */ +void RegisterGlobals(int num) { + if (pGlobals == NULL) { + numGlobals = num; + + // Allocate RAM for pGlobals and make sure it's allocated + pGlobals = (int32 *)calloc(numGlobals, sizeof(int32)); + if (pGlobals == NULL) { + error("Cannot allocate memory for global data"); + } + + // Allocate RAM for interpret contexts and make sure it's allocated + icList = (INT_CONTEXT *)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT)); + if (icList == NULL) { + error("Cannot allocate memory for interpret contexts"); + } + + g_scheduler->setResourceCallback(FreeInterpretContextPr); + } else { + // Check size is still the same + assert(numGlobals == num); + + memset(pGlobals, 0, numGlobals * sizeof(int32)); + memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT)); + } +} + +void FreeGlobals(void) { + free(pGlobals); + pGlobals = NULL; + + free(icList); + icList = NULL; +} + +/** + * (Un)serialize the global data for save/restore game. + */ +void syncGlobInfo(Serializer &s) { + for (int i = 0; i < numGlobals; i++) { + s.syncAsSint32LE(pGlobals[i]); + } +} + +/** + * (Un)serialize an interpreter context for save/restore game. + */ +void INT_CONTEXT::syncWithSerializer(Serializer &s) { + if (s.isLoading()) { + // Null out the pointer fields + pProc = NULL; + code = NULL; + pinvo = NULL; + } + // Write out used fields + s.syncAsUint32LE(GSort); + s.syncAsUint32LE(hCode); + s.syncAsUint32LE(event); + s.syncAsSint32LE(hpoly); + s.syncAsSint32LE(actorid); + + for (int i = 0; i < PCODE_STACK_SIZE; ++i) + s.syncAsSint32LE(stack[i]); + + s.syncAsSint32LE(sp); + s.syncAsSint32LE(bp); + s.syncAsSint32LE(ip); + s.syncAsUint32LE(bHalt); + s.syncAsUint32LE(escOn); + s.syncAsSint32LE(myescEvent); +} + +/** + * Return pointer to and size of global data for save/restore game. + */ +void SaveInterpretContexts(INT_CONTEXT *sICInfo) { + memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT)); +} + +/** + * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code + * stream and advance the instruction pointer accordingly. + */ +static int32 Fetch(byte opcode, byte *code, int &ip) { + int32 tmp; + if (opcode & OPSIZE8) { + // Fetch and sign extend a 8 bit value to 32 bits. + tmp = *(int8 *)(code + ip); + ip += 1; + } else if (opcode & OPSIZE16) { + // Fetch and sign extend a 16 bit value to 32 bits. + tmp = (int16)READ_LE_UINT16(code + ip); + ip += 2; + } else { + // Fetch a 32 bit value. + tmp = (int32)READ_LE_UINT32(code + ip); + ip += 4; + } + return tmp; +} + +/** + * Interprets the PCODE instructions in the code array. + */ +void Interpret(CORO_PARAM, INT_CONTEXT *ic) { + do { + int tmp, tmp2; + int ip = ic->ip; + byte opcode = ic->code[ip++]; + debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK); + switch (opcode & OPMASK) { + case OP_HALT: // end of program + + ic->bHalt = true; + break; + + case OP_IMM: // loads immediate data onto stack + case OP_STR: // loads string handle onto stack + case OP_FILM: // loads film handle onto stack + case OP_CDFILM: // loads film handle onto stack + case OP_FONT: // loads font handle onto stack + case OP_PAL: // loads palette handle onto stack + + ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip); + break; + + case OP_ZERO: // loads zero onto stack + ic->stack[++ic->sp] = 0; + break; + + case OP_ONE: // loads one onto stack + ic->stack[++ic->sp] = 1; + break; + + case OP_MINUSONE: // loads minus one onto stack + ic->stack[++ic->sp] = -1; + break; + + case OP_LOAD: // loads local variable onto stack + + ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)]; + break; + + case OP_GLOAD: // loads global variable onto stack + + tmp = Fetch(opcode, ic->code, ip); + assert(0 <= tmp && tmp < numGlobals); + ic->stack[++ic->sp] = pGlobals[tmp]; + break; + + case OP_STORE: // pops stack and stores in local variable + + ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--]; + break; + + case OP_GSTORE: // pops stack and stores in global variable + + tmp = Fetch(opcode, ic->code, ip); + assert(0 <= tmp && tmp < numGlobals); + pGlobals[tmp] = ic->stack[ic->sp--]; + break; + + case OP_CALL: // procedure call + + tmp = Fetch(opcode, ic->code, ip); + //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds + ic->stack[ic->sp + 1] = 0; // static link + ic->stack[ic->sp + 2] = ic->bp; // dynamic link + ic->stack[ic->sp + 3] = ip; // return address + ic->bp = ic->sp + 1; // set new base pointer + ip = tmp; // set ip to procedure address + break; + + case OP_LIBCALL: // library procedure or function call + + tmp = Fetch(opcode, ic->code, ip); + // NOTE: Interpret() itself is not using the coroutine facilities, + // but still accepts a CORO_PARAM, so from the outside it looks + // like a coroutine. In fact it may still acts as a kind of "proxy" + // for some underlying coroutine. To enable this, we just pass on + // 'coroParam' to CallLibraryRoutine(). If we then detect that + // coroParam was set to a non-zero value, this means that some + // coroutine code did run at some point, and we are now supposed + // to sleep or die -- hence, we 'return' if coroParam != 0. + // + // This works because Interpret() is fully re-entrant: If we return + // now and are later called again, then we will end up in the very + // same spot (i.e. here). + // + // The reasons we do it this way, instead of turning Interpret into + // a 'proper' coroutine are (1) we avoid implementation problems + // (CORO_INVOKE involves adding 'case' statements, but Interpret + // already has a huge switch/case, so that would not work out of the + // box), (2) we incurr less overhead, (3) it's easier to debug, + // (4) it's simply cool ;). + tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState); + if (coroParam) + return; + ic->sp += tmp2; + LockCode(ic); + break; + + case OP_RET: // procedure return + + ic->sp = ic->bp - 1; // restore stack + ip = ic->stack[ic->sp + 3]; // return address + ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer + break; + + case OP_ALLOC: // allocate storage on stack + + ic->sp += Fetch(opcode, ic->code, ip); + break; + + case OP_JUMP: // unconditional jump + + ip = Fetch(opcode, ic->code, ip); + break; + + case OP_JMPFALSE: // conditional jump + + tmp = Fetch(opcode, ic->code, ip); + if (ic->stack[ic->sp--] == 0) { + // condition satisfied - do the jump + ip = tmp; + } + break; + + case OP_JMPTRUE: // conditional jump + + tmp = Fetch(opcode, ic->code, ip); + if (ic->stack[ic->sp--] != 0) { + // condition satisfied - do the jump + ip = tmp; + } + break; + + case OP_EQUAL: // tests top two items on stack for equality + case OP_LESS: // tests top two items on stack + case OP_LEQUAL: // tests top two items on stack + case OP_NEQUAL: // tests top two items on stack + case OP_GEQUAL: // tests top two items on stack + case OP_GREAT: // tests top two items on stack + case OP_LOR: // logical or of top two items on stack and replaces with result + case OP_LAND: // logical ands top two items on stack and replaces with result + + // pop one operand + ic->sp--; + assert(ic->sp >= 0); + tmp = ic->stack[ic->sp]; + tmp2 = ic->stack[ic->sp + 1]; + + // replace other operand with result of operation + switch (opcode) { + case OP_EQUAL: tmp = (tmp == tmp2); break; + case OP_LESS: tmp = (tmp < tmp2); break; + case OP_LEQUAL: tmp = (tmp <= tmp2); break; + case OP_NEQUAL: tmp = (tmp != tmp2); break; + case OP_GEQUAL: tmp = (tmp >= tmp2); break; + case OP_GREAT: tmp = (tmp > tmp2); break; + + case OP_LOR: tmp = (tmp || tmp2); break; + case OP_LAND: tmp = (tmp && tmp2); break; + } + + ic->stack[ic->sp] = tmp; + break; + + case OP_PLUS: // adds top two items on stack and replaces with result + case OP_MINUS: // subs top two items on stack and replaces with result + case OP_MULT: // multiplies top two items on stack and replaces with result + case OP_DIV: // divides top two items on stack and replaces with result + case OP_MOD: // divides top two items on stack and replaces with modulus + case OP_AND: // bitwise ands top two items on stack and replaces with result + case OP_OR: // bitwise ors top two items on stack and replaces with result + case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result + + // pop one operand + ic->sp--; + assert(ic->sp >= 0); + tmp = ic->stack[ic->sp]; + tmp2 = ic->stack[ic->sp + 1]; + + // replace other operand with result of operation + switch (opcode) { + case OP_PLUS: tmp += tmp2; break; + case OP_MINUS: tmp -= tmp2; break; + case OP_MULT: tmp *= tmp2; break; + case OP_DIV: tmp /= tmp2; break; + case OP_MOD: tmp %= tmp2; break; + case OP_AND: tmp &= tmp2; break; + case OP_OR: tmp |= tmp2; break; + case OP_EOR: tmp ^= tmp2; break; + } + ic->stack[ic->sp] = tmp; + break; + + case OP_NOT: // logical nots top item on stack + + ic->stack[ic->sp] = !ic->stack[ic->sp]; + break; + + case OP_COMP: // complements top item on stack + ic->stack[ic->sp] = ~ic->stack[ic->sp]; + break; + + case OP_NEG: // negates top item on stack + ic->stack[ic->sp] = -ic->stack[ic->sp]; + break; + + case OP_DUP: // duplicates top item on stack + ic->stack[ic->sp + 1] = ic->stack[ic->sp]; + ic->sp++; + break; + + case OP_ESCON: + ic->escOn = true; + ic->myescEvent = GetEscEvents(); + break; + + case OP_ESCOFF: + ic->escOn = false; + break; + + default: + error("Interpret() - Unknown opcode"); + } + + // check for stack under-overflow + assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE); + ic->ip = ip; + } while (!ic->bHalt); + + // make sure stack is unwound + assert(ic->sp == 0); + + FreeInterpretContextPi(ic); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h new file mode 100644 index 0000000000..1c7e0a942c --- /dev/null +++ b/engines/tinsel/pcode.h @@ -0,0 +1,155 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Virtual processor definitions + */ + +#ifndef TINSEL_PCODE_H // prevent multiple includes +#define TINSEL_PCODE_H + +#include "tinsel/events.h" // for USER_EVENT +#include "tinsel/sched.h" // for PROCESS + +namespace Tinsel { + +// forward declaration +class Serializer; +struct INV_OBJECT; + +enum RESUME_STATE { + RES_NOT, RES_1, RES_2 +}; + +enum { + PCODE_STACK_SIZE = 128 //!< interpeters stack size +}; + +enum GSORT { + GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE +}; + +struct INT_CONTEXT { + + // Elements for interpret context management + PROCESS *pProc; //!< processes owning this context + GSORT GSort; //!< sort of this context + + // Previously parameters to Interpret() + SCNHANDLE hCode; //!< scene handle of the code to execute + byte *code; //!< pointer to the code to execute + USER_EVENT event; //!< causal event + HPOLYGON hpoly; //!< associated polygon (if any) + int actorid; //!< associated actor (if any) + INV_OBJECT *pinvo; //!< associated inventory object + + // Previously local variables in Interpret() + int32 stack[PCODE_STACK_SIZE]; //!< interpeters run time stack + int sp; //!< stack pointer + int bp; //!< base pointer + int ip; //!< instruction pointer + bool bHalt; //!< set to exit interpeter + bool escOn; + int myescEvent; //!< only initialised to prevent compiler warning! + + RESUME_STATE resumeState; + + void syncWithSerializer(Serializer &s); +}; + + +/*----------------------------------------------------------------------*\ +|* Interpreter Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void Interpret(CORO_PARAM, INT_CONTEXT *ic); // Interprets the PCODE instructions in the code array + +INT_CONTEXT *InitInterpretContext( + GSORT gsort, + SCNHANDLE hCode, // code to execute + USER_EVENT event, // causal event + HPOLYGON hpoly, // associated polygon (if any) + int actorid, // associated actor (if any) + INV_OBJECT *pinvo); // associated inventory object + +INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric); + +void FreeMostInterpretContexts(void); +void FreeMasterInterpretContext(void); + +void SaveInterpretContexts(INT_CONTEXT *sICInfo); + +void RegisterGlobals(int num); +void FreeGlobals(void); + + +#define MAX_INTERPRET (NUM_PROCESS - 20) + +/*----------------------------------------------------------------------*\ +|* Library Procedure and Function codes parameter enums *| +\*----------------------------------------------------------------------*/ + +#define TAG_DEF 0 // For tagactor() +#define TAG_Q1TO3 1 // tag types +#define TAG_Q1TO4 2 // tag types + +#define CONV_DEF 0 // +#define CONV_BOTTOM 1 // conversation() parameter +#define CONV_END 2 // + +#define CONTROL_OFF 0 // control() +#define CONTROL_ON 1 // parameter +#define CONTROL_OFFV 2 // +#define CONTROL_OFFV2 3 // +#define CONTROL_STARTOFF 4 // + +#define NULL_ACTOR (-1) // For actor parameters +#define LEAD_ACTOR (-2) // + +#define RAND_NORM 0 // For random() frills +#define RAND_NORPT 1 // + +#define D_UP 1 +#define D_DOWN 0 + +#define TW_START 1 // topwindow() parameter +#define TW_END 2 // + +#define MIDI_DEF 0 +#define MIDI_LOOP 1 + +#define TRANS_DEF 0 +#define TRANS_CUT 1 +#define TRANS_FADE 2 + +#define FM_IN 0 // +#define FM_OUT 1 // fademidi() + +#define FG_ON 0 // +#define FG_OFF 1 // FrameGrab() + +#define ST_ON 0 // +#define ST_OFF 1 // SubTitles() + +} // end of namespace Tinsel + +#endif // TINSEL_PCODE_H diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp new file mode 100644 index 0000000000..b5488da3e8 --- /dev/null +++ b/engines/tinsel/pdisplay.cpp @@ -0,0 +1,652 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * CursorPositionProcess() + * TagProcess() + * PointProcess() + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +//extern int Overrun; // The overrun counter, in DOS_DW.C + +extern int newestString; // The overrun counter, in STRRES.C +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LPOSX 295 // X-co-ord of lead actor's position display +#define CPOSX 24 // X-co-ord of cursor's position display +#define OPOSX SCRN_CENTRE_X // X-co-ord of overrun counter's display +#define SPOSX SCRN_CENTRE_X // X-co-ord of string numbner's display + +#define POSY 0 // Y-co-ord of these position displays + +enum HotSpotTag { + NO_HOTSPOT_TAG, + POLY_HOTSPOT_TAG, + ACTOR_HOTSPOT_TAG +}; + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool DispPath = false; +static bool bShowString = false; + +static int TaggedActor = 0; +static HPOLYGON hTaggedPolygon = NOPOLY; + +static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON; + + +#ifdef DEBUG +/** + * Displays the cursor and lead actor's co-ordinates and the overrun + * counter. Also which path polygon the cursor is in, if required. + * + * This process is only started up if a Glitter showpos() call is made. + * Obviously, this is for testing purposes only... + */ +void CursorPositionProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int prevsX, prevsY; // Last screen top left + int prevcX, prevcY; // Last displayed cursor position + int prevlX, prevlY; // Last displayed lead actor position +// int prevOver; // Last displayed overrun + int prevString; // Last displayed string number + + OBJECT *cpText; // cursor position text object pointer + OBJECT *cpathText; // cursor path text object pointer + OBJECT *rpText; // text object pointer +// OBJECT *opText; // text object pointer + OBJECT *spText; // string number text object pointer + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->prevsX = -1; + _ctx->prevsY = -1; + _ctx->prevcX = -1; + _ctx->prevcY = -1; + _ctx->prevlX = -1; + _ctx->prevlY = -1; +// _ctx->prevOver = -1; + _ctx->prevString = -1; + + _ctx->cpText = NULL; + _ctx->cpathText = NULL; + _ctx->rpText = NULL; +// _ctx->opText = NULL; + _ctx->spText = NULL; + + + int aniX, aniY; // cursor/lead actor position + int Loffset, Toffset; // Screen top left + + char PositionString[64]; // sprintf() things into here + + PMACTOR pActor; // Lead actor + + while (1) { + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /*-----------------------------------*\ + | Cursor's position and path display. | + \*-----------------------------------*/ + GetCursorXY(&aniX, &aniY, false); + + // Change in cursor position? + if (aniX != _ctx->prevcX || aniY != _ctx->prevcY || + Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { + // kill current text objects + if (_ctx->cpText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText); + } + if (_ctx->cpathText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText); + _ctx->cpathText = NULL; + } + + // New text objects + sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset); + _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + if (DispPath) { + HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH); + if (hp == NOPOLY) + sprintf(PositionString, "No path"); + else + sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d", + PolyCornerX(hp, 0), PolyCornerY(hp, 0), + PolyCornerX(hp, 1), PolyCornerY(hp, 1), + PolyCornerX(hp, 2), PolyCornerY(hp, 2), + PolyCornerX(hp, 3), PolyCornerY(hp, 3)); + _ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, 4, POSY+ 10, hTagFontHandle(), 0); + } + + // update previous position + _ctx->prevcX = aniX; + _ctx->prevcY = aniY; + } + +#if 0 + /*------------------------*\ + | Overrun counter display. | + \*------------------------*/ + if (Overrun != _ctx->prevOver) { + // kill current text objects + if (_ctx->opText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText); + } + + sprintf(PositionString, "%d", Overrun); + _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + + // update previous value + _ctx->prevOver = Overrun; + } +#endif + + /*----------------------*\ + | Lead actor's position. | + \*----------------------*/ + pActor = GetMover(LEAD_ACTOR); + if (pActor && pActor->MActorState == NORM_MACTOR) { + // get lead's animation position + GetActorPos(LEAD_ACTOR, &aniX, &aniY); + + // Change in position? + if (aniX != _ctx->prevlX || aniY != _ctx->prevlY || + Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { + // Kill current text objects + if (_ctx->rpText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText); + } + + // create new text object list + sprintf(PositionString, "%d %d", aniX, aniY); + _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + + // update previous position + _ctx->prevlX = aniX; + _ctx->prevlY = aniY; + } + } + + /*-------------*\ + | String number | + \*-------------*/ + if (bShowString && newestString != _ctx->prevString) { + // kill current text objects + if (_ctx->spText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText); + } + + sprintf(PositionString, "String: %d", newestString); + _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE); + + // update previous value + _ctx->prevString = newestString; + } + + // update previous playfield position + _ctx->prevsX = Loffset; + _ctx->prevsY = Toffset; + + CORO_SLEEP(1); // allow re-scheduling + } + CORO_END_CODE; +} +#endif + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedActor(int ano) { + TaggedActor = ano; +} + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +int GetTaggedActor(void) { + return TaggedActor; +} + +/** + * Tag process keeps us updated as to which polygon is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedPoly(HPOLYGON hp) { + hTaggedPolygon = hp; +} + +HPOLYGON GetTaggedPoly(void) { + return hTaggedPolygon; +} + +/** + * Given cursor position and an actor number, ascertains whether the + * cursor is within the actor's tag area. + * Returns TRUE for a positive result, FALSE for negative. + * If TRUE, the mid-top co-ordinates of the actor's tag area are also + * returned. + */ +static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) { + int Top, Bot; // Top and bottom limits of active area + int left, right; // left and right of active area + int qrt = 0; // 1/4 of height (sometimes 1/2) + + // First check if within x-range + if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) { + Top = GetActorTop(ano); + Bot = GetActorBottom(ano); + + // y-range varies according to tag-type + switch (TagType(ano)) { + case TAG_DEF: + // Next to bottom 1/4 of the actor's area + qrt = (Bot - Top) >> 1; // Half actor's height + Top += qrt; // Top = mid-height + + qrt = qrt >> 1; // Quarter height + Bot -= qrt; // Bot = 1/4 way up + break; + + case TAG_Q1TO3: + // Top 3/4 of the actor's area + qrt = (Bot - Top) >> 2; // 1/4 actor's height + Bot -= qrt; // Bot = 1/4 way up + break; + + case TAG_Q1TO4: + // All the actor's area + break; + + default: + error("illegal tag area type"); + } + + // Now check if within y-range + if (aniY >= Top && aniY <= Bot) { + if (TagType(ano) == TAG_Q1TO3) + *pytext = Top + qrt; + else + *pytext = Top; + *pxtext = (left + right) / 2; + return true; + } + } + return false; +} + +/** + * See if the cursor is over a tagged actor's hot-spot. If so, display + * the tag or, if tag already displayed, maintain the tag's position on + * the screen. + */ +static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) { + static int Loffset = 0, Toffset = 0; // Values when tag was displayed + int nLoff, nToff; // new values, to keep tag in place + int ano; + int xtext, ytext; + bool newActor; + + // For each actor with a tag.... + FirstTaggedActor(); + while ((ano = NextTaggedActor()) != 0) { + if (InHotSpot(ano, curX, curY, &xtext, &ytext)) { + // Put up or maintain actor tag + if (*pTag != ACTOR_HOTSPOT_TAG) + newActor = true; + else if (ano != GetTaggedActor()) + newActor = true; // Different actor + else + newActor = false; // Same actor + + if (newActor) { + // Display actor's tag + + if (*ppText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + + *pTag = ACTOR_HOTSPOT_TAG; + SaveTaggedActor(ano); // This actor tagged + SaveTaggedPoly(NOPOLY); // No tagged polygon + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ); + *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE); + assert(*ppText); // Actor tag string produced NULL text + MultiSetZPosition(*ppText, Z_TAG_TEXT); + } else { + // Maintain actor tag's position + + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != Loffset || nToff != Toffset) { + MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); + Loffset = nLoff; + Toffset = nToff; + } + } + return true; + } + } + + // No tagged actor + if (*pTag == ACTOR_HOTSPOT_TAG) { + *pTag = NO_HOTSPOT_TAG; + SaveTaggedActor(0); + } + return false; +} + +/** + * Perhaps some comment in due course. + * + * Under control of PointProcess(), when the cursor is over a TAG or + * EXIT polygon, its pointState flag is set to POINTING. If its Glitter + * code contains a printtag() call, its tagState flag gets set to TAG_ON. + */ +static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) { + static int Loffset = 0, Toffset = 0; // Values when tag was displayed + int nLoff, nToff; // new values, to keep tag in place + HPOLYGON hp; + bool newPoly; + int shift; + + int tagx, tagy; // Tag display co-ordinates + SCNHANDLE hTagtext; // Tag text + + // For each polgon with a tag.... + for (int i = 0; i < MAX_POLY; i++) { + hp = GetPolyHandle(i); + + // Added code for un-tagged tags + if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) { + // This poly is entitled to be tagged + if (hp != GetTaggedPoly()) { + if (*ppText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + *ppText = NULL; + } + *pTag = POLY_HOTSPOT_TAG; + SaveTaggedActor(0); // No tagged actor + SaveTaggedPoly(hp); // This polygon tagged + } + return true; + } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) { + // Put up or maintain polygon tag + if (*pTag != POLY_HOTSPOT_TAG) + newPoly = true; // A new polygon (no current) + else if (hp != GetTaggedPoly()) + newPoly = true; // Different polygon + else + newPoly = false; // Same polygon + + if (newPoly) { + if (*ppText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + + *pTag = POLY_HOTSPOT_TAG; + SaveTaggedActor(0); // No tagged actor + SaveTaggedPoly(hp); // This polygon tagged + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + getPolyTagInfo(hp, &hTagtext, &tagx, &tagy); + + int strLen; + if (PolyTagHandle(hp) != 0) + strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ); + else + strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ); + + if (strLen == 0) + // No valid string returned, so leave ppText as NULL + ppText = NULL; + else { + // Handle displaying the tag text on-screen + *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, tagx - Loffset, tagy - Toffset, + hTagFontHandle(), TXT_CENTRE); + assert(*ppText); // Polygon tag string produced NULL text + MultiSetZPosition(*ppText, Z_TAG_TEXT); + + + /* + * New feature: Don't go off the side of the background + */ + shift = MultiRightmost(*ppText) + Loffset + 2; + if (shift >= BackgroundWidth()) // Not off right + MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0); + shift = MultiLeftmost(*ppText) + Loffset - 1; + if (shift <= 0) // Not off left + MultiMoveRelXY(*ppText, -shift, 0); + shift = MultiLowest(*ppText) + Toffset; + if (shift > BackgroundHeight()) // Not off bottom + MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift); + } + } else { + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != Loffset || nToff != Toffset) { + MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); + Loffset = nLoff; + Toffset = nToff; + } + } + return true; + } + } + + // No tagged polygon + if (*pTag == POLY_HOTSPOT_TAG) { + *pTag = NO_HOTSPOT_TAG; + SaveTaggedPoly(NOPOLY); + } + return false; +} + +/** + * Handle display of tagged actor and polygon tags. + * Tagged actor's get priority over polygons. + */ +void TagProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + HotSpotTag Tag; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pText = NULL; + _ctx->Tag = NO_HOTSPOT_TAG; + + SaveTaggedActor(0); // No tagged actor yet + SaveTaggedPoly(NOPOLY); // No tagged polygon yet + + while (1) { + if (TagsActive == TAGS_ON) { + int curX, curY; // cursor position + while (!GetCursorXYNoWait(&curX, &curY, true)) + CORO_SLEEP(1); + + if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText) + && !PolyTag(&_ctx->Tag, &_ctx->pText)) { + // Nothing tagged. Remove tag, if there is one + if (_ctx->pText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; + } + } + } else { + SaveTaggedActor(0); + SaveTaggedPoly(NOPOLY); + + // Remove tag, if there is one + if (_ctx->pText) { + // kill current text objects + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; + _ctx->Tag = NO_HOTSPOT_TAG; + } + } + + CORO_SLEEP(1); // allow re-scheduling + } + + CORO_END_CODE; +} + +/** + * Called from PointProcess() as appropriate. + */ +static void enteringpoly(HPOLYGON hp) { + SetPolyPointState(hp, POINTING); + + RunPolyTinselCode(hp, POINTED, BE_NONE, false); +} + +/** + * Called from PointProcess() as appropriate. + */ +static void leavingpoly(HPOLYGON hp) { + SetPolyPointState(hp, NOT_POINTING); + + if (PolyTagState(hp) == TAG_ON) { + // Delete this tag entry + SetPolyTagState(hp, TAG_OFF); + } +} + +/** + * For TAG and EXIT polygons, monitor cursor entering and leaving. + * Maintain the polygons' pointState and tagState flags accordingly. + * Also run the polygon's Glitter code when the cursor enters. + */ +void PointProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (1) { + int aniX, aniY; // cursor/tagged actor position + while (!GetCursorXYNoWait(&aniX, &aniY, true)) + CORO_SLEEP(1); + + /*----------------------------------*\ + | For polygons of type TAG and EXIT. | + \*----------------------------------*/ + for (int i = 0; i < MAX_POLY; i++) { + HPOLYGON hp = GetPolyHandle(i); + + if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) { + if (PolyPointState(hp) == NOT_POINTING) { + if (IsInPolygon(aniX, aniY, hp)) { + enteringpoly(hp); + } + } else if (PolyPointState(hp) == POINTING) { + if (!IsInPolygon(aniX, aniY, hp)) { + leavingpoly(hp); + } + } + } + } + + // allow re-scheduling + CORO_SLEEP(1); + } + + CORO_END_CODE; +} + +void DisableTags(void) { + TagsActive = TAGS_OFF; +} + +void EnableTags(void) { + TagsActive = TAGS_ON; +} + +bool DisableTagsIfEnabled(void) { + if (TagsActive == TAGS_OFF) + return false; + else { + TagsActive = TAGS_OFF; + return true; + } +} + +/** + * For testing purposes only. + * Causes CursorPositionProcess() to display, or not, the path that the + * cursor is in. + */ +void TogglePathDisplay(void) { + DispPath ^= 1; // Toggle path display (XOR with true) +} + + +void setshowstring(void) { + bShowString = true; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h new file mode 100644 index 0000000000..c2af1a5fcb --- /dev/null +++ b/engines/tinsel/pid.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * List of all process identifiers + */ + +#ifndef TINSEL_PID_H // prevent multiple includes +#define TINSEL_PID_H + +namespace Tinsel { + +#define PID_DESTROY 0x8000 // process id of any process that is to be destroyed between scenes + +#define PID_EFFECTS (0x0010 | PID_DESTROY) // generic special effects process id +#define PID_FLASH (PID_EFFECTS + 1) // flash colour process +#define PID_CYCLE (PID_EFFECTS + 2) // cycle colour range process +#define PID_MORPH (PID_EFFECTS + 3) // morph process +#define PID_FADER (PID_EFFECTS + 4) // fader process +#define PID_FADE_BGND (PID_EFFECTS + 5) // fade background colour process + +#define PID_BACKGND (0x0020 | PID_DESTROY) // background update process id + +#define PID_MOUSE 0x0030 // mouse button checking process id + +#define PID_JOYSTICK 0x0040 // joystick button checking process id + +#define PID_KEYBOARD 0x0050 // keyboard scanning process + +#define PID_CURSOR 0x0060 // cursor process +#define PID_CUR_TRAIL (PID_CURSOR + 1) // cursor trail process + +#define PID_SCROLL (0x0070 | PID_DESTROY) // scroll process + +#define PID_INVENTORY 0x0080 // inventory process + +#define PID_POSITION (0x0090 | PID_DESTROY) // cursor position process + +#define PID_TAG (0x00A0 | PID_DESTROY) // tag process + +#define PID_TCODE (0x00B0 | PID_DESTROY) // tinsel code process + +#define PID_MASTER_SCR 0x00C0 // tinsel master script process + +#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process + +#define PID_REEL (0x00E0 | PID_DESTROY) // process for each film reel + +#define PID_MIDI (0x00F0 | PID_DESTROY) // process to poll MIDI sound driver + +} // end of namespace Tinsel + +#endif // TINSEL_PID_H diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp new file mode 100644 index 0000000000..e32fc88d3d --- /dev/null +++ b/engines/tinsel/play.cpp @@ -0,0 +1,507 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Plays films within a scene, takes into account the actor in each 'column'. | + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" // stand() + +namespace Tinsel { + +/** + * Poke the background palette into an image. + */ +static void PokeInPalette(SCNHANDLE hMulFrame) { + const FRAME *pFrame; // Pointer to frame + IMAGE *pim; // Pointer to image + + // Could be an empty column + if (hMulFrame) { + pFrame = (const FRAME *)LockMem(hMulFrame); + + // get pointer to image + pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image + + pim->hImgPal = TO_LE_32(BackPal()); + } +} + + +int32 NoNameFunc(int actorID, bool bNewMover) { + PMACTOR pActor; + int32 retval; + + pActor = GetMover(actorID); + + if (pActor != NULL && !bNewMover) { + // If no path, just use first path in the scene + if (pActor->hCpath == NOPOLY) + retval = getPolyZfactor(FirstPathPoly()); + else + retval = getPolyZfactor(pActor->hCpath); + } else { + switch (actorMaskType(actorID)) { + case ACT_DEFAULT: + retval = 0; + break; + case ACT_MASK: + retval = 0; + break; + case ACT_ALWAYS: + retval = 10; + break; + default: + retval = actorMaskType(actorID); + break; + } + } + + return retval; +} + +struct PPINIT { + SCNHANDLE hFilm; // The 'film' + int16 x; // } Co-ordinates from the play() + int16 y; // } - set to (-1, -1) if none. + int16 z; // normally 0, set if from restore + int16 speed; // Film speed + int16 actorid; // Set if called from an actor code block + uint8 splay; // Set if called from splay() + uint8 bTop; // Set if called from topplay() + int16 sf; // SlowFactor - only used for moving actors + int16 column; // Column number, first column = 0 + + uint8 escOn; + int32 myescEvent; +}; + + +/** + * - Don't bother if this reel is already playing for this actor. + * - If explicit co-ordinates, use these, If embedded co-ordinates, + * leave alone, otherwise use actor's current position. + * - Moving actors get hidden during this play, other actors get + * _ctx->replaced by this play. + * - Column 0 of a film gets its appropriate Z-position, slave columns + * get slightly bigger Z-positions, in column order. + * - Play proceeds until the script finishes, another reel starts up for + * this actor, or the actor gets killed. + * - If called from an splay(), moving actor's co-ordinates are updated + * after the play, any walk still in progress will go on from there. + */ +void PlayReel(CORO_PARAM, const PPINIT *ppi) { + CORO_BEGIN_CONTEXT; + OBJECT *pPlayObj; // Object + ANIM thisAnim; // Animation structure + + bool mActor; // Gets set if this is a moving actor + bool lifeNoMatter; + bool replaced; + + const FREEL *pfreel; // The 'column' to play + int stepCount; + int frameCount; + int reelActor; + CORO_END_CONTEXT(_ctx); + + static int firstColZ = 0; // Z-position of column zero + static int32 fColZfactor = 0; // Z-factor of column zero's actor + + CORO_BEGIN_CODE(_ctx); + + const MULTI_INIT *pmi; // MULTI_INIT structure + PMACTOR pActor; + bool bNewMover; // Gets set if a moving actor that isn't in scene yet + + const FILM *pfilm; + + _ctx->lifeNoMatter = false; + _ctx->replaced = false; + pActor = NULL; + bNewMover = false; + + pfilm = (const FILM *)LockMem(ppi->hFilm); + _ctx->pfreel = &pfilm->reels[ppi->column]; + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj)); + + // Save actor's ID + _ctx->reelActor = (int32)FROM_LE_32(pmi->mulID); + + /**** New (experimental? bit 5/1/95 ****/ + if (!actorAlive(_ctx->reelActor)) + return; + /**** Delete a bit down there if this stays ****/ + + updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent); + + // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios + if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) { + // This in not the last film scheduled for this actor + + // It may be the last non-talk film though + if (isActorTalking(_ctx->reelActor)) + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk + + return; + } + if (isActorTalking(_ctx->reelActor)) { + // Note: will delete this and there'll be no need to store the talk film! + if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) { + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk + return; + } + } else { + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); + } + + // If this reel is already playing for this actor, just forget it. + if (actorReel(_ctx->reelActor) == _ctx->pfreel) + return; + + // Poke in the background palette + PokeInPalette(FROM_LE_32(pmi->hMulFrame)); + + // Set up and insert the multi-object + _ctx->pPlayObj = MultiInitObject(pmi); + if (!ppi->bTop) + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + // If co-ordinates are specified, use specified. + // Otherwise, use actor's position if there are not embedded co-ords. + // Add this first test for nth columns with offsets + // in plays with (x,y) + int tmpX, tmpY; + tmpX = ppi->x; + tmpY = ppi->y; + if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) { + } else if (tmpX != -1 || tmpY != -1) { + MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + } else if (!pmi->mulX && !pmi->mulY) { + GetActorPos(_ctx->reelActor, &tmpX, &tmpY); + MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + } + + // If it's a moving actor, this hides the moving actor + // used to do this only if (actorid == 0) - I don't know why + _ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf); + + // If it's a moving actor, get its MACTOR structure. + // If it isn't in the scene yet, get its task running - using + // stand() - to prevent a glitch at the end of the play. + if (_ctx->mActor) { + pActor = GetMover(_ctx->reelActor); + if (getMActorState(pActor) == NO_MACTOR) { + stand(_ctx->reelActor, MAGICX, MAGICY, 0); + bNewMover = true; + } + } + + // Register the fact that we're playing this for this actor + storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY); + + /**** Will get rid of this if the above is kept ****/ + // We may be temporarily resuscitating a dead actor + if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor)) + _ctx->lifeNoMatter = true; + + InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed); + + // If first column, set Z position as per + // Otherwise, column 0's + column number + // N.B. It HAS been ensured that the first column gets here first + if (ppi->z != 0) { + MultiSetZPosition(_ctx->pPlayObj, ppi->z); + storeActorZpos(_ctx->reelActor, ppi->z); + } else if (ppi->bTop) { + if (ppi->column == 0) { + firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor); + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + storeActorZpos(_ctx->reelActor, firstColZ); + } else { + MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); + storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); + } + } else if (ppi->column == 0) { + if (_ctx->mActor && !bNewMover) { + // If no path, just use first path in the scene + if (pActor->hCpath == NOPOLY) + fColZfactor = getPolyZfactor(FirstPathPoly()); + else + fColZfactor = getPolyZfactor(pActor->hCpath); + firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); + } else { + switch (actorMaskType(_ctx->reelActor)) { + case ACT_DEFAULT: + fColZfactor = 0; + firstColZ = 2; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + case ACT_MASK: + fColZfactor = 0; + firstColZ = MultiLowest(_ctx->pPlayObj); + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + case ACT_ALWAYS: + fColZfactor = 10; + firstColZ = 10000; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + default: + fColZfactor = actorMaskType(_ctx->reelActor); + firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); + if (firstColZ < 2) { + // This is an experiment! + firstColZ = 2; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + } + break; + } + } + storeActorZpos(_ctx->reelActor, firstColZ); + } else { + if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) { + fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover); + firstColZ = fColZfactor << 10; + } + MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); + storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); + } + + /* + * Play until the script finishes, + * another reel starts up for this actor, + * or the actor gets killed. + */ + _ctx->stepCount = 0; + _ctx->frameCount = 0; + do { + if (_ctx->stepCount++ == 0) { + _ctx->frameCount++; + storeActorSteps(_ctx->reelActor, _ctx->frameCount); + } + if (_ctx->stepCount == ppi->speed) + _ctx->stepCount = 0; + + if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished) + break; + + int x, y; + GetAniPosition(_ctx->pPlayObj, &x, &y); + storeActorPos(_ctx->reelActor, x, y); + + CORO_SLEEP(1); + + if (actorReel(_ctx->reelActor) != _ctx->pfreel) { + _ctx->replaced = true; + break; + } + + if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents()) + break; + + } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor)); + + // Register the fact that we're NOT playing this for this actor + if (actorReel(_ctx->reelActor) == _ctx->pfreel) + storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0); + + // Ditch the object + if (!ppi->bTop) + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + if (_ctx->mActor) { + if (!_ctx->replaced) + unHideMovingActor(_ctx->reelActor); // Restore moving actor + + // Update it's co-ordinates if this is an splay() + if (ppi->splay) + restoreMovement(_ctx->reelActor); + } + CORO_END_CODE; +} + +/** + * Run all animations that comprise the play film. + */ +static void playProcess(CORO_PARAM, const void *param) { + // get the stuff copied to process when it was created + PPINIT *ppi = (PPINIT *)param; + + PlayReel(coroParam, ppi); +} + +// ******************************************************* + + +// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios +void newestFilm(SCNHANDLE film, const FREEL *reel) { + const MULTI_INIT *pmi; // MULTI_INIT structure + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj)); + + setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film); +} + +// ******************************************************* + +/** + * Start up a play process for each column in a film. + * + * NOTE: The processes are started in reverse order so that the first + * column's process kicks in first. + */ +void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, + int myescEvent, bool bTop) { + const FILM *pfilm = (const FILM *)LockMem(film); + PPINIT ppi; + + assert(film != 0); // Trying to play NULL film + + // Now allowed empty films! + if (pfilm->numreels == 0) + return; // Nothing to do! + + ppi.hFilm = film; + ppi.x = x; + ppi.y = y; + ppi.z = 0; + ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + ppi.actorid = actorid; + ppi.splay = splay; + ppi.bTop = bTop; + ppi.sf = sfact; + ppi.escOn = escOn; + ppi.myescEvent = myescEvent; + + // Start display process for each reel in the film + for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) { + newestFilm(film, &pfilm->reels[i]); + + ppi.column = i; + g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(PPINIT)); + } +} + +/** + * Start up a play process for each slave column in a film. + * Play the first column directly from the parent process. + */ +void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, + bool escOn, int myescEvent, bool bTop) { + CORO_BEGIN_CONTEXT; + PPINIT ppi; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(film != 0); // Trying to play NULL film + const FILM *pfilm; + + pfilm = (const FILM *)LockMem(film); + + // Now allowed empty films! + if (pfilm->numreels == 0) + return; // Already played to completion! + + _ctx->ppi.hFilm = film; + _ctx->ppi.x = x; + _ctx->ppi.y = y; + _ctx->ppi.z = 0; + _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + _ctx->ppi.actorid = actorid; + _ctx->ppi.splay = splay; + _ctx->ppi.bTop = bTop; + _ctx->ppi.sf = sfact; + _ctx->ppi.escOn = escOn; + _ctx->ppi.myescEvent = myescEvent; + + // Start display process for each secondary reel in the film + for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) { + newestFilm(film, &pfilm->reels[i]); + + _ctx->ppi.column = i; + g_scheduler->createProcess(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT)); + } + + newestFilm(film, &pfilm->reels[0]); + + _ctx->ppi.column = 0; + CORO_INVOKE_1(PlayReel, &_ctx->ppi); + + CORO_END_CODE; +} + +/** + * Start up a play process for a particular column in a film. + * + * NOTE: This is specifically for actors during a restore scene. + */ +void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) { + const FILM *pfilm = (const FILM *)LockMem(film); + PPINIT ppi; + + ppi.hFilm = film; + ppi.x = x; + ppi.y = y; + ppi.z = z; + ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + ppi.actorid = 0; + ppi.splay = false; + ppi.bTop = false; + ppi.sf = 0; + ppi.column = reelnum; + + // FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when + // returning to a scene because escOn and myescEvent were undefined. Need to make sure whether + // restored objects should have any particular combination of these two values + ppi.escOn = false; + ppi.myescEvent = GetEscEvents(); + + assert(pfilm->numreels); + + newestFilm(film, &pfilm->reels[reelnum]); + + // Start display process for the reel + g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(ppi)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp new file mode 100644 index 0000000000..d73e290277 --- /dev/null +++ b/engines/tinsel/polygons.cpp @@ -0,0 +1,1862 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/actors.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + +/** different types of polygon */ +enum POLY_TYPE { + POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT, + POLY_EXIT, POLY_TAG +}; + + +// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4 +// so reduced this back to 6 (from 12) for now. +#define MAXADJ 6 // Max number of known adjacent paths + +struct POLYGON { + + PTYPE polytype; // Polygon type + + int subtype; // refer type in REFER polygons + // NODE/NORMAL in PATH polygons + + int pIndex; // Index into compiled polygon data + + /* + * Data duplicated from compiled polygon data + */ + short cx[4]; // Corners (clockwise direction) + short cy[4]; + int polyID; + + /* For TAG and EXIT (and EFFECT in future?) polygons only */ + TSTATE tagState; + PSTATE pointState; + SCNHANDLE oTagHandle; // Override tag. + + /* For Path polygons only */ + bool tried; + + /* + * Internal derived data for speed and conveniance + * set up by FiddlyBit() + */ + short ptop; // + short pbottom; // Enclosing external rectangle + short pleft; // + short pright; // + + short ltop[4]; // + short lbottom[4]; // Rectangles enclosing each side + short lleft[4]; // + short lright[4]; // + + int a[4]; // y1-y2 } + int b[4]; // x2-x1 } See IsInPolygon() + long c[4]; // y1x2 - x1y2 } + + /* + * Internal derived data for speed and conveniance + * set up by PseudoCentre() + */ + int pcentrex; // Pseudo-centre + int pcentrey; // + + /** + * List of adjacent polygons. For Path polygons only. + * set up by SetPathAdjacencies() + */ + POLYGON *adjpaths[MAXADJ]; + +}; + + +#define MAXONROUTE 40 + +#include "common/pack-start.h" // START STRUCT PACKING + +/** lineinfo struct - one per (node-1) in a node path */ +struct LINEINFO { + + int32 a; + int32 b; + int32 c; + + int32 a2; //!< a squared + int32 b2; //!< b squared + int32 a2pb2; //!< a squared + b squared + int32 ra2pb2; //!< root(a squared + b squared) + + int32 ab; + int32 ac; + int32 bc; +} PACKED_STRUCT; + +/** polygon struct - one per polygon */ +struct POLY { + int32 type; //!< type of polygon + int32 x[4], y[4]; // Polygon definition + + int32 tagx, tagy; // } For tagged polygons + SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT + + int32 nodex, nodey; // EXIT, TAG, REFER + SCNHANDLE hFilm; //!< film reel handle for EXIT, TAG + + int32 reftype; //!< Type of REFER + + int32 id; // } EXIT and TAG + + int32 scale1, scale2; // } + int32 reel; // } PATH and NPATH + int32 zFactor; // } + + //The arrays now stored externally + int32 nodecount; //!<The number of nodes in this polygon + int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present + int32 plinelist; + + SCNHANDLE hScript; //!< handle of code segment for polygon events +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int MaxPolys = MAX_POLY; + +static POLYGON *Polys[MAX_POLY+1]; + +static POLYGON *Polygons = 0; + +static SCNHANDLE pHandle = 0; // } Set at start of each scene +static int noofPolys = 0; // } + +static POLYGON extraBlock; // Used for dynamic blocking + +static int pathsOnRoute = 0; +static const POLYGON *RoutePaths[MAXONROUTE]; + +static POLYGON *RouteEnd = 0; + +#ifdef DEBUG +int highestYet = 0; +#endif + + + +//----------------- LOCAL MACROS -------------------- + +// The str parameter is no longer used +#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY); +#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys); + +static HPOLYGON PolyIndex(const POLYGON *pp) { + for (int j = 0; j <= MAX_POLY; j++) { + if (Polys[j] == pp) + return j; + } + + error("PolyIndex(): polygon not found"); + return NOPOLY; +} + +/** + * Returns TRUE if the point is within the polygon supplied. + * + * Firstly, the point must be within the smallest imaginary rectangle + * which encloses the polygon. + * + * Then, from each corner of the polygon, if the point is within an + * imaginary rectangle enclosing the clockwise-going side from that + * corner, the gradient of a line from the corner to the point must be + * less than (or more negative than) the gradient of that side: + * + * If the corners' coordinates are designated (x1, y1) and (x2, y2), and + * the point in question's (xt, yt), then: + * gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt) + * (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1) + * (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1) + * xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1 + * xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2 + * + * If the point passed one of the four 'side tests', and failed none, + * then it must be within the polygon. If the point was not tested, it + * may be within the internal rectangle not covered by the above tests. + * + * Most polygons contain an internal rectangle which does not fall into + * any of the above side-related tests. Such a rectangle will always + * have two polygon corners above it and two corners to the left of it. + */ +bool IsInPolygon(int xt, int yt, HPOLYGON hp) { + const POLYGON *pp; + int i; + bool BeenTested = false; + int pl = 0, pa = 0; + + CHECK_HP_OR(hp, "Out of range polygon handle (1)"); + pp = Polys[hp]; + assert(pp != NULL); // Testing whether in a NULL polygon + + /* Is point within the external rectangle? */ + if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom) + return false; + + // For each corner/side + for (i = 0; i < 4; i++) { + // If within this side's 'testable' area + // i.e. within the width of the line in y direction of end of line + // or within the height of the line in x direction of end of line + if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i]))) + || (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) { + if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i]) + return false; + else + BeenTested = true; + } + } + + if (BeenTested) { + // New dodgy code 29/12/94 + if (pp->polytype == BLOCKING) { + // For each corner/side + for (i = 0; i < 4; i++) { + // Pretend the corners of blocking polys are not in the poly. + if (xt == pp->cx[i] && yt == pp->cy[i]) + return false; + } + } + return true; + } else { + // Is point within the internal rectangle? + for (i = 0; i < 4; i++) { + if (pp->cx[i] < xt) + pl++; + if (pp->cy[i] < yt) + pa++; + } + + if (pa == 2 && pl == 2) + return true; + else + return false; + } +} + +/** + * Finds a polygon of the specified type containing the supplied point. + */ + +HPOLYGON InPolygon(int xt, int yt, PTYPE type) { + for (int j = 0; j <= MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == type) { + if (IsInPolygon(xt, yt, j)) + return j; + } + } + return NOPOLY; +} + +/** + * Given a blocking polygon, current co-ordinates of an actor, and the + * co-ordinates of where the actor is heading, works out which corner of + * the blocking polygon to head around. + */ + +void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) { + const POLYGON *pp; + int i; + int xd, yd; // distance per axis + int ThisD, SmallestD = 1000; + int D1, D2; + int NearestToHere = 1000, NearestToTarget; + unsigned At = 10; // Corner already at + + int bcx[4], bcy[4]; // Bogus corners + + CHECK_HP_OR(hp, "Out of range polygon handle (2)"); + pp = Polys[hp]; + + // Work out a point outside each corner + for (i = 0; i < 4; i++) { + int next, prev; + + // X-direction + next = pp->cx[i] - pp->cx[(i+1)%4]; + prev = pp->cx[i] - pp->cx[(i+3)%4]; + if (next <= 0 && prev <= 0) + bcx[i] = pp->cx[i] - 4; // Both points to the right + else if (next >= 0 && prev >= 0) + bcx[i] = pp->cx[i] + 4; // Both points to the left + else + bcx[i] = pp->cx[i]; + + // Y-direction + next = pp->cy[i] - pp->cy[(i+1)%4]; + prev = pp->cy[i] - pp->cy[(i+3)%4]; + if (next <= 0 && prev <= 0) + bcy[i] = pp->cy[i] - 4; // Both points below + else if (next >= 0 && prev >= 0) + bcy[i] = pp->cy[i] + 4; // Both points above + else + bcy[i] = pp->cy[i]; + } + + // Find nearest corner to where we are, + // but not the one we're stood at. + + for (i = 0; i < 4; i++) { // For 4 corners +// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]); + ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]); + if (ThisD < SmallestD) { + // Ignore this corner if it's not in a path + if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY || + InPolygon(bcx[i], bcy[i], PATH) == NOPOLY) + continue; + + // Are we stood at this corner? + if (ThisD > 4) { + // No - it's the nearest we've found yet. + NearestToHere = i; + SmallestD = ThisD; + } else { + // Stood at/next to this corner + At = i; + } + } + } + + // If we're not already at a corner, go to the nearest corner + + if (At == 10) { + // Not stood at a corner +// assert(NearestToHere != 1000); // At blocking corner, not found near corner! + // Better to give up than to assert fail! + if (NearestToHere == 1000) { + // Send it to where it is now + // i.e. leave x and y alone + } else { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } + } else { + // Already at a corner. Go to an adjacent corner. + // First, find out which adjacent corner is nearest the target. + xd = ABS(tarx - pp->cx[(At + 1) % 4]); + yd = ABS(tary - pp->cy[(At + 1) % 4]); + D1 = xd + yd; + xd = ABS(tarx - pp->cx[(At + 3) % 4]); + yd = ABS(tary - pp->cy[(At + 3) % 4]); + D2 = xd + yd; + NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4; + if (NearestToTarget == NearestToHere) { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } else { + // Need to decide whether it's better to go to the nearest to + // here and then on to the target, or to the nearest to the + // target and on from there. + xd = ABS(pp->cx[At] - pp->cx[NearestToHere]); + D1 = xd; + xd = ABS(pp->cx[NearestToHere] - tarx); + D1 += xd; + + yd = ABS(pp->cy[At] - pp->cy[NearestToHere]); + D1 += yd; + yd = ABS(pp->cy[NearestToHere] - tary); + D1 += yd; + + xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]); + D2 = xd; + xd = ABS(pp->cx[NearestToTarget] - tarx); + D2 += xd; + + yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]); + D2 += yd; + yd = ABS(pp->cy[NearestToTarget] - tary); + D2 += yd; + + if (D2 > D1) { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } else { + *x = bcx[NearestToTarget]; + *y = bcy[NearestToTarget]; + } + } + } +} + + +/** + * Try do drop a perpendicular to each inter-node line from the point + * and remember the shortest (if any). + * Find which node is nearest to the point. + * The shortest of these gives the best point in the node path. +*/ +void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) { + const POLYGON *pp; + + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Compiled polygon data + int dropD; // length of perpendicular (i.e. distance of point from line) + int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line + int d1, d2; // distance from perpendicular intersect to line's end nodes + int32 *nlistx, *nlisty; + + int shortestD = 10000; // Shortest distance found + int nearestL = -1; // Nearest line + int nearestN; // Nearest Node + + int h = *x; // For readability/conveniance + int k = *y; // - why aren't these #defines? + LINEINFO *llist; // Inter-node line structure + + CHECK_HP(hp, "Out of range polygon handle (3)"); + pp = Polys[hp]; + + // Pointer to polygon data + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist)); + + // Look for fit of perpendicular to lines between nodes + for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) { + const int32 a = (int)FROM_LE_32(llist[i].a); + const int32 b = (int)FROM_LE_32(llist[i].b); + const int32 c = (int)FROM_LE_32(llist[i].c); + +#if 1 + if (true) { + //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b); + + // TODO: If the comments of the LINEINFO struct are correct, then it contains mostly + // duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices + // we support, calculatin a product of two ints is not an issue. + // So we can just load & endian convert a,b,c, then replace stuff like + // (int)FROM_LE_32(line->ab) + // by simply a*b, which makes it easier to understand what the code does, too. + // Just in case there is some bugged data, I leave this code here for verifying it. + // Let's leave it in for some time. + // + // One bad thing: We use sqrt to compute a square root. Might not be a good idea, + // speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later. + + LINEINFO *line = &llist[i]; + int32 a2 = (int)FROM_LE_32(line->a2); //!< a squared + int32 b2 = (int)FROM_LE_32(line->b2); //!< b squared + int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); //!< a squared + b squared + int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); //!< root(a squared + b squared) + + int32 ab = (int)FROM_LE_32(line->ab); + int32 ac = (int)FROM_LE_32(line->ac); + int32 bc = (int)FROM_LE_32(line->bc); + + assert(a*a == a2); + assert(b*b == b2); + assert(a*b == ab); + assert(a*c == ac); + assert(b*c == bc); + + assert(a2pb2 == a*a + b*b); + assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b)); + } +#endif + + + if (a == 0 && b == 0) + continue; // Line is just a point! + + // X position of dropped perpendicular intersection with line + dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); + + // X distances from intersection to end nodes + d1 = dropX - (int)FROM_LE_32(nlistx[i]); + d2 = dropX - (int)FROM_LE_32(nlistx[i+1]); + + // if both -ve or both +ve, no fit + if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) + continue; +//#if 0 + // Y position of sidweays perpendicular intersection with line + dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); + + // Y distances from intersection to end nodes + d1 = dropY - (int)FROM_LE_32(nlisty[i]); + d2 = dropY - (int)FROM_LE_32(nlisty[i+1]); + + // if both -ve or both +ve, no fit + if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) + continue; +//#endif + dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b); + dropD = ABS(dropD); + if (dropD < shortestD) { + shortestD = dropD; + nearestL = i; + } + } + + // Distance to nearest node + nearestN = NearestNodeWithin(hp, h, k); + dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN])); + + // Go to a node or a point on a line + if (dropD < shortestD) { + // A node is nearest + *x = (int)FROM_LE_32(nlistx[nearestN]); + *y = (int)FROM_LE_32(nlisty[nearestN]); + *pline = nearestN; + } else { + assert(nearestL != -1); + + // A point on a line is nearest + const int32 a = (int)FROM_LE_32(llist[nearestL].a); + const int32 b = (int)FROM_LE_32(llist[nearestL].b); + const int32 c = (int)FROM_LE_32(llist[nearestL].c); + dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); + dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); + *x = dropX; + *y = dropY; + *pline = nearestL; + } + + assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!) +} + +/** + * Returns TRUE if two paths are asdjacent. + */ +bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) { + const POLYGON *pp1, *pp2; + + CHECK_HP(hPath1, "Out of range polygon handle (4)"); + CHECK_HP(hPath2, "Out of range polygon handle (500)"); + + if (hPath1 == hPath2) + return true; + + pp1 = Polys[hPath1]; + pp2 = Polys[hPath2]; + + for (int j = 0; j < MAXADJ; j++) + if (pp1->adjpaths[j] == pp2) + return true; + + return false; +} + +static const POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) { + POLYGON *x; + + // For each path adjacent to this one + for (int j = 0; j < MAXADJ; j++) { + x = current->adjpaths[j]; // call the adj. path x + if (x == whereto) { + RoutePaths[pathsOnRoute++] = x; + return x; // Got there! + } + + if (x == NULL) + break; // no more adj. paths to look at + + if (x->tried) + continue; // don't double back + + if (x == last) + continue; // don't double back + + x->tried = true; + if (TryPath(current, whereto, x) != NULL) { + RoutePaths[pathsOnRoute++] = x; + assert(pathsOnRoute < MAXONROUTE); + return x; // Got there in this direction + } else + x->tried = false; + } + + return NULL; +} + + +/** + * Sort out the first path to head to for the imminent leg of a walk. + */ +static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) { + // TODO: Fingolfin says: This code currently uses DFS (depth first search), + // in the TryPath function, to compute a path between 'from' and 'to'. + // However, a BFS (breadth first search) might yield more natural results, + // at least in cases where there are multiple possible paths. + // There is a small risk of regressions caused by such a change, though. + // + // Also, the overhead of computing a DFS again and again could be avoided + // by computing a path matrix (like we do in the SCUMM engine). + int i; + + CHECK_HP(from, "Out of range polygon handle (501a)"); + CHECK_HP(to, "Out of range polygon handle (501b)"); + + if (IsAdjacentPath(from, to)) + return to; + + for (i = 0; i < MAX_POLY; i++) { // For each polygon.. + POLYGON *p = Polys[i]; + if (p && p->polytype == PATH) //...if it's a path + p->tried = false; + } + Polys[from]->tried = true; + pathsOnRoute = 0; + + const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]); + + assert(p != NULL); // Trying to find route between unconnected paths + + // Don't go a roundabout way to an adjacent path. + for (i = 0; i < pathsOnRoute; i++) { + CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)"); + if (IsAdjacentPath(from, PolyIndex(RoutePaths[i]))) + return PolyIndex(RoutePaths[i]); + } + return PolyIndex(p); +} + +/** + * Indirect method of calling PathOnTheWay(), to put the burden of + * recursion onto the main stack. + */ +HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) { + CHECK_HP(hFrom, "Out of range polygon handle (6)"); + CHECK_HP(hTo, "Out of range polygon handle (7)"); + + // Reuse already computed result + if (RouteEnd == Polys[hTo]) { + for (int i = 0; i < pathsOnRoute; i++) { + CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)"); + if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) { + return PolyIndex(RoutePaths[i]); + } + } + } + + RouteEnd = Polys[hTo]; + return PathOnTheWay(hFrom, hTo); +} + + +/** + * Given a node path, work out which end node is nearest the given point. + */ + +int NearestEndNode(HPOLYGON hPath, int x, int y) { + const POLYGON *pp; + + int d1, d2; + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hPath, "Out of range polygon handle (8)"); + pp = Polys[hPath]; + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + const int nodecount = (int)FROM_LE_32(ptp->nodecount); + + d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0])); + d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1])); + + return (d2 > d1) ? 0 : nodecount - 1; +} + + +/** + * Given a start path and a destination path, find which pair of end + * nodes is nearest together. + * Return which node in the start path is part of the closest pair. + */ + +int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) { + const POLYGON *pSpath, *pDpath; + + int ns, nd; // 'top' nodes in each path + int dist, NearDist; + int NearNode; + uint8 *pps; // Compiled polygon data + const POLY *ps, *pd; // Pointer to compiled polygon data + int32 *snlistx, *snlisty; + int32 *dnlistx, *dnlisty; + + CHECK_HP(hSpath, "Out of range polygon handle (9)"); + CHECK_HP(hDpath, "Out of range polygon handle (10)"); + pSpath = Polys[hSpath]; + pDpath = Polys[hDpath]; + + pps = LockMem(pHandle); // All polygons + ps = (const POLY *)pps + pSpath->pIndex; // Start polygon + pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon + + ns = (int)FROM_LE_32(ps->nodecount) - 1; + nd = (int)FROM_LE_32(pd->nodecount) - 1; + + snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx)); + snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty)); + dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx)); + dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty)); + + // start[0] to dest[0] + NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0])); + NearNode = 0; + + // start[0] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd])); + if (dist < NearDist) + NearDist = dist; + + // start[top] to dest[0] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0])); + if (dist < NearDist) { + NearDist = dist; + NearNode = ns; + } + + // start[top] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd])); + if (dist < NearDist) { + NearNode = ns; + } + + return NearNode; +} + +/** + * Given a follow nodes path and a co-ordinate, finds which node in the + * path is nearest to the co-ordinate. + */ +int NearestNodeWithin(HPOLYGON hNpath, int x, int y) { + int ThisDistance, SmallestDistance = 1000; + int NumNodes; // Number of nodes in this follow nodes path + int NearestYet = 0; // Number of nearest node + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hNpath, "Out of range polygon handle (11)"); + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + NumNodes = (int)FROM_LE_32(ptp->nodecount); + + for (int i = 0; i < NumNodes; i++) { + ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i])); + + if (ThisDistance < SmallestDistance) { + NearestYet = i; + SmallestDistance = ThisDistance; + } + } + + return NearestYet; +} + +/** + * Given a point and start and destination paths, find the nearest + * corner (if any) of the start path which is within the destination + * path. If there is no such corner, find the nearest corner of the + * destination path which falls within the source path. + */ +void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) { + const POLYGON *psp, *pdp; + int j; + int ncorn = 0; // nearest corner + HPOLYGON hNpath = NOPOLY; // path containing nearest corner + int ThisD, SmallestD = 1000; + + CHECK_HP(hStartPoly, "Out of range polygon handle (12)"); + CHECK_HP(hDestPoly, "Out of range polygon handle (13)"); + + psp = Polys[hStartPoly]; + pdp = Polys[hDestPoly]; + + // Nearest corner of start path in destination path. + + for (j = 0; j < 4; j++) { + if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) { + ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]); + if (ThisD < SmallestD) { + hNpath = hStartPoly; + ncorn = j; + // Try to ignore it if virtually stood on it + if (ThisD > 4) + SmallestD = ThisD; + } + } + } + if (SmallestD == 1000) { + // Nearest corner of destination path in start path. + for (j = 0; j < 4; j++) { + if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) { + ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]); + if (ThisD < SmallestD) { + hNpath = hDestPoly; + ncorn = j; + // Try to ignore it if virtually stood on it + if (ThisD > 4) + SmallestD = ThisD; + } + } + } + } + + if (hNpath != NOPOLY) { + *x = Polys[hNpath]->cx[ncorn]; + *y = Polys[hNpath]->cy[ncorn]; + } else + error("NearestCorner() failure"); +} + +bool IsPolyCorner(HPOLYGON hPath, int x, int y) { + CHECK_HP(hPath, "Out of range polygon handle (37)"); + + for (int i = 0; i < 4; i++) { + if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y) + return true; + } + return false; +} + +/** + * Given a path polygon and a Y co-ordinate, return a scale value. + */ +int GetScale(HPOLYGON hPath, int y) { + const POLY *ptp; // Pointer to compiled polygon data + int zones; // Number of different scales + int zlen; // Depth of each scale zone + int scale; + int top; + + // To try and fix some unknown potential bug + if (hPath == NOPOLY) + return SCALE_LARGE; + + CHECK_HP(hPath, "Out of range polygon handle (14)"); + + ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex; + + // Path is of a constant scale? + if (FROM_LE_32(ptp->scale2) == 0) + return FROM_LE_32(ptp->scale1); + + assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2)); + + zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1; + zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones; + + scale = FROM_LE_32(ptp->scale1); + top = Polys[hPath]->ptop; + + do { + top += zlen; + if (y < top) + return scale; + } while (--scale); + + return FROM_LE_32(ptp->scale2); +} + +/** + * Give the co-ordinates of a node in a node path. + */ +void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) { + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hNpath, "Out of range polygon handle (15)"); + assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path! + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + // Might have just walked to the node from above. + if (node == (int)FROM_LE_32(ptp->nodecount)) + node -= 1; + + *px = (int)FROM_LE_32(nlistx[node]); + *py = (int)FROM_LE_32(nlisty[node]); +} + +/** + * Get tag text handle and tag co-ordinates of a polygon. + */ + +void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (16)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *tagx = (int)FROM_LE_32(pp->tagx); + *tagy = (int)FROM_LE_32(pp->tagy); + *hTagText = FROM_LE_32(pp->hTagtext); +} + +/** + * Get polygon's film reel handle. + */ + +SCNHANDLE getPolyFilm(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (17)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hFilm); +} + +/** + * Get polygon's associated node. + */ + +void getPolyNode(HPOLYGON hp, int *px, int *py) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (18)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *px = (int)FROM_LE_32(pp->nodex); + *py = (int)FROM_LE_32(pp->nodey); +} + +/** + * Get handle to polygon's glitter code. + */ + +SCNHANDLE getPolyScript(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (19)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hScript); +} + +REEL getPolyReelType(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + // To try and fix some unknown potential bug (toyshop entrance) + if (hp == NOPOLY) + return REEL_ALL; + + CHECK_HP(hp, "Out of range polygon handle (20)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (REEL)FROM_LE_32(pp->reel); +} + +int32 getPolyZfactor(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (21)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->zFactor); +} + +int numNodes(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (22)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->nodecount); +} + +// ************************************************************************* +// +// Code concerned with killing and reviving TAG and EXIT polygons. +// And code to enable this information to be saved and restored. +// +// ************************************************************************* + +struct TAGSTATE { + int tid; + bool enabled; +}; + +#define MAX_SCENES 256 +#define MAX_TAGS 2048 +#define MAX_EXITS 512 + +static struct { + SCNHANDLE sid; + int nooftags; + int offset; +} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES]; + +static TAGSTATE TagStates[MAX_TAGS]; +static TAGSTATE ExitStates[MAX_EXITS]; + +static int nextfreeT = 0, numScenesT = 0; +static int nextfreeE = 0, numScenesE = 0; + +static int currentTScene = 0; +static int currentEScene = 0; + +bool deadPolys[MAX_POLY]; // Currently just for dead blocks + +void RebootDeadTags(void) { + nextfreeT = numScenesT = 0; + nextfreeE = numScenesE = 0; + + memset(SceneTags, 0, sizeof(SceneTags)); + memset(SceneExits, 0, sizeof(SceneExits)); + memset(TagStates, 0, sizeof(TagStates)); + memset(ExitStates, 0, sizeof(ExitStates)); + memset(deadPolys, 0, sizeof(deadPolys)); +} + +/** + * (Un)serialize the dead tag and exit data for save/restore game. + */ +void syncPolyInfo(Serializer &s) { + int i; + + for (i = 0; i < MAX_SCENES; i++) { + s.syncAsUint32LE(SceneTags[i].sid); + s.syncAsSint32LE(SceneTags[i].nooftags); + s.syncAsSint32LE(SceneTags[i].offset); + } + + for (i = 0; i < MAX_SCENES; i++) { + s.syncAsUint32LE(SceneExits[i].sid); + s.syncAsSint32LE(SceneExits[i].nooftags); + s.syncAsSint32LE(SceneExits[i].offset); + } + + for (i = 0; i < MAX_TAGS; i++) { + s.syncAsUint32LE(TagStates[i].tid); + s.syncAsSint32LE(TagStates[i].enabled); + } + + for (i = 0; i < MAX_EXITS; i++) { + s.syncAsUint32LE(ExitStates[i].tid); + s.syncAsSint32LE(ExitStates[i].enabled); + } + + s.syncAsSint32LE(nextfreeT); + s.syncAsSint32LE(numScenesT); + s.syncAsSint32LE(nextfreeE); + s.syncAsSint32LE(numScenesE); +} + +/** + * This is all totally different to the way the rest of the way polygon + * data is stored and restored, more specifically, different to how dead + * tags and exits are handled, because of the piecemeal design-by-just- + * thought-of-this approach employed. + */ + +void SaveDeadPolys(bool *sdp) { + memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool)); +} + +void RestoreDeadPolys(bool *sdp) { + memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool)); +} + +/** + * Convert a BLOCKING to an EX_BLOCK poly. + */ +void DisableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) { + Polys[i]->polytype = EX_BLOCK; + deadPolys[i] = true; + } + } +} + +/** + * Convert an EX_BLOCK to a BLOCKING poly. + */ +void EnableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) { + Polys[i]->polytype = BLOCKING; + deadPolys[i] = false; + } + } +} + +/** + * Convert an EX_TAG to a TAG poly. + */ +void EnableTag(int tagno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = TAG; + } + } + + TAGSTATE *pts; + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = true; + break; + } + } +} + +/** + * Convert an EX_EXIT to a EXIT poly. + */ +void EnableExit(int exitno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) { + Polys[i]->polytype = EXIT; + } + } + + TAGSTATE *pts; + pts = &ExitStates[SceneExits[currentEScene].offset]; + for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { + if (pts->tid == exitno) { + pts->enabled = true; + break; + } + } +} + +/** + * Convert a TAG to an EX_TAG poly. + */ +void DisableTag(int tagno) { + TAGSTATE *pts; + + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = EX_TAG; + Polys[i]->tagState = TAG_OFF; + Polys[i]->pointState = NOT_POINTING; + } + } + + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = false; + break; + } + } +} + +/** + * Convert a EXIT to an EX_EXIT poly. + */ +void DisableExit(int exitno) { + TAGSTATE *pts; + + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) { + Polys[i]->polytype = EX_EXIT; + Polys[i]->tagState = TAG_OFF; + Polys[i]->pointState = NOT_POINTING; + } + } + + pts = &ExitStates[SceneExits[currentEScene].offset]; + for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { + if (pts->tid == exitno) { + pts->enabled = false; + break; + } + } +} + +HPOLYGON FirstPathPoly(void) { + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]->polytype == PATH) + return i; + } + error("FirstPathPoly() - no PATH polygons!"); + return NOPOLY; +} + +HPOLYGON GetPolyHandle(int i) { + assert(i >= 0 && i <= MAX_POLY); + + return (Polys[i] != NULL) ? i : NOPOLY; +} + +// ************************************************************************** +// +// Code called to initialise or wrap up a scene: +// +// ************************************************************************** + +/** + * Called at the start of a scene, when all polygons have been + * initialised, to work out which paths are adjacent to which. + */ +static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) { + const POLYGON *pp1, *pp2; + int i, j; + int retval = 0; + + CHECK_HP(hp1, "Out of range polygon handle (23)"); + CHECK_HP(hp2, "Out of range polygon handle (24)"); + pp1 = Polys[hp1]; + pp2 = Polys[hp2]; + + // Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1) + for (i = 0; i < 4; i++) { + if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2)) + retval++; + if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1)) + retval++; + } + + // Common corners only count once + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j]) + retval--; + } + } + return retval; +} + +static void SetPathAdjacencies() { + POLYGON *p1, *p2; // Polygon pointers + + // For each polygon.. + for (int i1 = 0; i1 < MAX_POLY-1; i1++) { + // Get polygon, but only carry on if it's a path + p1 = Polys[i1]; + if (!p1 || p1->polytype != PATH) + continue; + + // For each subsequent polygon.. + for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) { + // Get polygon, but only carry on if it's a path + p2 = Polys[i2]; + if (!p2 || p2->polytype != PATH) + continue; + + int j = DistinctCorners(i1, i2); + + if (j >= 2) { + // Paths are adjacent + for (j = 0; j < MAXADJ; j++) + if (p1->adjpaths[j] == NULL) { + p1->adjpaths[j] = p2; + break; + } +#ifdef DEBUG + if (j > highestYet) + highestYet = j; +#endif + assert(j < MAXADJ); // Number of adjacent paths limit + for (j = 0; j < MAXADJ; j++) { + if (p2->adjpaths[j] == NULL) { + p2->adjpaths[j] = p1; + break; + } + } +#ifdef DEBUG + if (j > highestYet) + highestYet = j; +#endif + assert(j < MAXADJ); // Number of adjacent paths limit + } + } + } +} + +/** + * Ensure NPATH nodes are not inside another PATH/NPATH polygon. + * Only bother with end nodes for now. + */ +#ifdef DEBUG +void CheckNPathIntegrity() { + uint8 *pps; // Compiled polygon data + const POLYGON *rp; // Run-time polygon structure + HPOLYGON hp; + const POLY *cp; // Compiled polygon structure + int i, j; // Loop counters + int n; // Last node in current path + int32 *nlistx, *nlisty; + + pps = LockMem(pHandle); // All polygons + + for (i = 0; i < MAX_POLY; i++) { // For each polygon.. + rp = Polys[i]; + if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path + // Get compiled polygon structure + cp = (const POLY *)pps + rp->pIndex; // This polygon + nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty)); + + n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node + assert(n >= 1); // Node paths must have at least 2 nodes + + hp = PolyIndex(rp); + for (j = 0; j <= n; j++) { + if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) { + sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]); + error(tBufferAddr()); + } + } + + // Check end nodes are not in adjacent path + for (j = 0; j < MAXADJ; j++) { // For each adjacent path + if (rp->adjpaths[j] == NULL) + break; + + if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + } + } + } +} +#endif + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExBlocks() { + for (int i = 0; i < MAX_POLY; i++) { + if (deadPolys[i]) { + if (Polys[i] && Polys[i]->polytype == BLOCKING) + Polys[i]->polytype = EX_BLOCK; +#ifdef DEBUG + else + error("Impossible message!"); +#endif + } + } +} + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExTags(SCNHANDLE ph) { + TAGSTATE *pts; + int i, j; + + for (i = 0; i < numScenesT; i++) { + if (SceneTags[i].sid == ph) { + currentTScene = i; + + pts = &TagStates[SceneTags[i].offset]; + for (j = 0; j < SceneTags[i].nooftags; j++, pts++) { + if (!pts->enabled) + DisableTag(pts->tid); + } + return; + } + } + + i = numScenesT++; + currentTScene = i; + assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit + + SceneTags[i].sid = ph; + SceneTags[i].offset = nextfreeT; + SceneTags[i].nooftags = 0; + + for (j = 0; j < MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == TAG) { + TagStates[nextfreeT].tid = Polys[j]->polyID; + TagStates[nextfreeT].enabled = true; + nextfreeT++; + assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit + SceneTags[i].nooftags++; + } + } +} + +/** + * Called at the start of a scene, nobbles EXIT polygons which should be dead. + */ +static void SetExExits(SCNHANDLE ph) { + TAGSTATE *pts; + int i, j; + + for (i = 0; i < numScenesE; i++) { + if (SceneExits[i].sid == ph) { + currentEScene = i; + + pts = &ExitStates[SceneExits[i].offset]; + for (j = 0; j < SceneExits[i].nooftags; j++, pts++) { + if (!pts->enabled) + DisableExit(pts->tid); + } + return; + } + } + + i = numScenesE++; + currentEScene = i; + assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit + + SceneExits[i].sid = ph; + SceneExits[i].offset = nextfreeE; + SceneExits[i].nooftags = 0; + + for (j = 0; j < MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == EXIT) { + ExitStates[nextfreeE].tid = Polys[j]->polyID; + ExitStates[nextfreeE].enabled = true; + nextfreeE++; + assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit + SceneExits[i].nooftags++; + } + } +} + +/** + * Works out some fixed numbers for a polygon. + */ +static void FiddlyBit(POLYGON *p) { + int t1, t2; // General purpose temp. variables + + // Enclosing external rectangle + t1 = MAX(p->cx[0], p->cx[1]); + t2 = MAX(p->cx[2], p->cx[3]); + p->pright = MAX(t1, t2); + + t1 = MIN(p->cx[0], p->cx[1]); + t2 = MIN(p->cx[2], p->cx[3]); + p->pleft = MIN(t1, t2); + + t1 = MAX(p->cy[0], p->cy[1]); + t2 = MAX(p->cy[2], p->cy[3]); + p->pbottom = MAX(t1, t2); + + t1 = MIN(p->cy[0], p->cy[1]); + t2 = MIN(p->cy[2], p->cy[3]); + p->ptop = MIN(t1, t2); + + // Rectangles enclosing each side and each side's magic numbers + for (t1 = 0; t1 < 4; t1++) { + p->lright[t1] = MAX(p->cx[t1], p->cx[(t1+1)%4]); + p->lleft[t1] = MIN(p->cx[t1], p->cx[(t1+1)%4]); + + p->ltop[t1] = MIN(p->cy[t1], p->cy[(t1+1)%4]); + p->lbottom[t1] = MAX(p->cy[t1], p->cy[(t1+1)%4]); + + p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4]; + p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1]; + p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4]; + } +} + +/** + * Calculate a point approximating to the centre of a polygon. + * Not very sophisticated. + */ +static void PseudoCentre(POLYGON *p) { + p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4; + p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4; + + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { + int i, top = 0, bot = 0; + + for (i = p->ptop; i <= p->pbottom; i++) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + top = i; + break; + } + } + for (i = p->pbottom; i >= p->ptop; i--) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + bot = i; + break; + } + } + p->pcentrex = (top+bot)/2; + } +#ifdef DEBUG + // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { + sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?", + p->cx[0], p->cy[0]); + error(tBufferAddr()); + } +#endif +} + +/** + * Allocate a POLYGON structure. + */ +static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) { + for (int i = 0; i < MaxPolys; i++) { + if (!Polys[i]) { + POLYGON *p = Polys[i] = &Polygons[i]; + memset(p, 0, sizeof(POLYGON)); + + p->polytype = type; // Polygon type + p->pIndex = pno; + p->tagState = TAG_OFF; + p->pointState = NOT_POINTING; + p->polyID = FROM_LE_32(pp->id); // Identifier + + for (int j = 0; j < 4; j++) { // Polygon definition + p->cx[j] = (short)FROM_LE_32(pp->x[j]); + p->cy[j] = (short)FROM_LE_32(pp->y[j]); + } + + return p; + } + } + + error("Exceeded MaxPolys"); + return NULL; +} + +/** + * Initialise an EXIT polygon. + */ +static void InitExit(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(EXIT, pp, pno)); +} + +/** + * Initialise a PATH or NPATH polygon. + */ +static void InitPath(const POLY *pp, bool NodePath, int pno) { + POLYGON *p; + + p = GetPolyEntry(PATH, pp, pno); // Obtain a slot + + if (NodePath) { + p->subtype = NODE; + } else { + p->subtype = NORMAL; + } + + // Clear out ajacent path pointers + memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *)); + + FiddlyBit(p); + PseudoCentre(p); +} + + +/** + * Initialise a BLOCKING polygon. + */ +static void InitBlock(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(BLOCKING, pp, pno)); +} + +/** + * Initialise an extra BLOCKING polygon related to a moving actor. + * The width of the polygon depends on the width of the actor which is + * trying to walk through the actor you first thought of. + * This is for dynamic blocking. + */ +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) { + int caX, caY; // Calling actor co-ords + int taX, taY; // Test actor co-ords + int left, right; + + GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords + GetMActorPosition(ta, &taX, &taY); // Test actor co-ords + + left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX); + right = GetMActorRight(ta) + (caX - GetMActorLeft(ca)); + + memset(&extraBlock, 0, sizeof(extraBlock)); + + // The 3s on the y co-ordinates used to be 10s + extraBlock.cx[0] = (short)(left - 2); + extraBlock.cy[0] = (short)(taY - 3); + extraBlock.cx[1] = (short)(right + 2); + extraBlock.cy[1] = (short)(taY - 3); + extraBlock.cx[2] = (short)(right + 2); + extraBlock.cy[2] = (short)(taY + 3); + extraBlock.cx[3] = (short)(left - 2); + extraBlock.cy[3] = (short)(taY + 3); + + FiddlyBit(&extraBlock); // Is this necessary? + + Polys[MAX_POLY] = &extraBlock; + return MAX_POLY; +} + +/** + * Initialise an EFFECT polygon. + */ +static void InitEffect(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(EFFECT, pp, pno)); +} + + +/** + * Initialise a REFER polygon. + */ +static void InitRefer(const POLY *pp, int pno) { + POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot + + p->subtype = FROM_LE_32(pp->reftype); // Refer type + + FiddlyBit(p); +} + + +/** + * Initialise a TAG polygon. + */ +static void InitTag(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(TAG, pp, pno)); +} + + +/** + * Called at the start of a scene to initialise the polys in that scene. + */ +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) { + const POLY *pp; // Pointer to compiled data polygon structure + + pHandle = ph; + noofPolys = numPoly; + + if (Polygons == NULL) { + // first time - allocate memory for process list + Polygons = (POLYGON *)calloc(MaxPolys, sizeof(POLYGON)); + + // make sure memory allocated + if (Polygons == NULL) { + error("Cannot allocate memory for polygon data"); + } + } + + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]) { + Polys[i]->pointState = NOT_POINTING; + Polys[i] = NULL; + } + } + + memset(RoutePaths, 0, sizeof(RoutePaths)); + + if (!bRestart) + memset(deadPolys, 0, sizeof(deadPolys)); + + pp = (const POLY *)LockMem(ph); + for (int i = 0; i < numPoly; i++, pp++) { + switch (FROM_LE_32(pp->type)) { + case POLY_PATH: + InitPath(pp, false, i); + break; + + case POLY_NPATH: + InitPath(pp, true, i); + break; + + case POLY_BLOCK: + InitBlock(pp, i); + break; + + case POLY_REFER: + InitRefer(pp, i); + break; + + case POLY_EFFECT: + InitEffect(pp, i); + break; + + case POLY_EXIT: + InitExit(pp, i); + break; + + case POLY_TAG: + InitTag(pp, i); + break; + + default: + error("Unknown polygon type"); + } + } + SetPathAdjacencies(); // Paths need to know the facts +#ifdef DEBUG + CheckNPathIntegrity(); +#endif + SetExTags(ph); // Some tags may have been killed + SetExExits(ph); // Some exits may have been killed + + if (bRestart) + SetExBlocks(); // Some blocks may have been killed +} + +/** + * Called at the end of a scene to ditch all polygons. + */ +void DropPolygons() { + pathsOnRoute = 0; + memset(RoutePaths, 0, sizeof(RoutePaths)); + RouteEnd = NULL; + + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]) { + Polys[i]->pointState = NOT_POINTING; + Polys[i] = NULL; + } + } + noofPolys = 0; + free(Polygons); + Polygons = NULL; +} + + + +PTYPE PolyType(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (25)"); + + return Polys[hp]->polytype; +} + +int PolySubtype(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (26)"); + + return Polys[hp]->subtype; +} + +int PolyCentreX(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (27)"); + + return Polys[hp]->pcentrex; +} + +int PolyCentreY(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (28)"); + + return Polys[hp]->pcentrey; +} + +int PolyCornerX(HPOLYGON hp, int n) { + CHECK_HP(hp, "Out of range polygon handle (29)"); + + return Polys[hp]->cx[n]; +} + +int PolyCornerY(HPOLYGON hp, int n) { + CHECK_HP(hp, "Out of range polygon handle (30)"); + + return Polys[hp]->cy[n]; +} + +PSTATE PolyPointState(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (31)"); + + return Polys[hp]->pointState; +} + +TSTATE PolyTagState(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (32)"); + + return Polys[hp]->tagState; +} + +SCNHANDLE PolyTagHandle(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (33)"); + + return Polys[hp]->oTagHandle; +} + +void SetPolyPointState(HPOLYGON hp, PSTATE ps) { + CHECK_HP(hp, "Out of range polygon handle (34)"); + + Polys[hp]->pointState = ps; +} + +void SetPolyTagState(HPOLYGON hp, TSTATE ts) { + CHECK_HP(hp, "Out of range polygon handle (35)"); + + Polys[hp]->tagState = ts; +} + +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) { + CHECK_HP(hp, "Out of range polygon handle (36)"); + + Polys[hp]->oTagHandle = th; +} + +void MaxPolygons(int numPolys) { + assert(numPolys <= MAX_POLY); + + MaxPolys = numPolys; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h new file mode 100644 index 0000000000..90c57d5f99 --- /dev/null +++ b/engines/tinsel/polygons.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Definition of POLYGON structure and functions in POLYGONS.C + */ + +#ifndef TINSEL_POLYGONS_H // prevent multiple includes +#define TINSEL_POLYGONS_H + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/scene.h" // for PPOLY and REEL + +namespace Tinsel { + + +// Polygon Types +enum PTYPE { + TEST, PATH, EXIT, BLOCKING, + EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK +}; + +// subtype +enum { + NORMAL = 0, + NODE = 1 // For paths +}; + +// tagState +enum TSTATE { + TAG_OFF, TAG_ON +}; + +// pointState +enum PSTATE { + NO_POINT, NOT_POINTING, POINTING +}; + + +enum { + NOPOLY = -1 +}; + + + +/*-------------------------------------------------------------------------*/ + +bool IsInPolygon(int xt, int yt, HPOLYGON p); +HPOLYGON InPolygon(int xt, int yt, PTYPE type); +void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary); +void FindBestPoint(HPOLYGON path, int *x, int *y, int *line); +bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2); +HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to); +int NearestEndNode(HPOLYGON path, int x, int y); +int NearEndNode(HPOLYGON spath, HPOLYGON dpath); +int NearestNodeWithin(HPOLYGON npath, int x, int y); +void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath); +bool IsPolyCorner(HPOLYGON hPath, int x, int y); +int GetScale(HPOLYGON path, int y); +void getNpathNode(HPOLYGON npath, int node, int *px, int *py); +void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy); +SCNHANDLE getPolyFilm(HPOLYGON p); +void getPolyNode(HPOLYGON p, int *px, int *py); +SCNHANDLE getPolyScript(HPOLYGON p); +REEL getPolyReelType(HPOLYGON p); +int32 getPolyZfactor(HPOLYGON p); +int numNodes(HPOLYGON pp); +void RebootDeadTags(void); +void DisableBlock(int blockno); +void EnableBlock(int blockno); +void DisableTag(int tagno); +void EnableTag(int tagno); +void DisableExit(int exitno); +void EnableExit(int exitno); +HPOLYGON FirstPathPoly(void); +HPOLYGON GetPolyHandle(int i); +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart); +void DropPolygons(void); + + +void SaveDeadPolys(bool *sdp); +void RestoreDeadPolys(bool *sdp); + +/*-------------------------------------------------------------------------*/ + +PTYPE PolyType(HPOLYGON hp); // ->type +int PolySubtype(HPOLYGON hp); // ->subtype +int PolyCentreX(HPOLYGON hp); // ->pcentrex +int PolyCentreY(HPOLYGON hp); // ->pcentrey +int PolyCornerX(HPOLYGON hp, int n); // ->cx[n] +int PolyCornerY(HPOLYGON hp, int n); // ->cy[n] +PSTATE PolyPointState(HPOLYGON hp); // ->pointState +TSTATE PolyTagState(HPOLYGON hp); // ->tagState +SCNHANDLE PolyTagHandle(HPOLYGON hp); // ->oTagHandle + +void SetPolyPointState(HPOLYGON hp, PSTATE ps); // ->pointState +void SetPolyTagState(HPOLYGON hp, TSTATE ts); // ->tagState +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle + +void MaxPolygons(int maxPolys); + +/*-------------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_POLYGONS_H */ diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp new file mode 100644 index 0000000000..a9b24bcac9 --- /dev/null +++ b/engines/tinsel/rince.cpp @@ -0,0 +1,709 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.c" + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static MACTOR Movers[MAX_MOVERS]; + + +/** + * RebootMovers + */ +void RebootMovers(void) { + memset(Movers, 0, sizeof(Movers)); +} + +/** + * Given an actor number, return pointer to its moving actor structure, + * if it is a moving actor. + */ +PMACTOR GetMover(int ano) { + int i; + + // Slot 0 is reserved for lead actor + if (ano == LeadId() || ano == LEAD_ACTOR) + return &Movers[0]; + + for (i = 1; i < MAX_MOVERS; i++) + if (Movers[i].actorID == ano) + return &Movers[i]; + + return NULL; +} + +/** + * Register an actor as being a moving one. + */ +PMACTOR SetMover(int ano) { + int i; + + // Slot 0 is reserved for lead actor + if (ano == LeadId() || ano == LEAD_ACTOR) { + Movers[0].actorToken = TOKEN_LEAD; + Movers[0].actorID = LeadId(); + return &Movers[0]; + } + + // Check it hasn't already been declared + for (i = 1; i < MAX_MOVERS; i++) { + if (Movers[i].actorID == ano) { + // Actor is already a moving actor + return &Movers[i]; + } + } + + // Find an empty slot + for (i = 1; i < MAX_MOVERS; i++) + if (!Movers[i].actorID) { + Movers[i].actorToken = TOKEN_LEAD + i; + Movers[i].actorID = ano; + return &Movers[i]; + } + + error("Too many moving actors"); +} + +/** + * Given an index, returns the associated moving actor. + * + * At the time of writing, used by the effect process. + */ +PMACTOR GetLiveMover(int index) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + if (Movers[index].MActorState == NORM_MACTOR) + return &Movers[index]; + else + return NULL; +} + +bool IsMAinEffectPoly(int index) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + return Movers[index].InEffect; +} + +void SetMAinEffectPoly(int index, bool tf) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + Movers[index].InEffect = tf; +} + +/** + * Remove a moving actor from the current scene. + */ +void KillMActor(PMACTOR pActor) { + if (pActor->MActorState == NORM_MACTOR) { + pActor->MActorState = NO_MACTOR; + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); + pActor->actorObj = NULL; + assert(g_scheduler->getCurrentProcess() != pActor->pProc); + g_scheduler->killProcess(pActor->pProc); + } +} + +/** + * getMActorState + */ +MAS getMActorState(PMACTOR pActor) { + return pActor->MActorState; +} + +/** + * If the actor's object exists, move it behind the background. + * MultiHideObject() is deliberately not used, as StepAnimScript() calls + * cause the object to re-appear. + */ +void hideMActor(PMACTOR pActor, int sf) { + assert(pActor); // Hiding null moving actor + + pActor->aHidden = true; + pActor->SlowFactor = sf; + + if (pActor->actorObj) + MultiSetZPosition(pActor->actorObj, -1); +} + +/** + * getMActorHideState + */ +bool getMActorHideState(PMACTOR pActor) { + if (pActor) + return pActor->aHidden; + else + return false; +} + +/** + * unhideMActor + */ +void unhideMActor(PMACTOR pActor) { + assert(pActor); // unHiding null moving actor + + pActor->aHidden = false; + + // Make visible on the screen + if (pActor->actorObj) { + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + } +} + +/** + * Get it into our heads that there's nothing doing. + * Called at the end of a scene. + */ +void DropMActors(void) { + for (int i = 0; i < MAX_MOVERS; i++) { + Movers[i].MActorState = NO_MACTOR; + Movers[i].objx = 0; + Movers[i].objy = 0; + Movers[i].actorObj = NULL; // No moving actor objects + + Movers[i].hCpath = NOPOLY; // No moving actor path + } +} + + +/** + * Reposition a moving actor. + */ +void MoveMActor(PMACTOR pActor, int x, int y) { + int z; + int node; + HPOLYGON hPath; + + assert(pActor); // Moving null moving actor + assert(pActor->actorObj); + + pActor->objx = x; + pActor->objy = y; + MultiSetAniXY(pActor->actorObj, x, y); + + hPath = InPolygon(x, y, PATH); + if (hPath != NOPOLY) { + pActor->hCpath = hPath; + if (PolySubtype(hPath) == NODE) { + node = NearestNodeWithin(hPath, x, y); + getNpathNode(hPath, node, &pActor->objx, &pActor->objy); + pActor->hFnpath = hPath; + pActor->line = node; + pActor->npstatus = GOING_UP; + } else { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + } + + z = GetScale(hPath, pActor->objy); + pActor->scale = z; + SetMActorStanding(pActor); + } else { + pActor->bNoPath = true; + + pActor->hFnpath = NOPOLY; // Ain't in one + pActor->npstatus = NOT_IN; + + // Ensure legal reel and scale + if (pActor->dirn < 0 || pActor->dirn > 3) + pActor->dirn = FORWARD; + if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) + pActor->scale = 1; + } +} + +/** + * Get position of a moving actor. + */ +void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) { + assert(pActor); // Getting null moving actor's position + + if (pActor->actorObj != NULL) + GetAniPosition(pActor->actorObj, paniX, paniY); + else { + *paniX = 0; + *paniY = 0; + } +} + +/** + * Moving actor's mid-top position. + */ +void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) { + assert(pActor); // Getting null moving actor's mid-top position + assert(pActor->actorObj); // Getting null moving actor's mid-top position + + *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2; + *aniY = MultiHighest(pActor->actorObj); +} + +/** + * Moving actor's left-most co-ordinate. + */ +int GetMActorLeft(PMACTOR pActor) { + assert(pActor); // Getting null moving actor's leftmost position + assert(pActor->actorObj); // Getting null moving actor's leftmost position + + return MultiLeftmost(pActor->actorObj); +} + +/** + * Moving actor's right-most co-ordinate. + */ +int GetMActorRight(PMACTOR pActor) { + assert(pActor); // Getting null moving actor's rightmost position + assert(pActor->actorObj); // Getting null moving actor's rightmost position + + return MultiRightmost(pActor->actorObj); +} + +/** + * See if moving actor is stood within a polygon. + */ +bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) { + assert(pActor); // Checking if null moving actor is in polygon + assert(pActor->actorObj); // Checking if null moving actor is in polygon + + int aniX, aniY; + GetAniPosition(pActor->actorObj, &aniX, &aniY); + + return IsInPolygon(aniX, aniY, hp); +} + +/** + * Change which reel is playing for a moving actor. + */ +void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) { + const FILM *pfilm; + + assert(pActor->actorObj); // Altering null moving actor's animation script + + if (fn == AR_POPREEL) { + film = pActor->pushedfilm; // Use the saved film + } + if (fn == AR_PUSHREEL) { + // Save the one we're replacing + pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0; + } + + if (film == 0) { + if (pActor->TagReelRunning) { + // Revert to 'normal' actor + SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); + pActor->TagReelRunning = false; + } + } else { + pActor->lastfilm = film; // Remember this one + + pfilm = (const FILM *)LockMem(film); + assert(pfilm != NULL); + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + pActor->scount = 0; + + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + + if (fn == AR_WALKREEL) { + pActor->TagReelRunning = false; + pActor->walkReel = true; + } else { + pActor->TagReelRunning = true; + pActor->walkReel = false; + +#ifdef DEBUG + assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else + StepAnimScript(&pActor->actorAnim); // 04/01/95 +#endif + } + + // Hang on, we may not want him yet! 04/01/95 + if (pActor->aHidden) + MultiSetZPosition(pActor->actorObj, -1); + } +} + +/** + * Return the actor's direction. + */ +DIRREEL GetMActorDirection(PMACTOR pActor) { + return pActor->dirn; +} + +/** + * Return the actor's scale. + */ +int GetMActorScale(PMACTOR pActor) { + return pActor->scale; +} + +/** + * Point actor in specified derection + */ +void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) { + pActor->dirn = dirn; +} + +/** + * MAmoving + */ +bool MAmoving(PMACTOR pActor) { + return pActor->bMoving; +} + +/** + * Return an actor's walk ticket. + */ +int GetActorTicket(PMACTOR pActor) { + return pActor->ticket; +} + +/** + * Get actor to adopt its appropriate standing reel. + */ +void SetMActorStanding(PMACTOR pActor) { + assert(pActor->actorObj); + AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL); +} + +/** + * Get actor to adopt its appropriate walking reel. + */ +void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) { + SCNHANDLE whichReel; + const FILM *pfilm; + + // Kill off any play that may be going on for this actor + // and restore the real actor + storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0); + unhideMActor(pActor); + + // Don't do it if using a special walk reel + if (pActor->walkReel) + return; + + if (force || pActor->scale != scale || pActor->dirn != reel) { + assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel + + // If scale change and both are regular scales + // and there's a scaling reel in the right direction + if (pActor->scale != scale + && scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES + && (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) { +// error("Cripes!"); + ; // Use what is now in 'whichReel' + } else { + whichReel = pActor->WalkReels[scale-1][reel]; + assert(whichReel); // no reel + } + + pfilm = (const FILM *)LockMem(whichReel); + assert(pfilm != NULL); // no film + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1); + + // Synchronised walking reels + SkipFrames(&pActor->actorAnim, pActor->scount); + + pActor->scale = scale; + pActor->dirn = reel; + } +} + +/** + * Sort some stuff out at actor start-up time. + */ +static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) { + HPOLYGON hPath; + int node; + int z; + + pActor->objx = xpos; + pActor->objy = ypos; + + /*-------------------------------------- + | If Actor is in a follow nodes path, | + | position it at the nearest node. | + --------------------------------------*/ + hPath = InPolygon(xpos, ypos, PATH); + + if (hPath != NOPOLY) { + pActor->hCpath = hPath; + if (PolySubtype(hPath) == NODE) { + node = NearestNodeWithin(hPath, xpos, ypos); + getNpathNode(hPath, node, &pActor->objx, &pActor->objy); + pActor->hFnpath = hPath; + pActor->line = node; + pActor->npstatus = GOING_UP; + } + + z = GetScale(hPath, pActor->objy); + } else { + pActor->bNoPath = true; + + z = GetScale(FirstPathPoly(), pActor->objy); + } + SetMActorWalkReel(pActor, FORWARD, z, false); +} + +/** + * Clear everything out at actor start-up time. + */ +static void InitMActor(PMACTOR pActor) { + + pActor->objx = pActor->objy = 0; + pActor->targetX = pActor->targetY = -1; + pActor->ItargetX = pActor->ItargetY = -1; + pActor->hIpath = NOPOLY; + pActor->UtargetX = pActor->UtargetY = -1; + pActor->hUpath = NOPOLY; + pActor->hCpath = NOPOLY; + + pActor->over = false; + pActor->InDifficulty = NO_PROB; + + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + pActor->line = 0; + + pActor->Tline = 0; + + pActor->TagReelRunning = false; + + if (pActor->dirn != FORWARD || pActor->dirn != AWAY + || pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL) + pActor->dirn = FORWARD; + + if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) + pActor->scale = 1; + + pActor->scount = 0; + + pActor->fromx = pActor->fromy = 0; + + pActor->bMoving = false; + pActor->bNoPath = false; + pActor->bIgPath = false; + pActor->walkReel = false; + + pActor->actorObj = NULL; + + pActor->lastfilm = 0; + pActor->pushedfilm = 0; + + pActor->InEffect = false; + pActor->aHidden = false; // 20/2/95 +} + +static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) { + const FILM *pfilm; + const MULTI_INIT *pmi; + const FRAME *pFrame; + IMAGE *pim; + + + assert(BackPal()); // Can't start actor without a background palette + assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels + + InitMActor(pActor); + InitialPathChecks(pActor, X, Y); + + pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]); + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj)); + +//--- + pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + + // get pointer to image + pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image + pim->hImgPal = TO_LE_32(BackPal()); +//--- + pActor->actorObj = MultiInitObject(pmi); + +/**/ assert(pActor->actorID == id); + pActor->actorID = id; + + // add it to display list + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); + storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0); + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + pActor->scount = 0; + + MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy); + + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + + // Make him the right size + SetMActorStanding(pActor); + +//**** if added 18/11/94, am + if (X != MAGICX && Y != MAGICY) { + hideMActor(pActor, 0); // Allows a play to come in before this appears + pActor->aHidden = false; // ...but don't stay hidden + } + + pActor->MActorState = NORM_MACTOR; +} + +/** + * Moving actor process - 1 per moving actor in current scene. + */ +void MActorProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = *(PMACTOR *)param; + + CORO_BEGIN_CODE(_ctx); + + while (1) { + if (pActor->TagReelRunning) { + if (!pActor->aHidden) +#ifdef DEBUG + assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else + StepAnimScript(&pActor->actorAnim); +#endif + } else + DoMoveActor(pActor); + + CORO_SLEEP(1); // allow rescheduling + + } + + CORO_END_CODE; +} + +void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) { + MActorProcessHelper(X, Y, id, pActor); + pActor->pProc = g_scheduler->createProcess(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR)); +} + + +/** + * Check for moving actor collision. + */ +PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { + int caX; // Calling actor's pos'n + int caL, caR; // Calling actor's left and right + int taX, taY; // Test actor's pos'n + int taL, taR; // Test actor's left and right + + caX = pActor->objx; + if (pActor->hFnpath != NOPOLY || bNoBlocking) + return NULL; + + caL = GetMActorLeft(pActor) + x - caX; + caR = GetMActorRight(pActor) + x - caX; + + for (int i = 0; i < MAX_MOVERS; i++) { + if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR) + continue; + + // At around the same height? + GetMActorPosition(&Movers[i], &taX, &taY); + if (Movers[i].hFnpath != NOPOLY) + continue; + + if (ABS(y - taY) > 2) // 2 was 8 + continue; + + // To the left? + taL = GetMActorLeft(&Movers[i]); + if (caR <= taL) + continue; + + // To the right? + taR = GetMActorRight(&Movers[i]); + if (caL >= taR) + continue; + + return &Movers[i]; + } + return NULL; +} + +/** + * Copies key information for savescn.c to store away. + */ +void SaveMovers(SAVED_MOVER *sMoverInfo) { + for (int i = 0; i < MAX_MOVERS; i++) { + sMoverInfo[i].MActorState= Movers[i].MActorState; + sMoverInfo[i].actorID = Movers[i].actorID; + sMoverInfo[i].objx = Movers[i].objx; + sMoverInfo[i].objy = Movers[i].objy; + sMoverInfo[i].lastfilm = Movers[i].lastfilm; + + memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + } +} + +void RestoreAuxScales(SAVED_MOVER *sMoverInfo) { + for (int i = 0; i < MAX_MOVERS; i++) { + memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h new file mode 100644 index 0000000000..7ccf017c65 --- /dev/null +++ b/engines/tinsel/rince.h @@ -0,0 +1,209 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_RINCE_H // prevent multiple includes +#define TINSEL_RINCE_H + +#include "tinsel/anim.h" // for ANIM +#include "tinsel/scene.h" // for TFTYPE + +namespace Tinsel { + +struct OBJECT; +struct PROCESS; + +enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING}; + +enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER}; + +enum MAS {NO_MACTOR, NORM_MACTOR}; + +enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY }; + +enum { + NUM_MAINSCALES = 5, + NUM_AUXSCALES = 5, + TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES +}; + +struct MACTOR { + + int objx; /* Co-ordinates object */ + int objy; + + int targetX, targetY; + int ItargetX, ItargetY; /* Intermediate destination */ + HPOLYGON hIpath; + int UtargetX, UtargetY; /* Ultimate destination */ + HPOLYGON hUpath; + + HPOLYGON hCpath; + + bool over; + int ticket; + + IND InDifficulty; + + /* For use in 'follow nodes' polygons */ + HPOLYGON hFnpath; + NPS npstatus; + int line; + + int Tline; // NEW + + bool TagReelRunning; + + + /* Used internally */ + DIRREEL dirn; // Current reel + int scale; // Current scale + int scount; // Step count for walking reel synchronisation + + unsigned fromx; + unsigned fromy; + + bool bMoving; // Set this to TRUE during a walk + + bool bNoPath; + bool bIgPath; + bool walkReel; + + OBJECT *actorObj; // Actor's object + ANIM actorAnim; // Actor's animation script + + SCNHANDLE lastfilm; // } Used by AlterActor() + SCNHANDLE pushedfilm; // } + + int actorID; + int actorToken; + + SCNHANDLE WalkReels[TOTAL_SCALES][4]; + SCNHANDLE StandReels[TOTAL_SCALES][4]; + SCNHANDLE TalkReels[TOTAL_SCALES][4]; + + MAS MActorState; + + bool aHidden; + int SlowFactor; // Slow down movement while hidden + + bool stop; + + /* NOTE: If effect polys can overlap, this needs improving */ + bool InEffect; + + PROCESS *pProc; +}; +typedef MACTOR *PMACTOR; + +//--------------------------------------------------------------------------- + + +void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor); + + +enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL }; + + +MACTOR *GetMover(int ano); +MACTOR *SetMover(int ano); +void KillMActor(MACTOR *pActor); +MACTOR *GetLiveMover(int index); + +MAS getMActorState(MACTOR *psActor); + +void hideMActor(MACTOR *pActor, int sf); +bool getMActorHideState(MACTOR *pActor); +void unhideMActor(MACTOR *pActor); +void DropMActors(void); +void MoveMActor(MACTOR *pActor, int x, int y); + +void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY); +void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY); +int GetMActorLeft(MACTOR *pActor); +int GetMActorRight(MACTOR *pActor); + +bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly); +void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn); +DIRREEL GetMActorDirection(MACTOR *pActor); +int GetMActorScale(MACTOR *pActor); +void SetMActorDirection(MACTOR *pActor, DIRREEL dirn); +void SetMActorStanding(MACTOR *actor); +void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force); + +MACTOR *InMActorBlock(MACTOR *pActor, int x, int y); + +void RebootMovers(void); + +bool IsMAinEffectPoly(int index); +void SetMAinEffectPoly(int index, bool tf); + +bool MAmoving(MACTOR *pActor); + +int GetActorTicket(MACTOR *pActor); + +/*----------------------------------------------------------------------*/ + +struct SAVED_MOVER { + + MAS MActorState; + int actorID; + int objx; + int objy; + SCNHANDLE lastfilm; + + SCNHANDLE WalkReels[TOTAL_SCALES][4]; + SCNHANDLE StandReels[TOTAL_SCALES][4]; + SCNHANDLE TalkReels[TOTAL_SCALES][4]; + +}; + +void SaveMovers(SAVED_MOVER *sMoverInfo); +void RestoreAuxScales(SAVED_MOVER *sMoverInfo); + +/*----------------------------------------------------------------------*/ + +/* +* Dodgy bit... +* These functions are now in MAREELS.C, but I can't be bothered to +* create a new header file. +*/ +SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn); + +void setscalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away); +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel); +void RebootScalingReels(void); + +enum { + MAGICX = -101, + MAGICY = -102 +}; + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_RINCE_H */ diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp new file mode 100644 index 0000000000..1a6cc1202a --- /dev/null +++ b/engines/tinsel/saveload.cpp @@ -0,0 +1,475 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/inventory.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/serializer.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" + +#include "common/savefile.h" + +namespace Tinsel { + + +/** + * The current savegame format version. + * Our save/load system uses an elaborate scheme to allow us to modify the + * savegame while keeping full backward compatibility, in the sense that newer + * ScummVM versions always are able to load old savegames. + * In order to achieve that, we store a version in the savegame files, and whenever + * the savegame layout is modified, the version is incremented. + * + * This roughly works by marking each savegame entry with a range of versions + * for which it is valid; the save/load code iterates over all entries, but + * only saves/loads those which are valid for the version of the savegame + * which is being loaded/saved currently. + */ +#define CURRENT_VER 1 +// TODO: Not yet used + +/** + * An auxillary macro, used to specify savegame versions. We use this instead + * of just writing the raw version, because this way they stand out more to + * the reading eye, making it a bit easier to navigate through the code. + */ +#define VER(x) x + + + + +//----------------- EXTERN FUNCTIONS -------------------- + +// in DOS_DW.C +extern void syncSCdata(Serializer &s); + +// in DOS_MAIN.C +//char HardDriveLetter(void); + +// in PCODE.C +extern void syncGlobInfo(Serializer &s); + +// in POLYGONS.C +extern void syncPolyInfo(Serializer &s); + +// in SAVESCN.CPP +extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut); + +//----------------- LOCAL DEFINES -------------------- + +struct SaveGameHeader { + uint32 id; + uint32 size; + uint32 ver; + char desc[SG_DESC_LEN]; + struct tm dateTime; +}; + +enum { + SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM" + SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int numSfiles = 0; +static SFILES savedFiles[MAX_SFILES]; + +static bool NeedLoad = true; + +static SAVED_DATA *srsd = 0; +static int RestoreGameNumber = 0; +static char *SaveSceneName = 0; +static const char *SaveSceneDesc = 0; +static int *SaveSceneSsCount = 0; +static char *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]' + +static SRSTATE SRstate = SR_IDLE; + +//------------- SAVE/LOAD SUPPORT METHODS ---------------- + +static void syncTime(Serializer &s, struct tm &t) { + s.syncAsUint16LE(t.tm_year); + s.syncAsByte(t.tm_mon); + s.syncAsByte(t.tm_mday); + s.syncAsByte(t.tm_hour); + s.syncAsByte(t.tm_min); + s.syncAsByte(t.tm_sec); + if (s.isLoading()) { + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = 0; + } +} + +static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) { + s.syncAsUint32LE(hdr.id); + s.syncAsUint32LE(hdr.size); + s.syncAsUint32LE(hdr.ver); + + s.syncBytes((byte *)hdr.desc, SG_DESC_LEN); + hdr.desc[SG_DESC_LEN - 1] = 0; + + syncTime(s, hdr.dateTime); + + int tmp = hdr.size - s.bytesSynced(); + // Perform sanity check + if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024) + return false; + // Skip over any extra bytes + while (tmp-- > 0) { + byte b = 0; + s.syncAsByte(b); + } + return true; +} + +static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) { + SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels }; + + s.syncAsUint32LE(sm.MActorState); + s.syncAsSint32LE(sm.actorID); + s.syncAsSint32LE(sm.objx); + s.syncAsSint32LE(sm.objy); + s.syncAsUint32LE(sm.lastfilm); + + for (int pIndex = 0; pIndex < 3; ++pIndex) { + SCNHANDLE *p = pList[pIndex]; + for (int i = 0; i < TOTAL_SCALES * 4; ++i) + s.syncAsUint32LE(*p++); + } +} + +static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) { + s.syncAsUint16LE(sa.actorID); + s.syncAsUint16LE(sa.z); + s.syncAsUint32LE(sa.bAlive); + s.syncAsUint32LE(sa.presFilm); + s.syncAsUint16LE(sa.presRnum); + s.syncAsUint16LE(sa.presX); + s.syncAsUint16LE(sa.presY); +} + +extern void syncAllActorsAlive(Serializer &s); + +static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) { + s.syncAsSint32LE(ns.ln); + s.syncAsSint32LE(ns.c1); + s.syncAsSint32LE(ns.c2); +} + +static void syncSavedData(Serializer &s, SAVED_DATA &sd) { + s.syncAsUint32LE(sd.SavedSceneHandle); + s.syncAsUint32LE(sd.SavedBgroundHandle); + for (int i = 0; i < MAX_MOVERS; ++i) + syncSavedMover(s, sd.SavedMoverInfo[i]); + for (int i = 0; i < MAX_SAVED_ACTORS; ++i) + syncSavedActor(s, sd.SavedActorInfo[i]); + + s.syncAsSint32LE(sd.NumSavedActors); + s.syncAsSint32LE(sd.SavedLoffset); + s.syncAsSint32LE(sd.SavedToffset); + for (int i = 0; i < MAX_INTERPRET; ++i) + sd.SavedICInfo[i].syncWithSerializer(s); + for (int i = 0; i < MAX_POLY; ++i) + s.syncAsUint32LE(sd.SavedDeadPolys[i]); + s.syncAsUint32LE(sd.SavedControl); + s.syncAsUint32LE(sd.SavedMidi); + s.syncAsUint32LE(sd.SavedLoop); + s.syncAsUint32LE(sd.SavedNoBlocking); + + // SavedNoScrollData + for (int i = 0; i < MAX_VNOSCROLL; ++i) + syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]); + for (int i = 0; i < MAX_HNOSCROLL; ++i) + syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]); + s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV); + s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH); +} + + +/** + * Called when saving a game to a new file. + * Generates a new, unique, filename. + */ +static char *NewName(void) { + static char result[FNAMELEN]; + int i; + int ano = 1; // Allocated number + + while (1) { + Common::String fname = _vm->getSavegameFilename(ano); + strcpy(result, fname.c_str()); + + for (i = 0; i < numSfiles; i++) + if (!strcmp(savedFiles[i].name, result)) + break; + + if (i == numSfiles) + break; + ano++; + } + + return result; +} + +/** + * Interrogate the current DOS directory for saved game files. + * Store the file details, ordered by time, in savedFiles[] and return + * the number of files found). + */ +int getList(void) { + // No change since last call? + // TODO/FIXME: Just always reload this data? Be careful about slow downs!!! + if (!NeedLoad) + return numSfiles; + + int i; + + const Common::String pattern = _vm->getSavegamePattern(); + Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str()); + + numSfiles = 0; + + for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) { + if (numSfiles >= MAX_SFILES) + break; + + const Common::String &fname = *file; + Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str()); + if (f == NULL) { + continue; + } + + // Try to load save game header + Serializer s(f, 0); + SaveGameHeader hdr; + bool validHeader = syncSaveGameHeader(s, hdr); + delete f; + if (!validHeader) { + continue; // Invalid header, or savegame too new -> skip it + // TODO: In SCUMM, we still show an entry for the save, but with description + // "incompatible version". + } + + i = numSfiles; +#ifndef DISABLE_SAVEGAME_SORTING + for (i = 0; i < numSfiles; i++) { + if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) { + Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]); + break; + } + } +#endif + + strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN); + strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN); + savedFiles[i].desc[SG_DESC_LEN - 1] = 0; + savedFiles[i].dateTime = hdr.dateTime; + + ++numSfiles; + } + + // Next getList() needn't do its stuff again + NeedLoad = false; + + return numSfiles; +} + + +char *ListEntry(int i, letype which) { + if (i == -1) + i = numSfiles; + + assert(i >= 0); + + if (i < numSfiles) + return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc; + else + return NULL; +} + +static void DoSync(Serializer &s) { + int sg; + + syncSavedData(s, *srsd); + syncGlobInfo(s); // Glitter globals + syncInvInfo(s); // Inventory data + + // Held object + if (s.isSaving()) + sg = WhichItemHeld(); + s.syncAsSint32LE(sg); + if (s.isLoading()) + HoldItem(sg); + + syncTimerInfo(s); // Timer data + syncPolyInfo(s); // Dead polygon data + syncSCdata(s); // Hook Scene and delayed scene + + s.syncAsSint32LE(*SaveSceneSsCount); + + if (*SaveSceneSsCount != 0) { + SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData; + for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr) + syncSavedData(s, *sdPtr); + } + + syncAllActorsAlive(s); +} + +/** + * DoRestore + */ +static bool DoRestore(void) { + Common::InSaveFile *f; + uint32 id; + + f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name); + if (f == NULL) { + return false; + } + + Serializer s(f, 0); + SaveGameHeader hdr; + if (!syncSaveGameHeader(s, hdr)) { + delete f; // Invalid header, or savegame too new -> skip it + return false; + } + + DoSync(s); + + id = f->readSint32LE(); + if (id != (uint32)0xFEEDFACE) + error("Incompatible saved game"); + + bool failed = f->ioFailed(); + + delete f; + + return !failed; +} + +/** + * DoSave + */ +static void DoSave(void) { + Common::OutSaveFile *f; + const char *fname; + + // Next getList() must do its stuff again + NeedLoad = true; + + if (SaveSceneName == NULL) + SaveSceneName = NewName(); + if (SaveSceneDesc[0] == 0) + SaveSceneDesc = "unnamed"; + + fname = SaveSceneName; + + f = _vm->getSaveFileMan()->openForSaving(fname); + if (f == NULL) + return; + + Serializer s(0, f); + + // Write out a savegame header + SaveGameHeader hdr; + hdr.id = SAVEGAME_ID; + hdr.size = SAVEGAME_HEADER_SIZE; + hdr.ver = CURRENT_VER; + memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN); + hdr.desc[SG_DESC_LEN - 1] = 0; + g_system->getTimeAndDate(hdr.dateTime); + if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) { + goto save_failure; + } + + DoSync(s); + + // Write out the special Id for Discworld savegames + f->writeUint32LE(0xFEEDFACE); + if (f->ioFailed()) + goto save_failure; + + f->finalize(); + delete f; + return; + +save_failure: + delete f; + _vm->getSaveFileMan()->removeSavefile(fname); +} + +/** + * ProcessSRQueue + */ +void ProcessSRQueue(void) { + switch (SRstate) { + case SR_DORESTORE: + if (DoRestore()) { + RestoreScene(srsd, false); + } + SRstate = SR_IDLE; + break; + + case SR_DOSAVE: + DoSave(); + SRstate = SR_IDLE; + break; + default: + break; + } +} + + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { + assert(SRstate == SR_IDLE); + + SaveSceneName = name; + SaveSceneDesc = desc; + SaveSceneSsCount = pSsCount; + SaveSceneSsData = (char *)pSsData; + srsd = sd; + SRstate = SR_DOSAVE; +} + +void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { + assert(num >= 0); + + RestoreGameNumber = num; + SaveSceneSsCount = pSsCount; + SaveSceneSsData = (char *)pSsData; + srsd = sd; + SRstate = SR_DORESTORE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp new file mode 100644 index 0000000000..9f0d7b9039 --- /dev/null +++ b/engines/tinsel/savescn.cpp @@ -0,0 +1,336 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h" // FadeOutFast() +#include "tinsel/graphics.h" // ClearScreen() +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/tinlib.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern SCNHANDLE GetBgroundHandle(void); +extern void SetDoFadeIn(bool tf); + +// In DOS_DW.C +void RestoreMasterProcess(INT_CONTEXT *pic); + +// in EVENTS.C (declared here and not in events.h because of strange goings-on) +void RestoreProcess(INT_CONTEXT *pic); + +// in PLAY.C +extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y); + +// in SCENE.C +extern SCNHANDLE GetSceneHandle(void); +extern void NewScene(SCNHANDLE scene, int entry); + + + + +//----------------- LOCAL DEFINES -------------------- + +enum { + RS_COUNT = 5, // Restore scene count + + MAX_NEST = 4 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool ASceneIsSaved = false; + +static int savedSceneCount = 0; + +//static SAVED_DATA s_ssData[MAX_NEST]; +static SAVED_DATA *s_ssData = 0; +static SAVED_DATA sgData; + +static SAVED_DATA *s_rsd = 0; + +static int s_restoreSceneCount = 0; + +static bool bNoFade = false; + +//----------------- FORWARD REFERENCES -------------------- + + + +void InitialiseSs(void) { + if (s_ssData == NULL) { + s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA)); + if (s_ssData == NULL) { + error("Cannot allocate memory for scene changes"); + } + } else + savedSceneCount = 0; +} + +void FreeSs(void) { + if (s_ssData) { + free(s_ssData); + s_ssData = NULL; + } +} + +/** + * Save current scene. + * @param sd Pointer to the scene data + */ +void SaveScene(SAVED_DATA *sd) { + sd->SavedSceneHandle = GetSceneHandle(); + sd->SavedBgroundHandle = GetBgroundHandle(); + SaveMovers(sd->SavedMoverInfo); + sd->NumSavedActors = SaveActors(sd->SavedActorInfo); + PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset); + SaveInterpretContexts(sd->SavedICInfo); + SaveDeadPolys(sd->SavedDeadPolys); + sd->SavedControl = TestToken(TOKEN_CONTROL); + CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop); + sd->SavedNoBlocking = bNoBlocking; + GetNoScrollData(&sd->SavedNoScrollData); + + ASceneIsSaved = true; +} + +/** + * Initiate restoration of the saved scene. + * @param sd Pointer to the scene data + * @param bFadeOut Flag to perform a fade out + */ +void RestoreScene(SAVED_DATA *sd, bool bFadeOut) { + s_rsd = sd; + + if (bFadeOut) + s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count + else + s_restoreSceneCount = RS_COUNT; // Set restore scene count +} + +/** + * Checks that all non-moving actors are playing the same reel as when + * the scene was saved. + * Also 'stand' all the moving actors at their saved positions. + */ +void sortActors(SAVED_DATA *rsd) { + for (int i = 0; i < rsd->NumSavedActors; i++) { + ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive); + + // Should be playing the same reel. + if (rsd->SavedActorInfo[i].presFilm != 0) { + if (!actorAlive(rsd->SavedActorInfo[i].actorID)) + continue; + + playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z, + rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY); + } + } + + RestoreAuxScales(rsd->SavedMoverInfo); + for (int i = 0; i < MAX_MOVERS; i++) { + if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR) + stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx, + rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm); + } +} + + +//--------------------------------------------------------------------------- + +void ResumeInterprets(SAVED_DATA *rsd) { + // Master script only affected on restore game, not restore scene + if (rsd == &sgData) { + g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1); + FreeMasterInterpretContext(); + } + + for (int i = 0; i < MAX_INTERPRET; i++) { + switch (rsd->SavedICInfo[i].GSort) { + case GS_NONE: + break; + + case GS_INVENTORY: + if (rsd->SavedICInfo[i].event != POINTED) { + RestoreProcess(&rsd->SavedICInfo[i]); + } + break; + + case GS_MASTER: + // Master script only affected on restore game, not restore scene + if (rsd == &sgData) + RestoreMasterProcess(&rsd->SavedICInfo[i]); + break; + + case GS_ACTOR: + RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]); + break; + + case GS_POLYGON: + case GS_SCENE: + RestoreProcess(&rsd->SavedICInfo[i]); + break; + } + } +} + +/** + * Do restore scene + * @param n Id + */ +static int DoRestoreScene(SAVED_DATA *rsd, int n) { + switch (n) { + case RS_COUNT + COUNTOUT_COUNT: + // Trigger pre-load and fade and start countdown + FadeOutFast(NULL); + break; + + case RS_COUNT: + _vm->_sound->stopAllSamples(); + ClearScreen(); + RestoreDeadPolys(rsd->SavedDeadPolys); + NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM); + SetDoFadeIn(!bNoFade); + bNoFade = false; + startupBackground(rsd->SavedBgroundHandle); + KillScroll(); + PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset); + bNoBlocking = rsd->SavedNoBlocking; + RestoreNoScrollData(&rsd->SavedNoScrollData); +/* + break; + + case RS_COUNT - 1: +*/ + sortActors(rsd); + break; + + case 2: + break; + + case 1: + RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop); + if (rsd->SavedControl) + control(CONTROL_ON); // TOKEN_CONTROL was free + ResumeInterprets(rsd); + } + + return n - 1; +} + +/** + * Restore game + * @param num num + */ +void RestoreGame(int num) { + KillInventory(); + + RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData); + + // Actual restoring is performed by ProcessSRQueue +} + +/** + * Save game + * @param name Name of savegame + * @param desc Description of savegame + */ +void SaveGame(char *name, char *desc) { + // Get current scene data + SaveScene(&sgData); + + RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData); + + // Actual saving is performed by ProcessSRQueue +} + + +//--------------------------------------------------------------------------------- + +bool IsRestoringScene() { + if (s_restoreSceneCount) { + s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount); + } + + return s_restoreSceneCount ? true : false; +} + +/** + * Please Restore Scene + */ +void PleaseRestoreScene(bool bFade) { + // only called by restore_scene PCODE + if (s_restoreSceneCount == 0) { + assert(savedSceneCount >= 1); // No saved scene to restore + + if (ASceneIsSaved) + RestoreScene(&s_ssData[--savedSceneCount], bFade); + if (!bFade) + bNoFade = true; + } +} + +/** + * Please Save Scene + */ +void PleaseSaveScene(CORO_PARAM) { + // only called by save_scene PCODE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(savedSceneCount < MAX_NEST); // nesting limit reached + + // Don't save the same thing multiple times! + // FIXME/TODO: Maybe this can be changed to an assert? + if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle()) + CORO_KILL_SELF(); + + SaveScene(&s_ssData[savedSceneCount++]); + + CORO_END_CODE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h new file mode 100644 index 0000000000..a999c9bffa --- /dev/null +++ b/engines/tinsel/savescn.h @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_SAVESCN_H +#define TINSEL_SAVESCN_H + +#include <time.h> // for time_t struct + +#include "tinsel/actors.h" // SAVED_ACTOR +#include "tinsel/dw.h" // SCNHANDLE +#include "tinsel/rince.h" // SAVED_MOVER +#include "tinsel/pcode.h" // INT_CONTEXT +#include "tinsel/scroll.h" // SCROLLDATA + +namespace Tinsel { + +enum { + SG_DESC_LEN = 40, // Max. saved game description length + MAX_SFILES = 30, + + // FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the + // name field in savedFiles. Raising it to 256 as a preliminary fix. + FNAMELEN = 256 // 8.3 +}; + +struct SFILES { + char name[FNAMELEN]; + char desc[SG_DESC_LEN + 2]; + struct tm dateTime; +}; + +struct SAVED_DATA { + SCNHANDLE SavedSceneHandle; // Scene handle + SCNHANDLE SavedBgroundHandle; // Background handle + SAVED_MOVER SavedMoverInfo[MAX_MOVERS]; // Moving actors + SAVED_ACTOR SavedActorInfo[MAX_SAVED_ACTORS]; // } Actors + int NumSavedActors; // } + int SavedLoffset, SavedToffset; // Screen offsets + INT_CONTEXT SavedICInfo[MAX_INTERPRET]; // Interpret contexts + bool SavedDeadPolys[MAX_POLY]; + bool SavedControl; + SCNHANDLE SavedMidi; // } + bool SavedLoop; // } Midi + bool SavedNoBlocking; + SCROLLDATA SavedNoScrollData; +}; + + +enum SRSTATE { + SR_IDLE, SR_DORESTORE, SR_DONERESTORE, + SR_DOSAVE, SR_DONESAVE, SR_ABORTED +}; + +void PleaseRestoreScene(bool bFade); +void PleaseSaveScene(CORO_PARAM); + +bool IsRestoringScene(); + + +enum letype{ + LE_NAME, LE_DESC +}; + +char *ListEntry(int i, letype which); +int getList(void); + +void RestoreGame(int num); +void SaveGame(char *name, char *desc); + +void ProcessSRQueue(void); + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); +void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); + +void InitialiseSs(void); +void FreeSs(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_SAVESCN_H */ diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp new file mode 100644 index 0000000000..70700c16a3 --- /dev/null +++ b/engines/tinsel/scene.cpp @@ -0,0 +1,306 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Starts up new scenes. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/film.h" +#include "tinsel/move.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" // stopAllSamples() +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" // process IDs +#include "tinsel/polygons.h" +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void DropBackground(void); + +// in EFFECT.C +extern void EffectPolyProcess(CORO_PARAM, const void *); + +// in PDISPLAY.C +#ifdef DEBUG +extern void CursorPositionProcess(CORO_PARAM, const void *); +#endif +extern void TagProcess(CORO_PARAM, const void *); +extern void PointProcess(CORO_PARAM, const void *); +extern void EnableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +#include "common/pack-start.h" // START STRUCT PACKING + +/** scene structure - one per scene */ +struct SCENE_STRUC { + int32 numEntrance; //!< number of entrances in this scene + int32 numPoly; //!< number of various polygons in this scene + int32 numActor; //!< number of actors in this scene + int32 defRefer; //!< Default refer direction + SCNHANDLE hSceneScript; //!< handle to scene script + SCNHANDLE hEntrance; //!< handle to table of entrances + SCNHANDLE hPoly; //!< handle to table of polygons + SCNHANDLE hActor; //!< handle to table of actors +} PACKED_STRUCT; + +/** entrance structure - one per entrance */ +struct ENTRANCE_STRUC { + int32 eNumber; //!< entrance number + SCNHANDLE hScript; //!< handle to entrance script +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#ifdef DEBUG +static bool ShowPosition = false; // Set when showpos() has been called +#endif + +static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene() + + +/** + * Started up for scene script and entrance script. + */ +static void SceneTinselProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + SCNHANDLE *ss = (SCNHANDLE *)param; + assert(*ss); // Must have some code to run + + CORO_BEGIN_CODE(_ctx); + + _ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +/** + * Get the SCENE_STRUC + * Initialise polygons for the scene + * Initialise the actors for this scene + * Run the appropriate entrance code (if any) + * Get the default refer type + */ +static void LoadScene(SCNHANDLE scene, int entry) { + const SCENE_STRUC *ss; + const ENTRANCE_STRUC *es; + uint i; + + // Scene structure + SceneHandle = scene; // Save scene handle in case of Save_Scene() + + LockMem(SceneHandle); // Make sure scene is loaded + LockScene(SceneHandle); // Prevent current scene from being discarded + + ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE); + assert(ss != NULL); + + // Initialise all the polygons for this scene + InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM)); + + // Initialise the actors for this scene + StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM)); + + if (entry != NO_ENTRY_NUM) { + + // Run the appropriate entrance code (if any) + es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance)); + for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) { + if (FROM_LE_32(es->eNumber) == (uint)entry) { + if (es->hScript) + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript)); + break; + } + } + + if (i == FROM_LE_32(ss->numEntrance)) + error("Non-existant scene entry number"); + + if (ss->hSceneScript) + g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript)); + } + + // Default refer type + SetDefaultRefer(FROM_LE_32(ss->defRefer)); +} + + +/** + * Wrap up the last scene. + */ +void EndScene(void) { + if (SceneHandle != 0) { + UnlockScene(SceneHandle); + SceneHandle = 0; + } + + KillInventory(); // Close down any open inventory + + DropPolygons(); // No polygons + DropNoScrolls(); // No no-scrolls + DropBackground(); // No background + DropMActors(); // No moving actors + DropCursor(); // No cursor + DropActors(); // No actor reels running + FreeAllTokens(); // No-one has tokens + FreeMostInterpretContexts(); // Only master script still interpreting + + _vm->_sound->stopAllSamples(); // Kill off any still-running sample + + // init the palette manager + ResetPalAllocator(); + + // init the object manager + KillAllObjects(); + + // kill all destructable process + g_scheduler->killMatchingProcess(PID_DESTROY, PID_DESTROY); +} + +/** + * + */ +void PrimeBackground(void) { + // structure for playfields + static PLAYFIELD playfield[] = { + { // FIELD WORLD + NULL, // display list + 0, // init field x + 0, // init field y + 0, // x vel + 0, // y vel + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect + false // moved flag + }, + { // FIELD STATUS + NULL, // display list + 0, // init field x + 0, // init field y + 0, // x vel + 0, // y vel + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect + false // moved flag + } + }; + + // structure for background + static BACKGND backgnd = { + BLACK, // sky colour + Common::Point(0, 0), // initial world pos + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // scroll limits + 0, // no background update process + NULL, // no x scroll table + NULL, // no y scroll table + 2, // 2 playfields + playfield, // playfield pointer + false // no auto-erase + }; + + InitBackground(&backgnd); +} + +/** + * Start up the standard stuff for the next scene. + */ + +void PrimeScene(void) { + + bNoBlocking = false; + + RestartCursor(); // Restart the cursor + EnableTags(); // Next scene with tags enabled + + g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0); + g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0); + +#ifdef DEBUG + if (ShowPosition) + g_scheduler->createProcess(PID_POSITION, CursorPositionProcess, NULL, 0); +#endif + + g_scheduler->createProcess(PID_TAG, TagProcess, NULL, 0); + g_scheduler->createProcess(PID_TAG, PointProcess, NULL, 0); + + // init the current background + PrimeBackground(); +} + +/** + * Wrap up the last scene and start up the next scene. + */ + +void NewScene(SCNHANDLE scene, int entry) { + EndScene(); // Wrap up the last scene. + + PrimeScene(); // Start up the standard stuff for the next scene. + + LoadScene(scene, entry); +} + +#ifdef DEBUG +/** + * Sets the ShowPosition flag, causing the cursor position process to be + * created in each scene. + */ + +void setshowpos(void) { + ShowPosition = true; +} +#endif + +/** + * Return the current scene handle. + */ + +SCNHANDLE GetSceneHandle(void) { + return SceneHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h new file mode 100644 index 0000000000..d0fc6e1ae3 --- /dev/null +++ b/engines/tinsel/scene.h @@ -0,0 +1,73 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Scene parsing defines + */ + +#ifndef TINSEL_SCENE_H +#define TINSEL_SCENE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +enum { + MAX_NODES = 32, //!< maximum nodes in a Node Path + MAX_NOSCROLL = 16, //!< maximum number of NoScroll commands in a scene + MAX_ENTRANCE = 25, //!< maximum number of entrances in a scene + MAX_POLY = 256, //!< maximum number of polygons in a scene + MAX_ACTOR = 32 //!< maximum number of actors in a scene +}; + +/** reference direction */ +enum REFTYPE { + REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT +}; + +enum TFTYPE { + TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS +}; + +/** different actor masks */ +enum MASK_TYPE{ + ACT_DEFAULT, + ACT_MASK = -1, + ACT_ALWAYS = -2 +}; + +/** different scales */ +enum SCALE { + SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL, + SCALE_COMPACT, SCALE_TINY, + SCALE_AUX1, SCALE_AUX2, SCALE_AUX3, + SCALE_AUX4, SCALE_AUX5 +}; + +/** different reels */ +enum REEL { + REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT +}; + +} // end of namespace Tinsel + +#endif // TINSEL_SCENE_H diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp new file mode 100644 index 0000000000..72cfeaf6b0 --- /dev/null +++ b/engines/tinsel/sched.cpp @@ -0,0 +1,345 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Process scheduler. + */ + +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + +Scheduler *g_scheduler = 0; + +/** process structure */ +struct PROCESS { + PROCESS *pNext; //!< pointer to next process in active or free list + + CoroContext state; //!< the state of the coroutine + CORO_ADDR coroAddr; //!< the entry point of the coroutine + + int sleepTime; //!< number of scheduler cycles to sleep + int pid; //!< process ID + char param[PARAM_SIZE]; //!< process specific info +}; + + +Scheduler::Scheduler() { + processList = 0; + pFreeProcesses = 0; + pCurrent = 0; + +#ifdef DEBUG + // diagnostic process counters + numProcs = 0; + maxProcs = 0; +#endif + + pRCfunction = 0; + + active = new PROCESS; + + g_scheduler = this; // FIXME HACK +} + +Scheduler::~Scheduler() { + free(processList); + processList = NULL; + + delete active; + active = 0; +} + +/** + * Kills all processes and places them on the free list. + */ +void Scheduler::reset() { + +#ifdef DEBUG + // clear number of process in use + numProcs = 0; +#endif + + if (processList == NULL) { + // first time - allocate memory for process list + processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS)); + + // make sure memory allocated + if (processList == NULL) { + error("Cannot allocate memory for process data"); + } + + // fill with garbage + memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS)); + } + + // no active processes + pCurrent = active->pNext = NULL; + + // place first process on free list + pFreeProcesses = processList; + + // link all other processes after first + for (int i = 1; i < NUM_PROCESS; i++) { + processList[i - 1].pNext = processList + i; + } + + // null the last process + processList[NUM_PROCESS - 1].pNext = NULL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of process used at once. + */ +void Scheduler::printStats(void) { + printf("%i process of %i used.\n", maxProcs, NUM_PROCESS); +} +#endif + + +/** + * Give all active processes a chance to run + */ +void Scheduler::schedule(void) { + // start dispatching active process list + PROCESS *pPrevProc = active; + PROCESS *pProc = active->pNext; + while (pProc != NULL) { + if (--pProc->sleepTime <= 0) { + // process is ready for dispatch, activate it + pCurrent = pProc; + pProc->coroAddr(pProc->state, pProc->param); + pCurrent = NULL; + if (!pProc->state || pProc->state->_sleep <= 0) { + // Coroutine finished + killProcess(pProc); + pProc = pPrevProc; + } else { + pProc->sleepTime = pProc->state->_sleep; + } + } + pPrevProc = pProc; + pProc = pProc->pNext; + } +} + + +/** + * Creates a new process. + * + * @param pid process identifier + * @param CORO_ADDR coroutine start address + * @param pParam process specific info + * @param sizeParam size of process specific info + */ +PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) { + PROCESS *pProc; + + // get a free process + pProc = pFreeProcesses; + + // trap no free process + assert(pProc != NULL); // Out of processes + +#ifdef DEBUG + // one more process in use + if (++numProcs > maxProcs) + maxProcs = numProcs; +#endif + + // get link to next free process + pFreeProcesses = pProc->pNext; + + if (pCurrent != NULL) { + // place new process before the next active process + pProc->pNext = pCurrent->pNext; + + // make this new process the next active process + pCurrent->pNext = pProc; + } else { // no active processes, place process at head of list + pProc->pNext = active->pNext; + active->pNext = pProc; + } + + // set coroutine entry point + pProc->coroAddr = coroAddr; + + // clear coroutine state + pProc->state = 0; + + // wake process up as soon as possible + pProc->sleepTime = 1; + + // set new process id + pProc->pid = pid; + + // set new process specific info + if (sizeParam) { + assert(sizeParam > 0 && sizeParam <= PARAM_SIZE); + + // set new process specific info + memcpy(pProc->param, pParam, sizeParam); + } + + + // return created process + return pProc; +} + +/** + * Kills the specified process. + * + * @param pKillProc which process to kill + */ +void Scheduler::killProcess(PROCESS *pKillProc) { + PROCESS *pProc, *pPrev; // process list pointers + + // make sure a valid process pointer + assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1); + + // can not kill the current process using killProcess ! + assert(pCurrent != pKillProc); + +#ifdef DEBUG + // one less process in use + --numProcs; + assert(numProcs >= 0); +#endif + + // search the active list for the process + for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { + if (pProc == pKillProc) { + // found process in active list + + // Free process' resources + if (pRCfunction != NULL) + (pRCfunction)(pProc); + + delete pProc->state; + + // make prev point to next to unlink pProc + pPrev->pNext = pProc->pNext; + + // link first free process after pProc + pProc->pNext = pFreeProcesses; + + // make pProc the first free process + pFreeProcesses = pProc; + + return; + } + } + + // process not found in active list if we get to here + error("killProcess(): tried to kill a process not in the list of active processes"); +} + + + +/** + * Returns a pointer to the currently running process. + */ +PROCESS *Scheduler::getCurrentProcess(void) { + return pCurrent; +} + +/** + * Returns the process identifier of the specified process. + * + * @param pProc which process + */ +int Scheduler::getCurrentPID() const { + PROCESS *pProc = pCurrent; + + // make sure a valid process pointer + assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1); + + // return processes PID + return pProc->pid; +} + +/** + * Kills any process matching the specified PID. The current + * process cannot be killed. + * + * @param pidKill process identifier of process to kill + * @param pidMask mask to apply to process identifiers before comparison + * @return The number of processes killed is returned. + */ +int Scheduler::killMatchingProcess(int pidKill, int pidMask) { + int numKilled = 0; + PROCESS *pProc, *pPrev; // process list pointers + + for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { + if ((pProc->pid & pidMask) == pidKill) { + // found a matching process + + // dont kill the current process + if (pProc != pCurrent) { + // kill this process + numKilled++; + + // make prev point to next to unlink pProc + pPrev->pNext = pProc->pNext; + + // link first free process after pProc + pProc->pNext = pFreeProcesses; + + // make pProc the first free process + pFreeProcesses = pProc; + + // set to a process on the active list + pProc = pPrev; + } + } + } + +#ifdef DEBUG + // adjust process in use + numProcs -= numKilled; + assert(numProcs >= 0); +#endif + + // return number of processes killed + return numKilled; +} + + + +/** + * Set pointer to a function to be called by killProcess(). + * + * May be called by a resource allocator, the function supplied is + * called by killProcess() to allow the resource allocator to free + * resources allocated to the dying process. + * + * @param pFunc Function to be called by killProcess() + */ +void Scheduler::setResourceCallback(VFPTRPP pFunc) { + pRCfunction = pFunc; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h new file mode 100644 index 0000000000..0d90b3bb9f --- /dev/null +++ b/engines/tinsel/sched.h @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Data structures used by the process scheduler + */ + +#ifndef TINSEL_SCHED_H // prevent multiple includes +#define TINSEL_SCHED_H + +#include "tinsel/dw.h" // new data types +#include "tinsel/coroutine.h" + +namespace Tinsel { + +// the size of process specific info +#define PARAM_SIZE 32 + +// the maximum number of processes +#define NUM_PROCESS 64 + +typedef void (*CORO_ADDR)(CoroContext &, const void *); + + +struct PROCESS; + +/** + * Create and manage "processes" (really coroutines). + */ +class Scheduler { +public: + /** Pointer to a function of the form "void function(PPROCESS)" */ + typedef void (*VFPTRPP)(PROCESS *); + +private: + + /** list of all processes */ + PROCESS *processList; + + /** active process list - also saves scheduler state */ + PROCESS *active; + + /** pointer to free process list */ + PROCESS *pFreeProcesses; + + /** the currently active process */ + PROCESS *pCurrent; + +#ifdef DEBUG + // diagnostic process counters + int numProcs; + int maxProcs; +#endif + + /** + * Called from killProcess() to enable other resources + * a process may be allocated to be released. + */ + VFPTRPP pRCfunction; + + +public: + + Scheduler(); + ~Scheduler(); + + void reset(); + + #ifdef DEBUG + void printStats(); + #endif + + void schedule(); + + PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam); + void killProcess(PROCESS *pKillProc); + + PROCESS *getCurrentProcess(); + int getCurrentPID() const; + int killMatchingProcess(int pidKill, int pidMask); + + + void setResourceCallback(VFPTRPP pFunc); + +}; + +extern Scheduler *g_scheduler; // FIXME: Temporary global var, to be used until everything has been OOifyied + +} // end of namespace Tinsel + +#endif // TINSEL_SCHED_H diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp new file mode 100644 index 0000000000..b14b1c5962 --- /dev/null +++ b/engines/tinsel/scn.cpp @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * A (some would say very) small collection of utility functions. + */ + +#include "common/endian.h" +#include "common/util.h" + +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/scn.h" +#include "tinsel/tinsel.h" // for _vm + +namespace Tinsel { + +/** + * Given a scene handle and a chunk id, gets the scene in RAM and + * locates the requested chunk. + * @param handle Scene handle + * @param chunk Chunk Id + */ +byte *FindChunk(SCNHANDLE handle, uint32 chunk) { + byte *bptr = LockMem(handle); + uint32 *lptr = (uint32 *)bptr; + uint32 add; + + // V1 chunk types can be found by substracting 2 from the + // chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are + // the same in V1 and V2 + if (_vm->getVersion() == TINSEL_V1 && + chunk != CHUNK_STRING && chunk != CHUNK_BITMAP) + chunk -= 0x2L; + + while (1) { + if (READ_LE_UINT32(lptr) == chunk) + return (byte *)(lptr + 2); + + ++lptr; + add = READ_LE_UINT32(lptr); + if (!add) + return NULL; + + lptr = (uint32 *)(bptr + add); + } +} + +/** + * Get the actor id from a film (column 0) + */ +int extractActor(SCNHANDLE film) { + const FILM *pfilm = (const FILM *)LockMem(film); + const FREEL *preel = &pfilm->reels[0]; + const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj)); + return (int)FROM_LE_32(pmi->mulID); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h new file mode 100644 index 0000000000..29f3dc51fc --- /dev/null +++ b/engines/tinsel/scn.h @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCN_H // prevent multiple includes +#define TINSEL_SCN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + + +// chunk identifier numbers + +// V2 chunks + +#define CHUNK_STRING 0x33340001L // same in V1 and V2 +#define CHUNK_BITMAP 0x33340002L // same in V1 and V2 +#define CHUNK_CHARPTR 0x33340003L // not used! +#define CHUNK_CHARMATRIX 0x33340004L // not used! +#define CHUNK_PALETTE 0x33340005L // not used! +#define CHUNK_IMAGE 0x33340006L // not used! +#define CHUNK_ANI_FRAME 0x33340007L // not used! +#define CHUNK_FILM 0x33340008L // not used! +#define CHUNK_FONT 0x33340009L // not used! +#define CHUNK_PCODE 0x3334000AL +#define CHUNK_ENTRANCE 0x3334000BL // not used! +#define CHUNK_POLYGONS 0x3334000CL // not used! +#define CHUNK_ACTORS 0x3334000DL // not used! +#define CHUNK_SCENE 0x3334000EL +#define CHUNK_TOTAL_ACTORS 0x3334000FL +#define CHUNK_TOTAL_GLOBALS 0x33340010L +#define CHUNK_TOTAL_OBJECTS 0x33340011L +#define CHUNK_OBJECTS 0x33340012L +#define CHUNK_MIDI 0x33340013L // not used! +#define CHUNK_SAMPLE 0x33340014L // not used! +#define CHUNK_TOTAL_POLY 0x33340015L +#define CHUNK_MBSTRING 0x33340022L // Multi-byte characters + +#define INDEX_FILENAME "index" // name of index file + +byte *FindChunk(SCNHANDLE handle, uint32 chunk); +int extractActor(SCNHANDLE film); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCN_H */ diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp new file mode 100644 index 0000000000..aa1bc67298 --- /dev/null +++ b/engines/tinsel/scroll.cpp @@ -0,0 +1,432 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles scrolling + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/sched.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LEFT 'L' +#define RIGHT 'R' +#define UP 'U' +#define DOWN 'D' + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding + +static int scrollActor = 0; +static PMACTOR psActor = 0; +static int oldx = 0, oldy = 0; + +/** Boundaries and numbers of boundaries */ +static SCROLLDATA sd = { + { + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} + }, + { + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} + }, + 0, + 0 + }; + +static int ImageH = 0, ImageW = 0; + +static bool ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on, + // the cursor is kept over that polygon + // whilst scrolling + +static int scrollPixels = SCROLLPIXELS; + + +/** + * Reset the ScrollCursor flag + */ +void DontScrollCursor(void) { + ScrollCursor = false; +} + +/** + * Set the ScrollCursor flag + */ +void DoScrollCursor(void) { + ScrollCursor = true; +} + +/** + * Configure a no-scroll boundary for a scene. + */ +void SetNoScroll(int x1, int y1, int x2, int y2) { + if (x1 == x2) { + /* Vertical line */ + assert(sd.NumNoH < MAX_HNOSCROLL); + + sd.NoHScroll[sd.NumNoH].ln = x1; // X pos of vertical line + sd.NoHScroll[sd.NumNoH].c1 = y1; + sd.NoHScroll[sd.NumNoH].c2 = y2; + sd.NumNoH++; + } else if (y1 == y2) { + /* Horizontal line */ + assert(sd.NumNoV < MAX_VNOSCROLL); + + sd.NoVScroll[sd.NumNoV].ln = y1; // Y pos of horizontal line + sd.NoVScroll[sd.NumNoV].c1 = x1; + sd.NoVScroll[sd.NumNoV].c2 = x2; + sd.NumNoV++; + } else { + /* No-scroll lines must be horizontal or vertical */ + } +} + +/** + * Does the obvious - called at the end of a scene. + */ +void DropNoScrolls(void) { + sd.NumNoH = sd.NumNoV = 0; +} + +/** + * Called from scroll process when it thinks that a scroll is in order. + * Checks for no-scroll boundaries and sets off a scroll if allowed. + */ +static void NeedScroll(int direction) { + uint i; + int BottomLine, RightCol; + int Loffset, Toffset; + + // get background offsets + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + switch (direction) { + case LEFT: /* Picture will go left, 'camera' right */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoH; i++) { + if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 && + ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || + (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || + (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) + return; + } + + if (LeftScroll <= 0) { + scrollPixels = SCROLLPIXELS; + LeftScroll = RLSCROLL; + } + break; + + case RIGHT: /* Picture will go right, 'camera' left */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + + for (i = 0; i < sd.NumNoH; i++) { + if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 && + ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || + (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || + (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) + return; + } + + if (LeftScroll >= 0) { + scrollPixels = SCROLLPIXELS; + LeftScroll = -RLSCROLL; + } + break; + + case UP: /* Picture will go upwards, 'camera' downwards */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoV; i++) { + if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) && + ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || + (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || + (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) + return; + } + + if (DownScroll <= 0) { + scrollPixels = SCROLLPIXELS; + DownScroll = UDSCROLL; + } + break; + + case DOWN: /* Picture will go downwards, 'camera' upwards */ + + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoV; i++) { + if (Toffset >= sd.NoVScroll[i].ln - 1 && Toffset <= sd.NoVScroll[i].ln + 1 && + ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || + (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || + (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) + return; + } + + if (DownScroll >= 0) { + scrollPixels = SCROLLPIXELS; + DownScroll = -UDSCROLL; + } + break; + } +} + +/** + * Called from scroll process - Scrolls the image as appropriate. + */ +static void ScrollImage(void) { + int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag + int Loffset, Toffset; + int curX, curY; + + // get background offsets + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /* + * Keeping cursor on a tag? + */ + if (ScrollCursor) { + GetCursorXY(&curX, &curY, true); + if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) { + OldLoffset = Loffset; + OldToffset = Toffset; + } else + ScrollCursor = false; + } + + /* + * Horizontal scrolling + */ + if (LeftScroll > 0) { + LeftScroll -= scrollPixels; + if (LeftScroll < 0) { + Loffset += LeftScroll; + LeftScroll = 0; + } + Loffset += scrollPixels; // Move right + if (Loffset > ImageW - SCREEN_WIDTH) + Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right + } else if (LeftScroll < 0) { + LeftScroll += scrollPixels; + if (LeftScroll > 0) { + Loffset += LeftScroll; + LeftScroll = 0; + } + Loffset -= scrollPixels; // Move left + if (Loffset < 0) + Loffset = 0; // Now at extreme left + } + + /* + * Vertical scrolling + */ + if (DownScroll > 0) { + DownScroll -= scrollPixels; + if (DownScroll < 0) { + Toffset += DownScroll; + DownScroll = 0; + } + Toffset += scrollPixels; // Move down + + if (Toffset > ImageH - SCREEN_HEIGHT) + Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom + + } else if (DownScroll < 0) { + DownScroll += scrollPixels; + if (DownScroll > 0) { + Toffset += DownScroll; + DownScroll = 0; + } + Toffset -= scrollPixels; // Move up + + if (Toffset < 0) + Toffset = 0; // Now at extreme top + } + + /* + * Move cursor if keeping cursor on a tag. + */ + if (ScrollCursor) + AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset); + + PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset); +} + + +/** + * See if the actor on whom the camera is is approaching an edge. + * Request a scroll if he is. + */ +static void MonitorScroll(void) { + int newx, newy; + int Loffset, Toffset; + + /* + * Only do it if the actor is there and is visible + */ + if (!psActor || getMActorHideState(psActor) + || getMActorState(psActor) == NO_MACTOR) + return; + + GetActorPos(scrollActor, &newx, &newy); + + if (oldx == newx && oldy == newy) + return; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /* + * Approaching right side or left side of the screen? + */ + if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) { + if (newx > oldx) + NeedScroll(LEFT); + } else if (newx < Loffset + RLDISTANCE && Loffset) { + if (newx < oldx) + NeedScroll(RIGHT); + } + + /* + * Approaching bottom or top of the screen? + */ + if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) { + if (newy > oldy) + NeedScroll(UP); + } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) { + if (newy < oldy) + NeedScroll(DOWN); + } + + oldx = newx; + oldy = newy; +} + +/** + * Decide when to scroll and scroll when decided to. + */ +void ScrollProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + ImageH = BackgroundHeight(); // Dimensions + ImageW = BackgroundWidth(); // of this scene. + + // Give up if there'll be no purpose in this process + if (ImageW == SCREEN_WIDTH && ImageH == SCREEN_HEIGHT) + CORO_KILL_SELF(); + + LeftScroll = DownScroll = 0; // No iterations outstanding + oldx = oldy = 0; + scrollPixels = SCROLLPIXELS; + + if (!scrollActor) + scrollActor = LeadId(); + + psActor = GetMover(scrollActor); + + while (1) { + MonitorScroll(); // Set scroll requirement + + if (LeftScroll || DownScroll) // Scroll if required + ScrollImage(); + + CORO_SLEEP(1); // allow re-scheduling + } + + CORO_END_CODE; +} + +/** + * Change which actor the camera is following. + */ +void ScrollFocus(int ano) { + if (scrollActor != ano) { + oldx = oldy = 0; + scrollActor = ano; + + psActor = ano ? GetMover(scrollActor) : NULL; + } +} + +/** + * Scroll to abslote position. + */ +void ScrollTo(int x, int y, int iter) { + int Loffset, Toffset; // for background offsets + + scrollPixels = iter != 0 ? iter : SCROLLPIXELS; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets + + LeftScroll = x - Loffset; + DownScroll = y - Toffset; +} + +/** + * Kill of any current scroll. + */ +void KillScroll(void) { + LeftScroll = DownScroll = 0; +} + + +void GetNoScrollData(SCROLLDATA *ssd) { + memcpy(ssd, &sd, sizeof(SCROLLDATA)); +} + +void RestoreNoScrollData(SCROLLDATA *ssd) { + memcpy(&sd, ssd, sizeof(SCROLLDATA)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h new file mode 100644 index 0000000000..ac903157f2 --- /dev/null +++ b/engines/tinsel/scroll.h @@ -0,0 +1,77 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCROLL_H // prevent multiple includes +#define TINSEL_SCROLL_H + +namespace Tinsel { + +#define SCROLLPIXELS 8 // Number of pixels to scroll per iteration + +#define RLDISTANCE 50 // Distance from edge that triggers a scroll +#define UDDISTANCE 20 + +// Number of iterations to make +#define RLSCROLL 160 // 20*8 = 160 = half a screen +#define UDSCROLL 100 // 12.5*8 = 100 = half a screen + + +// These structures defined here so boundaries can be saved +struct NOSCROLLB { + int ln; + int c1; + int c2; +}; + +#define MAX_HNOSCROLL 10 +#define MAX_VNOSCROLL 10 + +struct SCROLLDATA{ + NOSCROLLB NoVScroll[MAX_VNOSCROLL]; // Vertical no-scroll boundaries + NOSCROLLB NoHScroll[MAX_HNOSCROLL]; // Horizontal no-scroll boundaries + unsigned NumNoV, NumNoH; // Counts of no-scroll boundaries +}; + + + +void DontScrollCursor(void); +void DoScrollCursor(void); + +void SetNoScroll(int x1, int y1, int x2, int y2); +void DropNoScrolls(void); + +void ScrollProcess(CORO_PARAM, const void *); + +void ScrollFocus(int actor); +void ScrollTo(int x, int y, int iter); + +void KillScroll(void); + +void GetNoScrollData(SCROLLDATA *ssd); +void RestoreNoScrollData(SCROLLDATA *ssd); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCROLL_H */ diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h new file mode 100644 index 0000000000..98ee398ef8 --- /dev/null +++ b/engines/tinsel/serializer.h @@ -0,0 +1,131 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_SERIALIZER_H +#define TINSEL_SERIALIZER_H + +#include "common/scummsys.h" +#include "common/savefile.h" + + +namespace Tinsel { + + +#define SYNC_AS(SUFFIX,TYPE,SIZE) \ + template <class T> \ + void syncAs ## SUFFIX(T &val) { \ + if (_loadStream) \ + val = static_cast<T>(_loadStream->read ## SUFFIX()); \ + else { \ + TYPE tmp = val; \ + _saveStream->write ## SUFFIX(tmp); \ + } \ + _bytesSynced += SIZE; \ + } + + +// TODO: Write comment for this +// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines? +class Serializer { +public: + Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out) + : _loadStream(in), _saveStream(out), _bytesSynced(0) { + assert(in || out); + } + + bool isSaving() { return (_saveStream != 0); } + bool isLoading() { return (_loadStream != 0); } + + uint bytesSynced() const { return _bytesSynced; } + + void syncBytes(byte *buf, uint16 size) { + if (_loadStream) + _loadStream->read(buf, size); + else + _saveStream->write(buf, size); + _bytesSynced += size; + } + + SYNC_AS(Byte, byte, 1) + + SYNC_AS(Uint16LE, uint16, 2) + SYNC_AS(Uint16BE, uint16, 2) + SYNC_AS(Sint16LE, int16, 2) + SYNC_AS(Sint16BE, int16, 2) + + SYNC_AS(Uint32LE, uint32, 4) + SYNC_AS(Uint32BE, uint32, 4) + SYNC_AS(Sint32LE, int32, 4) + SYNC_AS(Sint32BE, int32, 4) + +protected: + Common::SeekableReadStream *_loadStream; + Common::OutSaveFile *_saveStream; + + uint _bytesSynced; +}; + +#undef SYNC_AS + +// TODO: Make a subclass "VersionedSerializer", which makes it easy to support +// multiple versions of a savegame format (again inspired by SCUMM). +/* +class VersionedSerializer : public Serializer { +public: + // "version" is the version of the savegame we are loading/creating + VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version) + : Serializer(in, out), _version(version) { + assert(in || out); + } + + void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) { + if (_version < minVersion || _version > maxVersion) + return; // Do nothing if too old or too new + if (_loadStream) { + _loadStream->read(buf, size); + } else { + _saveStream->write(buf, size); + } + } + ... + +}; + +*/ + +// Mixin class / interface +// TODO Maybe call it ISerializable or SerializableMixin ? +// TODO: Taken from SCUMM engine -- move to common/ code? +class Serializable { +public: + virtual ~Serializable() {} + virtual void saveLoadWithSerializer(Serializer *ser) = 0; +}; + + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp new file mode 100644 index 0000000000..e2a24dbd47 --- /dev/null +++ b/engines/tinsel/sound.cpp @@ -0,0 +1,211 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * sound functionality + */ + +#include "tinsel/sound.h" + +#include "tinsel/dw.h" +#include "tinsel/config.h" +#include "tinsel/music.h" +#include "tinsel/strres.h" +#include "tinsel/tinsel.h" + +#include "common/endian.h" +#include "common/file.h" +#include "common/system.h" + +#include "sound/mixer.h" +#include "sound/audiocd.h" + +namespace Tinsel { + +//--------------------------- General data ---------------------------------- + +SoundManager::SoundManager(TinselEngine *vm) : + //_vm(vm), // TODO: Enable this once global _vm var is gone + _sampleIndex(0), _sampleIndexLen(0) { +} + +SoundManager::~SoundManager() { + free(_sampleIndex); +} + +/** + * Plays the specified sample through the sound driver. + * @param id Identifier of sample to be played + * @param type type of sound (voice or sfx) + * @param handle sound handle + */ +bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) { + // Floppy version has no sample file + if (_vm->getFeatures() & GF_FLOPPY) + return false; + + // no sample driver? + if (!_vm->_mixer->isReady()) + return false; + + // stop any currently playing sample + _vm->_mixer->stopHandle(_handle); + + // make sure id is in range + assert(id > 0 && id < _sampleIndexLen); + + // get file offset for this sample + uint32 dwSampleIndex = _sampleIndex[id]; + + // move to correct position in the sample file + _sampleStream.seek(dwSampleIndex); + if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex) + error("File %s is corrupt", SAMPLE_FILE); + + // read the length of the sample + uint32 sampleLen = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error("File %s is corrupt", SAMPLE_FILE); + + // allocate a buffer + void *sampleBuf = malloc(sampleLen); + assert(sampleBuf); + + // read all of the sample + if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen) + error("File %s is corrupt", SAMPLE_FILE); + + // FIXME: Should set this in a different place ;) + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound); + //_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice); + + + // play it + _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050, + Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED); + + if (handle) + *handle = _handle; + + return true; +} + +/** + * Returns TRUE if there is a sample for the specified sample identifier. + * @param id Identifier of sample to be checked + */ +bool SoundManager::sampleExists(int id) { + if (_vm->_mixer->isReady()) { + // make sure id is in range + if (id > 0 && id < _sampleIndexLen) { + // check for a sample index + if (_sampleIndex[id]) + return true; + } + } + + // no sample driver or no sample + return false; +} + +/** + * Returns true if a sample is currently playing. + */ +bool SoundManager::sampleIsPlaying(void) { + return _vm->_mixer->isSoundHandleActive(_handle); +} + +/** + * Stops any currently playing sample. + */ +void SoundManager::stopAllSamples(void) { + // stop currently playing sample + _vm->_mixer->stopHandle(_handle); +} + +/** + * Opens and inits all sound sample files. + */ +void SoundManager::openSampleFiles(void) { + // Floppy and demo versions have no sample files + if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO) + return; + + Common::File f; + + if (_sampleIndex) + // already allocated + return; + + // open sample index file in binary mode + if (f.open(SAMPLE_INDEX)) { + // get length of index file + f.seek(0, SEEK_END); // move to end of file + _sampleIndexLen = f.pos(); // get file pointer + f.seek(0, SEEK_SET); // back to beginning + + if (_sampleIndex == NULL) { + // allocate a buffer for the indices + _sampleIndex = (uint32 *)malloc(_sampleIndexLen); + + // make sure memory allocated + if (_sampleIndex == NULL) { + // disable samples if cannot alloc buffer for indices + // TODO: Disabled sound if we can't load the sample index? + return; + } + } + + // load data + if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen) + // file must be corrupt if we get to here + error("File %s is corrupt", SAMPLE_FILE); + +#ifdef SCUMM_BIG_ENDIAN + // Convert all ids from LE to native format + for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) { + _sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i); + } +#endif + + // close the file + f.close(); + + // convert file size to size in DWORDs + _sampleIndexLen /= sizeof(uint32); + } else + error("Cannot find file %s", SAMPLE_INDEX); + + // open sample file in binary mode + if (!_sampleStream.open(SAMPLE_FILE)) + error("Cannot find file %s", SAMPLE_FILE); + +/* + // gen length of the largest sample + sampleBuffer.size = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error("File %s is corrupt", SAMPLE_FILE); +*/ +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h new file mode 100644 index 0000000000..56618eeb8e --- /dev/null +++ b/engines/tinsel/sound.h @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * This file contains the Sound Driver data structures etc. + */ + +#ifndef TINSEL_SOUND_H +#define TINSEL_SOUND_H + +#include "common/file.h" +#include "common/file.h" + +#include "sound/mixer.h" + +#include "tinsel/dw.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +#define MAXSAMPVOL 127 + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +class SoundManager { +protected: + + //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone + + /** Sample handle */ + Audio::SoundHandle _handle; + + /** Sample index buffer and number of entries */ + uint32 *_sampleIndex; + + /** Number of entries in the sample index */ + long _sampleIndexLen; + + /** file stream for sample file */ + Common::File _sampleStream; + +public: + + SoundManager(TinselEngine *vm); + ~SoundManager(); + + bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); + void stopAllSamples(void); // Stops any currently playing sample + + bool sampleExists(int id); + bool sampleIsPlaying(void); + + // TODO: Internal method, make this protected? + void openSampleFiles(void); +}; + +} // end of namespace Tinsel + +#endif // TINSEL_SOUND_H diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp new file mode 100644 index 0000000000..abf5a880f6 --- /dev/null +++ b/engines/tinsel/strres.cpp @@ -0,0 +1,209 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "common/file.h" +#include "common/endian.h" + +namespace Tinsel { + +#ifdef DEBUG +// Diagnostic number +int newestString; +#endif + +// buffer for resource strings +static uint8 *textBuffer = 0; + +// language resource string filenames +static const char *languageFiles[] = { + "english.txt", + "french.txt", + "german.txt", + "italian.txt", + "spanish.txt" +}; + +// Set if we're handling 2-byte characters. +bool bMultiByte = false; + +/** + * Called to load a resource file for a different language + * @param newLang The new language + */ +void ChangeLanguage(LANGUAGE newLang) { + Common::File f; + uint32 textLen = 0; // length of buffer + + if (textBuffer) { + // free the previous buffer + free(textBuffer); + textBuffer = NULL; + } + + // Try and open the specified language file. If it fails, and the language + // isn't English, try falling back on opening 'english.txt' - some foreign + // language versions reused it rather than their proper filename + if (!f.open(languageFiles[newLang])) { + if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH])) + error("Cannot find file %s", languageFiles[newLang]); + } + + // Check whether the file is compressed or not - for compressed files the + // first long is the filelength and for uncompressed files it is the chunk + // identifier + textLen = f.readUint32LE(); + if (f.ioFailed()) + error("File %s is corrupt", languageFiles[newLang]); + + if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) { + // the file is uncompressed + + bMultiByte = (textLen == CHUNK_MBSTRING); + + // get length of uncompressed file + textLen = f.size(); + f.seek(0, SEEK_SET); // Set to beginning of file + + if (textBuffer == NULL) { + // allocate a text buffer for the strings + textBuffer = (uint8 *)malloc(textLen); + + // make sure memory allocated + assert(textBuffer); + } + + // load data + if (f.read(textBuffer, textLen) != textLen) + // file must be corrupt if we get to here + error("File %s is corrupt", languageFiles[newLang]); + + // close the file + f.close(); + } else { // the file must be compressed + error("Compression handling has been removed!"); + } +} + +/** + * Loads a string resource identified by id. + * @param id identifier of string to be loaded + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax) { +#ifdef DEBUG + // For diagnostics + newestString = id; +#endif + + // base of string resource table + uint8 *pText = textBuffer; + + // index into text resource file + uint32 index = 0; + + // number of chunks to skip + int chunkSkip = id / STRINGS_PER_CHUNK; + + // number of strings to skip when in the correct chunk + int strSkip = id % STRINGS_PER_CHUNK; + + // length of string + int len; + + // skip to the correct chunk + while (chunkSkip-- != 0) { + // make sure chunk id is correct + assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING); + + if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) { + // TEMPORARY DIRTY BODGE + strcpy(pBuffer, "!! HIGH STRING !!"); + + // string does not exist + return 0; + } + + // get index to next chunk + index = READ_LE_UINT32(pText + index + sizeof(uint32)); + } + + // skip over chunk id and offset + index += (2 * sizeof(uint32)); + + // pointer to strings + pText = pText + index; + + // skip to the correct string + while (strSkip-- != 0) { + // skip to next string + pText += *pText + 1; + } + + // get length of string + len = *pText; + + if (len) { + // the string exists + + // copy the string to the buffer + if (len < bufferMax) { + memcpy(pBuffer, pText + 1, len); + + // null terminate + pBuffer[len] = 0; + + // number of chars copied + return len + 1; + } else { + memcpy(pBuffer, pText + 1, bufferMax - 1); + + // null terminate + pBuffer[bufferMax - 1] = 0; + + // number of chars copied + return bufferMax; + } + } + + // TEMPORARY DIRTY BODGE + strcpy(pBuffer, "!! NULL STRING !!"); + + // string does not exist + return 0; +} + +void FreeTextBuffer() { + if (textBuffer) { + free(textBuffer); + textBuffer = NULL; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h new file mode 100644 index 0000000000..fac287492b --- /dev/null +++ b/engines/tinsel/strres.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#ifndef TINSEL_STRRES_H +#define TINSEL_STRRES_H + +#include "common/scummsys.h" +#include "tinsel/scn.h" + +namespace Tinsel { + +#define STRINGS_PER_CHUNK 64 // number of strings per chunk in the language text files +#define FIRST_STR_ID 1 // id number of first string in string table +#define MAX_STRING_SIZE 255 // maximum size of a string in the resource table +#define MAX_STRRES_SIZE 300000 // maximum size of string resource file + +// Set if we're handling 2-byte characters. +extern bool bMultiByte; + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +/** + * Called to load a resource file for a different language + * @param newLang The new language + */ +void ChangeLanguage(LANGUAGE newLang); + +/** + * Loads a string resource identified by id. + * @param id identifier of string to be loaded + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax); + +/** + * Frees the text buffer allocated from ChangeLanguage() + */ +void FreeTextBuffer(); + +} // end of namespace Tinsel + +#endif + diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp new file mode 100644 index 0000000000..dab97f7fdf --- /dev/null +++ b/engines/tinsel/text.cpp @@ -0,0 +1,279 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Text utilities. + */ + +#include "tinsel/dw.h" +#include "tinsel/graphics.h" // object plotting +#include "tinsel/handle.h" +#include "tinsel/sched.h" // process scheduler defines +#include "tinsel/strres.h" // bMultiByte +#include "tinsel/text.h" // text defines + +namespace Tinsel { + +/** + * Returns the length of one line of a string in pixels. + * @param szStr String + * @param pFont Which font to use for dimensions + */ +int StringLengthPix(char *szStr, const FONT *pFont) { + int strLen; // accumulated length of string + byte c; + SCNHANDLE hImg; + + // while not end of string or end of line + for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) { + if (bMultiByte) { + if (c & 0x80) + c = ((c & ~0x80) << 8) + *++szStr; + } + hImg = FROM_LE_32(pFont->fontDef[c]); + + if (hImg) { + // there is a IMAGE for this character + const IMAGE *pChar = (const IMAGE *)LockMem(hImg); + + // add width of font bitmap + strLen += FROM_LE_16(pChar->imgWidth); + } else + // use width of space character + strLen += FROM_LE_32(pFont->spaceSize); + + // finally add the inter-character spacing + strLen += FROM_LE_32(pFont->xSpacing); + } + + // return length of line in pixels - minus inter-char spacing for last character + strLen -= FROM_LE_32(pFont->xSpacing); + return (strLen > 0) ? strLen : 0; +} + +/** + * Returns the justified x start position of a line of text. + * @param szStr String to output + * @param xPos X position of string + * @param pFont Which font to use + * @param mode Mode flags for the string + */ +int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) { + if (mode & TXT_CENTRE) { + // centre justify the text + + // adjust x positioning by half the length of line in pixels + xPos -= StringLengthPix(szStr, pFont) / 2; + } else if (mode & TXT_RIGHT) { + // right justify the text + + // adjust x positioning by length of line in pixels + xPos -= StringLengthPix(szStr, pFont); + } + + // return text line x start position + return xPos; +} + +/** + * Main text outputting routine. If a object list is specified a + * multi-object is created for the whole text and a pointer to the head + * of the list is returned. + * @param pList Object list to add text to + * @param szStr String to output + * @param colour Colour for monochrome text + * @param xPos X position of string + * @param yPos Y position of string + * @param hFont Which font to use + * @param mode Mode flags for the string + */ +OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos, + SCNHANDLE hFont, int mode) { + int xJustify; // x position of text after justification + int yOffset; // offset to next line of text + OBJECT *pFirst; // head of multi-object text list + OBJECT *pChar = 0; // object ptr for the character + byte c; + SCNHANDLE hImg; + const IMAGE *pImg; + + // make sure there is a linked list to add text to + assert(pList); + + // get font pointer + const FONT *pFont = (const FONT *)LockMem(hFont); + + // init head of text list + pFirst = NULL; + + // get image for capital W + assert(pFont->fontDef[(int)'W']); + pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W'])); + + // get height of capital W for offset to next line + yOffset = FROM_LE_16(pImg->imgHeight); + + while (*szStr) { + // x justify the text according to the mode flags + xJustify = JustifyText(szStr, xPos, pFont, mode); + + // repeat until end of string or end of line + while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) { + if (bMultiByte) { + if (c & 0x80) + c = ((c & ~0x80) << 8) + *++szStr; + } + hImg = FROM_LE_32(pFont->fontDef[c]); + + if (hImg == 0) { + // no image for this character + + // add font spacing for a space character + xJustify += FROM_LE_32(pFont->spaceSize); + } else { // printable character + + int aniX, aniY; // char image animation offsets + + OBJ_INIT oi; + oi.hObjImg = FROM_LE_32(pFont->fontInit.hObjImg); + oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags); + oi.objID = FROM_LE_32(pFont->fontInit.objID); + oi.objX = FROM_LE_32(pFont->fontInit.objX); + oi.objY = FROM_LE_32(pFont->fontInit.objY); + oi.objZ = FROM_LE_32(pFont->fontInit.objZ); + + // allocate and init a character object + if (pFirst == NULL) + // first time - init head of list + pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!! + else + // chain to multi-char list + pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!! + + // convert image handle to pointer + pImg = (const IMAGE *)LockMem(hImg); + + // fill in character object + pChar->hImg = hImg; // image def + pChar->width = FROM_LE_16(pImg->imgWidth); // width of chars bitmap + pChar->height = FROM_LE_16(pImg->imgHeight); // height of chars bitmap + pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap + + // check for absolute positioning + if (mode & TXT_ABSOLUTE) + pChar->flags |= DMA_ABS; + + // set characters colour - only effective for mono fonts + pChar->constant = colour; + + // get Y animation offset + GetAniOffset(hImg, pChar->flags, &aniX, &aniY); + + // set x position - ignore animation point + pChar->xPos = intToFrac(xJustify); + + // set y position - adjust for animation point + pChar->yPos = intToFrac(yPos - aniY); + + if (mode & TXT_SHADOW) { + // we want to shadow the character + OBJECT *pShad; + + // allocate a object for the shadow and chain to multi-char list + pShad = pChar->pSlave = AllocObject(); + + // copy the character for a shadow + CopyObject(pShad, pChar); + + // add shadow offsets to characters position + pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow)); + pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow)); + + // shadow is behind the character + pShad->zPos--; + + // shadow is always mono + pShad->flags = DMA_CNZ | DMA_CHANGED; + + // check for absolute positioning + if (mode & TXT_ABSOLUTE) + pShad->flags |= DMA_ABS; + + // shadow always uses first palette entry + // should really alloc a palette here also ???? + pShad->constant = 1; + + // add shadow to object list + InsertObject(pList, pShad); + } + + // add character to object list + InsertObject(pList, pChar); + + // move to end of list + if (pChar->pSlave) + pChar = pChar->pSlave; + + // add character spacing + xJustify += FROM_LE_16(pImg->imgWidth); + } + + // finally add the inter-character spacing + xJustify += FROM_LE_32(pFont->xSpacing); + + // next character in string + ++szStr; + } + + // adjust the text y position and add the inter-line spacing + yPos += yOffset + FROM_LE_32(pFont->ySpacing); + + // check for newline + if (c == LF_CHAR) + // next character in string + ++szStr; + } + + // return head of list + return pFirst; +} + +/** + * Is there an image for this character in this font? + * @param hFont which font to use + * @param c character to test + */ +bool IsCharImage(SCNHANDLE hFont, char c) { + byte c2 = (byte)c; + + // Inventory save game name editor needs to be more clever for + // multi-byte characters. This bodge will stop it erring. + if (bMultiByte && (c2 & 0x80)) + return false; + + // get font pointer + const FONT *pFont = (const FONT *)LockMem(hFont); + + return pFont->fontDef[c2] != 0; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h new file mode 100644 index 0000000000..78998831a1 --- /dev/null +++ b/engines/tinsel/text.h @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TEXT_H // prevent multiple includes +#define TINSEL_TEXT_H + +#include "tinsel/object.h" // object manager defines + +namespace Tinsel { + +/** text mode flags - defaults to left justify */ +enum { + TXT_CENTRE = 0x0001, //!< centre justify text + TXT_RIGHT = 0x0002, //!< right justify text + TXT_SHADOW = 0x0004, //!< shadow each character + TXT_ABSOLUTE = 0x0008 //!< position of text is absolute (only for object text) +}; + +/** maximum number of characters in a font */ +#define MAX_FONT_CHARS 256 + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** + * Text font data structure. + * @note only the pointer is used so the size of fontDef[] is not important. + * It is currently set at 300 because it suited me for debugging. + */ +struct FONT { + int xSpacing; //!< x spacing between characters + int ySpacing; //!< y spacing between characters + int xShadow; //!< x shadow offset + int yShadow; //!< y shadow offset + int spaceSize; //!< x spacing to use for a space character + OBJ_INIT fontInit; //!< structure used to init text objects + SCNHANDLE fontDef[300]; //!< image handle array for all characters in the font +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/** structure for passing the correct parameters to ObjectTextOut */ +struct TEXTOUT { + OBJECT *pList; //!< object list to add text to + char *szStr; //!< string to output + int colour; //!< colour for monochrome text + int xPos; //!< x position of string + int yPos; //!< y position of string + SCNHANDLE hFont; //!< which font to use + int mode; //!< mode flags for the string + int sleepTime; //!< sleep time between each character (if non-zero) +}; + + +/*----------------------------------------------------------------------*\ +|* Text Function Prototypes *| +\*----------------------------------------------------------------------*/ + +OBJECT *ObjectTextOut( // output a string of text + OBJECT *pList, // object list to add text to + char *szStr, // string to output + int colour, // colour for monochrome text + int xPos, // x position of string + int yPos, // y position of string + SCNHANDLE hFont, // which font to use + int mode); // mode flags for the string + +OBJECT *ObjectTextOutIndirect( // output a string of text + TEXTOUT *pText); // pointer to TextOut struct with all parameters + +bool IsCharImage( // Is there an image for this character in this font? + SCNHANDLE hFont, // which font to use + char c); // character to test + +} // end of namespace Tinsel + +#endif // TINSEL_TEXT_H diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp new file mode 100644 index 0000000000..c7b9d3708b --- /dev/null +++ b/engines/tinsel/timers.cpp @@ -0,0 +1,192 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles timers. + * + * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed + * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use + * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS + */ + +#include "tinsel/timers.h" +#include "tinsel/dw.h" +#include "tinsel/serializer.h" + +#include "common/system.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define MAX_TIMERS 16 + +struct TIMER { + int tno; /**< Timer number */ + int ticks; /**< Tick count */ + int secs; /**< Second count */ + int delta; /**< Increment/decrement value */ + bool frame; /**< If set, in ticks, otherwise in seconds */ +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static TIMER timers[MAX_TIMERS]; + + +//-------------------------------------------------------- + +/** + * Gets the current time in number of ticks. + * + * DOS timer ticks is the number of 54.9254ms since midnight. Converting the + * millisecond count won't give the exact same 'since midnight' count, but I + * figure that as long as the timing interval is more or less accurate, it + * shouldn't be a problem. + */ + +uint32 DwGetCurrentTime() { + return g_system->getMillis() * 55 / 1000; +} + +/** + * Resets all of the timer slots + */ + +void RebootTimers(void) { + memset(timers, 0, sizeof(timers)); +} + +/** + * (Un)serialize the timer data for save/restore game. + */ +void syncTimerInfo(Serializer &s) { + for (int i = 0; i < MAX_TIMERS; i++) { + s.syncAsSint32LE(timers[i].tno); + s.syncAsSint32LE(timers[i].ticks); + s.syncAsSint32LE(timers[i].secs); + s.syncAsSint32LE(timers[i].delta); + s.syncAsSint32LE(timers[i].frame); + } +} + +/** + * Find the timer numbered thus, if one is thus numbered. + * @param num number of the timer + * @return the timer with the specified number, or NULL if there is none + */ +static TIMER *findTimer(int num) { + for (int i = 0; i < MAX_TIMERS; i++) { + if (timers[i].tno == num) + return &timers[i]; + } + return NULL; +} + +/** + * Find an empty timer slot. + */ +static TIMER *allocateTimer(int num) { + assert(num); // zero is not allowed as a timer number + assert(!findTimer(num)); // Allocating already existant timer + + for (int i = 0; i < MAX_TIMERS; i++) { + if (!timers[i].tno) { + timers[i].tno = num; + return &timers[i]; + } + } + + error("Too many timers"); +} + +/** + * Update all timers, as appropriate. + */ +void FettleTimers(void) { + for (int i = 0; i < MAX_TIMERS; i++) { + if (!timers[i].tno) + continue; + + timers[i].ticks += timers[i].delta; // Update tick value + + if (timers[i].frame) { + if (timers[i].ticks < 0) + timers[i].ticks = 0; // Have reached zero + } else { + if (timers[i].ticks < 0) { + timers[i].ticks = ONE_SECOND; + timers[i].secs--; + if (timers[i].secs < 0) + timers[i].secs = 0; // Have reached zero + } else if (timers[i].ticks == ONE_SECOND) { + timers[i].ticks = 0; + timers[i].secs++; // Another second has passed + } + } + } +} + +/** + * Start a timer up. + */ +void DwSetTimer(int num, int sval, bool up, bool frame) { + TIMER *pt; + + assert(num); // zero is not allowed as a timer number + + pt = findTimer(num); + if (pt == NULL) + pt = allocateTimer(num); + + pt->delta = up ? 1 : -1; // Increment/decrement value + pt->frame = frame; + + if (frame) { + pt->secs = 0; + pt->ticks = sval; + } else { + pt->secs = sval; + pt->ticks = 0; + } +} + +/** + * Return the current count of a timer. + */ +int Timer(int num) { + TIMER *pt; + + pt = findTimer(num); + + if (pt == NULL) + return -1; + + if (pt->frame) + return pt->ticks; + else + return pt->secs; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h new file mode 100644 index 0000000000..75eb87ee2b --- /dev/null +++ b/engines/tinsel/timers.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_TIMERS_H // prevent multiple includes +#define TINSEL_TIMERS_H + +#include "common/scummsys.h" +#include "tinsel/dw.h" + +namespace Tinsel { + +class Serializer; + +#define ONE_SECOND 24 + +uint32 DwGetCurrentTime(void); + +void RebootTimers(void); + +void syncTimerInfo(Serializer &s); + +void FettleTimers(void); + +void DwSetTimer(int num, int sval, bool up, bool frame); + +int Timer(int num); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp new file mode 100644 index 0000000000..e8364e20dd --- /dev/null +++ b/engines/tinsel/tinlib.cpp @@ -0,0 +1,2980 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Glitter library functions. + * + * In the main called only from PCODE.C + * Function names are the same as Glitter code function names. + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#define BODGE + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/coroutine.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart; // restart flag - set to restart the game +extern bool bHasRestarted; // Set after a restart + +// In DOS_MAIN.C +// TODO/FIXME: From dos_main.c: "Only used on PSX so far" +int clRunMode = 0; + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern void ChangePalette(SCNHANDLE hPal); +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + +// in DOS_DW.C +extern void SetHookScene(SCNHANDLE scene, int entrance, int transition); +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +extern void UnHookScene(void); +extern void SuspendHook(void); +extern void UnSuspendHook(void); + +// in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); +bool DisableTagsIfEnabled(void); +extern void setshowstring(void); + +// in PLAY.C +extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); +extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); + +// in SCENE.C +extern void setshowpos(void); + +#ifdef BODGE +// In DOS_HAND.C +bool ValidHandle(SCNHANDLE offset); + +// In SCENE.C +SCNHANDLE GetSceneHandle(void); +#endif + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bEnableF1; + + +//----------------- LOCAL DEFINES -------------------- + +#define JAP_TEXT_TIME (2*ONE_SECOND) + +/*----------------------------------------------------------------------*\ +|* Library Procedure and Function codes *| +\*----------------------------------------------------------------------*/ + +enum LIB_CODE { + ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4, + ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10, + BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15, + CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20, + DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25, + DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31, + GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36, + INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42, + MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48, + PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54, + PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59, + SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65, + SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71, + STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79, + TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85, + WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91, + WHICHINVENTORY = 92, + ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97, + TALKAT, UNHOOKSCENE, WAITFRAME, DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103, + TALKATS = 104, + DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109, + NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113, + RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119, + HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124 +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +// Saved cursor co-ordinates for control(on) to restore cursor position +// as it was at control(off). +// They are global so that movecursor(..) has a net effect if it +// precedes control(on). +static int controlX = 0, controlY = 0; + +static int offtype = 0; // used by control() +static uint32 lastValue = 0; // used by dw_random() +static int scrollCount = 0; // used by scroll() + +static bool NotPointedRunning = false; // Used in printobj and printobjPointed + +static COLORREF s_talkfontColor = 0; + +//----------------- FORWARD REFERENCES -------------------- + +void resetidletime(void); +void stopmidi(void); +void stopsample(void); +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime); + + +/** + * NOT A LIBRARY FUNCTION + * + * Poke supplied colours into the DAC queue. + */ +static void setTextPal(COLORREF col) { + s_talkfontColor = col; + UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor); +} + + +static int TextTime(char *pTstring) { + if (isJapanMode()) + return JAP_TEXT_TIME; + else if (!speedText) + return strlen(pTstring) + ONE_SECOND; + else + return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100; +} + +/*--------------------------------------------------------------------------*/ + + +/** + * Set actor's attributes. + * - currently only the text colour. + */ +void actorattr(int actor, int r1, int g1, int b1) { + storeActorAttr(actor, r1, g1, b1); +} + +/** + * Return the actor's direction. + */ +int actordirection(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor != NULL); // not a moving actor + + return (int)GetMActorDirection(pActor); +} + +/** + * Return the actor's scale. + */ +int actorscale(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor != NULL); // not a moving actor + + return (int)GetMActorScale(pActor); +} + +/** + * Returns the x or y position of an actor. + */ +int actorpos(int xory, int actor) { + int x, y; + + GetActorPos(actor, &x, &y); + return (xory == ACTORXPOS) ? x : y; +} + +/** + * Make all actors alive at the start of each scene. + */ +void actorson(void) { + setactorson(); +} + +/** + * Adds an icon to the conversation window. + */ +void addicon(int icon) { + AddToInventory(INV_CONV, icon, false); +} + +/** + * Place the object in inventory 1 or 2. + */ +void addinv(int invno, int object) { + assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number + + AddToInventory(invno, object, false); +} + +/** + * Define an actor's walk and stand reels for an auxilliary scale. + */ +void auxscale(int actor, int scale, SCNHANDLE *rp) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor); // Can't set aux scale for a non-moving actor + + int j; + for (j = 0; j < 4; ++j) + pActor->WalkReels[scale-1][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[scale-1][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->TalkReels[scale-1][j] = *rp++; +} + +/** + * Defines the background image for a scene. + */ +void background(SCNHANDLE bfilm) { + startupBackground(bfilm); +} + +/** + * Sets focus of the scroll process. + */ +void camera(int actor) { + ScrollFocus(actor); +} + +/** + * A CDPLAY() is imminent. + */ +void cdload(SCNHANDLE start, SCNHANDLE next) { + assert(start && next && start != next); // cdload() fault + +// TODO/FIXME +// LoadExtraGraphData(start, next); +} + +/** + * Clear the hooked scene (if any) + */ + +void clearhookscene() { + SetHookScene(0, 0, 0); +} + +/** + * Guess what. + */ + +void closeinventory(void) { + KillInventory(); +} + +/** + * Turn off cursor and take control from player - and variations on the theme. + * OR Restore cursor and return control to the player. + */ + +void control(int param) { + bEnableF1 = false; + + switch (param) { + case CONTROL_STARTOFF: + GetControlToken(); // Take control + DisableTags(); // Switch off tags + DwHideCursor(); // Blank out cursor + offtype = param; + break; + + case CONTROL_OFF: + case CONTROL_OFFV: + case CONTROL_OFFV2: + if (TestToken(TOKEN_CONTROL)) { + GetControlToken(); // Take control + + DisableTags(); // Switch off tags + GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position + + // There may be a button timing out + GetToken(TOKEN_LEFT_BUT); + FreeToken(TOKEN_LEFT_BUT); + } + + if (offtype == CONTROL_STARTOFF) + GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position + + offtype = param; + + if (param == CONTROL_OFF) + DwHideCursor(); // Blank out cursor + else if (param == CONTROL_OFFV) { + UnHideCursor(); + FreezeCursor(); + } else if (param == CONTROL_OFFV2) { + UnHideCursor(); + } + break; + + case CONTROL_ON: + if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF) + SetCursorXY(controlX, controlY);// ... where it was + + FreeControlToken(); // Release control + + if (!InventoryActive()) + EnableTags(); // Tags back on + + RestoreMainCursor(); // Re-instate cursor... + } +} + +/** + * Open or close the conversation window. + */ + +void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) { + assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block + + switch (fn) { + case CONV_END: // Close down conversation + CloseDownConv(); + break; + + case CONV_DEF: // Default (i.e. TOP of screen) + case CONV_BOTTOM: // BOTTOM of screen + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + break; + + if (IsConvWindow()) + break; + + KillInventory(); + convPos(fn); + ConvPoly(hp); + PopUpInventory(INV_CONV); // Conversation window + ConvAction(INV_OPENICON); // CONVERSATION event + break; + } +} + +/** + * Add icon to conversation window's permanent default list. + */ + +void convicon(int icon) { + AddIconToPermanentDefaultList(icon); +} + +/** + * Returns the x or y position of the cursor. + */ + +int cursorpos(int xory) { + int x, y; + + GetCursorXY(&x, &y, true); + return (xory == CURSORXPOS) ? x : y; +} + +/** + * Declare conversation window. + */ + +void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + idec_convw(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare config strings. + */ + +void dec_cstrings(SCNHANDLE *tp) { + setConfigStrings(tp); +} + +/** + * Declare cursor's reels. + */ + +void dec_cursor(SCNHANDLE bfilm) { + DwInitCursor(bfilm); +} + +/** + * Declare the language flags. + */ + +void dec_flags(SCNHANDLE hf) { + setFlagFilms(hf); +} + +/** + * Declare inventory 1's parameters. + */ + +void dec_inv1(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv1(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare inventory 2's parameters. + */ + +void dec_inv2(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv2(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare the bits that the inventory windows are constructed from. + */ + +void dec_invw(SCNHANDLE hf) { + setInvWinParts(hf); +} + +/** + * Declare lead actor. + * - the actor's id, walk and stand reels for all the regular scales, + * and the tag text. + */ + +void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) { + PMACTOR pActor; // Moving actor structure + + Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged + setleadid(id); // Establish this as the lead + SetMover(id); // Establish as a moving actor + + pActor = GetMover(id); // Get moving actor structure + assert(pActor); + + // Store all those reels + int i, j; + for (i = 0; i < 5; ++i) { + for (j = 0; j < 4; ++j) + pActor->WalkReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->TalkReels[i][j] = *rp++; + } + + + for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { + for (j = 0; j < 4; ++j) { + pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; + pActor->StandReels[i][j] = pActor->StandReels[2][j]; + pActor->TalkReels[i][j] = pActor->TalkReels[4][j]; + } + } +} + +/** + * Declare the text font. + */ + +void dec_tagfont(SCNHANDLE hf) { + TagFontHandle(hf); // Store the font handle +} + +/** + * Declare the text font. + */ + +void dec_talkfont(SCNHANDLE hf) { + TalkFontHandle(hf); // Store the font handle +} + +/** + * Remove an icon from the conversation window. + */ + +void delicon(int icon) { + RemFromInventory(INV_CONV, icon); +} + +/** + * Delete the object from inventory 1 or 2. + */ + +void delinv(int object) { + if (!RemFromInventory(INV_1, object)) // Remove from inventory 1... + RemFromInventory(INV_2, object); // ...or 2 (whichever) + + DropItem(object); // Stop holding it +} + +/** + * enablef1 + */ + +void enablef1(void) { + bEnableF1 = true; +} + +/** + * fademidi(in/out) + */ + +void fademidi(CORO_PARAM, int inout) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + assert(inout == FM_IN || inout == FM_OUT); + + // To prevent compiler complaining + if (inout == FM_IN || inout == FM_OUT) + CORO_SLEEP(1); + CORO_END_CODE; +} + +/** + * Guess what. + */ + +int getinvlimit(int invno) { + return InvGetLimit(invno); +} + +/** + * Returns TRUE if the game has been restarted, FALSE if not. + */ +bool hasrestarted(void) { + return bHasRestarted; +} + +/** + * Returns which object is currently held. + */ + +int heldobject(void) { + return WhichItemHeld(); +} + +/** + * Removes a player from the screen, probably when he's about to be + * replaced by an animation. + * + * Not believed to work anymore! (hide() is not used). + */ + +void hide(int actor) { + HideActor(actor); +} + +/** + * hookscene(scene, entrance, transition) + */ + +void hookscene(SCNHANDLE scene, int entrance, int transition) { + SetHookScene(scene, entrance, transition); +} + +/** + * idletime + */ + +int idletime(void) { + uint32 x; + + x = getUserEventTime() / ONE_SECOND; + + if (!TestToken(TOKEN_CONTROL)) + resetidletime(); + + return (int)x; +} + +/** + * invdepict + */ +void invdepict(int object, SCNHANDLE hFilm) { + invObjectFilm(object, hFilm); +} + +/** + * See if an object is in the inventory. + */ +int ininventory(int object) { + return (InventoryPos(object) != INV_NOICON); +} + +/** + * Open an inventory. + */ +void inventory(int invno, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory + + PopUpInventory(invno); +} + +/** + * See if an object is in the inventory. + */ +int inwhichinv(int object) { + if (WhichItemHeld() == object) + return 0; + + if (IsInInventory(object, INV_1)) + return 1; + + if (IsInInventory(object, INV_2)) + return 2; + + return -1; +} + +/** + * Kill an actor. + */ +void killactor(int actor) { + DisableActor(actor); +} + +/** + * Turn a blocking polygon off. + */ +void killblock(int block) { + DisableBlock(block); +} + +/** + * Turn an exit off. + */ +void killexit(int exit) { + DisableExit(exit); +} + +/** + * Turn a tag off. + */ +void killtag(int tagno) { + DisableTag(tagno); +} + +/** + * Returns the left or top offset of the screen. + */ +int ltoffset(int lort) { + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + return (lort == LEFTOFFSET) ? Loffset : Toffset; +} + +/** + * Set new cursor position. + */ +void movecursor(int x, int y) { + SetCursorXY(x, y); + + controlX = x; // Save these values so that + controlY = y; // control(on) doesn't undo this +} + +/** + * Triggers change to a new scene. + */ +void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + +#ifdef BODGE + if (!ValidHandle(scene)) { + scene = GetSceneHandle(); + entrance = 1; + } + assert(scene); // Non-existant first scene! +#endif + + SetNewScene(scene, entrance, transition); + +#if 1 + // Prevent tags and cursor re-appearing + GetControl(CONTROL_STARTOFF); +#endif + + // Prevent code subsequent to this call running before scene changes + if (g_scheduler->getCurrentPID() != PID_MASTER_SCR) + CORO_KILL_SELF(); + CORO_END_CODE; +} + +/** + * Disable dynamic blocking for current scene. + */ +void noblocking(void) { + bNoBlocking = true; +} + +/** + * Define a no-scroll boundary for the current scene. + */ +void noscroll(int x1, int y1, int x2, int y2) { + SetNoScroll(x1, y1, x2, y2); +} + +/** + * Hold the specified object. + */ +void objectheld(int object) { + HoldItem(object); +} + +/** + * Set the top left offset of the screen. + */ +void offset(int x, int y) { + KillScroll(); + PlayfieldSetPos(FIELD_WORLD, x, y); +} + +/** + * Play a film. + */ +void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact, + bool escOn, int myescEvent, bool bTop) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(film != 0); // play(): Trying to play NULL film + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // If this actor is dead, call a stop to the calling process + if (actorid && !actorAlive(actorid)) + CORO_KILL_SELF(); + + // 7/4/95 + if (!escOn) + myescEvent = GetEscEvents(); + + if (compit == 1) { + // Play to completion before returning + CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop)); + } else if (compit == 2) { + error("play(): compit == 2 - please advise John"); + } else { + // Kick off the play and return. + playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop); + } + CORO_END_CODE; +} + +/** + * Play a midi file. + */ +void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { + // FIXME: This is a workaround for the FIXME below + if (GetMidiVolume() == 0) + return; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + assert(loop == MIDI_DEF || loop == MIDI_LOOP); + + PlayMidiSequence(hMidi, loop == MIDI_LOOP); + + // FIXME: The following check messes up the script arguments when + // entering the secret door in the bookshelf in the library, + // leading to a crash, when the music volume is set to 0 (MidiPlaying() + // always false then). + // + // Why exactly this happens is unclear. An analysis of the involved + // script(s) might reveal more. + // + // Note: This check&sleep was added in DW v2. It was most likely added + // to ensure that the MIDI song started playing before the next opcode + // is executed. + if (!MidiPlaying()) + CORO_SLEEP(1); + + if (complete) { + while (MidiPlaying()) + CORO_SLEEP(1); + } + CORO_END_CODE; +} + +/** + * Play a sample. + */ +void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + Audio::SoundHandle handle; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't play SFX if voice is already playing + if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType)) + return; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) { + _vm->_sound->stopAllSamples(); // Stop any currently playing sample + return; + } + + if (volSound != 0 && _vm->_sound->sampleExists(sample)) { + _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle); + + if (complete) { + while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) { + _vm->_mixer->stopHandle(_ctx->handle); + break; + } + + CORO_SLEEP(1); + } + } + } else { + // Prevent Glitter lock-up + CORO_SLEEP(1); + } + CORO_END_CODE; +} + +/** + * Play a sample. + */ +void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not appropriate + if (_vm->_sound->sampleIsPlaying()) { + // return, but prevent Glitter lock-up + CORO_SLEEP(1); + return; + } + + CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent)); + CORO_END_CODE; +} + +/** + * Trigger pre-loading of a scene's data. + */ +void preparescene(SCNHANDLE scene) { +#ifdef BODGE + if (!ValidHandle(scene)) + return; +#endif +} + +/** + * Print the given text at the given place for the given time. + * + * Print(....., h) -> hold = 1 (not used) + * Print(....., s) -> hold = 2 (sustain) + */ +void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + int myleftEvent; + bool bSample; // Set if a sample is playing + Audio::SoundHandle handle; + int timeout; + int time; + CORO_END_CONTEXT(_ctx); + + bool bJapDoPrintText; // Bodge to get-around Japanese bodge + + CORO_BEGIN_CODE(_ctx); + + _ctx->pText = NULL; + _ctx->bSample = false; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // Kick off the voice sample + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } + + // Calculate display time + LoadStringRes(text, tBufferAddr(), TBUFSZ); + bJapDoPrintText = false; + if (time == 0) { + // This is a 'talky' print + _ctx->time = TextTime(tBufferAddr()); + + // Cut short-able if sustain was not set + _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents(); + } else { + _ctx->time = time * ONE_SECOND; + _ctx->myleftEvent = 0; + if (isJapanMode()) + bJapDoPrintText = true; + } + + // Print the text + if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) { + int Loffset, Toffset; // Screen position + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // string produced NULL text + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + /* + * New feature: Don't go off the side of the background + */ + int shift; + shift = MultiRightmost(_ctx->pText) + 2; + if (shift >= BackgroundWidth()) // Not off right + MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0); + shift = MultiLeftmost(_ctx->pText) - 1; + if (shift <= 0) // Not off left + MultiMoveRelXY(_ctx->pText, -shift, 0); + shift = MultiLowest(_ctx->pText); + if (shift > BackgroundHeight()) // Not off bottom + MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift); + } + + // Give up if nothing printed and no sample + if (_ctx->pText == NULL && !_ctx->bSample) + return; + + // Leave it up until time runs out or whatever + _ctx->timeout = SAMPLETIMEOUT; + do { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + // Abort if left click - hardwired feature for talky-print! + // Will be ignored if myleftevent happens to be 0! + // Abort if sample times out + if ((escOn && myescEvent != GetEscEvents()) + || (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents()) + || (_ctx->bSample && --_ctx->timeout <= 0)) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Must wait for time + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->time-- <= 0) + break; + } + + } while (1); + + // Delete the text + if (_ctx->pText != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _vm->_mixer->stopHandle(_ctx->handle); + + CORO_END_CODE; +} + + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item); +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText); + +/** + * Print the given inventory object's name or whatever. + */ +void printobj(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, const int event) { + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + int textx, texty; + int item; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(pinvo != 0); // printobj() may only be called from an object code block + + if (text == (SCNHANDLE)-1) { // 'OFF' + NotPointedRunning = true; + return; + } + if (text == (SCNHANDLE)-2) { // 'ON' + NotPointedRunning = false; + return; + } + + GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position.. + _ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position + + if (_ctx->item == INV_NOICON) + return; + + if (event != POINTED) { + NotPointedRunning = true; // Get POINTED text to die + CORO_SLEEP(1); // Give it chance to + } else + NotPointedRunning = false; // There may have been an OFF without an ON + + // Display the text and set it's Z position + if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) { + int xshift; + + LoadStringRes(text, tBufferAddr(), TBUFSZ); // The text string + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // printobj() string produced NULL text + MultiSetZPosition(_ctx->pText, Z_INV_ITEXT); + + // Don't go off the side of the screen + xshift = MultiLeftmost(_ctx->pText); + if (xshift < 0) { + MultiMoveRelXY(_ctx->pText, - xshift, 0); + _ctx->textx -= xshift; + } + xshift = MultiRightmost(_ctx->pText); + if (xshift > SCREEN_WIDTH) { + MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); + _ctx->textx += SCREEN_WIDTH - xshift; + } + } else + _ctx->pText = NULL; + + if (event == POINTED) { + // FIXME: Is there ever an associated sound if in POINTED mode??? + assert(!_vm->_sound->sampleExists(text)); + CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item)); + } else { + CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText); + } + + // Delete the text, if haven't already + if (_ctx->pText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + + CORO_END_CODE; +} + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Have to give way to non-POINTED-generated text + // and go away if the item gets picked up + int x, y; + do { + // Give up if this item gets picked up + if (WhichItemHeld() == pinvo->id) + break; + + // Give way to non-POINTED-generated text + if (NotPointedRunning) { + // Delete the text, and wait for the all-clear + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText); + pText = NULL; + while (NotPointedRunning) + CORO_SLEEP(1); + + GetCursorXY(&x, &y, false); + if (InvItem(&x, &y, false) != item) + break; + + // Re-display in the same place + LoadStringRes(text, tBufferAddr(), TBUFSZ); + pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, textx, texty, hTagFontHandle(), TXT_CENTRE); + assert(pText); // printobj() string produced NULL text + MultiSetZPosition(pText, Z_INV_ITEXT); + } + + CORO_SLEEP(1); + + // Carry on until the cursor leaves this icon + GetCursorXY(&x, &y, false); + } while (InvItemId(x, y) == pinvo->id); + + CORO_END_CODE; +} + +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) { + CORO_BEGIN_CONTEXT; + bool bSample; // Set if a sample is playing + Audio::SoundHandle handle; + + int myleftEvent; + bool took_control; + int ticks; + int timeout; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Kick off the voice sample + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } else + _ctx->bSample = false; + + _ctx->myleftEvent = GetLeftEvents(); + _ctx->took_control = GetControl(CONTROL_OFF); + + // Display for a time, but abort if conversation gets hidden + if (isJapanMode()) + _ctx->ticks = JAP_TEXT_TIME; + else if (pText) + _ctx->ticks = TextTime(tBufferAddr()); + else + _ctx->ticks = 0; + + _ctx->timeout = SAMPLETIMEOUT; + do { + CORO_SLEEP(1); + --_ctx->timeout; + + // Abort if left click - hardwired feature for talky-print! + // Abort if sample times out + // Abort if conversation hidden + if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid()) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Must wait for time + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->ticks-- <= 0) + break; + } + } while (1); + + NotPointedRunning = false; // Let POINTED text back in + + if (_ctx->took_control) + control(CONTROL_ON); // Free control if we took it + + _vm->_mixer->stopHandle(_ctx->handle); + + CORO_END_CODE; +} + +/** + * Register the fact that this poly would like its tag displayed. + */ +void printtag(HPOLYGON hp, SCNHANDLE text) { + assert(hp != NOPOLY); // printtag() may only be called from a polygon code block + + if (PolyTagState(hp) == TAG_OFF) { + SetPolyTagState(hp, TAG_ON); + SetPolyTagHandle(hp, text); + } +} + +/** + * quitgame + */ +void quitgame(void) { + stopmidi(); + stopsample(); + _vm->quitFlag = true; +} + +/** + * Return a random number between optional limits. + */ +int dw_random(int n1, int n2, int norpt) { + int i = 0; + uint32 value; + + do { + value = n1 + _vm->getRandomNumber(n2 - n1); + } while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10)); + + lastValue = value; + return value; +} + +/** + * resetidletime + */ +void resetidletime(void) { + resetUserEventTime(); +} + +/** + * restartgame + */ +void restartgame(void) { + stopmidi(); + stopsample(); + bRestart = true; +} + +/** + * Restore saved scene. + */ +void restore_scene(bool bFade) { + UnSuspendHook(); + PleaseRestoreScene(bFade); +} + +/** + * runmode + */ +int runmode(void) { + return clRunMode; +} + +/** + * sampleplaying + */ +bool sampleplaying(bool escOn, int myescEvent) { + // escape effects introduced 14/12/95 to fix + // while (sampleplaying()) pause; + + if (escOn && myescEvent != GetEscEvents()) + return false; + + return _vm->_sound->sampleIsPlaying(); +} + +/** + * Save current scene. + */ +void save_scene(CORO_PARAM) { + PleaseSaveScene(coroParam); + SuspendHook(); +} + +/** + * scalingreels + */ +void scalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { + + setscalingreels(actor, scale, direction, left, right, forward, away); +} + +/** + * Return the icon that caused the CONVERSE event. + */ + +int scanicon(void) { + return convIcon(); +} + +/** + * Scroll the screen to target co-ordinates. + */ + +void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int mycount; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + if (escOn && myescEvent != GetEscEvents()) { + // Instant completion! + offset(x, y); + } else { + _ctx->mycount = ++scrollCount; + + ScrollTo(x, y, iter); + + if (comp) { + int Loffset, Toffset; + do { + CORO_SLEEP(1); + + // If escapable and ESCAPE is pressed... + if (escOn && myescEvent != GetEscEvents()) { + // Instant completion! + offset(x, y); + break; + } + + // give up if have been superseded + if (_ctx->mycount != scrollCount) + CORO_KILL_SELF(); + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + } while (Loffset != x || Toffset != y); + } + } + CORO_END_CODE; +} + +/** + * Un-kill an actor. + */ +void setactor(int actor) { + EnableActor(actor); +} + +/** + * Turn a blocking polygon on. + */ + +void setblock(int blockno) { + EnableBlock(blockno); +} + +/** + * Turn an exit on. + */ + +void setexit(int exitno) { + EnableExit(exitno); +} + +/** + * Guess what. + */ +void setinvlimit(int invno, int n) { + InvSetLimit(invno, n); +} + +/** + * Guess what. + */ +void setinvsize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Guess what. + */ +void setlanguage(LANGUAGE lang) { + assert(lang == TXT_ENGLISH || lang == TXT_FRENCH + || lang == TXT_GERMAN || lang == TXT_ITALIAN + || lang == TXT_SPANISH); // ensure language is valid + + ChangeLanguage(lang); +} + +/** + * Set palette + */ +void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + ChangePalette(hPal); +} + +/** + * Turn a tag on. + */ +void settag(int tagno) { + EnableTag(tagno); +} + +/** + * Initialise a timer. + */ +void settimer(int timerno, int start, bool up, bool frame) { + DwSetTimer(timerno, start, up != 0, frame != 0); +} + +#ifdef DEBUG +/** + * Enable display of diagnostic co-ordinates. + */ +void showpos(void) { + setshowpos(); +} + +/** + * Enable display of diagnostic co-ordinates. + */ +void showstring(void) { + setshowstring(); +} +#endif + +/** + * Special play - slow down associated actor's movement while the play + * is running. After the play, position the actor where the play left + * it and continue walking, if the actor still is. + */ + +void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false); +} + +/** + * (Re)Position an actor. + * If moving actor is not around yet in this scene, start it up. + */ + +void stand(int actor, int x, int y, SCNHANDLE film) { + PMACTOR pActor; // Moving actor structure + + pActor = GetMover(actor); + if (pActor) { + if (pActor->MActorState == NO_MACTOR) { + // create a moving actor process + MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor); + + if (film == TF_NONE) { + SetMActorStanding(pActor); + } else { + switch (film) { + case TF_NONE: + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + SetMActorStanding(pActor); + break; + + default: + AlterMActor(pActor, film, AR_NORMAL); + break; + } + } + } else { + switch (film) { + case TF_NONE: + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + + default: + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + AlterMActor(pActor, film, AR_NORMAL); + break; + } + } + } else if (actor == NULL_ACTOR) { + // + } else { + assert(film != 0); // Trying to play NULL film + + // Kick off the play and return. + playFilm(film, x, y, actor, false, 0, false, 0, false); + } +} + +/** + * Position the actor at the polygon's tag node. + */ +void standtag(int actor, HPOLYGON hp) { + SCNHANDLE film; + int pnodex, pnodey; + + assert(hp != NOPOLY); // standtag() may only be called from a polygon code block + + // Lead actor uses tag node film + film = getPolyFilm(hp); + getPolyNode(hp, &pnodex, &pnodey); + if (film && (actor == LEAD_ACTOR || actor == LeadId())) + stand(actor, pnodex, pnodey, film); + else + stand(actor, pnodex, pnodey, 0); +} + +/** + * Kill a moving actor's walk. + */ +void stop(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor); // Trying to stop a null actor + + GetToken(pActor->actorToken); // Kill the walk process + pActor->stop = true; // Cause the actor to stop + FreeToken(pActor->actorToken); +} + +void stopmidi(void) { + StopMidi(); // Stop any currently playing midi +} + +void stopsample(void) { + _vm->_sound->stopAllSamples(); // Stop any currently playing sample +} + +void subtitles(int onoff) { + assert (onoff == ST_ON || onoff == ST_OFF); + + if (isJapanMode()) + return; // Subtitles are always off in JAPAN version (?) + + if (onoff == ST_ON) + bSubtitles = true; + else + bSubtitles = false; +} + +/** + * Special walk. + * Walk into or out of a legal path. + */ +void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + bool took_control; // Set if this function takes control + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // For lead actor, lock out the user (if not already locked out) + if (actor == LeadId() || actor == LEAD_ACTOR) + _ctx->took_control = GetControl(CONTROL_OFFV2); + else + _ctx->took_control = false; + + HPOLYGON hPath; + + hPath = InPolygon(x1, y1, PATH); + if (hPath != NOPOLY) { + // Walking out of a path + stand(actor, x1, y1, 0); + } else { + hPath = InPolygon(x2, y2, PATH); + // One of them has to be in a path + assert(hPath != NOPOLY); //one co-ordinate must be in a legal path + + // Walking into a path + stand(actor, x2, y2, 0); // Get path's characteristics + stand(actor, x1, y1, 0); + } + + CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent)); + + // Free control if we took it + if (_ctx->took_control) + control(CONTROL_ON); + + CORO_END_CODE; +} + +/** + * Define a tagged actor. + */ + +void tagactor(int actor, SCNHANDLE text, int tp) { + Tag_Actor(actor, text, tp); +} + +/** + * Text goes over actor's head while actor plays the talk reel. + */ + +void FinishTalkingReel(PMACTOR pActor, int actor) { + if (pActor) { + SetMActorStanding(pActor); + AlterMActor(pActor, 0, AR_POPREEL); + } else { + setActorTalking(actor, false); + playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false); + } +} + +void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int Loffset, Toffset; // Top left of display + int actor; // The speaking actor + PMACTOR pActor; // For moving actors + int myleftEvent; + int ticks; + bool bTookControl; // Set if this function takes control + bool bTookTags; // Set if this function disables tags + OBJECT *pText; // text object pointer + bool bSample; // Set if a sample is playing + bool bTalkReel; // Set while talk reel is playing + Audio::SoundHandle handle; + int timeout; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->Loffset = 0; + _ctx->Toffset = 0; + _ctx->ticks = 0; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + _ctx->myleftEvent = GetLeftEvents(); + + // If this actor is dead, call a stop to the calling process + if (actorid && !actorAlive(actorid)) + CORO_KILL_SELF(); + + /* + * Find out which actor is talking + * and with which direction if no film supplied + */ + TFTYPE direction; + switch (film) { + case TF_NONE: + case TF_UP: + case TF_DOWN: + case TF_LEFT: + case TF_RIGHT: + _ctx->actor = LeadId(); // If no film, actor is lead actor + direction = (TFTYPE)film; + break; + + default: + _ctx->actor = extractActor(film); + assert(_ctx->actor); // talk() - no actor ID in the reel + direction = TF_BOGUS; + break; + } + + /* + * Lock out the user (for lead actor, if not already locked out) + * May need to disable tags for other actors + */ + if (_ctx->actor == LeadId()) + _ctx->bTookControl = GetControl(CONTROL_OFF); + else + _ctx->bTookControl = false; + _ctx->bTookTags = DisableTagsIfEnabled(); + + /* + * Kick off the voice sample + */ + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } else + _ctx->bSample = false; + + /* + * Replace actor with the talk reel, saving the current one + */ + _ctx->pActor = GetMover(_ctx->actor); + if (_ctx->pActor) { + if (direction != TF_BOGUS) + film = GetMactorTalkReel(_ctx->pActor, direction); + AlterMActor(_ctx->pActor, film, AR_PUSHREEL); + } else { + setActorTalking(_ctx->actor, true); + setActorTalkFilm(_ctx->actor, film); + playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false); + } + _ctx->bTalkReel = true; + CORO_SLEEP(1); // Allow the play to come in + + /* + * Display the text. + */ + _ctx->pText = NULL; + if (isJapanMode()) { + _ctx->ticks = JAP_TEXT_TIME; + } else if (bSubtitles || !_ctx->bSample) { + int aniX, aniY; // actor position + int xshift, yshift; + /* + * Work out where to display the text + */ + PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset); + GetActorMidTop(_ctx->actor, &aniX, &aniY); + aniY -= _ctx->Toffset; + + setTextPal(getActorTcol(_ctx->actor)); + LoadStringRes(text, tBufferAddr(), TBUFSZ); + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // talk() string produced NULL text; + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + /* + * Set bottom of text just above the speaker's head + * But don't go off the top of the screen + */ + yshift = aniY - MultiLowest(_ctx->pText) - 2; // Just above head + MultiMoveRelXY(_ctx->pText, 0, yshift); // + yshift = MultiHighest(_ctx->pText); + if (yshift < 4) + MultiMoveRelXY(_ctx->pText, 0, 4 - yshift); // Not off top + + /* + * Don't go off the side of the screen + */ + xshift = MultiRightmost(_ctx->pText) + 2; + if (xshift >= SCREEN_WIDTH) // Not off right + MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); + xshift = MultiLeftmost(_ctx->pText) - 1; + if (xshift <= 0) // Not off left + MultiMoveRelXY(_ctx->pText, -xshift, 0); + /* + * Work out how long to talk. + * During this time, reposition the text if the screen scrolls. + */ + _ctx->ticks = TextTime(tBufferAddr()); + } + + _ctx->timeout = SAMPLETIMEOUT; + do { + // Keep text in place if scrolling + if (_ctx->pText != NULL) { + int nLoff, nToff; + + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) { + MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff); + _ctx->Loffset = nLoff; + _ctx->Toffset = nToff; + } + } + + CORO_SLEEP(1); + --_ctx->timeout; + + // Abort if escapable and ESCAPE is pressed + // Abort if left click - hardwired feature for talk! + // Abort if sample times out + if ((escOn && myescEvent != GetEscEvents()) + || (_ctx->myleftEvent != GetLeftEvents()) + || (_ctx->timeout <= 0)) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Talk reel stops at end of speech + FinishTalkingReel(_ctx->pActor, _ctx->actor); + _ctx->bTalkReel = false; + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->ticks-- <= 0) + break; + } + } while (1); + + /* + * The talk is over now - dump the text + * Stop the sample + * Restore the actor's film or standing reel + */ + if (_ctx->pText != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _vm->_mixer->stopHandle(_ctx->handle); + if (_ctx->bTalkReel) + FinishTalkingReel(_ctx->pActor, _ctx->actor); + + /* + * Restore user control and tags, as appropriate + * And, finally, release the talk token. + */ + if (_ctx->bTookControl) + control(CONTROL_ON); + if (_ctx->bTookTags) + EnableTags(); + + CORO_END_CODE; +} + +/** + * talkat(actor, x, y, text) + */ +void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) { + if (!coroParam) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text))) + setTextPal(getActorTcol(actor)); + } + + print(coroParam, x, y, text, 0, 0, escOn, myescEvent); +} + +/** + * talkats(actor, x, y, text, sustain) + */ +void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) { + if (!coroParam) { + assert(sustain == 2); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!isJapanMode()) + setTextPal(getActorTcol(actor)); + } + + print(coroParam, x, y, text, 0, sustain, escOn, myescEvent); +} + +/** + * Set talk font's palette entry. + */ +void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) { + if (isJapanMode()) + return; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure + if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits + if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // } + + setTextPal(RGB(r1, g1, b1)); +} + +/** + * Get a timer's current count. + */ +int timer(int timerno) { + return Timer(timerno); +} + +/** + * topplay(film, x, y, actor, hold, complete) + */ +void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) { + play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true); +} + +/** + * Open or close the 'top window' + */ + +void topwindow(int bpos) { + assert(bpos == TW_START || bpos == TW_END); + + switch (bpos) { + case TW_END: + KillInventory(); + break; + + case TW_START: + KillInventory(); + PopUpConf(TOPWIN); + break; + } +} + +/** + * unhookscene + */ + +void unhookscene(void) { + UnHookScene(); +} + +/** + * Un-define an actor as tagged. + */ + +void untagactor(int actor) { + UnTagActor(actor); +} + +/** + * vibrate + */ + +void vibrate(void) { +} + +/** + * waitframe(int actor, int frameNumber) + */ + +void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + while (getActorSteps(actor) < frameNumber) { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) + break; + } + CORO_END_CODE; +} + +/** + * Return when a key pressed or button pushed. + */ + +void waitkey(CORO_PARAM, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int startEvent; + int startX, startY; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + while (1) { + _ctx->startEvent = getUserEvents(); + // Store cursor position + while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false)) + CORO_SLEEP(1); + + while (_ctx->startEvent == getUserEvents()) { + CORO_SLEEP(1); + + // Not necessary to monitor escape as it's an event anyway + + int curX, curY; + GetCursorXY(&curX, &curY, false); // Store cursor position + if (curX != _ctx->startX || curY != _ctx->startY) + break; + + if (IsConfWindow()) + break; + } + + if (!IsConfWindow()) + return; + + do { + CORO_SLEEP(1); + } while (IsConfWindow()); + + CORO_SLEEP(ONE_SECOND / 2); // Let it die down + } + CORO_END_CODE; +} + +/** + * Pause for requested time. + */ + +void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int time; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!frame) + time *= ONE_SECOND; + + _ctx->time = time; + do { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) + break; + } while (_ctx->time--); + CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + */ +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + return; + } + + assert(pActor->hCpath != NOPOLY); // moving actor not in path + + GetToken(pActor->actorToken); + SetActorDest(pActor, x, y, igPath, film); + DontScrollCursor(); + + if (hold == 2) { + ; + } else { + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + FreeToken(pActor->actorToken); + return; + } + } + } + FreeToken(pActor->actorToken); + CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + * Wait to see if its aborted or completed. + */ +void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int ticket; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + retVal = true; + return; + } + + CORO_SLEEP(ONE_SECOND); + + assert(pActor->hCpath != NOPOLY); // moving actor not in path + + // Briefly aquire token to kill off any other normal walk + GetToken(pActor->actorToken); + FreeToken(pActor->actorToken); + + SetActorDest(pActor, x, y, false, film); + DontScrollCursor(); + + _ctx->ticket = GetActorTicket(pActor); + + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + if (_ctx->ticket != GetActorTicket(pActor)) { + retVal = false; + return; + } + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + retVal = true; + return; + } + } + + int endx, endy; + GetMActorPosition(pActor, &endx, &endy); + retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y); + + CORO_END_CODE; +} + +/** + * Declare a moving actor. + */ +void walkingactor(uint32 id, SCNHANDLE *rp) { + PMACTOR pActor; // Moving actor structure + + SetMover(id); // Establish as a moving actor + pActor = GetMover(id); + assert(pActor); + + // Store all those reels + int i, j; + for (i = 0; i < 5; ++i) { + for (j = 0; j < 4; ++j) + pActor->WalkReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[i][j] = *rp++; + } + + + for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { + for (j = 0; j < 4; ++j) { + pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; + pActor->StandReels[i][j] = pActor->StandReels[2][j]; + } + } +} + +/** + * Walk a moving actor towards the polygon's tag, but return when the + * actor enters the polygon. + */ + +void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + int aniX, aniY; // cursor/actor position + int pnodex, pnodey; + + assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + standtag(actor, hp); + return; + } + + GetToken(pActor->actorToken); + getPolyNode(hp, &pnodex, &pnodey); + SetActorDest(pActor, pnodex, pnodey, false, film); + DoScrollCursor(); + + do { + CORO_SLEEP(1); + + if (escOn && myescEvent != GetEscEvents()) { + // Straight there if escaped + standtag(actor, hp); + FreeToken(pActor->actorToken); + return; + } + + GetMActorPosition(pActor, &aniX, &aniY); + } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor)); + + FreeToken(pActor->actorToken); + + CORO_END_CODE; +} + +/** + * walktag(actor, reel, hold) + */ + +void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + int pnodex, pnodey; + + assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + standtag(actor, hp); + return; + } + + GetToken(pActor->actorToken); + getPolyNode(hp, &pnodex, &pnodey); + SetActorDest(pActor, pnodex, pnodey, false, film); + DoScrollCursor(); + + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + if (escOn && myescEvent != GetEscEvents()) { + // Straight there if escaped + standtag(actor, hp); + FreeToken(pActor->actorToken); + return; + } + } + + // Adopt the tag-related reel + SCNHANDLE pfilm = getPolyFilm(hp); + + switch (pfilm) { + case TF_NONE: + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + SetMActorStanding(pActor); + break; + + default: + if (actor == LEAD_ACTOR || actor == LeadId()) + AlterMActor(pActor, pfilm, AR_NORMAL); + else + SetMActorStanding(pActor); + break; + } + + FreeToken(pActor->actorToken); + CORO_END_CODE; +} + +/** + * whichinventory + */ + +int whichinventory(void) { + return WhichInventoryOpen(); +} + + +/** + * Subtract one less that the number of parameters from pp + * pp then points to the first parameter. + * + * If the library function has no return value: + * return -(the number of parameters) to pop them from the stack + * + * If the library function has a return value: + * return -(the number of parameters - 1) to pop most of them from + * the stack, and stick the return value in pp[0] + * @param operand Library function + * @param pp Top of parameter stack + */ +int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) { + debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent); + switch (operand) { + case ACTORATTR: + pp -= 3; // 4 parameters + actorattr(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case ACTORDIRECTION: + pp[0] = actordirection(pp[0]); + return 0; + + case ACTORREF: + error("actorref isn't a real function!"); + + case ACTORSCALE: + pp[0] = actorscale(pp[0]); + return 0; + + case ACTORSON: + actorson(); + return 0; + + case ACTORXPOS: + pp[0] = actorpos(ACTORXPOS, pp[0]); + return 0; + + case ACTORYPOS: + pp[0] = actorpos(ACTORYPOS, pp[0]); + return 0; + + case ADDICON: + addicon(pp[0]); + return -1; + + case ADDINV1: + addinv(INV_1, pp[0]); + return -1; + + case ADDINV2: + addinv(INV_2, pp[0]); + return -1; + + case ADDOPENINV: + addinv(INV_OPEN, pp[0]); + return -1; + + case AUXSCALE: + pp -= 13; // 14 parameters + auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2)); + return -14; + + case BACKGROUND: + background(pp[0]); + return -1; + + case CAMERA: + camera(pp[0]); + return -1; + + case CDLOAD: + pp -= 1; // 2 parameters + cdload(pp[0], pp[1]); + return -2; + + case CDPLAY: + error("cdplay isn't a real function!"); + + case CLEARHOOKSCENE: + clearhookscene(); + return 0; + + case CLOSEINVENTORY: + closeinventory(); + return 0; + + case CONTROL: + control(pp[0]); + return -1; + + case CONVERSATION: + conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent); + return -1; + + case CONVICON: + convicon(pp[0]); + return -1; + + case CURSORXPOS: + pp[0] = cursorpos(CURSORXPOS); + return 0; + + case CURSORYPOS: + pp[0] = cursorpos(CURSORYPOS); + return 0; + + case CUTSCENE: + error("cutscene isn't a real function!"); + + case DEC_CONVW: + pp -= 7; // 8 parameters + dec_convw(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_CSTRINGS: + pp -= 19; // 20 parameters + dec_cstrings((SCNHANDLE *)pp); + return -20; + + case DEC_CURSOR: + dec_cursor(pp[0]); + return -1; + + case DEC_FLAGS: + dec_flags(pp[0]); + return -1; + + case DEC_INV1: + pp -= 7; // 8 parameters + dec_inv1(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_INV2: + pp -= 7; // 8 parameters + dec_inv2(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_INVW: + dec_invw(pp[0]); + return -1; + + case DEC_LEAD: + pp -= 61; // 62 parameters + dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]); + return -62; + + case DEC_TAGFONT: + dec_tagfont(pp[0]); + return -1; + + case DEC_TALKFONT: + dec_talkfont(pp[0]); + return -1; + + case DELICON: + delicon(pp[0]); + return -1; + + case DELINV: + delinv(pp[0]); + return -1; + + case EFFECTACTOR: + assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code + + pp[0] = pic->actorid; + return 0; + + case ENABLEF1: + enablef1(); + return 0; + + case EVENT: + pp[0] = pic->event; + return 0; + + case FADEMIDI: + fademidi(coroParam, pp[0]); + return -1; + + case FRAMEGRAB: + return -1; + + case GETINVLIMIT: + pp[0] = getinvlimit(pp[0]); + return 0; + + case HASRESTARTED: + pp[0] = hasrestarted(); + return 0; + + case HELDOBJECT: + pp[0] = heldobject(); + return 0; + + case HIDE: + hide(pp[0]); + return -1; + + case HOOKSCENE: + pp -= 2; // 3 parameters + hookscene(pp[0], pp[1], pp[2]); + return -3; + + case IDLETIME: + pp[0] = idletime(); + return 0; + + case ININVENTORY: + pp[0] = ininventory(pp[0]); + return 0; // using return value + + case INVDEPICT: + pp -= 1; // 2 parameters + invdepict(pp[0], pp[1]); + return -2; + + case INVENTORY: + inventory(pp[0], pic->escOn, pic->myescEvent); + return -1; + + case INWHICHINV: + pp[0] = inwhichinv(pp[0]); + return 0; // using return value + + case KILLACTOR: + killactor(pp[0]); + return -1; + + case KILLBLOCK: + killblock(pp[0]); + return -1; + + case KILLEXIT: + killexit(pp[0]); + return -1; + + case KILLTAG: + killtag(pp[0]); + return -1; + + case LEFTOFFSET: + pp[0] = ltoffset(LEFTOFFSET); + return 0; + + case MOVECURSOR: + pp -= 1; // 2 parameters + movecursor(pp[0], pp[1]); + return -2; + + case NEWSCENE: + pp -= 2; // 3 parameters + if (*pResumeState == RES_2) + *pResumeState = RES_NOT; + else + newscene(coroParam, pp[0], pp[1], pp[2]); + return -3; + + case NOBLOCKING: + noblocking(); + return 0; + + case NOSCROLL: + pp -= 3; // 4 parameters + noscroll(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case OBJECTHELD: + objectheld(pp[0]); + return -1; + + case OFFSET: + pp -= 1; // 2 parameters + offset(pp[0], pp[1]); + return -2; + + case PLAY: + pp -= 5; // 6 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false); + else + play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false); + return -6; + + case PLAYMIDI: + pp -= 2; // 3 parameters + playmidi(coroParam, pp[0], pp[1], pp[2]); + return -3; + + case PLAYRTF: + error("playrtf only applies to cdi!"); + + case PLAYSAMPLE: + pp -= 1; // 2 parameters + playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case PREPARESCENE: + preparescene(pp[0]); + return -1; + + case PRINT: + pp -= 5; // 6 parameters + /* pp[2] was intended to be attribute */ + print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); + return -6; + + case PRINTOBJ: + printobj(coroParam, pp[0], pic->pinvo, pic->event); + return -1; + + case PRINTTAG: + printtag(pic->hpoly, pp[0]); + return -1; + + case QUITGAME: + quitgame(); + return 0; + + case RANDOM: + pp -= 2; // 3 parameters + pp[0] = dw_random(pp[0], pp[1], pp[2]); + return -2; // One holds return value + + case RESETIDLETIME: + resetidletime(); + return 0; + + case RESTARTGAME: + restartgame(); + return 0; + + case RESTORE_CUT: + restore_scene(false); + return 0; + + case RESTORE_SCENE: + restore_scene(true); + return 0; + + case RUNMODE: + pp[0] = runmode(); + return 0; + + case SAMPLEPLAYING: + pp[0] = sampleplaying(pic->escOn, pic->myescEvent); + return 0; + + case SAVE_SCENE: + if (*pResumeState == RES_1) + *pResumeState = RES_2; + else + save_scene(coroParam); + return 0; + + case SCALINGREELS: + pp -= 6; // 7 parameters + scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + return -7; + + case SCANICON: + pp[0] = scanicon(); + return 0; + + case SCROLL: + pp -= 3; // 4 parameters + scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); + return -4; + + case SETACTOR: + setactor(pp[0]); + return -1; + + case SETBLOCK: + setblock(pp[0]); + return -1; + + case SETEXIT: + setexit(pp[0]); + return -1; + + case SETINVLIMIT: + pp -= 1; // 2 parameters + setinvlimit(pp[0], pp[1]); + return -2; + + case SETINVSIZE: + pp -= 6; // 7 parameters + setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + return -7; + + case SETLANGUAGE: + setlanguage((LANGUAGE)pp[0]); + return -1; + + case SETPALETTE: + setpalette(pp[0], pic->escOn, pic->myescEvent); + return -1; + + case SETTAG: + settag(pp[0]); + return -1; + + case SETTIMER: + pp -= 3; // 4 parameters + settimer(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case SHOWPOS: +#ifdef DEBUG + showpos(); +#endif + return 0; + + case SHOWSTRING: +#ifdef DEBUG + showstring(); +#endif + return 0; + + case SPLAY: + pp -= 6; // 7 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent); + else + splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent); + return -7; + + case STAND: + pp -= 3; // 4 parameters + stand(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case STANDTAG: + standtag(pp[0], pic->hpoly); + return -1; + + case STOP: + stop(pp[0]); + return -1; + + case STOPMIDI: + stopmidi(); + return 0; + + case STOPSAMPLE: + stopsample(); + return 0; + + case SUBTITLES: + subtitles(pp[0]); + return -1; + + case SWALK: + pp -= 5; // 6 parameters + swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); + return -6; + + case TAGACTOR: + pp -= 2; // 3 parameters + tagactor(pp[0], pp[1], pp[2]); + return -3; + + case TALK: + pp -= 1; // 2 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent); + else + talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent); + return -2; + + case TALKAT: + pp -= 3; // 4 parameters + talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); + return -4; + + case TALKATS: + pp -= 4; // 5 parameters + talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent); + return -5; + + case TALKATTR: + pp -= 2; // 3 parameters + talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent); + return -3; + + case TIMER: + pp[0] = timer(pp[0]); + return 0; + + case TOPOFFSET: + pp[0] = ltoffset(TOPOFFSET); + return 0; + + case TOPPLAY: + pp -= 5; // 6 parameters + topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent); + return -6; + + case TOPWINDOW: + topwindow(pp[0]); + return -1; + + case TRYPLAYSAMPLE: + pp -= 1; // 2 parameters + tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case UNHOOKSCENE: + unhookscene(); + return 0; + + case UNTAGACTOR: + untagactor(pp[0]); + return -1; + + case VIBRATE: + vibrate(); + return 0; + + case WAITKEY: + waitkey(coroParam, pic->escOn, pic->myescEvent); + return 0; + + case WAITFRAME: + pp -= 1; // 2 parameters + waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case WAITTIME: + pp -= 1; // 2 parameters + waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case WALK: + pp -= 4; // 5 parameters + walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent); + return -5; + + case WALKED: { + pp -= 3; // 4 parameters + bool tmp; + walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp); + if (!coroParam) { + // Only write the result to the stack if walked actually completed running. + pp[0] = tmp; + } + } + return -3; + + case WALKINGACTOR: + pp -= 40; // 41 parameters + walkingactor(pp[0], (SCNHANDLE *)&pp[1]); + return -41; + + case WALKPOLY: + pp -= 2; // 3 parameters + walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); + return -3; + + case WALKTAG: + pp -= 2; // 3 parameters + walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); + return -3; + + case WHICHINVENTORY: + pp[0] = whichinventory(); + return 0; + + default: + error("Unsupported library function"); + } + + error("Can't possibly get here"); +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h new file mode 100644 index 0000000000..001de70896 --- /dev/null +++ b/engines/tinsel/tinlib.h @@ -0,0 +1,41 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TINLIB_H // prevent multiple includes +#define TINSEL_TINLIB_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Library functions in TINLIB.C + +void control(int param); +void stand(int actor, int x, int y, SCNHANDLE film); + +} // end of namespace Tinsel + +#endif // TINSEL_TINLIB_H diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp new file mode 100644 index 0000000000..1f56385283 --- /dev/null +++ b/engines/tinsel/tinsel.cpp @@ -0,0 +1,999 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/config-manager.h" +#include "common/stream.h" + +#include "graphics/cursorman.h" + +#include "base/plugins.h" +#include "base/version.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" +#include "sound/audiocd.h" + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" // MemoryInit +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/savescn.h" +#include "tinsel/scn.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/timers.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In BG.CPP +extern void SetDoFadeIn(bool tf); +extern void DropBackground(void); + +// In CURSOR.CPP +extern void CursorProcess(CORO_PARAM, const void *); + +// In INVENTORY.CPP +extern void InventoryProcess(CORO_PARAM, const void *); + +// In SCENE.CPP +extern void PrimeBackground(); +extern void NewScene(SCNHANDLE scene, int entry); +extern SCNHANDLE GetSceneHandle(void); + +// In TIMER.CPP +extern void FettleTimers(void); +extern void RebootTimers(void); + +//----------------- FORWARD DECLARATIONS --------------------- +void SetNewScene(SCNHANDLE scene, int entrance, int transition); + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bRestart = false; +bool bHasRestarted = false; + +#ifdef DEBUG +bool bFast; // set to make it go ludicrously fast +#endif + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Scene { + SCNHANDLE scene; // Memory handle for scene + int entry; // Entrance number + int trans; // Transition - not yet used +}; + +static Scene NextScene = { 0, 0, 0 }; +static Scene HookScene = { 0, 0, 0 }; +static Scene DelayedScene = { 0, 0, 0 }; + +static bool bHookSuspend = false; + +static PROCESS *pMouseProcess = 0; +static PROCESS *pKeyboardProcess = 0; + +// Stack of pending mouse button events +Common::List<Common::EventType> mouseButtons; + +// Stack of pending keypresses +Common::List<Common::Event> keypresses; + +//----------------- LOCAL PROCEDURES -------------------- + +/** + * Process to handle keypresses + */ +void KeyboardProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + while (true) { + if (keypresses.empty()) { + // allow scheduling + CORO_SLEEP(1); + continue; + } + + // Get the next keyboard event off the stack + Common::Event evt = *keypresses.begin(); + keypresses.erase(keypresses.begin()); + + // Switch for special keys + switch (evt.kbd.keycode) { + // Drag action + case Common::KEYCODE_LALT: + case Common::KEYCODE_RALT: + if (evt.type == Common::EVENT_KEYDOWN) { + if (!bSwapButtons) + ProcessButEvent(BE_RDSTART); + else + ProcessButEvent(BE_LDSTART); + } else { + if (!bSwapButtons) + ProcessButEvent(BE_LDEND); + else + ProcessButEvent(BE_RDEND); + } + continue; + + case Common::KEYCODE_LCTRL: + case Common::KEYCODE_RCTRL: + if (evt.type == Common::EVENT_KEYDOWN) { + ProcessKeyEvent(LOOK_KEY); + } else { + // Control key release + } + continue; + + default: + break; + } + + // At this point only key down events need processing + if (evt.type == Common::EVENT_KEYUP) + continue; + + if (_vm->_keyHandler != NULL) + // Keyboard is hooked, so pass it on to that handler first + if (!_vm->_keyHandler(evt.kbd)) + continue; + + switch (evt.kbd.keycode) { + /*** SPACE = WALKTO ***/ + case Common::KEYCODE_SPACE: + ProcessKeyEvent(WALKTO_KEY); + continue; + + /*** RETURN = ACTION ***/ + case Common::KEYCODE_RETURN: + case Common::KEYCODE_KP_ENTER: + ProcessKeyEvent(ACTION_KEY); + continue; + + /*** l = LOOK ***/ + case Common::KEYCODE_l: // LOOK + ProcessKeyEvent(LOOK_KEY); + continue; + + case Common::KEYCODE_ESCAPE: + // WORKAROUND: Check if any of the starting logo screens are active, and if so + // manually skip to the title screen, allowing them to be bypassed + { + int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0; + int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset; + if ((language == TXT_GERMAN) && + ((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) { + // Skip to title screen + // It seems the German CD version uses scenes 25,26,27,17 for the intro, + // instead of 13,14,15,11; also, the title screen is 11 instead of 10 + SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); + } else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) { + // Skip to title screen + SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); + } else { + // Not on an intro screen, so process the key normally + ProcessKeyEvent(ESC_KEY); + } + } + continue; + +#ifdef SLOW_RINCE_DOWN + case '>': + AddInterlude(1); + continue; + case '<': + AddInterlude(-1); + continue; +#endif + + case Common::KEYCODE_F1: + // Options dialog + ProcessKeyEvent(OPTION_KEY); + continue; + case Common::KEYCODE_F5: + // Save game + ProcessKeyEvent(SAVE_KEY); + continue; + case Common::KEYCODE_F7: + // Load game + ProcessKeyEvent(LOAD_KEY); + continue; + case Common::KEYCODE_q: + if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT)) + ProcessKeyEvent(QUIT_KEY); + continue; + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: + ProcessKeyEvent(PGUP_KEY); + continue; + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: + ProcessKeyEvent(PGDN_KEY); + continue; + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: + ProcessKeyEvent(HOME_KEY); + continue; + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: + ProcessKeyEvent(END_KEY); + continue; + default: + ProcessKeyEvent(NOEVENT_KEY); + break; + } + } + CORO_END_CODE; +} + +/** + * Process to handle changes in the mouse buttons. + */ +void MouseProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + bool lastLWasDouble; + bool lastRWasDouble; + uint32 lastLeftClick, lastRightClick; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->lastLWasDouble = false; + _ctx->lastRWasDouble = false; + _ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime(); + + while (true) { + // FIXME: I'm still keeping the ctrl/Alt handling in the ProcessKeyEvent method. + // Need to make sure that this works correctly + //DragKeys(); + + if (mouseButtons.empty()) { + // allow scheduling + CORO_SLEEP(1); + continue; + } + + // get next mouse button event + Common::EventType type = *mouseButtons.begin(); + mouseButtons.erase(mouseButtons.begin()); + + switch (type) { + case Common::EVENT_LBUTTONDOWN: + // left button press + if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)dclickSpeed) { + // signal left drag start + ProcessButEvent(BE_LDSTART); + + // signal left double click event + ProcessButEvent(BE_DLEFT); + + _ctx->lastLWasDouble = true; + } else { + // signal left drag start + ProcessButEvent(BE_LDSTART); + + // signal left single click event + ProcessButEvent(BE_SLEFT); + + _ctx->lastLWasDouble = false; + } + break; + + case Common::EVENT_LBUTTONUP: + // left button release + + // update click timer + if (_ctx->lastLWasDouble == false) + _ctx->lastLeftClick = DwGetCurrentTime(); + else + _ctx->lastLeftClick -= dclickSpeed; + + // signal left drag end + ProcessButEvent(BE_LDEND); + break; + + case Common::EVENT_RBUTTONDOWN: + // right button press + + if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)dclickSpeed) { + // signal right drag start + ProcessButEvent(BE_RDSTART); + + // signal right double click event + ProcessButEvent(BE_DRIGHT); + + _ctx->lastRWasDouble = true; + } else { + // signal right drag start + ProcessButEvent(BE_RDSTART); + + // signal right single click event + ProcessButEvent(BE_SRIGHT); + + _ctx->lastRWasDouble = false; + } + break; + + case Common::EVENT_RBUTTONUP: + // right button release + + // update click timer + if (_ctx->lastRWasDouble == false) + _ctx->lastRightClick = DwGetCurrentTime(); + else + _ctx->lastRightClick -= dclickSpeed; + + // signal right drag end + ProcessButEvent(BE_RDEND); + break; + + default: + break; + } + } + CORO_END_CODE; +} + +/** + * Run the master script. + * Continues between scenes, or until Interpret() returns. + */ +static void MasterScriptProcess(CORO_PARAM, const void *) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + _ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + CORO_END_CODE; +} + +/** + * Store the facts pertaining to a scene change. + */ + +void SetNewScene(SCNHANDLE scene, int entrance, int transition) { + if (HookScene.scene == 0 || bHookSuspend) { + // This scene comes next + NextScene.scene = scene; + NextScene.entry = entrance; + NextScene.trans = transition; + } else { + // This scene gets delayed + DelayedScene.scene = scene; + DelayedScene.entry = entrance; + DelayedScene.trans = transition; + + // The hooked scene comes next + NextScene.scene = HookScene.scene; + NextScene.entry = HookScene.entry; + NextScene.trans = HookScene.trans; + + HookScene.scene = 0; + } +} + +void SetHookScene(SCNHANDLE scene, int entrance, int transition) { + assert(HookScene.scene == 0); // scene already hooked + + HookScene.scene = scene; + HookScene.entry = entrance; + HookScene.trans = transition; +} + +void UnHookScene(void) { + assert(DelayedScene.scene != 0); // no scene delayed + + // The delayed scene can go now + NextScene.scene = DelayedScene.scene; + NextScene.entry = DelayedScene.entry; + NextScene.trans = DelayedScene.trans; + + DelayedScene.scene = 0; +} + +void SuspendHook(void) { + bHookSuspend = true; +} + +void UnSuspendHook(void) { + bHookSuspend = false; +} + +void syncSCdata(Serializer &s) { + s.syncAsUint32LE(HookScene.scene); + s.syncAsSint32LE(HookScene.entry); + s.syncAsSint32LE(HookScene.trans); + + s.syncAsUint32LE(DelayedScene.scene); + s.syncAsSint32LE(DelayedScene.entry); + s.syncAsSint32LE(DelayedScene.trans); +} + + +//----------------------------------------------------------------------- + +static void RestoredProcess(CORO_PARAM, const void *param) { + // COROUTINE + CORO_BEGIN_CONTEXT; + INT_CONTEXT *pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // get the stuff copied to process when it was created + _ctx->pic = *((INT_CONTEXT **)param); + + _ctx->pic = RestoreInterpretContext(_ctx->pic); + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +void RestoreProcess(INT_CONTEXT *pic) { + g_scheduler->createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic)); +} + +void RestoreMasterProcess(INT_CONTEXT *pic) { + g_scheduler->createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic)); +} + +// FIXME: CountOut is used by ChangeScene +static int CountOut = 1; // == 1 for immediate start of first scene + +/** + * If a scene restore is going on, just return (we don't update the + * screen during this time). + * If a scene change is required, 'order' the data for the new scene and + * start a fade out and a countdown. + * When the count expires, the screen will have faded. Ensure the scene | + * is loaded, clear the screen, and start the new scene. + */ +void ChangeScene() { + + if (IsRestoringScene()) + return; + + if (NextScene.scene != 0) { + if (!CountOut) { + switch (NextScene.trans) { + case TRANS_CUT: + CountOut = 1; + break; + + case TRANS_FADE: + default: + // Trigger pre-load and fade and start countdown + CountOut = COUNTOUT_COUNT; + FadeOutFast(NULL); + break; + } + } else if (--CountOut == 0) { + ClearScreen(); + + NewScene(NextScene.scene, NextScene.entry); + NextScene.scene = 0; + + switch (NextScene.trans) { + case TRANS_CUT: + SetDoFadeIn(false); + break; + + case TRANS_FADE: + default: + SetDoFadeIn(true); + break; + } + } + } +} + +/** + * LoadBasicChunks + */ + +void LoadBasicChunks(void) { + byte *cptr; + int numObjects; + + // Allocate RAM for savescene data + InitialiseSs(); + + // CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value + // TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS); + RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511); + + // CHUNK_TOTAL_GLOBALS seems to be missing in some versions. + // So if it is missing, set a reasonably high value for the number of globals. + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS); + RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512); + + cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS); + numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0; + + cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS); + +#ifdef SCUMM_BIG_ENDIAN + //convert to native endianness + INV_OBJECT *io = (INV_OBJECT *)cptr; + for (int i = 0; i < numObjects; i++, io++) { + io->id = FROM_LE_32(io->id); + io->hFilm = FROM_LE_32(io->hFilm); + io->hScript = FROM_LE_32(io->hScript); + io->attribute = FROM_LE_32(io->attribute); + } +#endif + + RegisterIcons(cptr, numObjects); + + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY); + if (cptr != NULL) + MaxPolygons(*cptr); +} + +//----------------- TinselEngine -------------------- + +// Global pointer to engine +TinselEngine *_vm; + +struct GameSettings { + const char *gameid; + const char *description; + byte id; + uint32 features; + const char *detectname; +}; + +static const GameSettings tinselSettings[] = { + {"tinsel", "Tinsel game", 0, 0, 0}, + + {NULL, NULL, 0, 0, NULL} +}; + +TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) : + Engine(syst), _gameDescription(gameDesc) { + _vm = this; + + // Setup mixer + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + + const GameSettings *g; + + const char *gameid = ConfMan.get("gameid").c_str(); + for (g = tinselSettings; g->gameid; ++g) + if (!scumm_stricmp(g->gameid, gameid)) + _gameId = g->id; + + int cd_num = ConfMan.getInt("cdrom"); + if (cd_num >= 0) + _system->openCD(cd_num); + + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + //bool adlib = (midiDriver == MD_ADLIB); + + MidiDriver *driver = MidiDriver::createMidi(midiDriver); + if (native_mt32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + _music = new MusicPlayer(driver); + //_music->setNativeMT32(native_mt32); + //_music->setAdlib(adlib); + + _musicVolume = ConfMan.getInt("music_volume"); + + _sound = new SoundManager(this); + + _mousePos.x = 0; + _mousePos.y = 0; + _keyHandler = NULL; + _dosPlayerDir = 0; + quitFlag = false; +} + +TinselEngine::~TinselEngine() { + delete _sound; + delete _music; + delete _console; + FreeSs(); + FreeTextBuffer(); + FreeHandleTable(); + FreeActors(); + FreeObjectList(); + FreeGlobals(); + delete _scheduler; +} + +int TinselEngine::init() { + // Initialize backend + _system->beginGFXTransaction(); + initCommonGFX(false); + _system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT); + _system->endGFXTransaction(); + + _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1); + + g_system->getEventManager()->registerRandomSource(_random, "tinsel"); + + _console = new Console(); + + _scheduler = new Scheduler(); + + // init memory manager + MemoryInit(); + + // load user configuration + ReadConfig(); + +#if 1 + // FIXME: The following is taken from RestartGame(). + // It may have to be adjusted a bit + RebootCursor(); + RebootDeadTags(); + RebootMovers(); + RebootTimers(); + RebootScalingReels(); + + DelayedScene.scene = HookScene.scene = 0; +#endif + + // Init palette and object managers, scheduler, keyboard and mouse + RestartDrivers(); + + // TODO: More stuff from dos_main.c may have to be added here + + // Set language - we'll be clever here and use the ScummVM language setting + language = TXT_ENGLISH; + switch (getLanguage()) { + case Common::FR_FRA: + language = TXT_FRENCH; + break; + case Common::DE_DEU: + language = TXT_GERMAN; + break; + case Common::IT_ITA: + language = TXT_ITALIAN; + break; + case Common::ES_ESP: + language = TXT_SPANISH; + break; + default: + language = TXT_ENGLISH; + } + ChangeLanguage(language); + + // load in graphics info + SetupHandleTable(); + + // Actors, globals and inventory icons + LoadBasicChunks(); + + return 0; +} + +Common::String TinselEngine::getSavegamePattern() const { + return _targetName + ".???"; +} + +Common::String TinselEngine::getSavegameFilename(int16 saveNum) const { + char filename[256]; + snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum); + return filename; +} + +#define GAME_FRAME_DELAY (1000 / ONE_SECOND) + +int TinselEngine::go() { + uint32 timerVal = 0; + + // Continuous game processes + CreateConstProcesses(); + + // allow game to run in the background + //RestartBackgroundProcess(); // FIXME: is this still needed? + + //dumpMusic(); // dumps all of the game's music in external XMIDI files + +#if 0 + // Load game from specified slot, if any + // FIXME: Not working correctly right now + if (ConfMan.hasKey("save_slot")) { + getList(); + RestoreGame(ConfMan.getInt("save_slot")); + } +#endif + + // Foreground loop + + while (!quitFlag) { + assert(_console); + if (_console->isAttached()) + _console->onFrame(); + + // Check for time to do next game cycle + if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) { + timerVal = g_system->getMillis(); + AudioCD.updateCD(); + NextGameCycle(); + } + + if (bRestart) { + RestartGame(); + bRestart = false; + bHasRestarted = true; // Set restarted flag + } + + // Save/Restore scene file transfers + ProcessSRQueue(); + +#ifdef DEBUG + if (bFast) + continue; // run flat-out +#endif + // Loop processing events while there are any pending + while (pollEvent()); + + g_system->delayMillis(10); + } + + // Write configuration + WriteConfig(); + + return 0; +} + + +void TinselEngine::NextGameCycle(void) { + // + ChangeScene(); + + // Allow a user event for this schedule + ResetEcount(); + + // schedule process + _scheduler->schedule(); + + // redraw background + DrawBackgnd(); + + // Why waste resources on yet another process? + FettleTimers(); +} + + +bool TinselEngine::pollEvent() { + Common::Event event; + + if (!g_system->getEventManager()->pollEvent(event)) + return false; + + // Handle the various kind of events + switch (event.type) { + case Common::EVENT_QUIT: + quitFlag = true; + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + // Add button to queue for the mouse process + mouseButtons.push_back(event.type); + break; + + case Common::EVENT_MOUSEMOVE: + _mousePos = event.mouse; + break; + + case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYUP: + ProcessKeyEvent(event); + break; + + default: + break; + } + + return true; +} + +/** + * Start the processes that continue between scenes. + */ + +void TinselEngine::CreateConstProcesses(void) { + // Process to run the master script + _scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0); + + // Processes to run the cursor and inventory, + _scheduler->createProcess(PID_CURSOR, CursorProcess, NULL, 0); + _scheduler->createProcess(PID_INVENTORY, InventoryProcess, NULL, 0); +} + +/** + * Restart the game + */ + +void TinselEngine::RestartGame(void) { + HoldItem(INV_NOICON); // Holding nothing + + DropBackground(); // No background + + // Ditches existing infrastructure background + PrimeBackground(); + + // Next scene change won't need to fade out + // -> reset the count used by ChangeScene + CountOut = 1; + + RebootCursor(); + RebootDeadTags(); + RebootMovers(); + RebootTimers(); + RebootScalingReels(); + + DelayedScene.scene = HookScene.scene = 0; + + // remove keyboard, mouse and joystick drivers + ChopDrivers(); + + // Init palette and object managers, scheduler, keyboard and mouse + RestartDrivers(); + + // Actors, globals and inventory icons + LoadBasicChunks(); + + // Continuous game processes + CreateConstProcesses(); +} + +/** + * Init palette and object managers, scheduler, keyboard and mouse. + */ + +void TinselEngine::RestartDrivers(void) { + // init the palette manager + ResetPalAllocator(); + + // init the object manager + KillAllObjects(); + + // init the process scheduler + _scheduler->reset(); + + // init the event handlers + pMouseProcess = _scheduler->createProcess(PID_MOUSE, MouseProcess, NULL, 0); + pKeyboardProcess = _scheduler->createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0); + + // open MIDI files + OpenMidiFiles(); + + // open sample files (only if mixer is ready) + if (_mixer->isReady()) { + _sound->openSampleFiles(); + } + + // Set midi volume + SetMidiVolume(volMidi); +} + +/** + * Remove keyboard, mouse and joystick drivers. + */ + +void TinselEngine::ChopDrivers(void) { + // remove sound driver + StopMidi(); + _sound->stopAllSamples(); + DeleteMidiBuffer(); + + // remove event drivers + _scheduler->killProcess(pMouseProcess); + _scheduler->killProcess(pKeyboardProcess); +} + +/** + * Process a keyboard event + */ + +void TinselEngine::ProcessKeyEvent(const Common::Event &event) { + + // Handle any special keys immediately + switch (event.kbd.keycode) { + case Common::KEYCODE_d: + if ((event.kbd.flags == Common::KBD_CTRL) && (event.type == Common::EVENT_KEYDOWN)) { + // Activate the debugger + assert(_console); + _console->attach(); + return; + } + break; + default: + break; + } + + // Check for movement keys + int idx = 0; + switch (event.kbd.keycode) { + case Common::KEYCODE_UP: + case Common::KEYCODE_KP8: + idx = MSK_UP; + break; + case Common::KEYCODE_DOWN: + case Common::KEYCODE_KP2: + idx = MSK_DOWN; + break; + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: + idx = MSK_LEFT; + break; + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: + idx = MSK_RIGHT; + break; + default: + break; + } + if (idx != 0) { + if (event.type == Common::EVENT_KEYDOWN) + _dosPlayerDir |= idx; + else + _dosPlayerDir &= ~idx; + return; + } + + // All other keypresses add to the queue for processing in KeyboardProcess + keypresses.push_back(event); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h new file mode 100644 index 0000000000..9ffadfe8c1 --- /dev/null +++ b/engines/tinsel/tinsel.h @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_H +#define TINSEL_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/util.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" + +#include "engines/engine.h" +#include "tinsel/debugger.h" +#include "tinsel/graphics.h" +#include "tinsel/sound.h" + +namespace Tinsel { + +class MusicPlayer; +class Scheduler; +class SoundManager; + +enum TinselGameID { + GID_DW1 = 0, + GID_DW2 = 1 +}; + +enum TinselGameFeatures { + GF_DEMO = 1 << 0, + GF_CD = 1 << 1, + GF_FLOPPY = 1 << 2, + GF_SCNFILES = 1 << 3 +}; + +enum TinselEngineVersion { + TINSEL_V1 = 1 << 0, + TINSEL_V2 = 1 << 1 +}; + +struct TinselGameDescription; + +enum TinselKeyDirection { + MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8, + MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN +}; + +typedef bool (*KEYFPTR)(const Common::KeyState &); + +class TinselEngine : public ::Engine { + int _gameId; + Common::KeyState _keyPressed; + Common::RandomSource _random; + Graphics::Surface _screenSurface; + Common::Point _mousePos; + uint8 _dosPlayerDir; + Console *_console; + Scheduler *_scheduler; + +protected: + + int init(); + int go(); + +public: + TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc); + virtual ~TinselEngine(); + int getGameId() { + return _gameId; + } + + const TinselGameDescription *_gameDescription; + uint32 getGameID() const; + uint32 getFeatures() const; + Common::Language getLanguage() const; + uint16 getVersion() const; + Common::Platform getPlatform() const; + bool quitFlag; + + SoundManager *_sound; + MusicPlayer *_music; + + KEYFPTR _keyHandler; +private: + //MusicPlayer *_music; + int _musicVolume; + + void NextGameCycle(void); + void CreateConstProcesses(void); + void RestartGame(void); + void RestartDrivers(void); + void ChopDrivers(void); + void ProcessKeyEvent(const Common::Event &event); + bool pollEvent(); + +public: + const Common::String getTargetName() const { return _targetName; } + Common::String getSavegamePattern() const; + Common::String getSavegameFilename(int16 saveNum) const; + Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; } + Graphics::Surface &screen() { return _screenSurface; } + + Common::Point getMousePosition() const { return _mousePos; } + void setMousePosition(const Common::Point &pt) { + g_system->warpMouse(pt.x, pt.y); + _mousePos = pt; + } + void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; } + int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); } + uint8 getKeyDirection() const { return _dosPlayerDir; } +}; + +// Global reference to the TinselEngine object +extern TinselEngine *_vm; + +} // End of namespace Tinsel + +#endif /* TINSEL_H */ diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp new file mode 100644 index 0000000000..0bdac0d6eb --- /dev/null +++ b/engines/tinsel/token.cpp @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#include "common/util.h" + +#include "tinsel/sched.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Token { + PROCESS *proc; +}; + +static Token tokens[NUMTOKENS]; + + +/** + * Release all tokens held by this process, and kill the process. + */ +static void TerminateProcess(PROCESS *tProc) { + + // Release tokens held by the process + for (int i = 0; i < NUMTOKENS; i++) { + if (tokens[i].proc == tProc) { + tokens[i].proc = NULL; + } + } + + // Kill the process + g_scheduler->killProcess(tProc); +} + +/** + * Gain control of the CONTROL token if it is free. + */ +void GetControlToken() { + const int which = TOKEN_CONTROL; + + if (tokens[which].proc == NULL) { + tokens[which].proc = g_scheduler->getCurrentProcess(); + } +} + +/** + * Release control of the CONTROL token. + */ +void FreeControlToken() { + // Allow anyone to free TOKEN_CONTROL + tokens[TOKEN_CONTROL].proc = NULL; +} + + +/** + * Gain control of a token. If the requested token is out of range, or + * is already held by the calling process, then the calling process + * will be killed off. + * + * Otherwise, the calling process will gain the token. If the token was + * held by another process, then the previous holder is killed off. + */ +void GetToken(int which) { + assert(TOKEN_LEAD <= which && which < NUMTOKENS); + + if (tokens[which].proc != NULL) { + assert(tokens[which].proc != g_scheduler->getCurrentProcess()); + TerminateProcess(tokens[which].proc); + } + + tokens[which].proc = g_scheduler->getCurrentProcess(); +} + +/** + * Release control of a token. If the requested token is not owned by + * the calling process, then the calling process will be killed off. + */ +void FreeToken(int which) { + assert(TOKEN_LEAD <= which && which < NUMTOKENS); + + assert(tokens[which].proc == g_scheduler->getCurrentProcess()); // we'd have been killed if some other proc had taken this token + + tokens[which].proc = NULL; +} + +/** + * If it's a valid token and it's free, returns true. + */ +bool TestToken(int which) { + if (which < 0 || which >= NUMTOKENS) + return false; + + return (tokens[which].proc == NULL); +} + +/** + * Call at the start of each scene. + */ +void FreeAllTokens(void) { + for (int i = 0; i < NUMTOKENS; i++) { + tokens[i].proc = NULL; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h new file mode 100644 index 0000000000..4ab4775bfb --- /dev/null +++ b/engines/tinsel/token.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_TOKEN_H +#define TINSEL_TOKEN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Fixed tokens + +enum { + TOKEN_CONTROL = 0, + TOKEN_LEAD, // = TOKEN_CONTROL + 1 + TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS, + + NUMTOKENS // = TOKEN_LEFT_BUT + 1 +}; + +// Token functions + +void GetControlToken(); +void FreeControlToken(); + +void GetToken(int which); +void FreeToken(int which); + +void FreeAllTokens(void); +bool TestToken(int which); + + +} // end of namespace Tinsel + +#endif // TINSEL_TOKEN_H diff --git a/engines/touche/midi.cpp b/engines/touche/midi.cpp index 14cb85912a..d77dbf5bfa 100644 --- a/engines/touche/midi.cpp +++ b/engines/touche/midi.cpp @@ -23,6 +23,7 @@ * */ +#include "common/config-manager.h" #include "common/stream.h" #include "sound/midiparser.h" @@ -31,9 +32,8 @@ namespace Touche { -MidiPlayer::MidiPlayer(MidiDriver *driver, bool nativeMT32) - : _driver(driver), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0), _nativeMT32(nativeMT32) { - assert(_driver); +MidiPlayer::MidiPlayer() + : _driver(0), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0) { memset(_channelsTable, 0, sizeof(_channelsTable)); memset(_channelsVolume, 0, sizeof(_channelsVolume)); open(); @@ -92,6 +92,9 @@ void MidiPlayer::setVolume(int volume) { } int MidiPlayer::open() { + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + _nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + _driver = MidiDriver::createMidi(midiDriver); int ret = _driver->open(); if (ret == 0) { _parser = MidiParser::createParser_SMF(); @@ -107,6 +110,7 @@ void MidiPlayer::close() { _mutex.lock(); _driver->setTimerCallback(NULL, NULL); _driver->close(); + delete _driver; _driver = 0; _parser->setMidiDriver(NULL); delete _parser; diff --git a/engines/touche/midi.h b/engines/touche/midi.h index 3b128593db..a518a4bb29 100644 --- a/engines/touche/midi.h +++ b/engines/touche/midi.h @@ -46,7 +46,7 @@ public: NUM_CHANNELS = 16 }; - MidiPlayer(MidiDriver *driver, bool nativeMT32); + MidiPlayer(); ~MidiPlayer(); void play(Common::ReadStream &stream, int size, bool loop = false); diff --git a/engines/touche/saveload.cpp b/engines/touche/saveload.cpp index eb647a1b42..4fcf6e114d 100644 --- a/engines/touche/saveload.cpp +++ b/engines/touche/saveload.cpp @@ -200,9 +200,6 @@ static void saveOrLoad(S &s, ProgramPointData &data) { saveOrLoad(s, data.order); } -template <class S, class A> -static void saveOrLoadCommonArray(S &s, A &array); - template <class A> static void saveOrLoadCommonArray(Common::WriteStream &stream, A &array) { uint count = array.size(); diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp index 6520fb5e4a..ac8e8a786a 100644 --- a/engines/touche/touche.cpp +++ b/engines/touche/touche.cpp @@ -91,10 +91,7 @@ int ToucheEngine::init() { setupOpcodes(); - int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); - bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); - MidiDriver *driver = MidiDriver::createMidi(midiDriver); - _midiPlayer = new MidiPlayer(driver, native_mt32); + _midiPlayer = new MidiPlayer; _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); |