diff options
-rw-r--r-- | sword2/anims.cpp | 262 | ||||
-rw-r--r-- | sword2/build_display.cpp | 83 | ||||
-rw-r--r-- | sword2/console.cpp | 12 | ||||
-rw-r--r-- | sword2/controls.cpp | 6 | ||||
-rw-r--r-- | sword2/driver/animation.cpp | 310 | ||||
-rw-r--r-- | sword2/driver/animation.h | 7 | ||||
-rw-r--r-- | sword2/driver/d_sound.cpp | 472 | ||||
-rw-r--r-- | sword2/driver/d_sound.h | 128 | ||||
-rw-r--r-- | sword2/events.cpp | 90 | ||||
-rw-r--r-- | sword2/function.cpp | 2859 | ||||
-rw-r--r-- | sword2/icons.cpp | 27 | ||||
-rw-r--r-- | sword2/layers.cpp | 12 | ||||
-rw-r--r-- | sword2/logic.cpp | 89 | ||||
-rw-r--r-- | sword2/logic.h | 27 | ||||
-rw-r--r-- | sword2/mouse.cpp | 257 | ||||
-rw-r--r-- | sword2/resman.cpp | 5 | ||||
-rw-r--r-- | sword2/save_rest.cpp | 74 | ||||
-rw-r--r-- | sword2/scroll.cpp | 37 | ||||
-rw-r--r-- | sword2/sound.cpp | 373 | ||||
-rw-r--r-- | sword2/sound.h | 192 | ||||
-rw-r--r-- | sword2/speech.cpp | 1015 | ||||
-rw-r--r-- | sword2/startup.cpp | 61 | ||||
-rw-r--r-- | sword2/startup.h | 30 | ||||
-rw-r--r-- | sword2/sword2.cpp | 12 | ||||
-rw-r--r-- | sword2/sword2.h | 44 | ||||
-rw-r--r-- | sword2/sync.cpp | 54 | ||||
-rw-r--r-- | sword2/walker.cpp | 639 |
27 files changed, 3435 insertions, 3742 deletions
diff --git a/sword2/anims.cpp b/sword2/anims.cpp index fe6d2ee303..385ff1885b 100644 --- a/sword2/anims.cpp +++ b/sword2/anims.cpp @@ -34,50 +34,12 @@ #include "sword2/maketext.h" #include "sword2/memory.h" #include "sword2/resman.h" +#include "sword2/sound.h" #include "sword2/driver/animation.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" namespace Sword2 { -int32 Logic::fnAnim(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 resource id of animation file - - // 0 means normal forward anim - return animate(params, false); -} - -int32 Logic::fnReverseAnim(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 resource id of animation file - - // 1 means reverse anim - return animate(params, true); -} - -int32 Logic::fnMegaTableAnim(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to animation table - - // 0 means normal forward anim - return megaTableAnimate(params, false); -} - -int32 Logic::fnReverseMegaTableAnim(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to animation table - - // 1 means reverse anim - return megaTableAnimate(params, true); -} - int32 Logic::animate(int32 *params, bool reverse) { // params: 0 pointer to object's logic structure // 1 pointer to object's graphic structure @@ -217,34 +179,6 @@ int32 Logic::megaTableAnimate(int32 *params, bool reverse) { return animate(pars, reverse); } -int32 Logic::fnSetFrame(int32 *params) { - // params: 0 pointer to object's graphic structure - // 1 resource id of animation file - // 2 frame flag (0=first 1=last) - - int32 res = params[1]; - assert(res); - - // open the resource (& check it's valid) - byte *anim_file = _vm->_resman->openResource(res); - - StandardHeader *head = (StandardHeader *) anim_file; - assert(head->fileType == ANIMATION_FILE); - - // set up pointer to the animation header - AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); - - // set up anim resource in graphic object - ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); - - ob_graphic->anim_resource = res; - ob_graphic->anim_pc = params[2] ? anim_head->noAnimFrames - 1 : 0; - - // Close the anim file and drop out of script - _vm->_resman->closeResource(ob_graphic->anim_resource); - return IR_CONT; -} - void Logic::setSpriteStatus(uint32 sprite, uint32 type) { ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(sprite); @@ -261,80 +195,6 @@ void Logic::setSpriteShading(uint32 sprite, uint32 type) { ob_graphic->type = (ob_graphic->type & 0x0000ffff) | type; } -int32 Logic::fnNoSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], NO_SPRITE); - return IR_CONT; -} - -int32 Logic::fnBackPar0Sprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], BGP0_SPRITE); - return IR_CONT; -} - -int32 Logic::fnBackPar1Sprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], BGP1_SPRITE); - return IR_CONT; -} - -int32 Logic::fnBackSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], BACK_SPRITE); - return IR_CONT; -} - -int32 Logic::fnSortSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], SORT_SPRITE); - return IR_CONT; -} - -int32 Logic::fnForeSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], FORE_SPRITE); - return IR_CONT; -} - -int32 Logic::fnForePar0Sprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], FGP0_SPRITE); - return IR_CONT; -} - -int32 Logic::fnForePar1Sprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteStatus(params[0], FGP1_SPRITE); - return IR_CONT; -} - -int32 Logic::fnShadedSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteShading(params[0], SHADED_SPRITE); - return IR_CONT; -} - -int32 Logic::fnUnshadedSprite(int32 *params) { - // params: 0 pointer to object's graphic structure - setSpriteShading(params[0], UNSHADED_SPRITE); - return IR_CONT; -} - -int32 Logic::fnAddSequenceText(int32 *params) { - // params: 0 text number - // 1 frame number to start the text displaying - // 2 frame number to stop the text dispalying - - assert(_sequenceTextLines < MAX_SEQUENCE_TEXT_LINES); - - _sequenceTextList[_sequenceTextLines].textNumber = params[0]; - _sequenceTextList[_sequenceTextLines].startFrame = params[1]; - _sequenceTextList[_sequenceTextLines].endFrame = params[2]; - _sequenceTextLines++; - return IR_CONT; -} - void Logic::createSequenceSpeech(MovieTextObject *sequenceText[]) { uint32 line; FrameHeader *frame; @@ -462,124 +322,4 @@ void Logic::clearSequenceSpeech(MovieTextObject *sequenceText[]) { _sequenceTextLines = 0; } -int32 Logic::fnSmackerLeadIn(int32 *params) { - // params: 0 id of lead-in music - byte *leadIn = _vm->_resman->openResource(params[0]); - - StandardHeader *header = (StandardHeader *) leadIn; - assert(header->fileType == WAV_FILE); - - leadIn += sizeof(StandardHeader); - - uint32 len = _vm->_resman->fetchLen(params[0]) - sizeof(StandardHeader); - uint32 rv = _vm->_sound->playFx(0, len, leadIn, 0, 0, RDSE_FXLEADIN); - - if (rv) - debug(5, "SFX ERROR: playFx() returned %.8x", rv); - - _vm->_resman->closeResource(params[0]); - - // fade out any music that is currently playing - fnStopMusic(NULL); - return IR_CONT; -} - -int32 Logic::fnSmackerLeadOut(int32 *params) { - // params: 0 id of lead-out music - - // ready for use in fnPlaySequence - _smackerLeadOut = params[0]; - return IR_CONT; -} - -int32 Logic::fnPlaySequence(int32 *params) { - // params: 0 pointer to null-terminated ascii filename - // 1 number of frames in the sequence, used for PSX. - - char filename[30]; - MovieTextObject *sequenceSpeechArray[MAX_SEQUENCE_TEXT_LINES + 1]; - byte *leadOut = NULL; - uint32 leadOutLen = 0; - - // The original code had some #ifdef blocks for skipping or muting the - // cutscenes - fondly described as "the biggest fudge in the history - // of computer games" - but at the very least we want to show the - // cutscene subtitles, so I removed them. - - debug(5, "fnPlaySequence(\"%s\");", (const char *) _vm->_memory->decodePtr(params[0])); - - // add the appropriate file extension & play it - - strcpy(filename, (const char *) _vm->_memory->decodePtr(params[0])); - - // Write to walkthrough file (zebug0.txt) - debug(5, "PLAYING SEQUENCE \"%s\"", filename); - - // now create the text sprites, if any - - if (_sequenceTextLines) - createSequenceSpeech(sequenceSpeechArray); - - // open the lead-out music resource, if there is one - - if (_smackerLeadOut) { - leadOut = _vm->_resman->openResource(_smackerLeadOut); - - StandardHeader *header = (StandardHeader *) leadOut; - assert(header->fileType == WAV_FILE); - - leadOut += sizeof(StandardHeader); - - leadOutLen = _vm->_resman->fetchLen(_smackerLeadOut) - sizeof(StandardHeader); - } - - // don't want to carry on streaming game music when smacker starts! - fnStopMusic(NULL); - - // pause sfx during sequence, except the one used for lead-in music - _vm->_sound->pauseFxForSequence(); - - MoviePlayer player(_vm); - uint32 rv; - - if (_sequenceTextLines && !_scriptVars[DEMO]) - rv = player.play(filename, sequenceSpeechArray, leadOut, leadOutLen); - else - rv = player.play(filename, NULL, leadOut, leadOutLen); - - // check the error return-value - if (rv) - debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv); - - // unpause sound fx again, in case we're staying in same location - _vm->_sound->unpauseFx(); - - // close the lead-out music resource - - if (_smackerLeadOut) { - _vm->_resman->closeResource(_smackerLeadOut); - _smackerLeadOut = 0; - } - - // now clear the text sprites, if any - - if (_sequenceTextLines) - clearSequenceSpeech(sequenceSpeechArray); - - // now clear the screen in case the Sequence was quitted (using ESC) - // rather than fading down to black - - _vm->_graphics->clearScene(); - - // zero the entire palette in case we're about to fade up! - - byte pal[4 * 256]; - - memset(pal, 0, sizeof(pal)); - _vm->_graphics->setPalette(0, 256, pal, RDPAL_INSTANT); - - debug(5, "fnPlaySequence FINISHED"); - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/build_display.cpp b/sword2/build_display.cpp index 1a0bc5cbf1..98f738f75b 100644 --- a/sword2/build_display.cpp +++ b/sword2/build_display.cpp @@ -557,18 +557,6 @@ void Sword2Engine::registerFrame(int32 *params, BuildUnit *build_unit) { _resman->closeResource(ob_graph->anim_resource); } -int32 Logic::fnRegisterFrame(int32 *params) { - // this call would be made from an objects service script 0 - - // params: 0 pointer to mouse structure or NULL for no write to - // mouse list (non-zero means write sprite-shape to - // mouse list) - // 1 pointer to graphic structure - // 2 pointer to mega structure or NULL if not a mega - - return _vm->registerFrame(params); -} - int32 Sword2Engine::registerFrame(int32 *params) { ObjectGraphic *ob_graph = (ObjectGraphic *) _memory->decodePtr(params[1]); @@ -640,58 +628,6 @@ void Sword2Engine::startNewPalette(void) { _thisScreen.new_palette = 0; } -int32 Logic::fnUpdatePlayerStats(int32 *params) { - // engine needs to know certain info about the player - - // params: 0 pointer to mega structure - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); - - _vm->_thisScreen.player_feet_x = ob_mega->feet_x; - _vm->_thisScreen.player_feet_y = ob_mega->feet_y; - - // for the script - _scriptVars[PLAYER_FEET_X] = ob_mega->feet_x; - _scriptVars[PLAYER_FEET_Y] = ob_mega->feet_y; - _scriptVars[PLAYER_CUR_DIR] = ob_mega->current_dir; - _scriptVars[SCROLL_OFFSET_X] = _vm->_thisScreen.scroll_offset_x; - - debug(5, "fnUpdatePlayerStats: %d %d", ob_mega->feet_x, ob_mega->feet_y); - - return IR_CONT; -} - -int32 Logic::fnFadeDown(int32 *params) { - // NONE means up! can only be called when screen is fully faded up - - // multiple calls wont have strange effects - - // params: none - - if (_vm->_graphics->getFadeStatus() == RDFADE_NONE) - _vm->_graphics->fadeDown(); - - return IR_CONT; -} - -int32 Logic::fnFadeUp(int32 *params) { - // params: none - - _vm->_graphics->waitForFade(); - - if (_vm->_graphics->getFadeStatus() == RDFADE_BLACK) - _vm->_graphics->fadeUp(); - - return IR_CONT; -} - -int32 Logic::fnSetPalette(int32 *params) { - // params: 0 resource number of palette file, or 0 if it's to be - // the palette from the current screen - - _vm->setFullPalette(params[0]); - return IR_CONT; -} - void Sword2Engine::setFullPalette(int32 palRes) { // fudge for hut interior // - unpausing should restore last palette as normal (could be screen @@ -763,23 +699,4 @@ void Sword2Engine::setFullPalette(int32 palRes) { _lastPaletteRes = palRes; } -int32 Logic::fnRestoreGame(int32 *params) { - // params: none - return IR_CONT; -} - -int32 Logic::fnChangeShadows(int32 *params) { - // params: none - - // if last screen was using a shading mask (see below) - if (_vm->_thisScreen.mask_flag) { - uint32 rv = _vm->_graphics->closeLightMask(); - if (rv) - error("Driver Error %.8x", rv); - _vm->_thisScreen.mask_flag = false; - } - - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/console.cpp b/sword2/console.cpp index 038f986bf0..4c0ab2f550 100644 --- a/sword2/console.cpp +++ b/sword2/console.cpp @@ -26,8 +26,8 @@ #include "sword2/maketext.h" #include "sword2/memory.h" #include "sword2/resman.h" +#include "sword2/sound.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" #include "common/debugger.cpp" @@ -206,7 +206,7 @@ bool Debugger::Cmd_ResList(int argc, const char **argv) { } bool Debugger::Cmd_Starts(int argc, const char **argv) { - _vm->_logic->conPrintStartMenu(); + _vm->conPrintStartMenu(); return true; } @@ -218,7 +218,7 @@ bool Debugger::Cmd_Start(int argc, const char **argv) { return true; } - _vm->_logic->conStart(atoi(argv[1])); + _vm->conStart(atoi(argv[1])); _vm->_graphics->setPalette(187, 1, pal, RDPAL_INSTANT); return true; } @@ -466,7 +466,7 @@ bool Debugger::Cmd_AnimTest(int argc, const char **argv) { } // Automatically do "s 32" to run the animation testing start script - _vm->_logic->conStart(32); + _vm->conStart(32); // Same as typing "VAR 912 <value>" at the console varSet(912, atoi(argv[1])); @@ -482,7 +482,7 @@ bool Debugger::Cmd_TextTest(int argc, const char **argv) { } // Automatically do "s 33" to run the text/speech testing start script - _vm->_logic->conStart(33); + _vm->conStart(33); // Same as typing "VAR 1230 <value>" at the console varSet(1230, atoi(argv[1])); @@ -501,7 +501,7 @@ bool Debugger::Cmd_LineTest(int argc, const char **argv) { } // Automatically do "s 33" to run the text/speech testing start script - _vm->_logic->conStart(33); + _vm->conStart(33); // Same as typing "VAR 1230 <value>" at the console varSet(1230, atoi(argv[1])); diff --git a/sword2/controls.cpp b/sword2/controls.cpp index 095d814ff6..5d2936b006 100644 --- a/sword2/controls.cpp +++ b/sword2/controls.cpp @@ -28,8 +28,8 @@ #include "sword2/logic.h" #include "sword2/resman.h" #include "sword2/router.h" +#include "sword2/sound.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" #define MAX_STRING_LEN 64 // 20 was too low; better to be safe ;) #define CHARACTER_OVERLAP 2 // overlap characters by 3 pixels @@ -1029,7 +1029,6 @@ public: _mixer->setVolumeForSoundType(SoundMixer::kMusicAudioDataType, _musicSlider->getValue()); _mixer->setVolumeForSoundType(SoundMixer::kSpeechAudioDataType, _speechSlider->getValue()); _mixer->setVolumeForSoundType(SoundMixer::kSFXAudioDataType, _fxSlider->getValue()); - _gui->_vm->_sound->buildPanTable(_gui->_stereoReversed); _gui->updateGraphicsLevel(_gfxSlider->getValue()); @@ -1515,7 +1514,6 @@ void Gui::readOptionSettings(void) { _vm->_sound->muteMusic(ConfMan.getBool("music_mute")); _vm->_sound->muteSpeech(ConfMan.getBool("speech_mute")); _vm->_sound->muteFx(ConfMan.getBool("sfx_mute")); - _vm->_sound->buildPanTable(_stereoReversed); } void Gui::writeOptionSettings(void) { @@ -1588,7 +1586,7 @@ void Gui::restartControl(void) { // Restart the game. To do this, we must... // Stop music instantly! - _vm->killMusic(); + _vm->_sound->stopMusic(); // In case we were dead - well we're not anymore! Logic::_scriptVars[DEAD] = 0; diff --git a/sword2/driver/animation.cpp b/sword2/driver/animation.cpp index 7777524dc1..ae1d6cc8ef 100644 --- a/sword2/driver/animation.cpp +++ b/sword2/driver/animation.cpp @@ -28,9 +28,10 @@ #include "sword2/sword2.h" #include "sword2/maketext.h" +#include "sword2/resman.h" +#include "sword2/sound.h" #include "sword2/driver/animation.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" #include "sword2/driver/menu.h" #include "sword2/driver/render.h" @@ -43,7 +44,6 @@ AnimationState::AnimationState(Sword2Engine *vm) AnimationState::~AnimationState() { } - #ifdef BACKEND_8BIT void AnimationState::setPalette(byte *pal) { @@ -168,12 +168,63 @@ void MoviePlayer::drawTextObject(AnimationState *anim, MovieTextObject *obj) { * @param musicOut lead-out music */ -int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *musicOut, uint32 musicOutLen) { +int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes) { + PlayingSoundHandle leadInHandle; + // This happens if the user quits during the "eye" smacker if (_vm->_quit) return RD_OK; + if (leadInRes) { + byte *leadIn = _vm->_resman->openResource(leadInRes); + uint32 leadInLen = _vm->_resman->fetchLen(leadInRes) - sizeof(StandardHeader); + StandardHeader *header = (StandardHeader *) leadIn; + + assert(header->fileType == WAV_FILE); + + leadIn += sizeof(StandardHeader); + + _vm->_sound->playFx(&leadInHandle, leadIn, leadInLen, SoundMixer::kMaxChannelVolume, 0, false, SoundMixer::kMusicAudioDataType); + } + + byte *leadOut = NULL; + uint32 leadOutLen = 0; + + if (leadOutRes) { + leadOut = _vm->_resman->openResource(leadOutRes); + leadOutLen = _vm->_resman->fetchLen(leadOutRes) - sizeof(StandardHeader); + StandardHeader *header = (StandardHeader *) leadOut; + + assert(header->fileType == WAV_FILE); + + leadOut += sizeof(StandardHeader); + } + #ifdef USE_MPEG2 + playMPEG(filename, text, leadOut, leadOutLen); +#else + // No MPEG2? Use the old 'Narration Only' hack + playDummy(filename, text, leadOut); +#endif + + _vm->_mixer->stopHandle(leadInHandle); + + // Wait for the lead-out to stop, if there is any. + while (_leadOutHandle.isActive()) { + _vm->_graphics->updateDisplay(); + _vm->_system->delayMillis(30); + } + + if (leadInRes) + _vm->_resman->closeResource(leadInRes); + + if (leadOutRes) + _vm->_resman->closeResource(leadOutRes); + + return RD_OK; +} + +void MoviePlayer::playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) { uint frameCounter = 0, textCounter = 0; PlayingSoundHandle handle; bool skipCutscene = false, textVisible = false; @@ -188,8 +239,8 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus if (!anim->init(filename)) { delete anim; // Missing Files? Use the old 'Narration Only' hack - playDummy(filename, text, musicOut, musicOutLen); - return RD_OK; + playDummy(filename, text, leadOut, leadOutLen); + return; } #ifndef BACKEND_8BIT @@ -249,11 +300,10 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus } anim->updateScreen(); - frameCounter++; - if (frameCounter == leadOutFrame && musicOut) - _vm->_sound->playFx(0, musicOutLen, musicOut, 0, 0, RDSE_FXLEADOUT); + if (frameCounter == leadOutFrame && leadOut) + _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, SoundMixer::kMaxChannelVolume, 0, false, SoundMixer::kMusicAudioDataType); OSystem::Event event; while (_sys->pollEvent(event)) { @@ -299,10 +349,9 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus anim->updateScreen(); - // Wait for the voice to stop playing. This is to make sure - // that we don't cut off the speech in mid-sentence, and - even - // more importantly - that we don't free the sound buffer while - // it's in use. + // Wait for the voice to stop playing. This is to make sure that we + // don't cut off the speech in mid-sentence, and - even more + // importantly - that we don't free the sound buffer while it's in use. if (skipCutscene) _snd->stopHandle(handle); @@ -313,30 +362,12 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus } // Clear the screen again - anim->clearScreen(); anim->updateScreen(); _vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); delete anim; - - // Wait for the lead-out to stop, if there is any. - _vm->_sound->waitForLeadOut(); - - // Lead-in and lead-out music are, as far as I can tell, only used for - // the animated cut-scenes, so this seems like a good place to close - // both of them. - - _vm->_sound->stopFx(-1); - _vm->_sound->stopFx(-2); - - return RD_OK; -#else - // No MPEG2? Use the old 'Narration Only' hack - playDummy(filename, text, musicOut, musicOutLen); - return RD_OK; -#endif } /** @@ -344,161 +375,152 @@ int32 MoviePlayer::play(const char *filename, MovieTextObject *text[], byte *mus * are missing. */ -int32 MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], byte *musicOut, uint32 musicOutLen) { +void MoviePlayer::playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen) { + if (!text) + return; + int frameCounter = 0, textCounter = 0; - if (text) { - byte oldPal[256 * 4]; - byte tmpPal[256 * 4]; - _vm->_graphics->clearScene(); + byte oldPal[256 * 4]; + byte tmpPal[256 * 4]; + + _vm->_graphics->clearScene(); - // HACK: Draw instructions - // - // I'm using the the menu area, because that's unlikely to be - // touched by anything else during the cutscene. + // HACK: Draw instructions + // + // I'm using the the menu area, because that's unlikely to be touched + // by anything else during the cutscene. - memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); - byte *data; + byte *data; - // Russian version substituted latin characters with cyrillic. That's why - // it renders completely unreadable with default message - if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) { - byte msg[] = "Po\344uk - to\344\345ko pev\345: hagmute k\344abuwy Ucke\343n, u\344u nocetute ca\343t npoekta u ckava\343te budeo po\344uku"; - data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); - } else { - // TODO: Translate message to other languages? + // Russian version substituted latin characters with cyrillic. That's + // why it renders completely unreadable with default message + if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) { + byte msg[] = "Po\344uk - to\344\345ko pev\345: hagmute k\344abuwy Ucke\343n, u\344u nocetute ca\343t npoekta u ckava\343te budeo po\344uku"; + data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); + } else { + // TODO: Translate message to other languages? #ifdef USE_MPEG2 - byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; + byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or visit www.scummvm.org to download cutscene videos"; #else - byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or recompile ScummVM with MPEG2 support"; + byte msg[] = "Cutscene - Narration Only: Press ESC to exit, or recompile ScummVM with MPEG2 support"; #endif - data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); - } - FrameHeader *frame = (FrameHeader *) data; - SpriteInfo msgSprite; - byte *msgSurface; + data = _vm->_fontRenderer->makeTextSprite(msg, RENDERWIDE, 255, _vm->_speechFontId); + } + + FrameHeader *frame = (FrameHeader *) data; + SpriteInfo msgSprite; + byte *msgSurface; - msgSprite.x = _vm->_graphics->_screenWide / 2 - frame->width / 2; - msgSprite.y = RDMENU_MENUDEEP / 2 - frame->height / 2; - msgSprite.w = frame->width; - msgSprite.h = frame->height; - msgSprite.type = RDSPR_NOCOMPRESSION; - msgSprite.data = data + sizeof(FrameHeader); + msgSprite.x = _vm->_graphics->_screenWide / 2 - frame->width / 2; + msgSprite.y = RDMENU_MENUDEEP / 2 - frame->height / 2; + msgSprite.w = frame->width; + msgSprite.h = frame->height; + msgSprite.type = RDSPR_NOCOMPRESSION; + msgSprite.data = data + sizeof(FrameHeader); - _vm->_graphics->createSurface(&msgSprite, &msgSurface); - _vm->_graphics->drawSurface(&msgSprite, msgSurface); - _vm->_graphics->deleteSurface(msgSurface); - free(data); + _vm->_graphics->createSurface(&msgSprite, &msgSurface); + _vm->_graphics->drawSurface(&msgSprite, msgSurface); + _vm->_graphics->deleteSurface(msgSurface); - // In case the cutscene has a long lead-in, start just before - // the first line of text. + free(data); - frameCounter = text[0]->startFrame - 12; + // In case the cutscene has a long lead-in, start just before the first + // line of text. - // Fake a palette that will hopefully make the text visible. - // In the opening cutscene it seems to use colours 1 (black?) - // and 255 (white?). + frameCounter = text[0]->startFrame - 12; - memcpy(oldPal, _vm->_graphics->_palette, sizeof(oldPal)); - memset(tmpPal, 0, sizeof(tmpPal)); - tmpPal[255 * 4 + 0] = 255; - tmpPal[255 * 4 + 1] = 255; - tmpPal[255 * 4 + 2] = 255; - _vm->_graphics->setPalette(0, 256, tmpPal, RDPAL_INSTANT); + // Fake a palette that will hopefully make the text visible. In the + // opening cutscene it seems to use colours 1 (black) and 255 (white). - PlayingSoundHandle handle; + memcpy(oldPal, _vm->_graphics->_palette, sizeof(oldPal)); + memset(tmpPal, 0, sizeof(tmpPal)); + tmpPal[255 * 4 + 0] = 255; + tmpPal[255 * 4 + 1] = 255; + tmpPal[255 * 4 + 2] = 255; + _vm->_graphics->setPalette(0, 256, tmpPal, RDPAL_INSTANT); - bool skipCutscene = false; + PlayingSoundHandle handle; - uint32 flags = SoundMixer::FLAG_16BITS; + bool skipCutscene = false; + + uint32 flags = SoundMixer::FLAG_16BITS; #ifndef SCUMM_BIG_ENDIAN - flags |= SoundMixer::FLAG_LITTLE_ENDIAN; + flags |= SoundMixer::FLAG_LITTLE_ENDIAN; #endif - while (1) { - if (!text[textCounter]) - break; + while (1) { + if (!text[textCounter]) + break; - if (frameCounter == text[textCounter]->startFrame) { - _vm->_graphics->clearScene(); - openTextObject(text[textCounter]); - drawTextObject(NULL, text[textCounter]); - if (text[textCounter]->speech) { - _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); - } - } - if (frameCounter == text[textCounter]->endFrame) { - closeTextObject(text[textCounter]); - _vm->_graphics->clearScene(); - _vm->_graphics->setNeedFullRedraw(); - textCounter++; + if (frameCounter == text[textCounter]->startFrame) { + _vm->_graphics->clearScene(); + openTextObject(text[textCounter]); + drawTextObject(NULL, text[textCounter]); + if (text[textCounter]->speech) { + _snd->playRaw(&handle, text[textCounter]->speech, text[textCounter]->speechBufferSize, 22050, flags); } + } - frameCounter++; - - _vm->_graphics->updateDisplay(); - - KeyboardEvent *ke = _vm->keyboardEvent(); + if (frameCounter == text[textCounter]->endFrame) { + closeTextObject(text[textCounter]); + _vm->_graphics->clearScene(); + _vm->_graphics->setNeedFullRedraw(); + textCounter++; + } - if ((ke && ke->keycode == 27) || _vm->_quit) { - _snd->stopHandle(handle); - skipCutscene = true; - break; - } + frameCounter++; + _vm->_graphics->updateDisplay(); - // Simulate ~12 frames per second. I don't know what - // frame rate the original movies had, or even if it - // was constant, but this seems to work reasonably. + KeyboardEvent *ke = _vm->keyboardEvent(); - _sys->delayMillis(90); + if ((ke && ke->keycode == 27) || _vm->_quit) { + _snd->stopHandle(handle); + skipCutscene = true; + break; } - // Wait for the voice to stop playing. This is to make sure - // that we don't cut off the speech in mid-sentence, and - even - // more importantly - that we don't free the sound buffer while - // it's in use. + // Simulate ~12 frames per second. I don't know what frame rate + // the original movies had, or even if it was constant, but + // this seems to work reasonably. - while (handle.isActive()) { - _vm->_graphics->updateDisplay(false); - _sys->delayMillis(100); - } - - closeTextObject(text[textCounter]); + _sys->delayMillis(90); + } - _vm->_graphics->clearScene(); - _vm->_graphics->setNeedFullRedraw(); + // Wait for the voice to stop playing. This is to make sure that we + // don't cut off the speech in mid-sentence, and - even more + // importantly - that we don't free the sound buffer while it's in use. - // HACK: Remove the instructions created above - Common::Rect r; + while (handle.isActive()) { + _vm->_graphics->updateDisplay(false); + _sys->delayMillis(100); + } - memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); - r.left = r.top = 0; - r.right = _vm->_graphics->_screenWide; - r.bottom = MENUDEEP; - _vm->_graphics->updateRect(&r); + closeTextObject(text[textCounter]); - // FIXME: For now, only play the lead-out music for cutscenes - // that have subtitles. + _vm->_graphics->clearScene(); + _vm->_graphics->setNeedFullRedraw(); - if (!skipCutscene && musicOut) { - _vm->_sound->playFx(0, musicOutLen, musicOut, 0, 0, RDSE_FXLEADOUT); - _vm->_sound->waitForLeadOut(); - } + // HACK: Remove the instructions created above + Common::Rect r; - _vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); - } + memset(_vm->_graphics->_buffer, 0, _vm->_graphics->_screenWide * MENUDEEP); + r.left = r.top = 0; + r.right = _vm->_graphics->_screenWide; + r.bottom = MENUDEEP; + _vm->_graphics->updateRect(&r); - // Lead-in and lead-out music are, as far as I can tell, only used for - // the animated cut-scenes, so this seems like a good place to close - // both of them. + // FIXME: For now, only play the lead-out music for cutscenes that have + // subtitles. - _vm->_sound->stopFx(-1); - _vm->_sound->stopFx(-2); + if (!skipCutscene && leadOut) + _vm->_sound->playFx(&_leadOutHandle, leadOut, leadOutLen, SoundMixer::kMaxChannelVolume, 0, false, SoundMixer::kMusicAudioDataType); - return RD_OK; + _vm->_graphics->setPalette(0, 256, oldPal, RDPAL_INSTANT); } } // End of namespace Sword2 diff --git a/sword2/driver/animation.h b/sword2/driver/animation.h index a49f785004..f34d8c8b06 100644 --- a/sword2/driver/animation.h +++ b/sword2/driver/animation.h @@ -65,17 +65,20 @@ private: byte *_textSurface; + PlayingSoundHandle _leadOutHandle; + static struct MovieInfo _movies[]; void openTextObject(MovieTextObject *obj); void closeTextObject(MovieTextObject *obj); void drawTextObject(AnimationState *anim, MovieTextObject *obj); - int32 playDummy(const char *filename, MovieTextObject *text[], byte *musicOut, uint32 musicOutLen); + void playMPEG(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen); + void playDummy(const char *filename, MovieTextObject *text[], byte *leadOut, uint32 leadOutLen); public: MoviePlayer(Sword2Engine *vm); - int32 play(const char *filename, MovieTextObject *text[], byte *musicOut, uint32 musicOutLen); + int32 play(const char *filename, MovieTextObject *text[], int32 leadInRes, int32 leadOutRes); }; } // End of namespace Sword2 diff --git a/sword2/driver/d_sound.cpp b/sword2/driver/d_sound.cpp index 4424842c4b..66baf797d5 100644 --- a/sword2/driver/d_sound.cpp +++ b/sword2/driver/d_sound.cpp @@ -34,8 +34,8 @@ #include "sound/wave.h" #include "sword2/sword2.h" #include "sword2/resman.h" +#include "sword2/sound.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" namespace Sword2 { @@ -138,8 +138,6 @@ static AudioStream *getAudioStream(File *fp, const char *base, int cd, uint32 id } } -#define BUFFER_SIZE 4096 - // ---------------------------------------------------------------------------- // Custom AudioStream class to handle Broken Sword II's audio compression. // ---------------------------------------------------------------------------- @@ -148,32 +146,6 @@ static AudioStream *getAudioStream(File *fp, const char *base, int cd, uint32 id #define GetCompressedSign(n) (((n) >> 3) & 1) #define GetCompressedAmplitude(n) ((n) & 7) -class CLUInputStream : public AudioStream { -private: - File *_file; - bool _firstTime; - uint32 _file_pos; - uint32 _end_pos; - int16 _outbuf[BUFFER_SIZE]; - byte _inbuf[BUFFER_SIZE]; - const int16 *_bufferEnd; - const int16 *_pos; - - uint16 _prev; - - void refill(); - inline bool eosIntern() const; -public: - CLUInputStream(File *file, int size); - ~CLUInputStream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool endOfData() const { return eosIntern(); } - bool isStereo() const { return false; } - int getRate() const { return 22050; } -}; - CLUInputStream::CLUInputStream(File *file, int size) : _file(file), _firstTime(true), _bufferEnd(_outbuf + BUFFER_SIZE) { @@ -191,10 +163,6 @@ CLUInputStream::~CLUInputStream() { _file->decRef(); } -inline bool CLUInputStream::eosIntern() const { - return _pos >= _bufferEnd; -} - int CLUInputStream::readBuffer(int16 *buffer, const int numSamples) { int samples = 0; while (samples < numSamples && !eosIntern()) { @@ -260,44 +228,6 @@ AudioStream *makeCLUStream(File *file, int size) { // The length of a fade-in/out, in milliseconds. #define FADE_LENGTH 3000 -class MusicInputStream : public AudioStream { -private: - int _cd; - uint32 _musicId; - AudioStream *_decoder; - int16 _buffer[BUFFER_SIZE]; - const int16 *_bufferEnd; - const int16 *_pos; - bool _remove; - uint32 _numSamples; - uint32 _samplesLeft; - bool _looping; - int32 _fading; - int32 _fadeSamples; - bool _paused; - - void refill(); - inline bool eosIntern() const; -public: - MusicInputStream(int cd, uint32 musicId, bool looping); - ~MusicInputStream(); - - int readBuffer(int16 *buffer, const int numSamples); - - bool endOfData() const { return eosIntern(); } - bool isStereo() const { return _decoder->isStereo(); } - int getRate() const { return _decoder->getRate(); } - - void fadeUp(); - void fadeDown(); - - bool isReady() { return _decoder != NULL; } - int32 isFading() { return _fading; } - - bool readyToRemove(); - int32 getTimeRemaining(); -}; - MusicInputStream::MusicInputStream(int cd, uint32 musicId, bool looping) : _cd(cd), _musicId(musicId), _bufferEnd(_buffer + BUFFER_SIZE), _remove(false), _looping(looping), _fading(0) { @@ -316,12 +246,6 @@ MusicInputStream::~MusicInputStream() { delete _decoder; } -inline bool MusicInputStream::eosIntern() const { - if (_looping) - return false; - return _remove || _pos >= _bufferEnd; -} - int MusicInputStream::readBuffer(int16 *buffer, const int numSamples) { if (!_decoder) return 0; @@ -462,58 +386,13 @@ int32 MusicInputStream::getTimeRemaining() { // Main sound class // ---------------------------------------------------------------------------- -Sound::Sound(Sword2Engine *vm) { - _vm = vm; - _mutex = _vm->_system->createMutex(); - - memset(_fx, 0, sizeof(_fx)); - - _soundOn = true; - - _speechPaused = false; - _speechMuted = false; - - _fxPaused = false; - _fxMuted = false; - - _musicPaused = false; - _musicMuted = false; - - _mixBuffer = NULL; - _mixBufferLen = 0; - - for (int i = 0; i < MAXMUS; i++) - _music[i] = NULL; - - _vm->_mixer->setupPremix(this, SoundMixer::kMusicAudioDataType); -} - -Sound::~Sound() { - int i; - - _vm->_mixer->setupPremix(0); - - for (i = 0; i < MAXMUS; i++) - delete _music[i]; - - free(_mixBuffer); - - for (i = 0; i < MAXFX; i++) - stopFxHandle(i); - - stopSpeech(); - - if (_mutex) - _vm->_system->deleteMutex(_mutex); -} - // AudioStream API int Sound::readBuffer(int16 *buffer, const int numSamples) { Common::StackLock lock(_mutex); int i; - if (!_soundOn || _musicPaused) + if (_musicPaused) return 0; for (i = 0; i < MAXMUS; i++) { @@ -560,60 +439,18 @@ bool Sound::isStereo() const { return false; } bool Sound::endOfData() const { return !fpMus.isOpen(); } int Sound::getRate() const { return 22050; } - -/** - * This function creates the pan table. - */ - -void Sound::buildPanTable(bool reverse) { - int i; - - for (i = 0; i < 16; i++) { - _panTable[i] = -127 + i * 8; - _panTable[i + 17] = (i + 1) * 8 - 1; - } - - _panTable[16] = 0; - - if (reverse) { - for (i = 0; i < 33; i++) - _panTable[i] = -_panTable[i]; - } -} - // ---------------------------------------------------------------------------- // MUSIC // ---------------------------------------------------------------------------- /** - * Mutes/Unmutes the music. - * @param mute If mute is false, restore the volume to the last set master - * level. Otherwise the music is muted (volume 0). - */ - -void Sound::muteMusic(bool mute) { - _musicMuted = mute; -} - -/** - * @return the music's mute state, true if mute, false if not mute - */ - -bool Sound::isMusicMute(void) { - return _musicMuted; -} - -/** * Stops the music dead in its tracks. Any music that is currently being * streamed is paused. */ -void Sound::pauseMusic(void) { +void Sound::pauseMusic() { Common::StackLock lock(_mutex); - if (!_soundOn) - return; - _musicPaused = true; } @@ -621,12 +458,9 @@ void Sound::pauseMusic(void) { * Restarts the music from where it was stopped. */ -void Sound::unpauseMusic(void) { +void Sound::unpauseMusic() { Common::StackLock lock(_mutex); - if (!_soundOn) - return; - _musicPaused = false; } @@ -634,35 +468,30 @@ void Sound::unpauseMusic(void) { * Fades out and stops the music. */ -void Sound::stopMusic(void) { +void Sound::stopMusic() { Common::StackLock lock(_mutex); + _loopingMusicId = 0; + for (int i = 0; i < MAXMUS; i++) if (_music[i]) _music[i]->fadeDown(); } -void Sound::waitForLeadOut(void) { - int i = getFxIndex(-1); - - if (i == MAXFX) - return; - - while (_fx[i]._handle.isActive()) { - _vm->_graphics->updateDisplay(); - _vm->_system->delayMillis(30); - } -} - /** * Streams music from a cluster file. * @param musicId the id of the music to stream * @param looping true if the music is to loop back to the start * @return RD_OK or an error code */ -int32 Sound::streamCompMusic(uint32 musicId, bool looping) { +int32 Sound::streamCompMusic(uint32 musicId, bool loop) { Common::StackLock lock(_mutex); + if (loop) + _loopingMusicId = musicId; + else + _loopingMusicId = 0; + int primary = -1; int secondary = -1; @@ -716,7 +545,7 @@ int32 Sound::streamCompMusic(uint32 musicId, bool looping) { if (secondary != -1) _music[secondary]->fadeDown(); - _music[primary] = new MusicInputStream(_vm->_resman->whichCd(), musicId, looping); + _music[primary] = new MusicInputStream(_vm->_resman->whichCd(), musicId, loop); if (!_music[primary]->isReady()) { delete _music[primary]; @@ -763,14 +592,6 @@ void Sound::muteSpeech(bool mute) { } /** - * @return the speech's mute state, true if mute, false if not mute - */ - -bool Sound::isSpeechMute(void) { - return _speechMuted; -} - -/** * Stops the speech dead in its tracks. */ @@ -792,14 +613,12 @@ void Sound::unpauseSpeech(void) { * Stops the speech from playing. */ -int32 Sound::stopSpeech(void) { - if (!_soundOn) - return RD_OK; - +int32 Sound::stopSpeech() { if (_soundHandleSpeech.isActive()) { _vm->_mixer->stopHandle(_soundHandleSpeech); return RD_OK; } + return RDERR_SPEECHNOTPLAYING; } @@ -807,7 +626,7 @@ int32 Sound::stopSpeech(void) { * @return Either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED */ -int32 Sound::getSpeechStatus(void) { +int32 Sound::getSpeechStatus() { return _soundHandleSpeech.isActive() ? RDSE_SAMPLEPLAYING : RDSE_SAMPLEFINISHED; } @@ -815,7 +634,7 @@ int32 Sound::getSpeechStatus(void) { * Returns either RDSE_QUIET or RDSE_SPEAKING */ -int32 Sound::amISpeaking(void) { +int32 Sound::amISpeaking() { if (!_speechMuted && !_speechPaused && _soundHandleSpeech.isActive()) return RDSE_SPEAKING; @@ -826,15 +645,15 @@ int32 Sound::amISpeaking(void) { * This function loads and decompresses a list of speech from a cluster, but * does not play it. This is used for cutscene voice-overs, presumably to * avoid having to read from more than one file on the CD during playback. - * @param speechid the text line id used to reference the speech + * @param speechId the text line id used to reference the speech * @param buf a pointer to the buffer that will be allocated for the sound */ -uint32 Sound::preFetchCompSpeech(uint32 speechid, uint16 **buf) { +uint32 Sound::preFetchCompSpeech(uint32 speechId, uint16 **buf) { File fp; uint32 numSamples; - AudioStream *input = getAudioStream(&fp, "speech", _vm->_resman->whichCd(), speechid, &numSamples); + AudioStream *input = getAudioStream(&fp, "speech", _vm->_resman->whichCd(), speechId, &numSamples); *buf = NULL; @@ -860,12 +679,12 @@ uint32 Sound::preFetchCompSpeech(uint32 speechid, uint16 **buf) { /** * This function loads, decompresses and plays a line of speech. An error * occurs if speech is already playing. - * @param speechid the text line id used to reference the speech + * @param speechId the text line id used to reference the speech * @param vol volume, 0 (minimum) to 16 (maximum) * @param pan panning, -16 (full left) to 16 (full right) */ -int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) { +int32 Sound::playCompSpeech(uint32 speechId, uint8 vol, int8 pan) { if (_speechMuted) return RD_OK; @@ -873,7 +692,7 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) { return RDERR_SPEECHPLAYING; File *fp = new File; - AudioStream *input = getAudioStream(fp, "speech", _vm->_resman->whichCd(), speechid, NULL); + AudioStream *input = getAudioStream(fp, "speech", _vm->_resman->whichCd(), speechId, NULL); // Make the AudioStream object the sole owner of the file so that it // will die along with the AudioStream when the speech has finished. @@ -885,7 +704,10 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) { // Modify the volume according to the master volume byte volume = _speechMuted ? 0 : vol * SoundMixer::kMaxChannelVolume / 16; - int8 p = _panTable[pan + 16]; + int8 p = (pan * 127) / 16; + + if (isReverseStereo()) + p = -p; // Start the speech playing _vm->_mixer->playInputStream(SoundMixer::kSpeechAudioDataType, &_soundHandleSpeech, input, -1, volume, p); @@ -897,19 +719,6 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) { // ---------------------------------------------------------------------------- /** - * @return the index of the sound effect with the ID passed in. - */ - -int32 Sound::getFxIndex(int32 id) { - for (int i = 0; i < MAXFX; i++) { - if (_fx[i]._id == id) - return i; - } - - return MAXFX; -} - -/** * Mutes/Unmutes the sound effects. * @param mute If mute is false, restore the volume to the last set master * level. Otherwise the sound effects are muted (volume 0). @@ -919,241 +728,62 @@ void Sound::muteFx(bool mute) { _fxMuted = mute; // Now update the volume of any fxs playing - for (int i = 0; i < MAXFX; i++) { - if (_fx[i]._id) { - byte volume = mute ? 0 : _fx[i]._volume * SoundMixer::kMaxChannelVolume / 16; - - _vm->_mixer->setChannelVolume(_fx[i]._handle, volume); + for (int i = 0; i < FXQ_LENGTH; i++) { + if (_fxQueue[i].resource) { + _vm->_mixer->setChannelVolume(_fxQueue[i].handle, mute ? 0 : _fxQueue[i].volume); } } } /** - * @return the sound effects's mute state, true if mute, false if not mute - */ - -bool Sound::isFxMute(void) { - return _fxMuted; -} - -/** * Sets the volume and pan of the sample which is currently playing * @param id the id of the sample * @param vol volume * @param pan panning */ -int32 Sound::setFxIdVolumePan(int32 id, uint8 vol, int8 pan) { - int32 i = getFxIndex(id); - - if (i == MAXFX) - return RDERR_FXNOTOPEN; - - if (vol > 14) - vol = 14; - - _fx[i]._volume = vol; - - if (!_fxMuted) { - _vm->_mixer->setChannelVolume(_fx[i]._handle, _fx[i]._volume * SoundMixer::kMaxChannelVolume / 16); - _vm->_mixer->setChannelBalance(_fx[i]._handle, _panTable[pan + 16]); - } - - return RD_OK; -} - -int32 Sound::setFxIdVolume(int32 id, uint8 vol) { - int32 i = getFxIndex(id); - - if (i == MAXFX) +int32 Sound::setFxIdVolumePan(int32 i, int vol, int pan) { + if (!_fxQueue[i].resource) return RDERR_FXNOTOPEN; - _fx[i]._volume = vol; + if (vol > 16) + vol = 16; - if (!_fxMuted) - _vm->_mixer->setChannelVolume(_fx[i]._handle, vol * SoundMixer::kMaxChannelVolume / 16); + _fxQueue[i].volume = (vol * SoundMixer::kMaxChannelVolume) / 16; - return RD_OK; -} + if (pan != -1) + _fxQueue[i].pan = (pan * 127) / 16; - -void Sound::pauseFx(void) { - if (_fxPaused) - return; - - for (int i = 0; i < MAXFX; i++) { - if (_fx[i]._id) { - _vm->_mixer->pauseHandle(_fx[i]._handle, true); - _fx[i]._paused = true; - } else - _fx[i]._paused = false; + if (!_fxMuted && _fxQueue[i].handle.isActive()) { + _vm->_mixer->setChannelVolume(_fxQueue[i].handle, _fxQueue[i].volume); + if (pan != -1) + _vm->_mixer->setChannelBalance(_fxQueue[i].handle, _fxQueue[i].pan); } - _fxPaused = true; + return RD_OK; } -void Sound::pauseFxForSequence(void) { +void Sound::pauseFx() { if (_fxPaused) return; - for (int i = 0; i < MAXFX; i++) { - if (_fx[i]._id && _fx[i]._id != -2) { - _vm->_mixer->pauseHandle(_fx[i]._handle, true); - _fx[i]._paused = true; - } else - _fx[i]._paused = false; + for (int i = 0; i < FXQ_LENGTH; i++) { + if (_fxQueue[i].resource) + _vm->_mixer->pauseHandle(_fxQueue[i].handle, true); } _fxPaused = true; } -void Sound::unpauseFx(void) { +void Sound::unpauseFx() { if (!_fxPaused) return; - for (int i = 0; i < MAXFX; i++) - if (_fx[i]._paused && _fx[i]._id) - _vm->_mixer->pauseHandle(_fx[i]._handle, false); + for (int i = 0; i < FXQ_LENGTH; i++) + if (_fxQueue[i].resource) + _vm->_mixer->pauseHandle(_fxQueue[i].handle, false); _fxPaused = false; } -bool Sound::isFxPlaying(int32 id) { - int i; - - i = getFxIndex(id); - if (i == MAXFX) - return false; - - return _fx[i]._handle.isActive(); -} - -/** - * This function closes a sound effect which has been previously opened for - * playing. Sound effects must be closed when they are finished with, otherwise - * you will run out of sound effect buffers. - * @param id the id of the sound to close - */ - -int32 Sound::stopFx(int32 id) { - int i; - - if (!_soundOn) - return RD_OK; - - i = getFxIndex(id); - - if (i == MAXFX) - return RDERR_FXNOTOPEN; - - stopFxHandle(i); - return RD_OK; -} - -/** - * This function plays a sound effect. If the effect has already been opened - * then 'data' should be NULL, and the sound effect will simply be obtained - * from the id passed in. If the effect has not been opened, then the WAV data - * should be passed in 'data'. The sound effect will be closed when it has - * finished playing. - * @param id the sound id - * @param data either NULL or the WAV data - * @param vol volume, 0 (minimum) to 16 (maximum) - * @param pan panning, -16 (full left) to 16 (full right) - * @param type either RDSE_FXSPOT or RDSE_FXLOOP - * @warning Zero is not a valid id - */ - -int32 Sound::playFx(int32 id, uint32 len, byte *data, uint8 vol, int8 pan, uint8 type) { - if (!_soundOn) - return RD_OK; - - byte volume = _fxMuted ? 0 : vol * SoundMixer::kMaxChannelVolume / 16; - SoundMixer::SoundType soundType = SoundMixer::kSFXAudioDataType; - - // All lead-ins and lead-outs I've heard are music, so we use - // the music volume setting for them. - - if (type == RDSE_FXLEADIN || type == RDSE_FXLEADOUT) { - id = (type == RDSE_FXLEADIN) ? -2 : -1; - volume = _musicMuted ? 0 : SoundMixer::kMaxChannelVolume; - soundType = SoundMixer::kMusicAudioDataType; - } - - Common::MemoryReadStream stream(data, len); - int rate, size; - byte flags; - - if (!loadWAVFromStream(stream, size, rate, flags)) { - warning("playFx: Not a valid WAV file"); - return RDERR_INVALIDWAV; - } - - int32 fxi = getFxIndex(id); - - if (fxi == MAXFX) { - // Find a free slot - fxi = getFxIndex(0); - - if (fxi == MAXFX) { - warning("openFx: Running out of sound slots"); - - // There aren't any free sound handles available. This - // usually shouldn't happen, but if it does we expire - // the first sound effect that isn't currently playing. - - for (fxi = 0; fxi < MAXFX; fxi++) - if (!_fx[fxi]._handle.isActive()) - break; - - // Still no dice? I give up! - - if (fxi == MAXFX) { - warning("openFx: No free sound slots"); - return RDERR_NOFREEBUFFERS; - } - } - - _fx[fxi]._id = id; - } - - if (_fx[fxi]._handle.isActive()) - return RDERR_FXALREADYOPEN; - - if (type == RDSE_FXLOOP) - flags |= SoundMixer::FLAG_LOOP; - else - flags &= ~SoundMixer::FLAG_LOOP; - - _fx[fxi]._volume = vol; - - int8 p = _panTable[pan + 16]; - - _vm->_mixer->playRaw(&_fx[fxi]._handle, data + stream.pos(), size, rate, flags, -1, volume, p, 0, 0, soundType); - - return RD_OK; -} - -void Sound::stopFxHandle(int i) { - if (_fx[i]._id) { - _vm->_mixer->stopHandle(_fx[i]._handle); - _fx[i]._id = 0; - _fx[i]._paused = false; - } -} - -/** - * This function clears all of the sound effects which are currently open or - * playing, irrespective of type. - */ - -void Sound::clearAllFx(void) { - if (!_soundOn) - return; - - for (int i = 0; i < MAXFX; i++) - if (_fx[i]._id && _fx[i]._id != -1 && _fx[i]._id != -2) - stopFxHandle(i); -} - } // End of namespace Sword2 diff --git a/sword2/driver/d_sound.h b/sword2/driver/d_sound.h deleted file mode 100644 index 33517fae61..0000000000 --- a/sword2/driver/d_sound.h +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright (C) 1994-1998 Revolution Software Ltd. - * Copyright (C) 2003-2005 The ScummVM project - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * $Header$ - */ - -#ifndef D_SOUND_H -#define D_SOUND_H - -#include "common/file.h" -#include "sound/audiostream.h" -#include "sound/mixer.h" - -namespace Sword2 { - -class MusicInputStream; - -// Max number of sound fx -#define MAXFX 16 -#define MAXMUS 2 - -enum { - kCLUMode = 1, - kMP3Mode, - kVorbisMode, - kFlacMode -}; - -extern void sword2_sound_handler(void *refCon); - -struct FxHandle { - int32 _id; - bool _paused; - int8 _volume; - PlayingSoundHandle _handle; -}; - -class Sound : public AudioStream { -private: - Sword2Engine *_vm; - - Common::MutexRef _mutex; - - int32 _panTable[33]; - bool _soundOn; - - MusicInputStream *_music[MAXMUS]; - int16 *_mixBuffer; - int _mixBufferLen; - - bool _musicPaused; - bool _musicMuted; - - PlayingSoundHandle _soundHandleSpeech; - bool _speechPaused; - bool _speechMuted; - - FxHandle _fx[MAXFX]; - bool _fxPaused; - bool _fxMuted; - - int32 getFxIndex(int32 id); - void stopFxHandle(int i); - -public: - Sound(Sword2Engine *vm); - ~Sound(); - - // AudioStream API - - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const; - bool endOfData() const; - int getRate() const; - - // End of AudioStream API - - void buildPanTable(bool reverse); - - void muteMusic(bool mute); - bool isMusicMute(void); - void pauseMusic(void); - void unpauseMusic(void); - void stopMusic(void); - void waitForLeadOut(void); - int32 streamCompMusic(uint32 musicId, bool looping); - int32 musicTimeRemaining(void); - - void muteSpeech(bool mute); - bool isSpeechMute(void); - void pauseSpeech(void); - void unpauseSpeech(void); - int32 stopSpeech(void); - int32 getSpeechStatus(void); - int32 amISpeaking(void); - uint32 preFetchCompSpeech(uint32 speechid, uint16 **buf); - int32 playCompSpeech(uint32 speechid, uint8 vol, int8 pan); - - void muteFx(bool mute); - bool isFxMute(void); - int32 setFxIdVolumePan(int32 id, uint8 vol, int8 pan); - int32 setFxIdVolume(int32 id, uint8 vol); - void pauseFx(void); - void pauseFxForSequence(void); - void unpauseFx(void); - bool isFxPlaying(int32 id); - int32 playFx(int32 id, uint32 len, uint8 *data, uint8 vol, int8 pan, uint8 type); - int32 stopFx(int32 id); - void clearAllFx(void); -}; - -} // End of namespace Sword2 - -#endif diff --git a/sword2/events.cpp b/sword2/events.cpp index ec84350bda..b56f999f20 100644 --- a/sword2/events.cpp +++ b/sword2/events.cpp @@ -88,96 +88,6 @@ void Logic::killAllIdsEvents(uint32 id) { } } -int32 Logic::fnRequestSpeech(int32 *params) { - // change current script - must be followed by a TERMINATE script - // directive - - // params: 0 id of target to catch the event and startup speech - // servicing - - // Full script id to interact with - megas run their own 7th script - sendEvent(params[0], (params[0] << 16) | 6); - return IR_CONT; -} - -int32 Logic::fnSetPlayerActionEvent(int32 *params) { - // we want to intercept the player character and have him interact - // with an object - from script this code is the same as the mouse - // engine calls when you click on an object - here, a third party - // does the clicking IYSWIM - - // note - this routine used CUR_PLAYER_ID as the target - - // params: 0 id to interact with - - setPlayerActionEvent(CUR_PLAYER_ID, params[0]); - return IR_CONT; -} - -int32 Logic::fnSendEvent(int32 *params) { - // we want to intercept the player character and have him interact - // with an object - from script - - // params: 0 id to receive event - // 1 script to run - - sendEvent(params[0], params[1]); - return IR_CONT; -} - -int32 Logic::fnCheckEventWaiting(int32 *params) { - // params: none - - _scriptVars[RESULT] = checkEventWaiting(); - return IR_CONT; -} - -// like fnCheckEventWaiting, but starts the event rather than setting RESULT -// to 1 - -int32 Logic::fnCheckForEvent(int32 *params) { - // params: none - - if (checkEventWaiting()) { - startEvent(); - return IR_TERMINATE; - } - - return IR_CONT; -} - -// combination of fnPause and fnCheckForEvent -// - ie. does a pause, but also checks for event each cycle - -int32 Logic::fnPauseForEvent(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 number of game-cycles to pause - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - if (checkEventWaiting()) { - ob_logic->looping = 0; - startEvent(); - return IR_TERMINATE; - } - - return fnPause(params); -} - -int32 Logic::fnClearEvent(int32 *params) { - // params: none - - clearEvent(_scriptVars[ID]); - return IR_CONT; -} - -int32 Logic::fnStartEvent(int32 *params) { - // params: none - - startEvent(); - return IR_TERMINATE; -} - // For the debugger uint32 Logic::countEvents(void) { diff --git a/sword2/function.cpp b/sword2/function.cpp index d7a2b1663f..9aa800bb8b 100644 --- a/sword2/function.cpp +++ b/sword2/function.cpp @@ -23,14 +23,17 @@ #include "common/system.h" #include "sword2/sword2.h" #include "sword2/defs.h" +#include "sword2/console.h" +#include "sword2/controls.h" #include "sword2/interpreter.h" #include "sword2/logic.h" #include "sword2/maketext.h" #include "sword2/memory.h" #include "sword2/resman.h" +#include "sword2/router.h" #include "sword2/sound.h" +#include "sword2/driver/animation.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" #include "sword2/driver/render.h" namespace Sword2 { @@ -45,39 +48,83 @@ int32 Logic::fnTestFlags(int32 *params) { return IR_CONT; } -int32 Logic::fnGosub(int32 *params) { - // params: 0 id of script +int32 Logic::fnRegisterStartPoint(int32 *params) { + // params: 0 id of startup script to call - key + // 1 pointer to ascii message - // Hurray, script subroutines. Logic goes up - pc is saved for current - // level. - logicUp(params[0]); - return IR_GOSUB; + int32 key = params[0]; + char *name = (char *) _vm->_memory->decodePtr(params[1]); + + _vm->registerStartPoint(key, name); + return IR_CONT; } -int32 Logic::fnNewScript(int32 *params) { - // change current script - must be followed by a TERMINATE script - // directive +int32 Logic::fnInitBackground(int32 *params) { + // this screen defines the size of the back buffer - // params: 0 id of script + // params: 0 res id of normal background layer - cannot be 0 + // 1 1 yes 0 no for a new palette - _scriptVars[PLAYER_ACTION] = 0; // must clear this - logicReplace(params[0]); - return IR_TERMINATE; + return _vm->initBackground(params[0], params[1]); } -int32 Logic::fnInteract(int32 *params) { - // Run targets action on a subroutine. Called by player on his base - // level 0 idle, for example. +/** + * This function is used by start scripts. + */ - // params: 0 id of target from which we derive action script - // reference +int32 Logic::fnSetSession(int32 *params) { + // params: 0 id of new run list - _scriptVars[PLAYER_ACTION] = 0; // must clear this - logicUp((params[0] << 16) | 2); // 3rd script of clicked on id + expressChangeSession(params[0]); + return IR_CONT; +} - // Out, up and around again - pc is saved for current level to be - // returned to. - return IR_GOSUB; +int32 Logic::fnBackSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], BACK_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSortSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], SORT_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForeSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], FORE_SPRITE); + return IR_CONT; +} + +int32 Logic::fnRegisterMouse(int32 *params) { + // this call would be made from an objects service script 0 + // the object would be one with no graphic but with a mouse - i.e. a + // floor or one whose mouse area is manually defined rather than + // intended to fit sprite shape + + // params: 0 pointer to ObjectMouse or 0 for no write to mouse + // list + + _vm->registerMouse((ObjectMouse *) _vm->_memory->decodePtr(params[0])); + return IR_CONT; +} + +int32 Logic::fnAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 resource id of animation file + + // 0 means normal forward anim + return animate(params, false); +} + +int32 Logic::fnRandom(int32 *params) { + // params: 0 min + // 1 max + + _scriptVars[RESULT] = _vm->_rnd.getRandomNumberRng(params[0], params[1]); + return IR_CONT; } int32 Logic::fnPreLoad(int32 *params) { @@ -93,42 +140,518 @@ int32 Logic::fnPreLoad(int32 *params) { return IR_CONT; } -int32 Logic::fnPreFetch(int32 *params) { - // Go fetch resource in the background. +int32 Logic::fnAddSubject(int32 *params) { + // params: 0 id + // 1 daves reference number - // params: 0 resource to fetch [guess] + if (_scriptVars[IN_SUBJECT] == 0) { + // This is the start of the new subject list. Set the default + // repsonse id to zero in case we're never passed one. + _defaultResponseId = 0; + } + + if (params[0] == -1) { + // Id -1 is used for setting the default response, i.e. the + // response when someone uses an object on a person and he + // doesn't know anything about it. See fnChoose() below. + + _defaultResponseId = params[1]; + } else { + debug(5, "fnAddSubject res %d, uid %d", params[0], params[1]); + _subjectList[_scriptVars[IN_SUBJECT]].res = params[0]; + _subjectList[_scriptVars[IN_SUBJECT]].ref = params[1]; + _scriptVars[IN_SUBJECT]++; + } return IR_CONT; } -int32 Logic::fnFetchWait(int32 *params) { - // Fetches a resource in the background but prevents the script from - // continuing until the resource is in memory. +int32 Logic::fnInteract(int32 *params) { + // Run targets action on a subroutine. Called by player on his base + // level 0 idle, for example. - // params: 0 resource to fetch [guess] + // params: 0 id of target from which we derive action script + // reference - return IR_CONT; + _scriptVars[PLAYER_ACTION] = 0; // must clear this + logicUp((params[0] << 16) | 2); // 3rd script of clicked on id + + // Out, up and around again - pc is saved for current level to be + // returned to. + return IR_GOSUB; } -int32 Logic::fnRelease(int32 *params) { - // Releases a resource from memory. Used for freeing memory for - // sprites that have just been used and will not be used again. - // Sometimes it is better to kick out a sprite straight away so that - // the memory can be used for more frequent animations. +int32 Logic::fnChoose(int32 *params) { + // params: none - // params: 0 resource to release [guess] + // This opcode is used to open the conversation menu. The human is + // switched off so there will be no normal mouse engine. - return IR_CONT; + // The player's choice is piggy-backed on the standard opcode return + // values, to be used with the CP_JUMP_ON_RETURNED opcode. As far as I + // can tell, this is the only function that uses that feature. + + uint i; + + _scriptVars[AUTO_SELECTED] = 0; + + if (_scriptVars[OBJECT_HELD]) { + // The player used an object on a person. In this case it + // triggered a conversation menu. Act as if the user tried to + // talk to the person about that object. If the person doesn't + // know anything about it, use the default response. + + uint32 response = _defaultResponseId; + + for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { + if (_subjectList[i].res == _scriptVars[OBJECT_HELD]) { + response = _subjectList[i].ref; + break; + } + } + + // The user won't be holding the object any more, and the + // conversation menu will be closed. + + _scriptVars[OBJECT_HELD] = 0; + _scriptVars[IN_SUBJECT] = 0; + return IR_CONT | (response << 3); + } + + if (_scriptVars[CHOOSER_COUNT_FLAG] == 0 && _scriptVars[IN_SUBJECT] == 1 && _subjectList[0].res == EXIT_ICON) { + // This is the first time the chooser is coming up in this + // conversation, there is only one subject and that's the + // EXIT icon. + // + // In other words, the player doesn't have anything to talk + // about. Skip it. + + // The conversation menu will be closed. We set AUTO_SELECTED + // because the speech script depends on it. + + _scriptVars[AUTO_SELECTED] = 1; + _scriptVars[IN_SUBJECT] = 0; + return IR_CONT | (_subjectList[0].ref << 3); + } + + byte *icon; + + if (!_choosing) { + // This is a new conversation menu. + + if (!_scriptVars[IN_SUBJECT]) + error("fnChoose with no subjects"); + + for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { + icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader) + RDMENU_ICONWIDE * RDMENU_ICONDEEP; + _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); + _vm->_resman->closeResource(_subjectList[i].res); + } + + for (; i < 15; i++) + _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL); + + _vm->_graphics->showMenu(RDMENU_BOTTOM); + _vm->setMouse(NORMAL_MOUSE_ID); + _choosing = true; + return IR_REPEAT; + } + + // The menu is there - we're just waiting for a click. We only care + // about left clicks. + + MouseEvent *me = _vm->mouseEvent(); + + if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || _vm->_mouseY < 400) + return IR_REPEAT; + + // Check for click on a menu. + + int hit = _vm->menuClick(_scriptVars[IN_SUBJECT]); + if (hit < 0) + return IR_REPEAT; + + // Hilight the clicked icon by greying the others. + + for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { + if ((int) i != hit) { + icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader); + _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); + _vm->_resman->closeResource(_subjectList[i].res); + } + } + + // For non-speech scripts that manually call the chooser + _scriptVars[RESULT] = _subjectList[hit].res; + + // The conversation menu will be closed + + _choosing = false; + _scriptVars[IN_SUBJECT] = 0; + _vm->setMouse(0); + + return IR_CONT | (_subjectList[hit].ref << 3); } -int32 Logic::fnRandom(int32 *params) { - // params: 0 min - // 1 max +/** + * Walk mega to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set + * RESULT to 1. + */ + +int32 Logic::fnWalk(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target x-coord + // 5 target y-coord + // 6 target direction (8 means end walk on ANY direction) + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[1]); + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); + + int16 target_x = (int16) params[4]; + int16 target_y = (int16) params[5]; + uint8 target_dir = (uint8) params[6]; + + ObjectWalkdata *ob_walkdata; + + // If this is the start of the walk, calculate the route. + + if (!ob_logic->looping) { + // If we're already there, don't even bother allocating + // memory and calling the router, just quit back & continue + // the script! This avoids an embarassing mega stand frame + // appearing for one cycle when we're already in position for + // an anim eg. repeatedly clicking on same object to repeat + // an anim - no mega frame will appear in between runs of the + // anim. + + if (ob_mega->feet_x == target_x && ob_mega->feet_y == target_y && ob_mega->current_dir == target_dir) { + _scriptVars[RESULT] = 0; + return IR_CONT; + } + + assert(params[6] >= 0 && params[6] <= 8); + + ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); + + ob_mega->walk_pc = 0; + + // Set up mem for _walkData in route_slots[] & set mega's + // 'route_slot_id' accordingly + + _router->allocateRouteMem(); + + int32 route = _router->routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir); + + // 0 = can't make route to target + // 1 = created route + // 2 = zero route but may need to turn + + if (route == 1 || route == 2) { + // so script fnWalk loop continues until end of + // walk-anim + + ob_logic->looping = 1; + + // need to animate the route now, so don't set result + // or return yet! + + // started walk + ob_mega->currently_walking = 1; + + // (see fnGetPlayerSaveData() in save_rest.cpp + } else { + _router->freeRouteMem(); + _scriptVars[RESULT] = 1; + return IR_CONT; + } + + // Walk is about to start, so set the mega's graphic resource + ob_graph->anim_resource = ob_mega->megaset_res; + } else if (_scriptVars[EXIT_FADING] && _vm->_graphics->getFadeStatus() == RDFADE_BLACK) { + // Double clicked an exit so quit the walk when screen is black + // ok, thats it - back to script and change screen + + ob_logic->looping = 0; + _router->freeRouteMem(); + + // Must clear in-case on the new screen there's a walk + // instruction (which would get cut short) + _scriptVars[EXIT_CLICK_ID] = 0; + + // finished walk + ob_mega->currently_walking = 0; + + // see fnGetPlayerSaveData() in save_rest.cpp + + _scriptVars[RESULT] = 0; + + // continue the script so that RESULT can be checked! + return IR_CONT; + } + + // get pointer to walkanim & current frame position + + WalkData *walkAnim = _router->getRouteMem(); + int32 walk_pc = ob_mega->walk_pc; + + // If stopping the walk early, overwrite the next step with a + // slow-out, then finish + + if (checkEventWaiting()) { + if (walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) { + // At the beginning of a step + ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); + _router->earlySlowOut(ob_mega, ob_walkdata); + } + } + + // Get new frame of walk + + ob_graph->anim_pc = walkAnim[walk_pc].frame; + ob_mega->current_dir = walkAnim[walk_pc].dir; + ob_mega->feet_x = walkAnim[walk_pc].x; + ob_mega->feet_y = walkAnim[walk_pc].y; + + // Check if NEXT frame is in fact the end-marker of the walk sequence + // so we can return to script just as the final (stand) frame of the + // walk is set - so that if followed by an anim, the anim's first + // frame replaces the final stand-frame of the walk (see below) + + // '512' is end-marker + if (walkAnim[walk_pc + 1].frame == 512) { + ob_logic->looping = 0; + _router->freeRouteMem(); + + // finished walk + ob_mega->currently_walking = 0; + + // (see fnGetPlayerSaveData() in save_rest.cpp + + // if George's walk has been interrupted to run a new action + // script for instance or Nico's walk has been interrupted by + // player clicking on her to talk + + // There used to be code here for checking if two megas were + // colliding, but that code had been commented out, and it + // was only run if a function that always returned zero + // returned non-zero. + + if (checkEventWaiting()) { + startEvent(); + _scriptVars[RESULT] = 1; + return IR_TERMINATE; + } else { + _scriptVars[RESULT] = 0; + + // CONTINUE the script so that RESULT can be checked! + // Also, if an anim command follows the fnWalk command, + // the 1st frame of the anim (which is always a stand + // frame itself) can replace the final stand frame of + // the walk, to hide the slight difference between the + // shrinking on the mega frames and the pre-shrunk anim + // start-frame. + + return IR_CONT; + } + } + + // Increment the walkanim frame number and come back next cycle + + ob_mega->walk_pc++; + return IR_REPEAT; +} + +/** + * Walk mega to start position of anim + */ + +int32 Logic::fnWalkToAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 anim resource id + + int32 pars[7]; + + // Walkdata is needed for earlySlowOut if player clicks elsewhere + // during the walk. + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = params[2]; + pars[3] = params[3]; + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + // If this is the start of the walk, read anim file to get start coords + + if (!ob_logic->looping) { + byte *anim_file = _vm->_resman->openResource(params[4]); + AnimHeader *anim_head = _vm->fetchAnimHeader( anim_file ); + + pars[4] = anim_head->feetStartX; + pars[5] = anim_head->feetStartY; + pars[6] = anim_head->feetStartDir; + + _vm->_resman->closeResource(params[4]); + + // If start coords not yet set in anim header, use the standby + // coords (which should be set beforehand in the script). + + if (pars[4] == 0 && pars[5] == 0) { + byte buf[NAME_LEN]; + + pars[4] = _standbyX; + pars[5] = _standbyY; + pars[6] = _standbyDir; + + debug(3, "WARNING: fnWalkToAnim(%s) used standby coords", _vm->fetchObjectName(params[4], buf)); + } + + assert(pars[6] >= 0 && pars[6] <= 7); + } + + return fnWalk(pars); +} + +/** + * Turn mega to the specified direction. Just needs to call fnWalk() with + * current feet coords, so router can produce anim of turn frames. + */ + +int32 Logic::fnTurn(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target direction + + int32 pars[7]; + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = params[2]; + pars[3] = params[3]; + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + // If this is the start of the turn, get the mega's current feet + // coords + the required direction + + if (!ob_logic->looping) { + assert(params[4] >= 0 && params[4] <= 7); + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); + + pars[4] = ob_mega->feet_x; + pars[5] = ob_mega->feet_y; + pars[6] = params[4]; + } + + return fnWalk(pars); +} + +/** + * Stand mega at (x,y,dir) + * Sets up the graphic object, but also needs to set the new 'current_dir' in + * the mega object, so the router knows in future + */ + +int32 Logic::fnStandAt(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 target x-coord + // 3 target y-coord + // 4 target direction + + assert(params[4] >= 0 && params[4] <= 7); + + ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); + + // set up the stand frame & set the mega's new direction + + ob_mega->feet_x = params[2]; + ob_mega->feet_y = params[3]; + ob_mega->current_dir = params[4]; + + // mega-set animation file + ob_graph->anim_resource = ob_mega->megaset_res; + + // dir + first stand frame (always frame 96) + ob_graph->anim_pc = params[4] + 96; - _scriptVars[RESULT] = _vm->_rnd.getRandomNumberRng(params[0], params[1]); return IR_CONT; } +/** + * Stand mega into the specified direction at current feet coords. + * Just needs to call fnStandAt() with current feet coords. + */ + +int32 Logic::fnStand(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 target direction + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); + + int32 pars[5]; + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = ob_mega->feet_x; + pars[3] = ob_mega->feet_y; + pars[4] = params[2]; + + return fnStandAt(pars); +} + +/** + * stand mega at end position of anim + */ + +int32 Logic::fnStandAfterAnim(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 anim resource id + + byte *anim_file = _vm->_resman->openResource(params[2]); + AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); + + int32 pars[5]; + + pars[0] = params[0]; + pars[1] = params[1]; + + pars[2] = anim_head->feetEndX; + pars[3] = anim_head->feetEndY; + pars[4] = anim_head->feetEndDir; + + // If start coords not available either use the standby coords (which + // should be set beforehand in the script) + + if (pars[2] == 0 && pars[3] == 0) { + byte buf[NAME_LEN]; + + pars[2] = _standbyX; + pars[3] = _standbyY; + pars[4] = _standbyDir; + + debug(3, "WARNING: fnStandAfterAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); + } + + assert(pars[4] >= 0 && pars[4] <= 7); + + _vm->_resman->closeResource(params[2]); + return fnStandAt(pars); +} + int32 Logic::fnPause(int32 *params) { // params: 0 pointer to object's logic structure // 1 number of game-cycles to pause @@ -152,6 +675,92 @@ int32 Logic::fnPause(int32 *params) { return IR_CONT; } +int32 Logic::fnMegaTableAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to animation table + + // 0 means normal forward anim + return megaTableAnimate(params, false); +} + +int32 Logic::fnAddMenuObject(int32 *params) { + // params: 0 pointer to a MenuObject structure to copy down + + _vm->addMenuObject((MenuObject *) _vm->_memory->decodePtr(params[0])); + return IR_CONT; +} + +/** + * Start a conversation. + * + * Note that fnStartConversation() might accidentally be called every time the + * script loops back for another chooser, but we only want to reset the chooser + * count flag the first time this function is called, i.e. when the talk flag + * is zero. + */ + +int32 Logic::fnStartConversation(int32 *params) { + // params: none + + if (_scriptVars[TALK_FLAG] == 0) { + // See fnChooser & speech scripts + _scriptVars[CHOOSER_COUNT_FLAG] = 0; + } + + fnNoHuman(params); + return IR_CONT; +} + +/** + * End a conversation. + */ + +int32 Logic::fnEndConversation(int32 *params) { + // params: none + + _vm->_graphics->hideMenu(RDMENU_BOTTOM); + + if (_vm->_mouseY > 399) { + // Will wait for cursor to move off the bottom menu + _vm->_mouseMode = MOUSE_holding; + } + + // In case DC forgets + _scriptVars[TALK_FLAG] = 0; + + return IR_CONT; +} + +int32 Logic::fnSetFrame(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 resource id of animation file + // 2 frame flag (0=first 1=last) + + int32 res = params[1]; + assert(res); + + // open the resource (& check it's valid) + byte *anim_file = _vm->_resman->openResource(res); + + StandardHeader *head = (StandardHeader *) anim_file; + assert(head->fileType == ANIMATION_FILE); + + // set up pointer to the animation header + AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); + + // set up anim resource in graphic object + ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); + + ob_graphic->anim_resource = res; + ob_graphic->anim_pc = params[2] ? anim_head->noAnimFrames - 1 : 0; + + // Close the anim file and drop out of script + _vm->_resman->closeResource(ob_graphic->anim_resource); + return IR_CONT; +} + int32 Logic::fnRandomPause(int32 *params) { // params: 0 pointer to object's logic structure // 1 minimum number of game-cycles to pause @@ -171,6 +780,65 @@ int32 Logic::fnRandomPause(int32 *params) { return fnPause(pars); } +int32 Logic::fnRegisterFrame(int32 *params) { + // this call would be made from an objects service script 0 + + // params: 0 pointer to mouse structure or NULL for no write to + // mouse list (non-zero means write sprite-shape to + // mouse list) + // 1 pointer to graphic structure + // 2 pointer to mega structure or NULL if not a mega + + return _vm->registerFrame(params); +} + +int32 Logic::fnNoSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], NO_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSendSync(int32 *params) { + // params: 0 sync's recipient + // 1 sync value + + for (int i = 0; i < MAX_syncs; i++) { + if (_syncList[i].id == 0) { + debug(5, "%d sends sync %d to %d", _scriptVars[ID], params[1], params[0]); + _syncList[i].id = params[0]; + _syncList[i].sync = params[1]; + return IR_CONT; + } + } + + // The original code didn't even check for this condition, so maybe + // it should be a fatal error? + + warning("No free sync slot"); + return IR_CONT; +} + +int32 Logic::fnUpdatePlayerStats(int32 *params) { + // engine needs to know certain info about the player + + // params: 0 pointer to mega structure + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); + + _vm->_thisScreen.player_feet_x = ob_mega->feet_x; + _vm->_thisScreen.player_feet_y = ob_mega->feet_y; + + // for the script + _scriptVars[PLAYER_FEET_X] = ob_mega->feet_x; + _scriptVars[PLAYER_FEET_Y] = ob_mega->feet_y; + _scriptVars[PLAYER_CUR_DIR] = ob_mega->current_dir; + _scriptVars[SCROLL_OFFSET_X] = _vm->_thisScreen.scroll_offset_x; + + debug(5, "fnUpdatePlayerStats: %d %d", ob_mega->feet_x, ob_mega->feet_y); + + return IR_CONT; +} + int32 Logic::fnPassGraph(int32 *params) { // makes an engine local copy of passed ObjectGraphic - run script 4 // of an object to request this used by fnTurnTo(id) etc @@ -184,6 +852,22 @@ int32 Logic::fnPassGraph(int32 *params) { return IR_CONT; } +int32 Logic::fnInitFloorMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); + + // floor is always lowest priority + + ob_mouse->x1 = 0; + ob_mouse->y1 = 0; + ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; + ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; + ob_mouse->priority = 9; + ob_mouse->pointer = NORMAL_MOUSE_ID; + return IR_CONT; +} + int32 Logic::fnPassMega(int32 *params) { // makes an engine local copy of passed graphic_structure and // mega_structure - run script 4 of an object to request this @@ -198,6 +882,1223 @@ int32 Logic::fnPassMega(int32 *params) { return IR_CONT; } +/** + * Turn mega to face point (x,y) on the floor + * Just needs to call fnWalk() with current feet coords & direction computed + * by whatTarget() + */ + +int32 Logic::fnFaceXY(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 target x-coord + // 5 target y-coord + + int32 pars[7]; + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = params[2]; + pars[3] = params[3]; + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + // If this is the start of the turn, get the mega's current feet + // coords + the required direction + + if (!ob_logic->looping) { + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); + + pars[4] = ob_mega->feet_x; + pars[5] = ob_mega->feet_y; + pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, params[4], params[5]); + } + + return fnWalk(pars); +} + +/** + * Causes no more objects in this logic loop to be processed. The logic engine + * will restart at the beginning of the new list. The current screen will not + * be drawn! + */ + +int32 Logic::fnEndSession(int32 *params) { + // params: 0 id of new run-list + + // terminate current and change to next run-list + expressChangeSession(params[0]); + + // stop the script - logic engine will now go around and the new + // screen will begin + return IR_STOP; +} + +int32 Logic::fnNoHuman(int32 *params) { + // params: none + + _vm->noHuman(); + _vm->clearPointerText(); + + // must be normal mouse situation or a largely neutral situation - + // special menus use noHuman + + // dont hide menu in conversations + if (_scriptVars[TALK_FLAG] == 0) + _vm->_graphics->hideMenu(RDMENU_BOTTOM); + + if (_vm->_mouseMode == MOUSE_system_menu) { + // close menu + _vm->_mouseMode = MOUSE_normal; + _vm->_graphics->hideMenu(RDMENU_TOP); + } + + return IR_CONT; +} + +int32 Logic::fnAddHuman(int32 *params) { + // params: none + + // for logic scripts + _scriptVars[MOUSE_AVAILABLE] = 1; + + // off + if (_vm->_mouseStatus) { + _vm->_mouseStatus = false; // on + _vm->_mouseTouching = 1; // forces engine to choose a cursor + } + + // clear this to reset no-second-click system + _scriptVars[CLICKED_ID] = 0; + + // this is now done outside the OBJECT_HELD check in case it's set to + // zero before now! + + // unlock the mouse from possible large object lock situtations - see + // syphon in rm 3 + + _vm->_mouseModeLocked = false; + + if (_scriptVars[OBJECT_HELD]) { + // was dragging something around + // need to clear this again + _scriptVars[OBJECT_HELD] = 0; + + // and these may also need clearing, just in case + _vm->_examiningMenuIcon = false; + Logic::_scriptVars[COMBINE_BASE] = 0; + + _vm->setLuggage(0); + } + + // if mouse is over menu area + if (_vm->_mouseY > 399) { + if (_vm->_mouseMode != MOUSE_holding) { + // VITAL - reset things & rebuild the menu + _vm->_mouseMode = MOUSE_normal; + _vm->setMouse(NORMAL_MOUSE_ID); + } else + _vm->setMouse(NORMAL_MOUSE_ID); + } + + // enabled/disabled from console; status printed with on-screen debug + // info + + if (_vm->_debugger->_testingSnR) { + uint8 black[4] = { 0, 0, 0, 0 }; + uint8 white[4] = { 255, 255, 255, 0 }; + + // testing logic scripts by simulating an instant Save & + // Restore + + _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); + + // stops all fx & clears the queue - eg. when leaving a + // location + + _vm->_sound->clearFxQueue(); + + // Trash all object resources so they load in fresh & restart + // their logic scripts + + _vm->_resman->killAllObjects(false); + + _vm->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); + } + + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy. + */ + +int32 Logic::fnWeWait(int32 *params) { + // params: 0 target + + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); + assert(head->fileType == GAME_OBJECT); + + // Run the target's get-speech-state script + + int32 target = params[0]; + char *raw_script_ad = (char *) head; + uint32 null_pc = 5; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(target); + + if (_scriptVars[RESULT] == 0) { + // The target is busy. Try again. + _vm->_debugger->_speechScriptWaiting = target; + return IR_REPEAT; + } + + // The target is waiting, i.e. not busy. + + _vm->_debugger->_speechScriptWaiting = 0; + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy, send a command to it, + * then wait for it to finish. + */ + +int32 Logic::fnTheyDoWeWait(int32 *params) { + // params: 0 pointer to ob_logic + // 1 target + // 2 command + // 3 ins1 + // 4 ins2 + // 5 ins3 + // 6 ins4 + // 7 ins5 + + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); + assert(head->fileType == GAME_OBJECT); + + // Run the target's get-speech-state script + + int32 target = params[1]; + char *raw_script_ad = (char *) head; + uint32 null_pc = 5; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(target); + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND] && ob_logic->looping == 0) { + // The target is waiting, i.e. not busy, and there is no other + // command queued. We haven't sent the command yet, so do it. + + debug(5, "fnTheyDoWeWait: sending command to %d", target); + + _vm->_debugger->_speechScriptWaiting = target; + ob_logic->looping = 1; + + _scriptVars[SPEECH_ID] = params[1]; + _scriptVars[INS_COMMAND] = params[2]; + _scriptVars[INS1] = params[3]; + _scriptVars[INS2] = params[4]; + _scriptVars[INS3] = params[5]; + _scriptVars[INS4] = params[6]; + _scriptVars[INS5] = params[7]; + + return IR_REPEAT; + } + + if (ob_logic->looping == 0) { + // The command has not been sent yet. Keep waiting. + _vm->_debugger->_speechScriptWaiting = target; + return IR_REPEAT; + } + + if (_scriptVars[RESULT] == 0) { + // The command has been sent, and the target is busy doing it. + // Wait for it to finish. + + debug(5, "fnTheyDoWeWait: Waiting for %d to finish", target); + + _vm->_debugger->_speechScriptWaiting = target; + return IR_REPEAT; + } + + debug(5, "fnTheyDoWeWait: %d finished", target); + + ob_logic->looping = 0; + _vm->_debugger->_speechScriptWaiting = 0; + return IR_CONT; +} + +/** + * Wait for a target to become waiting, i.e. not busy, then send a command to + * it. + */ + +int32 Logic::fnTheyDo(int32 *params) { + // params: 0 target + // 1 command + // 2 ins1 + // 3 ins2 + // 4 ins3 + // 5 ins4 + // 6 ins5 + + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); + assert (head->fileType == GAME_OBJECT); + + // Run the target's get-speech-state script + + int32 target = params[0]; + char *raw_script_ad = (char *) head; + uint32 null_pc = 5; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(target); + + if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND]) { + // The target is waiting, i.e. not busy, and there is no other + // command queued. Send the command. + + debug(5, "fnTheyDo: sending command to %d", target); + + _vm->_debugger->_speechScriptWaiting = 0; + + _scriptVars[SPEECH_ID] = params[0]; + _scriptVars[INS_COMMAND] = params[1]; + _scriptVars[INS1] = params[2]; + _scriptVars[INS2] = params[3]; + _scriptVars[INS3] = params[4]; + _scriptVars[INS4] = params[5]; + _scriptVars[INS5] = params[6]; + + return IR_CONT; + } + + // The target is busy. Come back again next cycle. + + _vm->_debugger->_speechScriptWaiting = target; + return IR_REPEAT; +} + +/** + * Route to the left or right hand side of target id, if possible. + */ + +int32 Logic::fnWalkToTalkToMega(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 id of target mega to face + // 5 distance + + int32 pars[7]; + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = params[2]; + pars[3] = params[3]; + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + // If this is the start of the walk, calculate the route. + + if (!ob_logic->looping) { + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); + + assert(head->fileType == GAME_OBJECT); + + // Call the base script. This is the graphic/mouse service + // call, and will set _engineMega to the ObjectMega of mega we + // want to route to. + + char *raw_script_ad = (char *) head; + uint32 null_pc = 3; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(params[4]); + + // Stand exactly beside the mega, ie. at same y-coord + pars[5] = _engineMega.feet_y; + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); + + // Apply scale factor to walk distance. Ay+B gives 256 * scale + // ie. 256 * 256 * true_scale for even better accuracy, ie. + // scale = (Ay + B) / 256 + + int scale = (ob_mega->scale_a * ob_mega->feet_y + ob_mega->scale_b) / 256; + int mega_separation = (params[5] * scale) / 256; + + debug(4, "Target is at (%d, %d), separation %d", _engineMega.feet_x, _engineMega.feet_y, mega_separation); + + if (_engineMega.feet_x < ob_mega->feet_x) { + // Target is left of us, so aim to stand to their + // right. Face down_left + + pars[4] = _engineMega.feet_x + mega_separation; + pars[6] = 5; + } else { + // Ok, must be right of us so aim to stand to their + // left. Face down_right. + + pars[4] = _engineMega.feet_x - mega_separation; + pars[6] = 3; + } + } + + return fnWalk(pars); +} + +int32 Logic::fnFadeDown(int32 *params) { + // NONE means up! can only be called when screen is fully faded up - + // multiple calls wont have strange effects + + // params: none + + if (_vm->_graphics->getFadeStatus() == RDFADE_NONE) + _vm->_graphics->fadeDown(); + + return IR_CONT; +} + +enum { + S_OB_GRAPHIC = 0, + S_OB_SPEECH = 1, + S_OB_LOGIC = 2, + S_OB_MEGA = 3, + + S_TEXT = 4, + S_WAV = 5, + S_ANIM = 6, + S_DIR_TABLE = 7, + S_ANIM_MODE = 8 +}; + +/** + * It's the super versatile fnSpeak. Text and wavs can be selected in any + * combination. + * + * @note We can assume no human - there should be no human, at least! + */ + +int32 Logic::fnISpeak(int32 *params) { + // params: 0 pointer to ob_graphic + // 1 pointer to ob_speech + // 2 pointer to ob_logic + // 3 pointer to ob_mega + // 4 encoded text number + // 5 wav res id + // 6 anim res id + // 7 anim table res id + // 8 animation mode 0 lip synced, + // 1 just straight animation + + static bool cycle_skip = false; + static bool speechRunning; + + // Set up the pointers which we know we'll always need + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[S_OB_LOGIC]); + ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[S_OB_GRAPHIC]); + + // FIRST TIME ONLY: create the text, load the wav, set up the anim, + // etc. + + if (!ob_logic->looping) { + // New fudge to wait for smacker samples to finish + // since they can over-run into the game + + if (_vm->_sound->getSpeechStatus() != RDSE_SAMPLEFINISHED) + return IR_REPEAT; + + // New fudge for 'fx' subtitles: If subtitles switched off, and + // we don't want to use a wav for this line either, then just + // quit back to script right now! + + if (!_vm->_gui->_subtitles && !wantSpeechForLine(params[S_WAV])) + return IR_CONT; + + // Drop out for 1st cycle to allow walks/anims to end and + // display last frame before system locks while speech loaded + + if (!cycle_skip) { + cycle_skip = true; + return IR_REPEAT; + } + + cycle_skip = false; + + _vm->_debugger->_textNumber = params[S_TEXT]; + + // Pull out the text line to get the official text number + // (for wav id). Once the wav id's go into all script text + // commands, we'll only need this for debugging. + + uint32 text_res = params[S_TEXT] / SIZE; + uint32 local_text = params[S_TEXT] & 0xffff; + + // For testing all text & speech! + // + // A script loop can send any text number to fnISpeak and it + // will only run the valid ones or return with 'result' equal + // to '1' or '2' to mean 'invalid text resource' and 'text + // number out of range' respectively + // + // See 'testing_routines' object in George's Player Character + // section of linc + + if (_scriptVars[SYSTEM_TESTING_TEXT]) { + if (!_vm->_resman->checkValid(text_res)) { + // Not a valid resource number - invalid (null + // resource) + _scriptVars[RESULT] = 1; + return IR_CONT; + } + + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(text_res); + + if (head->fileType != TEXT_FILE) { + // Invalid - not a text resource + _vm->_resman->closeResource(text_res); + _scriptVars[RESULT] = 1; + return IR_CONT; + } + + if (!_vm->checkTextLine((byte *) head, local_text)) { + // Line number out of range + _vm->_resman->closeResource(text_res); + _scriptVars[RESULT] = 2; + return IR_CONT; + } + + _vm->_resman->closeResource(text_res); + _scriptVars[RESULT] = 0; + } + + byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text); + _officialTextNumber = READ_LE_UINT16(text); + _vm->_resman->closeResource(text_res); + + // Prevent dud lines from appearing while testing text & speech + // since these will not occur in the game anyway + + if (_scriptVars[SYSTEM_TESTING_TEXT]) { + // If actor number is 0 and text line is just a 'dash' + // character + if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) { + _scriptVars[RESULT] = 3; + return IR_CONT; + } + } + + // Set the 'looping_flag' and the text-click-delays. We can + // left-click past the text after half a second, and + // right-click past it after a quarter of a second. + + ob_logic->looping = 1; + _leftClickDelay = 6; + _rightClickDelay = 3; + + if (_scriptVars[PLAYER_ID] != CUR_PLAYER_ID) + debug(5, "(%d) Nico: %s", _officialTextNumber, text + 2); + else { + byte buf[NAME_LEN]; + + debug(5, "(%d) %s: %s", _officialTextNumber, _vm->fetchObjectName(_scriptVars[ID], buf), text + 2); + } + + // Set up the speech animation + + if (params[S_ANIM]) { + // Just a straight anim. + _animId = params[6]; + } else if (params[S_DIR_TABLE]) { + // Use this direction table to derive the anim + // NB. ASSUMES WE HAVE A MEGA OBJECT!! + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[S_OB_MEGA]); + int32 *anim_table = (int32 *) _vm->_memory->decodePtr(params[S_DIR_TABLE]); + + _animId = anim_table[ob_mega->current_dir]; + } else { + // No animation choosen + _animId = 0; + } + + if (_animId) { + // Set the talker's graphic to the first frame of this + // speech anim for now. + + _speechAnimType = _scriptVars[SPEECHANIMFLAG]; + ob_graphic->anim_resource = _animId; + ob_graphic->anim_pc = 0; + } + + // Default back to looped lip synced anims. + _scriptVars[SPEECHANIMFLAG] = 0; + + // Set up _textX and _textY for speech panning and/or text + // sprite position. + + locateTalker(params); + + // Is it to be speech or subtitles or both? + + // Assume not running until know otherwise + speechRunning = false; + + // New fudge for 'fx' subtitles: If speech is selected, and + // this line is allowed speech (not if it's an fx subtitle!) + + if (!_vm->_sound->isSpeechMute() && wantSpeechForLine(_officialTextNumber)) { + // If the wavId parameter is zero because not yet + // compiled into speech command, we can still get it + // from the 1st 2 chars of the text line. + + if (!params[S_WAV]) + params[S_WAV] = (int32) _officialTextNumber; + + // Panning goes from -16 (left) to 16 (right) + int8 speech_pan = ((_textX - 320) * 16) / 320; + + if (speech_pan < -16) + speech_pan = -16; + else if (speech_pan > 16) + speech_pan = 16; + + uint32 rv = _vm->_sound->playCompSpeech(params[S_WAV], 16, speech_pan); + + if (rv == RD_OK) { + // Ok, we've got something to play. Set it + // playing now. (We might want to do this the + // next cycle, don't know yet.) + + speechRunning = true; + _vm->_sound->unpauseSpeech(); + } else { + debug(5, "ERROR: PlayCompSpeech(wav=%d (res=%d pos=%d)) returned %.8x", params[S_WAV], text_res, local_text, rv); + } + } + + if (_vm->_gui->_subtitles || !speechRunning) { + // We want subtitles, or the speech failed to load. + // Either way, we're going to show the text so create + // the text sprite. + + formText(params); + } + } + + // EVERY TIME: run a cycle of animation, if there is one + + if (_animId) { + // There is an animation - Increment the anim frame number. + ob_graphic->anim_pc++; + + byte *anim_file = _vm->_resman->openResource(ob_graphic->anim_resource); + AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); + + if (!_speechAnimType) { + // ANIM IS TO BE LIP-SYNC'ED & REPEATING + + if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames)) { + // End of animation - restart from frame 0 + ob_graphic->anim_pc = 0; + } else if (speechRunning && _vm->_sound->amISpeaking() == RDSE_QUIET) { + // The speech is running, but we're at a quiet + // bit. Restart from frame 0 (closed mouth). + ob_graphic->anim_pc = 0; + } + } else { + // ANIM IS TO PLAY ONCE ONLY + if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames) - 1) { + // Reached the last frame of the anim. Hold + // anim on this last frame + _animId = 0; + } + } + + _vm->_resman->closeResource(ob_graphic->anim_resource); + } else if (_speechAnimType) { + // Placed here so we actually display the last frame of the + // anim. + _speechAnimType = 0; + } + + // EVERY TIME: FIND OUT IF WE NEED TO STOP THE SPEECH NOW... + + // If there is a wav then we're using that to end the speech naturally + + bool speechFinished = false; + + // If playing a sample + + if (speechRunning) { + // Has it finished? + if (_vm->_sound->getSpeechStatus() == RDSE_SAMPLEFINISHED) + speechFinished = true; + } else if (!speechRunning && _speechTime) { + // Counting down text time because there is no sample - this + // ends the speech + + // if no sample then we're using _speechTime to end speech + // naturally + + _speechTime--; + if (!_speechTime) + speechFinished = true; + } + + // Ok, all is running along smoothly - but a click means stop + // unnaturally + + // So that we can go to the options panel while text & speech is + // being tested + if (_scriptVars[SYSTEM_TESTING_TEXT] == 0 || _vm->_mouseY > 0) { + MouseEvent *me = _vm->mouseEvent(); + + // Note that we now have TWO click-delays - one for LEFT + // button, one for RIGHT BUTTON + + if ((!_leftClickDelay && me && (me->buttons & RD_LEFTBUTTONDOWN)) || + (!_rightClickDelay && me && (me->buttons & RD_RIGHTBUTTONDOWN))) { + // Mouse click, after click_delay has expired -> end + // the speech. + + // if testing text & speech + if (_scriptVars[SYSTEM_TESTING_TEXT]) { + // and RB used to click past text + if (me->buttons & RD_RIGHTBUTTONDOWN) { + // then we want the previous line again + _scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 1; + } else { + // LB just want next line again + _scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 0; + } + } + + speechFinished = true; + + // if speech sample playing, halt it prematurely + if (speechRunning) + _vm->_sound->stopSpeech(); + } + } + + // If we are finishing the speech this cycle, do the business + + // !speechAnimType, as we want an anim which is playing once to have + // finished. + + if (speechFinished && !_speechAnimType) { + // If there is text, kill it + if (_speechTextBlocNo) { + _vm->_fontRenderer->killTextBloc(_speechTextBlocNo); + _speechTextBlocNo = 0; + } + + // if there is a speech anim, end it on closed mouth frame + if (_animId) { + _animId = 0; + ob_graphic->anim_pc = 0; + } + + speechRunning = false; + + // no longer in a script function loop + ob_logic->looping = 0; + + _vm->_debugger->_textNumber = 0; + + // reset to zero, in case text line not even extracted (since + // this number comes from the text line) + _officialTextNumber = 0; + + _scriptVars[RESULT] = 0; + return IR_CONT; + } + + // Speech still going, so decrement the click_delay if it's still + // active + + if (_leftClickDelay) + _leftClickDelay--; + + if (_rightClickDelay) + _rightClickDelay--; + + return IR_REPEAT; +} + +/** + * Reset the object and restart script 1 on level 0 + */ + +#define LEVEL (_curObjectHub->logic_level) + +int32 Logic::fnTotalRestart(int32 *params) { + // mega runs this to restart its base logic again - like being cached + // in again + + // params: none + + LEVEL = 0; + _curObjectHub->script_pc[0] = 1; + return IR_TERMINATE; +} + +int32 Logic::fnSetWalkGrid(int32 *params) { + // params: none + + warning("fnSetWalkGrid() is no longer a valid opcode"); + return IR_CONT; +} + +/** + * Receive and sequence the commands sent from the conversation script. We have + * to do this in a slightly tweeky manner as we can no longer have generic + * scripts. + */ + +enum { + INS_talk = 1, + INS_anim = 2, + INS_reverse_anim = 3, + INS_walk = 4, + INS_turn = 5, + INS_face = 6, + INS_trace = 7, + INS_no_sprite = 8, + INS_sort = 9, + INS_foreground = 10, + INS_background = 11, + INS_table_anim = 12, + INS_reverse_table_anim = 13, + INS_walk_to_anim = 14, + INS_set_frame = 15, + INS_stand_after_anim = 16, + INS_quit = 42 +}; + +int32 Logic::fnSpeechProcess(int32 *params) { + // params: 0 pointer to ob_graphic + // 1 pointer to ob_speech + // 2 pointer to ob_logic + // 3 pointer to ob_mega + // 4 pointer to ob_walkdata + + ObjectSpeech *ob_speech = (ObjectSpeech *) _vm->_memory->decodePtr(params[1]); + + while (1) { + int32 pars[9]; + + // Check which command we're waiting for, and call the + // appropriate function. Once we're done, clear the command + // and set wait_state to 1. + // + // Note: we could save a var and ditch wait_state and check + // 'command' for non zero means busy + // + // Note: I can't see that we ever check the value of wait_state + // but perhaps it accesses that memory location directly? + + switch (ob_speech->command) { + case 0: + break; + case INS_talk: + pars[0] = params[0]; // ob_graphic + pars[1] = params[1]; // ob_speech + pars[2] = params[2]; // ob_logic + pars[3] = params[3]; // ob_mega + pars[4] = ob_speech->ins1; // encoded text number + pars[5] = ob_speech->ins2; // wav res id + pars[6] = ob_speech->ins3; // anim res id + pars[7] = ob_speech->ins4; // anim table res id + pars[8] = ob_speech->ins5; // animation mode - 0 lip synced, 1 just straight animation + + if (fnISpeak(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_turn: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = ob_speech->ins1; // direction to turn to + + if (fnTurn(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_face: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = ob_speech->ins1; // target + + if (fnFaceMega(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = ob_speech->ins1; // anim res + + if (fnAnim(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_reverse_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = ob_speech->ins1; // anim res + + if (fnReverseAnim(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_table_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = ob_speech->ins1; // pointer to anim table + + if (fnMegaTableAnim(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_reverse_table_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = ob_speech->ins1; // pointer to anim table + + if (fnReverseMegaTableAnim(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_no_sprite: + fnNoSprite(params); // ob_graphic + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT ; + case INS_sort: + fnSortSprite(params); // ob_graphic + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT; + case INS_foreground: + fnForeSprite(params); // ob_graphic + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT; + case INS_background: + fnBackSprite(params); // ob_graphic + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT; + case INS_walk: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = ob_speech->ins1; // target x + pars[5] = ob_speech->ins2; // target y + pars[6] = ob_speech->ins3; // target direction + + if (fnWalk(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_walk_to_anim: + pars[0] = params[2]; // ob_logic + pars[1] = params[0]; // ob_graphic + pars[2] = params[3]; // ob_mega + pars[3] = params[4]; // ob_walkdata + pars[4] = ob_speech->ins1; // anim resource + + if (fnWalkToAnim(pars) != IR_REPEAT) { + ob_speech->command = 0; + ob_speech->wait_state = 1; + } + + return IR_REPEAT; + case INS_stand_after_anim: + pars[0] = params[0]; // ob_graphic + pars[1] = params[3]; // ob_mega + pars[2] = ob_speech->ins1; // anim resource + + fnStandAfterAnim(pars); + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT; + case INS_set_frame: + pars[0] = params[0]; // ob_graphic + pars[1] = ob_speech->ins1; // anim_resource + pars[2] = ob_speech->ins2; // FIRST_FRAME or LAST_FRAME + fnSetFrame(pars); + + ob_speech->command = 0; + ob_speech->wait_state = 1; + return IR_REPEAT; + case INS_quit: + // That's it - we're finished with this + ob_speech->command = 0; + // ob_speech->wait_state = 0; + return IR_CONT; + default: + // Unimplemented command - just cancel + ob_speech->command = 0; + ob_speech->wait_state = 1; + break; + } + + if (_scriptVars[SPEECH_ID] == _scriptVars[ID]) { + // There's a new command for us! Grab the command - + // potentially we only have this cycle to do this - and + // set things up so that the command will be picked up + // on the next iteration of the while loop. + + debug(5, "fnSpeechProcess: Received new command %d", _scriptVars[INS_COMMAND]); + + _scriptVars[SPEECH_ID] = 0; + + ob_speech->command = _scriptVars[INS_COMMAND]; + ob_speech->ins1 = _scriptVars[INS1]; + ob_speech->ins2 = _scriptVars[INS2]; + ob_speech->ins3 = _scriptVars[INS3]; + ob_speech->ins4 = _scriptVars[INS4]; + ob_speech->ins5 = _scriptVars[INS5]; + ob_speech->wait_state = 0; + + _scriptVars[INS_COMMAND] = 0; + } else { + // No new command. We could run a blink anim (or + // something) here. + + ob_speech->wait_state = 1; + return IR_REPEAT; + } + } +} + +int32 Logic::fnSetScaling(int32 *params) { + // params: 0 pointer to object's mega structure + // 1 scale constant A + // 2 scale constant B + + // 256 * s = A * y + B + + // Where s is system scale, which itself is (256 * actual_scale) ie. + // s == 128 is half size + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); + + ob_mega->scale_a = params[1]; + ob_mega->scale_b = params[2]; + + return IR_CONT; +} + +int32 Logic::fnStartEvent(int32 *params) { + // params: none + + startEvent(); + return IR_TERMINATE; +} + +int32 Logic::fnCheckEventWaiting(int32 *params) { + // params: none + + _scriptVars[RESULT] = checkEventWaiting(); + return IR_CONT; +} + +int32 Logic::fnRequestSpeech(int32 *params) { + // change current script - must be followed by a TERMINATE script + // directive + + // params: 0 id of target to catch the event and startup speech + // servicing + + // Full script id to interact with - megas run their own 7th script + sendEvent(params[0], (params[0] << 16) | 6); + return IR_CONT; +} + +int32 Logic::fnGosub(int32 *params) { + // params: 0 id of script + + // Hurray, script subroutines. Logic goes up - pc is saved for current + // level. + logicUp(params[0]); + return IR_GOSUB; +} + +/** + * Wait for a target to become waiting, i.e. not busy, or until we time out. + * This is useful when clicking on a target to talk to it, and it doesn't + * reply. This way, we won't lock up. + * + * If the target becomes waiting, RESULT is set to 0. If we time out, RESULT is + * set to 1. + */ + +int32 Logic::fnTimedWait(int32 *params) { + // params: 0 ob_logic + // 1 target + // 2 number of cycles before give up + + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); + assert(head->fileType == GAME_OBJECT); + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + if (!ob_logic->looping) { + // This is the first time, so set up the time-out. + ob_logic->looping = params[2]; + } + + // Run the target's get-speech-state script + + int32 target = params[1]; + char *raw_script_ad = (char *) head; + uint32 null_pc = 5; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(target); + + if (_scriptVars[RESULT] == 1) { + // The target is waiting, i.e. not busy + + _vm->_debugger->_speechScriptWaiting = 0; + + ob_logic->looping = 0; + _scriptVars[RESULT] = 0; + return IR_CONT; + } + + ob_logic->looping--; + + if (!ob_logic->looping) { + // Time's up. + + debug(5, "fnTimedWait: Timed out waiting for %d", target); + _vm->_debugger->_speechScriptWaiting = 0; + + // Clear the event that hasn't been picked up - in theory, + // none of this should ever happen. + + killAllIdsEvents(target); + _scriptVars[RESULT] = 1; + return IR_CONT; + } + + // Target is busy. Keep trying. + + _vm->_debugger->_speechScriptWaiting = target; + return IR_REPEAT; +} + +int32 Logic::fnPlayFx(int32 *params) { + // params: 0 sample resource id + // 1 type (FX_SPOT, FX_RANDOM, FX_LOOP) + // 2 delay (0..65535) + // 3 volume (0..16) + // 4 pan (-16..16) + + // example script: + // fnPlayFx (FXWATER, FX_LOOP, 0, 10, 15); + // // fx_water is just a local script flag + // fx_water = result; + // . + // . + // . + // fnStopFx (fx_water); + + int32 res = params[0]; + int32 type = params[1]; + int32 delay = params[2]; + int32 volume = params[3]; + int32 pan = params[4]; + + _vm->_sound->queueFx(res, type, delay, volume, pan); + return IR_CONT; +} + +int32 Logic::fnStopFx(int32 *params) { + // params: 0 position in queue + if (_vm->_sound->stopFx(params[0]) != RD_OK) + debug(5, "SFX ERROR: Trying to stop an inactive sound slot"); + + return IR_CONT; +} + +/** + * Start a tune playing, to play once or to loop until stopped or next one + * played. + */ + +int32 Logic::fnPlayMusic(int32 *params) { + // params: 0 tune id + // 1 loop flag (0 or 1) + + char filename[128]; + bool loopFlag; + uint32 rv; + + loopFlag = (params[1] == FX_LOOP); + + rv = _vm->_sound->streamCompMusic(params[0], loopFlag); + + if (rv) + debug(5, "ERROR: streamCompMusic(%s, %d, %d) returned error 0x%.8x", filename, params[0], loopFlag, rv); + + return IR_CONT; +} + +int32 Logic::fnStopMusic(int32 *params) { + // params: none + + _vm->_sound->stopMusic(); + return IR_CONT; +} + int32 Logic::fnSetValue(int32 *params) { // temp. function! @@ -213,6 +2114,317 @@ int32 Logic::fnSetValue(int32 *params) { return IR_CONT; } +int32 Logic::fnNewScript(int32 *params) { + // change current script - must be followed by a TERMINATE script + // directive + + // params: 0 id of script + + _scriptVars[PLAYER_ACTION] = 0; // must clear this + logicReplace(params[0]); + return IR_TERMINATE; +} + +/** + * Like getSync(), but called from scripts. Sets the RESULT variable to + * the sync value, or 0 if none is found. + */ + +int32 Logic::fnGetSync(int32 *params) { + // params: none + + int slot = getSync(); + + _scriptVars[RESULT] = (slot != -1) ? _syncList[slot].sync : 0; + return IR_CONT; +} + +/** + * Wait for sync to happen. Sets the RESULT variable to the sync value, once + * it has been found. + */ + +int32 Logic::fnWaitSync(int32 *params) { + // params: none + + debug(6, "fnWaitSync: %d waits", _scriptVars[ID]); + + int slot = getSync(); + + if (slot == -1) + return IR_REPEAT; + + debug(5, "fnWaitSync: %d got sync %d", _scriptVars[ID], _syncList[slot].sync); + _scriptVars[RESULT] = _syncList[slot].sync; + return IR_CONT; +} + +int32 Logic::fnRegisterWalkGrid(int32 *params) { + // params: none + + warning("fnRegisterWalkGrid() is no longer a valid opcode"); + return IR_CONT; +} + +int32 Logic::fnReverseMegaTableAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to animation table + + // 1 means reverse anim + return megaTableAnimate(params, true); +} + +int32 Logic::fnReverseAnim(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 resource id of animation file + + // 1 means reverse anim + return animate(params, true); +} + +/** + * Mark this object for killing - to be killed when player leaves this screen. + * Object reloads and script restarts upon re-entry to screen, which causes + * this object's startup logic to be re-run every time we enter the screen. + * "Which is nice." + * + * @note Call ONCE from object's logic script, i.e. in startup code, so not + * re-called every time script frops off and restarts! + */ + +int32 Logic::fnAddToKillList(int32 *params) { + // params: none + + // DON'T EVER KILL GEORGE! + if (_scriptVars[ID] == CUR_PLAYER_ID) + return IR_CONT; + + // Scan the list to see if it's already included + + for (uint32 i = 0; i < _kills; i++) { + if (_objectKillList[i] == _scriptVars[ID]) + return IR_CONT; + } + + assert(_kills < OBJECT_KILL_LIST_SIZE); // no room at the inn + + _objectKillList[_kills++] = _scriptVars[ID]; + + // "another one bites the dust" + + // When we leave the screen, all these object resources are to be + // cleaned out of memory and the kill list emptied by doing + // '_kills = 0', ensuring that all resources are in fact still in + // memory and, more importantly, closed before killing! + + return IR_CONT; +} + +/** + * Set the standby walk coords to be used by fnWalkToAnim() and + * fnStandAfterAnim() when the anim header's start/end coords are zero. + * Useful during development; can stay in final game anyway. + */ + +int32 Logic::fnSetStandbyCoords(int32 *params) { + // params: 0 x-coord + // 1 y-coord + // 2 direction (0..7) + + assert(params[2] >= 0 && params[2] <= 7); + + _standbyX = (int16) params[0]; + _standbyY = (int16) params[1]; + _standbyDir = (uint8) params[2]; + + return IR_CONT; +} + +int32 Logic::fnBackPar0Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], BGP0_SPRITE); + return IR_CONT; +} + +int32 Logic::fnBackPar1Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], BGP1_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForePar0Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], FGP0_SPRITE); + return IR_CONT; +} + +int32 Logic::fnForePar1Sprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteStatus(params[0], FGP1_SPRITE); + return IR_CONT; +} + +int32 Logic::fnSetPlayerActionEvent(int32 *params) { + // we want to intercept the player character and have him interact + // with an object - from script this code is the same as the mouse + // engine calls when you click on an object - here, a third party + // does the clicking IYSWIM + + // note - this routine used CUR_PLAYER_ID as the target + + // params: 0 id to interact with + + setPlayerActionEvent(CUR_PLAYER_ID, params[0]); + return IR_CONT; +} + +/** + * Set the special scroll offset variables + * + * Call when starting screens and to change the camera within screens + * + * call AFTER fnInitBackground() to override the defaults + */ + +int32 Logic::fnSetScrollCoordinate(int32 *params) { + // params: 0 feet_x value + // 1 feet_y value + + // Called feet_x and feet_y to retain intellectual compatibility with + // Sword1! + // + // feet_x & feet_y refer to the physical screen coords where the + // system will try to maintain George's feet + + _vm->_thisScreen.feet_x = params[0]; + _vm->_thisScreen.feet_y = params[1]; + return IR_CONT; +} + +/** + * Stand mega at start position of anim + */ + +int32 Logic::fnStandAtAnim(int32 *params) { + // params: 0 pointer to object's graphic structure + // 1 pointer to object's mega structure + // 2 anim resource id + + byte *anim_file = _vm->_resman->openResource(params[2]); + AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); + + int32 pars[5]; + + pars[0] = params[0]; + pars[1] = params[1]; + + pars[2] = anim_head->feetStartX; + pars[3] = anim_head->feetStartY; + pars[4] = anim_head->feetStartDir; + + // If start coords not available use the standby coords (which should + // be set beforehand in the script) + + if (pars[2] == 0 && pars[3] == 0) { + byte buf[NAME_LEN]; + + pars[2] = _standbyX; + pars[3] = _standbyY; + pars[4] = _standbyDir; + + debug(3, "WARNING: fnStandAtAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); + } + + assert(pars[4] >= 0 && pars[4] <= 7); + + _vm->_resman->closeResource(params[2]); + return fnStandAt(pars); +} + +#define SCROLL_MOUSE_WIDTH 20 + +int32 Logic::fnSetScrollLeftMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); + + // Highest priority + + ob_mouse->x1 = 0; + ob_mouse->y1 = 0; + ob_mouse->x2 = _vm->_thisScreen.scroll_offset_x + SCROLL_MOUSE_WIDTH; + ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; + ob_mouse->priority = 0; + + if (_vm->_thisScreen.scroll_offset_x > 0) { + // not fully scrolled to the left + ob_mouse->pointer = SCROLL_LEFT_MOUSE_ID; + } else { + // so the mouse area doesn't get registered + ob_mouse->pointer = 0; + } + + return IR_CONT; +} + +int32 Logic::fnSetScrollRightMouse(int32 *params) { + // params: 0 pointer to object's mouse structure + + ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); + + // Highest priority + + ob_mouse->x1 = _vm->_thisScreen.scroll_offset_x + _vm->_graphics->_screenWide - SCROLL_MOUSE_WIDTH; + ob_mouse->y1 = 0; + ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; + ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; + ob_mouse->priority = 0; + + if (_vm->_thisScreen.scroll_offset_x < _vm->_thisScreen.max_scroll_offset_x) { + // not fully scrolled to the right + ob_mouse->pointer = SCROLL_RIGHT_MOUSE_ID; + } else { + // so the mouse area doesn't get registered + ob_mouse->pointer = 0; + } + + return IR_CONT; +} + +int32 Logic::fnColour(int32 *params) { + // set border colour - useful during script development + // eg. set to colour during a timer situation, then black when timed + // out + + // params 0: colour (see defines above) + +#ifdef SWORD2_DEBUG + // what colour? + switch (params[0]) { + case BLACK: + _vm->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); + break; + case WHITE: + _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); + break; + case RED: + _vm->_graphics->setPalette(0, 1, red, RDPAL_INSTANT); + break; + case GREEN: + _vm->_graphics->setPalette(0, 1, green, RDPAL_INSTANT); + break; + case BLUE: + _vm->_graphics->setPalette(0, 1, blue, RDPAL_INSTANT); + break; + } +#endif + + return IR_CONT; +} + #ifdef SWORD2_DEBUG #define BLACK 0 #define WHITE 1 @@ -262,34 +2474,300 @@ int32 Logic::fnFlash(int32 *params) { return IR_CONT; } +int32 Logic::fnPreFetch(int32 *params) { + // Go fetch resource in the background. -int32 Logic::fnColour(int32 *params) { - // set border colour - useful during script development - // eg. set to colour during a timer situation, then black when timed - // out + // params: 0 resource to fetch [guess] - // params 0: colour (see defines above) + return IR_CONT; +} -#ifdef SWORD2_DEBUG - // what colour? - switch (params[0]) { - case BLACK: - _vm->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); - break; - case WHITE: - _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); - break; - case RED: - _vm->_graphics->setPalette(0, 1, red, RDPAL_INSTANT); - break; - case GREEN: - _vm->_graphics->setPalette(0, 1, green, RDPAL_INSTANT); - break; - case BLUE: - _vm->_graphics->setPalette(0, 1, blue, RDPAL_INSTANT); - break; +/** + * Reverse of fnPassPlayerSaveData() - run script 8 of player object. + */ + +int32 Logic::fnGetPlayerSaveData(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + + byte *logic_ptr = _vm->_memory->decodePtr(params[0]); + byte *graphic_ptr = _vm->_memory->decodePtr(params[1]); + byte *mega_ptr = _vm->_memory->decodePtr(params[2]); + + // Copy from savegame header to player object + + memcpy(logic_ptr, &_vm->_saveGameHeader.logic, sizeof(ObjectLogic)); + memcpy(graphic_ptr, &_vm->_saveGameHeader.graphic, sizeof(ObjectGraphic)); + memcpy(mega_ptr, &_vm->_saveGameHeader.mega, sizeof(ObjectMega)); + + // Any walk-data must be cleared - the player will be set to stand if + // he was walking when saved. + + ObjectMega *ob_mega = (ObjectMega *) mega_ptr; + + if (ob_mega->currently_walking) { + ob_mega->currently_walking = 0; + + int32 pars[3]; + + pars[0] = params[1]; // ob_graphic; + pars[1] = params[2]; // ob_mega + pars[2] = ob_mega->current_dir; + + fnStand(pars); + + // Reset looping flag (which would have been 1 during fnWalk) + ObjectLogic *ob_logic = (ObjectLogic *) logic_ptr; + + ob_logic->looping = 0; } -#endif + + return IR_CONT; +} + +/** + * Copies the 4 essential player structures into the savegame header - run + * script 7 of player object to request this. + * + * Remember, we cannot simply read a compact any longer but instead must + * request it from the object itself. + */ + +int32 Logic::fnPassPlayerSaveData(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + + // Copy from player object to savegame header + + memcpy(&_vm->_saveGameHeader.logic, _vm->_memory->decodePtr(params[0]), sizeof(ObjectLogic)); + memcpy(&_vm->_saveGameHeader.graphic, _vm->_memory->decodePtr(params[1]), sizeof(ObjectGraphic)); + memcpy(&_vm->_saveGameHeader.mega, _vm->_memory->decodePtr(params[2]), sizeof(ObjectMega)); + + return IR_CONT; +} + +int32 Logic::fnSendEvent(int32 *params) { + // we want to intercept the player character and have him interact + // with an object - from script + + // params: 0 id to receive event + // 1 script to run + + sendEvent(params[0], params[1]); + return IR_CONT; +} + +/** + * Add this walkgrid resource to the list of those used for routing in this + * location. Note that this is ignored if the resource is already in the list. + */ + +int32 Logic::fnAddWalkGrid(int32 *params) { + // params: 0 id of walkgrid resource + + // All objects that add walkgrids must be restarted whenever we + // re-enter a location. + + // DON'T EVER KILL GEORGE! + if (_scriptVars[ID] != 8) { + // Need to call this in case it wasn't called in script! + fnAddToKillList(NULL); + } + + _router->addWalkGrid(params[0]); + fnPreLoad(params); + return IR_CONT; +} + +/** + * Remove this walkgrid resource from the list of those used for routing in + * this location. Note that this is ignored if the resource isn't actually + * in the list. + */ + +int32 Logic::fnRemoveWalkGrid(int32 *params) { + // params: 0 id of walkgrid resource + + _router->removeWalkGrid(params[0]); + return IR_CONT; +} + +// like fnCheckEventWaiting, but starts the event rather than setting RESULT +// to 1 + +int32 Logic::fnCheckForEvent(int32 *params) { + // params: none + + if (checkEventWaiting()) { + startEvent(); + return IR_TERMINATE; + } + + return IR_CONT; +} + +// combination of fnPause and fnCheckForEvent +// - ie. does a pause, but also checks for event each cycle + +int32 Logic::fnPauseForEvent(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 number of game-cycles to pause + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + if (checkEventWaiting()) { + ob_logic->looping = 0; + startEvent(); + return IR_TERMINATE; + } + + return fnPause(params); +} + +int32 Logic::fnClearEvent(int32 *params) { + // params: none + + clearEvent(_scriptVars[ID]); + return IR_CONT; +} + +int32 Logic::fnFaceMega(int32 *params) { + // params: 0 pointer to object's logic structure + // 1 pointer to object's graphic structure + // 2 pointer to object's mega structure + // 3 pointer to object's walkdata structure + // 4 id of target mega to face + + int32 pars[7]; + + pars[0] = params[0]; + pars[1] = params[1]; + pars[2] = params[2]; + pars[3] = params[3]; + + ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); + + // If this is the start of the walk, decide where to walk to. + + if (!ob_logic->looping) { + StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); + + assert(head->fileType == GAME_OBJECT); + + // Call the base script. This is the graphic/mouse service + // call, and will set _engineMega to the ObjectMega of mega we + // want to turn to face. + + char *raw_script_ad = (char *) head; + uint32 null_pc = 3; + + runScript(raw_script_ad, raw_script_ad, &null_pc); + + _vm->_resman->closeResource(params[4]); + + ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); + + pars[3] = params[3]; + pars[4] = ob_mega->feet_x; + pars[5] = ob_mega->feet_y; + pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, _engineMega.feet_x, _engineMega.feet_y); + } + + return fnWalk(pars); +} + +int32 Logic::fnPlaySequence(int32 *params) { + // params: 0 pointer to null-terminated ascii filename + // 1 number of frames in the sequence, used for PSX. + + char filename[30]; + MovieTextObject *sequenceSpeechArray[MAX_SEQUENCE_TEXT_LINES + 1]; + + // The original code had some #ifdef blocks for skipping or muting the + // cutscenes - fondly described as "the biggest fudge in the history + // of computer games" - but at the very least we want to show the + // cutscene subtitles, so I removed them. + + debug(5, "fnPlaySequence(\"%s\");", (const char *) _vm->_memory->decodePtr(params[0])); + + // add the appropriate file extension & play it + + strcpy(filename, (const char *) _vm->_memory->decodePtr(params[0])); + + // Write to walkthrough file (zebug0.txt) + debug(5, "PLAYING SEQUENCE \"%s\"", filename); + + // now create the text sprites, if any + + if (_sequenceTextLines) + createSequenceSpeech(sequenceSpeechArray); + + // don't want to carry on streaming game music when smacker starts! + fnStopMusic(NULL); + + // pause sfx during sequence + _vm->_sound->pauseFx(); + + MoviePlayer player(_vm); + uint32 rv; + + if (_sequenceTextLines && !_scriptVars[DEMO]) + rv = player.play(filename, sequenceSpeechArray, _smackerLeadIn, _smackerLeadOut); + else + rv = player.play(filename, NULL, _smackerLeadIn, _smackerLeadOut); + + // check the error return-value + if (rv) + debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv); + + // unpause sound fx again, in case we're staying in same location + _vm->_sound->unpauseFx(); + + _smackerLeadIn = 0; + _smackerLeadOut = 0; + + // now clear the text sprites, if any + + if (_sequenceTextLines) + clearSequenceSpeech(sequenceSpeechArray); + + // now clear the screen in case the Sequence was quitted (using ESC) + // rather than fading down to black + + _vm->_graphics->clearScene(); + + // zero the entire palette in case we're about to fade up! + + byte pal[4 * 256]; + + memset(pal, 0, sizeof(pal)); + _vm->_graphics->setPalette(0, 256, pal, RDPAL_INSTANT); + + debug(5, "fnPlaySequence FINISHED"); + return IR_CONT; +} + +int32 Logic::fnShadedSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteShading(params[0], SHADED_SPRITE); + return IR_CONT; +} + +int32 Logic::fnUnshadedSprite(int32 *params) { + // params: 0 pointer to object's graphic structure + setSpriteShading(params[0], UNSHADED_SPRITE); + return IR_CONT; +} + +int32 Logic::fnFadeUp(int32 *params) { + // params: none + + _vm->_graphics->waitForFade(); + + if (_vm->_graphics->getFadeStatus() == RDFADE_BLACK) + _vm->_graphics->fadeUp(); return IR_CONT; } @@ -313,6 +2791,33 @@ int32 Logic::fnDisplayMsg(int32 *params) { return IR_CONT; } +int32 Logic::fnSetObjectHeld(int32 *params) { + // params: 0 luggage icon to set + + _vm->setLuggage(params[0]); + + _scriptVars[OBJECT_HELD] = params[0]; + _vm->_currentLuggageResource = params[0]; + + // mode locked - no menu available + _vm->_mouseModeLocked = true; + return IR_CONT; +} + +int32 Logic::fnAddSequenceText(int32 *params) { + // params: 0 text number + // 1 frame number to start the text displaying + // 2 frame number to stop the text dispalying + + assert(_sequenceTextLines < MAX_SEQUENCE_TEXT_LINES); + + _sequenceTextList[_sequenceTextLines].textNumber = params[0]; + _sequenceTextList[_sequenceTextLines].startFrame = params[1]; + _sequenceTextList[_sequenceTextLines].endFrame = params[2]; + _sequenceTextLines++; + return IR_CONT; +} + int32 Logic::fnResetGlobals(int32 *params) { // fnResetGlobals is used by the demo - so it can loop back & restart // itself @@ -346,6 +2851,132 @@ int32 Logic::fnResetGlobals(int32 *params) { return IR_CONT; } +int32 Logic::fnSetPalette(int32 *params) { + // params: 0 resource number of palette file, or 0 if it's to be + // the palette from the current screen + + _vm->setFullPalette(params[0]); + return IR_CONT; +} + +// use this in the object's service script prior to registering the mouse area +// ie. before fnRegisterMouse or fnRegisterFrame +// - best if kept at very top of service script + +int32 Logic::fnRegisterPointerText(int32 *params) { + // params: 0 local id of text line to use as pointer text + + assert(_vm->_curMouse < TOTAL_mouse_list); + + // current object id - used for checking pointer_text when mouse area + // registered (in fnRegisterMouse and fnRegisterFrame) + + _vm->_mouseList[_vm->_curMouse].id = _scriptVars[ID]; + _vm->_mouseList[_vm->_curMouse].pointer_text = params[0]; + return IR_CONT; +} + +int32 Logic::fnFetchWait(int32 *params) { + // Fetches a resource in the background but prevents the script from + // continuing until the resource is in memory. + + // params: 0 resource to fetch [guess] + + return IR_CONT; +} + +int32 Logic::fnRelease(int32 *params) { + // Releases a resource from memory. Used for freeing memory for + // sprites that have just been used and will not be used again. + // Sometimes it is better to kick out a sprite straight away so that + // the memory can be used for more frequent animations. + + // params: 0 resource to release [guess] + + return IR_CONT; +} + +int32 Logic::fnPrepareMusic(int32 *params) { + // params: 1 id of music to prepare [guess] + return IR_CONT; +} + +int32 Logic::fnSoundFetch(int32 *params) { + // params: 0 id of sound to fetch [guess] + return IR_CONT; +} + +int32 Logic::fnSmackerLeadIn(int32 *params) { + // params: 0 id of lead-in music + + // ready for use in fnPlaySequence + _smackerLeadIn = params[0]; + return IR_CONT; +} + +int32 Logic::fnSmackerLeadOut(int32 *params) { + // params: 0 id of lead-out music + + // ready for use in fnPlaySequence + _smackerLeadOut = params[0]; + return IR_CONT; +} + +/** + * Stops all FX and clears the entire FX queue. + */ + +int32 Logic::fnStopAllFx(int32 *params) { + // params: none + + _vm->_sound->clearFxQueue(); + return IR_CONT; +} + +int32 Logic::fnCheckPlayerActivity(int32 *params) { + // Used to decide when to trigger music cues described as "no player + // activity for a while" + + // params: 0 threshold delay in seconds, ie. what we want to + // check the actual delay against + + uint32 threshold = params[0] * 12; // in game cycles + + // if the actual delay is at or above the given threshold + if (_vm->_playerActivityDelay >= threshold) { + // reset activity delay counter, now that we've got a + // positive check + + _vm->_playerActivityDelay = 0; + _scriptVars[RESULT] = 1; + } else + _scriptVars[RESULT] = 0; + + return IR_CONT; +} + +int32 Logic::fnResetPlayerActivityDelay(int32 *params) { + // Use if you want to deliberately reset the "no player activity" + // counter for any reason + + // params: none + + _vm->_playerActivityDelay = 0; + return IR_CONT; +} + +int32 Logic::fnCheckMusicPlaying(int32 *params) { + // params: none + + // sets result to no. of seconds of current tune remaining + // or 0 if no music playing + + // in seconds, rounded up to the nearest second + _scriptVars[RESULT] = _vm->_sound->musicTimeRemaining(); + + return IR_CONT; +} + // FIXME: // // The original credits used a different font. I think it's stored in the @@ -374,7 +3005,7 @@ struct CreditsLine { #define CREDITS_LINE_SPACING 20 int32 Logic::fnPlayCredits(int32 *params) { - uint32 loopingMusicId = _vm->_loopingMusicId; + uint32 loopingMusicId = _vm->_sound->getLoopingMusicId(); // This function just quits the game if this is the playable demo, ie. // credits are NOT played in the demo any more! @@ -723,4 +3354,96 @@ int32 Logic::fnPlayCredits(int32 *params) { return IR_CONT; } +int32 Logic::fnSetScrollSpeedNormal(int32 *params) { + // params: none + + _vm->_scrollFraction = 16; + return IR_CONT; +} + +int32 Logic::fnSetScrollSpeedSlow(int32 *params) { + // params: none + + _vm->_scrollFraction = 32; + return IR_CONT; +} + +// called from speech scripts to remove the chooser bar when it's not +// appropriate to keep it displayed + +int32 Logic::fnRemoveChooser(int32 *params) { + // params: none + + _vm->_graphics->hideMenu(RDMENU_BOTTOM); + return IR_CONT; +} + +/** + * Alter the volume and pan of a currently playing FX + */ + +int32 Logic::fnSetFxVolAndPan(int32 *params) { + // params: 0 id of fx (ie. the id returned in 'result' from + // fnPlayFx + // 1 new volume (0..16) + // 2 new pan (-16..16) + + debug(5, "fnSetFxVolAndPan(%d, %d, %d)", params[0], params[1], params[2]); + + _vm->_sound->setFxIdVolumePan(params[0], params[1], params[2]); + return IR_CONT; +} + +/** + * Alter the volume of a currently playing FX + */ + +int32 Logic::fnSetFxVol(int32 *params) { + // params: 0 id of fx (ie. the id returned in 'result' from + // fnPlayFx + // 1 new volume (0..16) + + _vm->_sound->setFxIdVolumePan(params[0], params[1]); + return IR_CONT; +} + +int32 Logic::fnRestoreGame(int32 *params) { + // params: none + return IR_CONT; +} + +int32 Logic::fnRefreshInventory(int32 *params) { + // called from 'menu_look_or_combine' script in 'menu_master' object + // to update the menu to display a combined object while George runs + // voice-over. Note that 'object_held' must be set to the graphic of + // the combined object + + // params: none + + // can reset this now + _scriptVars[COMBINE_BASE] = 0; + + // so that the icon in 'object_held' is coloured while the rest are + // grey + _vm->_examiningMenuIcon = true; + _vm->buildMenu(); + _vm->_examiningMenuIcon = false; + + return IR_CONT; +} + +int32 Logic::fnChangeShadows(int32 *params) { + // params: none + + // if last screen was using a shading mask (see below) + if (_vm->_thisScreen.mask_flag) { + uint32 rv = _vm->_graphics->closeLightMask(); + if (rv) + error("Driver Error %.8x", rv); + _vm->_thisScreen.mask_flag = false; + } + + return IR_CONT; +} + } // End of namespace Sword2 diff --git a/sword2/icons.cpp b/sword2/icons.cpp index 5157842b13..7146743d8a 100644 --- a/sword2/icons.cpp +++ b/sword2/icons.cpp @@ -29,33 +29,6 @@ namespace Sword2 { -int32 Logic::fnAddMenuObject(int32 *params) { - // params: 0 pointer to a MenuObject structure to copy down - - _vm->addMenuObject((MenuObject *) _vm->_memory->decodePtr(params[0])); - return IR_CONT; -} - -int32 Logic::fnRefreshInventory(int32 *params) { - // called from 'menu_look_or_combine' script in 'menu_master' object - // to update the menu to display a combined object while George runs - // voice-over. Note that 'object_held' must be set to the graphic of - // the combined object - - // params: none - - // can reset this now - _scriptVars[COMBINE_BASE] = 0; - - // so that the icon in 'object_held' is coloured while the rest are - // grey - _vm->_examiningMenuIcon = true; - _vm->buildMenu(); - _vm->_examiningMenuIcon = false; - - return IR_CONT; -} - void Sword2Engine::addMenuObject(MenuObject *obj) { assert(_totalTemp < TOTAL_engine_pockets); memcpy(&_tempList[_totalTemp], obj, sizeof(MenuObject)); diff --git a/sword2/layers.cpp b/sword2/layers.cpp index 67c1238c12..86debbd8c3 100644 --- a/sword2/layers.cpp +++ b/sword2/layers.cpp @@ -31,19 +31,11 @@ #include "sword2/interpreter.h" #include "sword2/logic.h" #include "sword2/resman.h" +#include "sword2/sound.h" #include "sword2/driver/d_draw.h" namespace Sword2 { -int32 Logic::fnInitBackground(int32 *params) { - // this screen defines the size of the back buffer - - // params: 0 res id of normal background layer - cannot be 0 - // 1 1 yes 0 no for a new palette - - return _vm->initBackground(params[0], params[1]); -} - /** * This function is called when entering a new room. * @param res resource id of the normal background layer @@ -60,7 +52,7 @@ int32 Sword2Engine::initBackground(int32 res, int32 new_palette) { _resman->passTime(); _resman->expireOldResources(); - clearFxQueue(); + _sound->clearFxQueue(); _graphics->waitForFade(); debug(1, "CHANGED TO LOCATION \"%s\"", fetchObjectName(res, buf)); diff --git a/sword2/logic.cpp b/sword2/logic.cpp index a2c59934fb..db739c348f 100644 --- a/sword2/logic.cpp +++ b/sword2/logic.cpp @@ -26,6 +26,7 @@ #include "sword2/logic.h" #include "sword2/resman.h" #include "sword2/router.h" +#include "sword2/sound.h" #define LEVEL (_curObjectHub->logic_level) @@ -36,15 +37,14 @@ namespace Sword2 { Logic::Logic(Sword2Engine *vm) : _vm(vm), _kills(0), _smackerLeadOut(0), _sequenceTextLines(0), _speechTime(0), _animId(0), _speechAnimType(0), _leftClickDelay(0), - _rightClickDelay(0), _defaultResponseId(0), _totalStartups(0), - _totalScreenManagers(0), _officialTextNumber(0), _speechTextBlocNo(0), + _rightClickDelay(0), _defaultResponseId(0), _officialTextNumber(0), + _speechTextBlocNo(0), _choosing(false) { _scriptVars = NULL; memset(_subjectList, 0, sizeof(_subjectList)); memset(_eventList, 0, sizeof(_eventList)); memset(_syncList, 0, sizeof(_syncList)); _router = new Router(_vm); - initStartMenu(); } Logic::~Logic() { @@ -212,7 +212,7 @@ void Logic::expressChangeSession(uint32 sesh_id) { // Various clean-ups _router->clearWalkGridList(); - _vm->clearFxQueue(); + _vm->_sound->clearFxQueue(); _router->freeAllRouteMem(); } @@ -225,34 +225,6 @@ uint32 Logic::getRunList(void) { } /** - * This function is by start scripts. - */ - -int32 Logic::fnSetSession(int32 *params) { - // params: 0 id of new run list - - expressChangeSession(params[0]); - return IR_CONT; -} - -/** - * Causes no more objects in this logic loop to be processed. The logic engine - * will restart at the beginning of the new list. The current screen will not - * be drawn! - */ - -int32 Logic::fnEndSession(int32 *params) { - // params: 0 id of new run-list - - // terminate current and change to next run-list - expressChangeSession(params[0]); - - // stop the script - logic engine will now go around and the new - // screen will begin - return IR_STOP; -} - -/** * Move the current object up a level. Called by fnGosub command. Remember: * only the logic object has access to _curObjectHub. */ @@ -307,59 +279,6 @@ void Logic::examineRunList(void) { Debug_Printf("No run list set\n"); } -/** - * Reset the object and restart script 1 on level 0 - */ - -int32 Logic::fnTotalRestart(int32 *params) { - // mega runs this to restart its base logic again - like being cached - // in again - - // params: none - - LEVEL = 0; - _curObjectHub->script_pc[0] = 1; - return IR_TERMINATE; -} - -/** - * Mark this object for killing - to be killed when player leaves this screen. - * Object reloads and script restarts upon re-entry to screen, which causes - * this object's startup logic to be re-run every time we enter the screen. - * "Which is nice." - * - * @note Call ONCE from object's logic script, i.e. in startup code, so not - * re-called every time script frops off and restarts! - */ - -int32 Logic::fnAddToKillList(int32 *params) { - // params: none - - // DON'T EVER KILL GEORGE! - if (_scriptVars[ID] == CUR_PLAYER_ID) - return IR_CONT; - - // Scan the list to see if it's already included - - for (uint32 i = 0; i < _kills; i++) { - if (_objectKillList[i] == _scriptVars[ID]) - return IR_CONT; - } - - assert(_kills < OBJECT_KILL_LIST_SIZE); // no room at the inn - - _objectKillList[_kills++] = _scriptVars[ID]; - - // "another one bites the dust" - - // When we leave the screen, all these object resources are to be - // cleaned out of memory and the kill list emptied by doing - // '_kills = 0', ensuring that all resources are in fact still in - // memory and, more importantly, closed before killing! - - return IR_CONT; -} - void Logic::resetKillList(void) { _kills = 0; } diff --git a/sword2/logic.h b/sword2/logic.h index dcbdf547f0..f535ac4581 100644 --- a/sword2/logic.h +++ b/sword2/logic.h @@ -24,7 +24,6 @@ #define _LOGIC #include "sword2/speech.h" -#include "sword2/startup.h" namespace Sword2 { @@ -68,7 +67,8 @@ private: EventUnit _eventList[MAX_events]; - // Stores resource id of the wav to use as lead-out from smacker + // Resource id of the wav to use as lead-in/lead-out from smacker + uint32 _smackerLeadIn; uint32 _smackerLeadOut; int32 animate(int32 *params, bool reverse); @@ -133,26 +133,6 @@ private: void formText(int32 *params); bool wantSpeechForLine(uint32 wavId); - uint32 _totalStartups; - uint32 _totalScreenManagers; - uint32 _startRes; - - struct StartUp { - char description[MAX_description]; - - // id of screen manager object - uint32 start_res_id; - - // tell the manager which startup you want (if there are more - // than 1) (i.e more than 1 entrance to a screen and/or - // separate game boots) - uint32 key; - }; - - StartUp _startList[MAX_starts]; - - bool initStartMenu(void); - int16 _standbyX; // see fnSetStandbyCoords() int16 _standbyY; int16 _standbyDir; @@ -180,9 +160,6 @@ public: // could alternately use logic->looping of course bool _choosing; - void conPrintStartMenu(void); - void conStart(int start); - int runScript(char *scriptData, char *objectData, uint32 *offset); void sendEvent(uint32 id, uint32 interact_id); diff --git a/sword2/mouse.cpp b/sword2/mouse.cpp index bc08005389..c0062ba635 100644 --- a/sword2/mouse.cpp +++ b/sword2/mouse.cpp @@ -30,7 +30,6 @@ #include "sword2/resman.h" #include "sword2/sound.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" namespace Sword2 { @@ -199,14 +198,14 @@ void Sword2Engine::systemMenuMouse(void) { // playing when returning from control panels because control panel // music will overwrite it! - safe_looping_music_id = _loopingMusicId; + safe_looping_music_id = _sound->getLoopingMusicId(); pars[0] = 221; pars[1] = FX_LOOP; _logic->fnPlayMusic(pars); - // restore proper looping_music_id - _loopingMusicId = safe_looping_music_id; + // HACK: Restore proper looping_music_id + _sound->setLoopingMusicId(safe_looping_music_id); _graphics->processMenu(); @@ -262,8 +261,8 @@ void Sword2Engine::systemMenuMouse(void) { // then restart it! NB. If a game has been restored the music will be // restarted twice, but this shouldn't cause any harm. - if (_loopingMusicId) { - pars[0] = _loopingMusicId; + if (_sound->getLoopingMusicId()) { + pars[0] = _sound->getLoopingMusicId(); pars[1] = FX_LOOP; _logic->fnPlayMusic(pars); } else @@ -1081,250 +1080,4 @@ void Sword2Engine::monitorPlayerActivity(void) { } } -int32 Logic::fnNoHuman(int32 *params) { - // params: none - - _vm->noHuman(); - _vm->clearPointerText(); - - // must be normal mouse situation or a largely neutral situation - - // special menus use noHuman - - // dont hide menu in conversations - if (_scriptVars[TALK_FLAG] == 0) - _vm->_graphics->hideMenu(RDMENU_BOTTOM); - - if (_vm->_mouseMode == MOUSE_system_menu) { - // close menu - _vm->_mouseMode = MOUSE_normal; - _vm->_graphics->hideMenu(RDMENU_TOP); - } - - return IR_CONT; -} - -int32 Logic::fnAddHuman(int32 *params) { - // params: none - - // for logic scripts - _scriptVars[MOUSE_AVAILABLE] = 1; - - // off - if (_vm->_mouseStatus) { - _vm->_mouseStatus = false; // on - _vm->_mouseTouching = 1; // forces engine to choose a cursor - } - - // clear this to reset no-second-click system - _scriptVars[CLICKED_ID] = 0; - - // this is now done outside the OBJECT_HELD check in case it's set to - // zero before now! - - // unlock the mouse from possible large object lock situtations - see - // syphon in rm 3 - - _vm->_mouseModeLocked = false; - - if (_scriptVars[OBJECT_HELD]) { - // was dragging something around - // need to clear this again - _scriptVars[OBJECT_HELD] = 0; - - // and these may also need clearing, just in case - _vm->_examiningMenuIcon = false; - Logic::_scriptVars[COMBINE_BASE] = 0; - - _vm->setLuggage(0); - } - - // if mouse is over menu area - if (_vm->_mouseY > 399) { - if (_vm->_mouseMode != MOUSE_holding) { - // VITAL - reset things & rebuild the menu - _vm->_mouseMode = MOUSE_normal; - _vm->setMouse(NORMAL_MOUSE_ID); - } else - _vm->setMouse(NORMAL_MOUSE_ID); - } - - // enabled/disabled from console; status printed with on-screen debug - // info - - if (_vm->_debugger->_testingSnR) { - uint8 black[4] = { 0, 0, 0, 0 }; - uint8 white[4] = { 255, 255, 255, 0 }; - - // testing logic scripts by simulating an instant Save & - // Restore - - _vm->_graphics->setPalette(0, 1, white, RDPAL_INSTANT); - - // stops all fx & clears the queue - eg. when leaving a - // location - - _vm->clearFxQueue(); - - // Trash all object resources so they load in fresh & restart - // their logic scripts - - _vm->_resman->killAllObjects(false); - - _vm->_graphics->setPalette(0, 1, black, RDPAL_INSTANT); - } - - return IR_CONT; -} - -int32 Logic::fnRegisterMouse(int32 *params) { - // this call would be made from an objects service script 0 - // the object would be one with no graphic but with a mouse - i.e. a - // floor or one whose mouse area is manually defined rather than - // intended to fit sprite shape - - // params: 0 pointer to ObjectMouse or 0 for no write to mouse - // list - - _vm->registerMouse((ObjectMouse *) _vm->_memory->decodePtr(params[0])); - return IR_CONT; -} - -// use this in the object's service script prior to registering the mouse area -// ie. before fnRegisterMouse or fnRegisterFrame -// - best if kept at very top of service script - -int32 Logic::fnRegisterPointerText(int32 *params) { - // params: 0 local id of text line to use as pointer text - - assert(_vm->_curMouse < TOTAL_mouse_list); - - // current object id - used for checking pointer_text when mouse area - // registered (in fnRegisterMouse and fnRegisterFrame) - - _vm->_mouseList[_vm->_curMouse].id = _scriptVars[ID]; - _vm->_mouseList[_vm->_curMouse].pointer_text = params[0]; - return IR_CONT; -} - -int32 Logic::fnInitFloorMouse(int32 *params) { - // params: 0 pointer to object's mouse structure - - ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); - - // floor is always lowest priority - - ob_mouse->x1 = 0; - ob_mouse->y1 = 0; - ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; - ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; - ob_mouse->priority = 9; - ob_mouse->pointer = NORMAL_MOUSE_ID; - return IR_CONT; -} - -#define SCROLL_MOUSE_WIDTH 20 - -int32 Logic::fnSetScrollLeftMouse(int32 *params) { - // params: 0 pointer to object's mouse structure - - ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); - - // Highest priority - - ob_mouse->x1 = 0; - ob_mouse->y1 = 0; - ob_mouse->x2 = _vm->_thisScreen.scroll_offset_x + SCROLL_MOUSE_WIDTH; - ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; - ob_mouse->priority = 0; - - if (_vm->_thisScreen.scroll_offset_x > 0) { - // not fully scrolled to the left - ob_mouse->pointer = SCROLL_LEFT_MOUSE_ID; - } else { - // so the mouse area doesn't get registered - ob_mouse->pointer = 0; - } - - return IR_CONT; -} - -int32 Logic::fnSetScrollRightMouse(int32 *params) { - // params: 0 pointer to object's mouse structure - - ObjectMouse *ob_mouse = (ObjectMouse *) _vm->_memory->decodePtr(params[0]); - - // Highest priority - - ob_mouse->x1 = _vm->_thisScreen.scroll_offset_x + _vm->_graphics->_screenWide - SCROLL_MOUSE_WIDTH; - ob_mouse->y1 = 0; - ob_mouse->x2 = _vm->_thisScreen.screen_wide - 1; - ob_mouse->y2 = _vm->_thisScreen.screen_deep - 1; - ob_mouse->priority = 0; - - if (_vm->_thisScreen.scroll_offset_x < _vm->_thisScreen.max_scroll_offset_x) { - // not fully scrolled to the right - ob_mouse->pointer = SCROLL_RIGHT_MOUSE_ID; - } else { - // so the mouse area doesn't get registered - ob_mouse->pointer = 0; - } - - return IR_CONT; -} - -int32 Logic::fnSetObjectHeld(int32 *params) { - // params: 0 luggage icon to set - - _vm->setLuggage(params[0]); - - _scriptVars[OBJECT_HELD] = params[0]; - _vm->_currentLuggageResource = params[0]; - - // mode locked - no menu available - _vm->_mouseModeLocked = true; - return IR_CONT; -} - -// called from speech scripts to remove the chooser bar when it's not -// appropriate to keep it displayed - -int32 Logic::fnRemoveChooser(int32 *params) { - // params: none - - _vm->_graphics->hideMenu(RDMENU_BOTTOM); - return IR_CONT; -} - -int32 Logic::fnCheckPlayerActivity(int32 *params) { - // Used to decide when to trigger music cues described as "no player - // activity for a while" - - // params: 0 threshold delay in seconds, ie. what we want to - // check the actual delay against - - uint32 threshold = params[0] * 12; // in game cycles - - // if the actual delay is at or above the given threshold - if (_vm->_playerActivityDelay >= threshold) { - // reset activity delay counter, now that we've got a - // positive check - - _vm->_playerActivityDelay = 0; - _scriptVars[RESULT] = 1; - } else - _scriptVars[RESULT] = 0; - - return IR_CONT; -} - -int32 Logic::fnResetPlayerActivityDelay(int32 *params) { - // Use if you want to deliberately reset the "no player activity" - // counter for any reason - - // params: none - - _vm->_playerActivityDelay = 0; - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/resman.cpp b/sword2/resman.cpp index 0ac49fdb9a..ad1292ea38 100644 --- a/sword2/resman.cpp +++ b/sword2/resman.cpp @@ -27,6 +27,7 @@ #include "sword2/memory.h" #include "sword2/resman.h" #include "sword2/router.h" +#include "sword2/sound.h" #include "sword2/driver/d_draw.h" #define Debug_Printf _vm->_debugger->DebugPrintf @@ -786,7 +787,7 @@ void ResourceManager::removeAll(void) { // will still believe that the sound resources are in memory, and that // it's ok to close them. - _vm->clearFxQueue(); + _vm->_sound->clearFxQueue(); for (uint i = 0; i < _totalResFiles; i++) remove(i); @@ -803,7 +804,7 @@ void ResourceManager::killAll(bool wantInfo) { // will still believe that the sound resources are in memory, and that // it's ok to close them. - _vm->clearFxQueue(); + _vm->_sound->clearFxQueue(); for (uint i = 0; i < _totalResFiles; i++) { // Don't nuke the global variables or the player object! diff --git a/sword2/save_rest.cpp b/sword2/save_rest.cpp index 6f2e1ed6fb..d3bd0035ff 100644 --- a/sword2/save_rest.cpp +++ b/sword2/save_rest.cpp @@ -122,7 +122,7 @@ void Sword2Engine::fillSaveBuffer(byte *buffer, uint32 size, byte *desc) { _saveGameHeader.runListId = _logic->getRunList(); _saveGameHeader.feet_x = _thisScreen.feet_x; _saveGameHeader.feet_y = _thisScreen.feet_y; - _saveGameHeader.music_id = _loopingMusicId; + _saveGameHeader.music_id = _sound->getLoopingMusicId(); memcpy(&_saveGameHeader.player_hub, _resman->openResource(CUR_PLAYER_ID) + sizeof(StandardHeader), sizeof(ObjectHub)); @@ -334,14 +334,12 @@ uint32 Sword2Engine::restoreFromBuffer(byte *buffer, uint32 size) { // Any music required will be started after we've returned from // restoreControl() - see systemMenuMouse() in mouse.cpp! - _loopingMusicId = _saveGameHeader.music_id; - // Restart any looping music. Originally this was - and still is - done // in systemMenuMouse(), but with ScummVM we have other ways of // restoring savegames so it's easier to put it here as well. - if (_loopingMusicId) { - pars[0] = _loopingMusicId; + if (_saveGameHeader.music_id) { + pars[0] = _saveGameHeader.music_id; pars[1] = FX_LOOP; _logic->fnPlayMusic(pars); } else @@ -470,70 +468,4 @@ uint32 Sword2Engine::calcChecksum(byte *buffer, uint32 size) { return total; } -/** - * Copies the 4 essential player structures into the savegame header - run - * script 7 of player object to request this. - * - * Remember, we cannot simply read a compact any longer but instead must - * request it from the object itself. - */ - -int32 Logic::fnPassPlayerSaveData(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - - // Copy from player object to savegame header - - memcpy(&_vm->_saveGameHeader.logic, _vm->_memory->decodePtr(params[0]), sizeof(ObjectLogic)); - memcpy(&_vm->_saveGameHeader.graphic, _vm->_memory->decodePtr(params[1]), sizeof(ObjectGraphic)); - memcpy(&_vm->_saveGameHeader.mega, _vm->_memory->decodePtr(params[2]), sizeof(ObjectMega)); - - return IR_CONT; -} - -/** - * Reverse of fnPassPlayerSaveData() - run script 8 of player object. - */ - -int32 Logic::fnGetPlayerSaveData(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - - byte *logic_ptr = _vm->_memory->decodePtr(params[0]); - byte *graphic_ptr = _vm->_memory->decodePtr(params[1]); - byte *mega_ptr = _vm->_memory->decodePtr(params[2]); - - // Copy from savegame header to player object - - memcpy(logic_ptr, &_vm->_saveGameHeader.logic, sizeof(ObjectLogic)); - memcpy(graphic_ptr, &_vm->_saveGameHeader.graphic, sizeof(ObjectGraphic)); - memcpy(mega_ptr, &_vm->_saveGameHeader.mega, sizeof(ObjectMega)); - - // Any walk-data must be cleared - the player will be set to stand if - // he was walking when saved. - - ObjectMega *ob_mega = (ObjectMega *) mega_ptr; - - if (ob_mega->currently_walking) { - ob_mega->currently_walking = 0; - - int32 pars[3]; - - pars[0] = params[1]; // ob_graphic; - pars[1] = params[2]; // ob_mega - pars[2] = ob_mega->current_dir; - - fnStand(pars); - - // Reset looping flag (which would have been 1 during fnWalk) - ObjectLogic *ob_logic = (ObjectLogic *) logic_ptr; - - ob_logic->looping = 0; - } - - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/scroll.cpp b/sword2/scroll.cpp index 890253ffe8..82fa0fa504 100644 --- a/sword2/scroll.cpp +++ b/sword2/scroll.cpp @@ -135,41 +135,4 @@ void Sword2Engine::setScrolling(void) { } } -/** - * Set the special scroll offset variables - * - * Call when starting screens and to change the camera within screens - * - * call AFTER fnInitBackground() to override the defaults - */ - -int32 Logic::fnSetScrollCoordinate(int32 *params) { - // params: 0 feet_x value - // 1 feet_y value - - // Called feet_x and feet_y to retain intellectual compatibility with - // Sword1! - // - // feet_x & feet_y refer to the physical screen coords where the - // system will try to maintain George's feet - - _vm->_thisScreen.feet_x = params[0]; - _vm->_thisScreen.feet_y = params[1]; - return IR_CONT; -} - -int32 Logic::fnSetScrollSpeedNormal(int32 *params) { - // params: none - - _vm->_scrollFraction = 16; - return IR_CONT; -} - -int32 Logic::fnSetScrollSpeedSlow(int32 *params) { - // params: none - - _vm->_scrollFraction = 32; - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/sound.cpp b/sword2/sound.cpp index 343ec997d5..b50735ccc2 100644 --- a/sword2/sound.cpp +++ b/sword2/sound.cpp @@ -30,87 +30,104 @@ #include "common/stdafx.h" #include "common/file.h" +#include "common/system.h" + #include "sword2/sword2.h" #include "sword2/defs.h" -#include "sword2/interpreter.h" #include "sword2/logic.h" #include "sword2/resman.h" #include "sword2/sound.h" -#include "sword2/driver/d_sound.h" + +#include "sound/wave.h" namespace Sword2 { -struct FxQueueEntry { - uint32 resource; // resource id of sample - byte *data; // pointer to WAV data - uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM) - uint8 volume; // 0..16 - int8 pan; // -16..16 - uint8 type; // FX_SPOT, FX_RANDOM or FX_LOOP -}; +Sound::Sound(Sword2Engine *vm) { + int i; -// FIXME: Should be in one of the classes, I guess... + _vm = vm; + _mutex = _vm->_system->createMutex(); -static FxQueueEntry fxQueue[FXQ_LENGTH]; + for (i = 0; i < FXQ_LENGTH; i++) + _fxQueue[i].resource = 0; -/** - * Initialise the FX queue by clearing all the entries. This is only used at - * the start of the game. Later when we need to clear the queue we must also - * stop the sound and close the resource. - */ + for (i = 0; i < MAXMUS; i++) + _music[i] = NULL; -void Sword2Engine::initFxQueue(void) { - for (int i = 0; i < FXQ_LENGTH; i++) - fxQueue[i].resource = 0; + _speechPaused = false; + _musicPaused = false; + _fxPaused = false; + + _speechMuted = false; + _musicMuted = false; + _fxMuted = false; + + _mixBuffer = NULL; + _mixBufferLen = 0; + + _vm->_mixer->setupPremix(this, SoundMixer::kMusicAudioDataType); +} + +Sound::~Sound() { + _vm->_mixer->setupPremix(0); + + clearFxQueue(); + stopMusic(); + stopSpeech(); + + for (int i = 0; i < MAXMUS; i++) + delete _music[i]; + + free(_mixBuffer); + + if (_mutex) + _vm->_system->deleteMutex(_mutex); } /** * Stop all sounds, close their resources and clear the FX queue. */ -void Sword2Engine::clearFxQueue(void) { +void Sound::clearFxQueue() { for (int i = 0; i < FXQ_LENGTH; i++) { - if (fxQueue[i].resource) { - _sound->stopFx(i + 1); - _resman->closeResource(fxQueue[i].resource); - fxQueue[i].resource = 0; + if (_fxQueue[i].resource) { + stopFx(i); } } } /** - * Process the FX queue once every game cycle + * Process the FX queue. This function is called once every game cycle. */ -void Sword2Engine::processFxQueue(void) { +void Sound::processFxQueue() { for (int i = 0; i < FXQ_LENGTH; i++) { - if (!fxQueue[i].resource) + if (!_fxQueue[i].resource) continue; - switch (fxQueue[i].type) { + switch (_fxQueue[i].type) { case FX_RANDOM: // 1 in 'delay' chance of this fx occurring - if (_rnd.getRandomNumber(fxQueue[i].delay) == 0) - triggerFx(i); + if (_vm->_rnd.getRandomNumber(_fxQueue[i].delay) == 0) + playFx(&_fxQueue[i]); break; case FX_SPOT: - if (fxQueue[i].delay) - fxQueue[i].delay--; + if (_fxQueue[i].delay) + _fxQueue[i].delay--; else { - triggerFx(i); - fxQueue[i].type = FX_SPOT2; + playFx(&_fxQueue[i]); + _fxQueue[i].type = FX_SPOT2; } break; case FX_LOOP: - triggerFx(i); - fxQueue[i].type = FX_LOOPING; + playFx(&_fxQueue[i]); + _fxQueue[i].type = FX_LOOPING; break; case FX_SPOT2: // Once the FX has finished remove it from the queue. - if (!_sound->isFxPlaying(i + 1)) { - _sound->stopFx(i + 1); - _resman->closeResource(fxQueue[i].resource); - fxQueue[i].resource = 0; + if (!_fxQueue[i].handle.isActive()) { + _vm->_resman->closeResource(_fxQueue[i].resource); + _fxQueue[i].resource = 0; } break; case FX_LOOPING: @@ -121,241 +138,139 @@ void Sword2Engine::processFxQueue(void) { } } -void Sword2Engine::triggerFx(uint8 i) { - int type; - - if (fxQueue[i].type == FX_LOOP) - type = RDSE_FXLOOP; - else - type = RDSE_FXSPOT; - - uint32 len = _resman->fetchLen(fxQueue[i].resource) - sizeof(StandardHeader); - uint32 rv = _sound->playFx(i + 1, len, fxQueue[i].data, fxQueue[i].volume, fxQueue[i].pan, type); - - if (rv) - debug(5, "SFX ERROR: playFx() returned %.8x", rv); -} - -void Sword2Engine::killMusic(void) { - _loopingMusicId = 0; // clear the 'looping' flag - _sound->stopMusic(); -} - -void Sword2Engine::pauseAllSound(void) { - _sound->pauseMusic(); - _sound->pauseSpeech(); - _sound->pauseFx(); -} - -void Sword2Engine::unpauseAllSound(void) { - _sound->unpauseMusic(); - _sound->unpauseSpeech(); - _sound->unpauseFx(); -} - -int32 Logic::fnPlayFx(int32 *params) { - // params: 0 sample resource id - // 1 type (FX_SPOT, FX_RANDOM, FX_LOOP) - // 2 delay (0..65535) - // 3 volume (0..16) - // 4 pan (-16..16) - - // example script: - // fnPlayFx (FXWATER, FX_LOOP, 0, 10, 15); - // // fx_water is just a local script flag - // fx_water = result; - // . - // . - // . - // fnStopFx (fx_water); +/** + * Queue a sound effect for playing later. + * @param res the sound resource number + * @param type the type of sound effect + * @param delay when to play the sound effect + * @param volume the sound effect volume (0 through 16) + * @param pan the sound effect panning (-16 through 16) + */ +void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan) { if (_vm->_wantSfxDebug) { - char type[10]; + const char *typeStr; - switch (params[1]) { + switch (type) { case FX_SPOT: - strcpy(type, "SPOT"); + typeStr = "SPOT"; break; case FX_LOOP: - strcpy(type, "LOOPED"); + typeStr = "LOOPED"; break; case FX_RANDOM: - strcpy(type, "RANDOM"); + typeStr = "RANDOM"; break; default: - strcpy(type, "INVALID"); + typeStr = "INVALID"; break; } byte buf[NAME_LEN]; - debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->fetchObjectName(params[0], buf), params[3], params[4], params[2], type); - } - - int i; - - // Find a free slot in the FX queue - - for (i = 0; i < FXQ_LENGTH; i++) { - if (!fxQueue[i].resource) - break; - } - - if (i == FXQ_LENGTH) { - warning("No free slot in FX queue"); - return IR_CONT; + debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->fetchObjectName(res, buf), volume, pan, delay, typeStr); } - fxQueue[i].resource = params[0]; - fxQueue[i].type = params[1]; - fxQueue[i].delay = params[2]; - - if (fxQueue[i].type == FX_RANDOM) { - // For spot effects and loops the dela is the number of frames - // to wait. For random effects, however, it's the average - // number of seconds between playing the sound, so we have to - // multiply by the frame rate. - fxQueue[i].delay *= 12; - } - - fxQueue[i].volume = params[3]; - fxQueue[i].pan = params[4]; + for (int i = 0; i < FXQ_LENGTH; i++) { + if (!_fxQueue[i].resource) { + byte *data = _vm->_resman->openResource(res); + StandardHeader *header = (StandardHeader *) data; - byte *data = _vm->_resman->openResource(params[0]); - StandardHeader *header = (StandardHeader *) data; + assert(header->fileType == WAV_FILE); - assert(header->fileType == WAV_FILE); + uint32 len = _vm->_resman->fetchLen(res) - sizeof(StandardHeader); - fxQueue[i].data = data + sizeof(StandardHeader); + if (type == FX_RANDOM) { + // For spot effects and loops the delay is the + // number of frames to wait. For random + // effects, however, it's the average number of + // seconds between playing the sound, so we + // have to multiply by the frame rate. + delay *= 12; + } - // Keep track of the index in the loop so that fnStopFx() can be used - // later to kill this sound. Mainly for FX_LOOP and FX_RANDOM. + volume = (volume * SoundMixer::kMaxChannelVolume) / 16; + pan = (pan * 127) / 16; - _scriptVars[RESULT] = i; - return IR_CONT; -} + _fxQueue[i].resource = res; + _fxQueue[i].data = data + sizeof(StandardHeader); + _fxQueue[i].len = len; + _fxQueue[i].delay = delay; + _fxQueue[i].volume = volume; + _fxQueue[i].pan = pan; + _fxQueue[i].type = type; -int32 Logic::fnSoundFetch(int32 *params) { - // params: 0 id of sound to fetch [guess] - return IR_CONT; -} + // Keep track of the index in the loop so that + // fnStopFx() can be used later to kill this sound. + // Mainly for FX_LOOP and FX_RANDOM. -/** - * Alter the volume and pan of a currently playing FX - */ - -int32 Logic::fnSetFxVolAndPan(int32 *params) { - // params: 0 id of fx (ie. the id returned in 'result' from - // fnPlayFx - // 1 new volume (0..16) - // 2 new pan (-16..16) - - debug(5, "fnSetFxVolAndPan(%d, %d, %d)", params[0], params[1], params[2]); + Logic::_scriptVars[RESULT] = i; + return; + } + } - _vm->_sound->setFxIdVolumePan(params[0] + 1, params[1], params[2]); - return IR_CONT; + warning("No free slot in FX queue"); } -/** - * Alter the volume of a currently playing FX - */ - -int32 Logic::fnSetFxVol(int32 *params) { - // params: 0 id of fx (ie. the id returned in 'result' from - // fnPlayFx - // 1 new volume (0..16) - - _vm->_sound->setFxIdVolume(params[0] + 1, params[1]); - return IR_CONT; +int32 Sound::playFx(FxQueueEntry *fx) { + return playFx(&fx->handle, fx->data, fx->len, fx->volume, fx->pan, (fx->type == FX_LOOP), SoundMixer::kSFXAudioDataType); } -int32 Logic::fnStopFx(int32 *params) { - // params: 0 position in queue +int32 Sound::playFx(PlayingSoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, SoundMixer::SoundType soundType) { + if (_fxMuted) + return RD_OK; - int32 i = params[0]; - uint32 rv = _vm->_sound->stopFx(i + 1); + if (handle->isActive()) + return RDERR_FXALREADYOPEN; - if (rv) - debug(5, "SFX ERROR: closeFx() returned %.8x", rv); + Common::MemoryReadStream stream(data, len); + int rate, size; + byte flags; - // Remove from queue - if (fxQueue[i].resource) { - _vm->_resman->closeResource(fxQueue[i].resource); - fxQueue[i].resource = 0; + if (!loadWAVFromStream(stream, size, rate, flags)) { + warning("playFX: Not a valid WAV file"); + return RDERR_INVALIDWAV; } - return IR_CONT; -} - -/** - * Stops all FX and clears the entire FX queue. - */ + if (isReverseStereo()) + flags |= SoundMixer::FLAG_REVERSE_STEREO; -int32 Logic::fnStopAllFx(int32 *params) { - // params: none + if (loop) + flags |= SoundMixer::FLAG_LOOP; - _vm->clearFxQueue(); - return IR_CONT; -} - -int32 Logic::fnPrepareMusic(int32 *params) { - // params: 1 id of music to prepare [guess] - return IR_CONT; + _vm->_mixer->playRaw(handle, data + stream.pos(), size, rate, flags, -1, vol, pan, 0, 0, soundType); + return RD_OK; } /** - * Start a tune playing, to play once or to loop until stopped or next one - * played. + * This function closes a sound effect which has been previously opened for + * playing. Sound effects must be closed when they are finished with, otherwise + * you will run out of sound effect buffers. + * @param i the index of the sound to close */ -int32 Logic::fnPlayMusic(int32 *params) { - // params: 0 tune id - // 1 loop flag (0 or 1) - - char filename[128]; - bool loopFlag; - uint32 rv; - - if (params[1] == FX_LOOP) { - loopFlag = true; +int32 Sound::stopFx(int32 i) { + if (!_fxQueue[i].resource) + return RDERR_FXNOTOPEN; - // keep a note of the id, for restarting after an - // interruption to gameplay - _vm->_loopingMusicId = params[0]; - } else { - loopFlag = false; + if (_fxQueue[i].handle.isActive()) + _vm->_mixer->stopHandle(_fxQueue[i].handle); - // don't need to restart this tune after control panel or - // restore - _vm->_loopingMusicId = 0; - } - - rv = _vm->_sound->streamCompMusic(params[0], loopFlag); - - if (rv) - debug(5, "ERROR: streamCompMusic(%s, %d, %d) returned error 0x%.8x", filename, params[0], loopFlag, rv); - - return IR_CONT; + _vm->_resman->closeResource(_fxQueue[i].resource); + _fxQueue[i].resource = 0; + return RD_OK; } -int32 Logic::fnStopMusic(int32 *params) { - // params: none - - _vm->_loopingMusicId = 0; // clear the 'looping' flag - _vm->_sound->stopMusic(); - return IR_CONT; +void Sound::pauseAllSound() { + pauseMusic(); + pauseSpeech(); + pauseFx(); } -int32 Logic::fnCheckMusicPlaying(int32 *params) { - // params: none - - // sets result to no. of seconds of current tune remaining - // or 0 if no music playing - - // in seconds, rounded up to the nearest second - _scriptVars[RESULT] = _vm->_sound->musicTimeRemaining(); - - return IR_CONT; +void Sound::unpauseAllSound() { + unpauseMusic(); + unpauseSpeech(); + unpauseFx(); } } // End of namespace Sword2 diff --git a/sword2/sound.h b/sword2/sound.h index 7649a725c8..d37ebeb450 100644 --- a/sword2/sound.h +++ b/sword2/sound.h @@ -31,12 +31,25 @@ #ifndef SOUND_H #define SOUND_H -// max number of fx in queue at once [DO NOT EXCEED 255] +#include "sound/audiostream.h" +#include "sound/mixer.h" + +// Max number of sound fx +#define MAXMUS 2 + +// Max number of fx in queue at once #define FXQ_LENGTH 32 +#define BUFFER_SIZE 4096 + namespace Sword2 { -// fx types +enum { + kCLUMode = 1, + kMP3Mode, + kVorbisMode, + kFlacMode +}; enum { // These three types correspond to types set by the scripts @@ -49,6 +62,181 @@ enum { FX_LOOPING = 4 }; +extern void sword2_sound_handler(void *refCon); + +class CLUInputStream : public AudioStream { +private: + File *_file; + bool _firstTime; + uint32 _file_pos; + uint32 _end_pos; + int16 _outbuf[BUFFER_SIZE]; + byte _inbuf[BUFFER_SIZE]; + const int16 *_bufferEnd; + const int16 *_pos; + + uint16 _prev; + + void refill(); + + inline bool eosIntern() const { + return _pos >= _bufferEnd; + } + +public: + CLUInputStream(File *file, int size); + ~CLUInputStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return eosIntern(); } + bool isStereo() const { return false; } + int getRate() const { return 22050; } +}; + +class MusicInputStream : public AudioStream { +private: + int _cd; + uint32 _musicId; + AudioStream *_decoder; + int16 _buffer[BUFFER_SIZE]; + const int16 *_bufferEnd; + const int16 *_pos; + bool _remove; + uint32 _numSamples; + uint32 _samplesLeft; + bool _looping; + int32 _fading; + int32 _fadeSamples; + bool _paused; + + void refill(); + + inline bool eosIntern() const { + if (_looping) + return false; + return _remove || _pos >= _bufferEnd; + } + +public: + MusicInputStream(int cd, uint32 musicId, bool looping); + ~MusicInputStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return eosIntern(); } + bool isStereo() const { return _decoder->isStereo(); } + int getRate() const { return _decoder->getRate(); } + + void fadeUp(); + void fadeDown(); + + bool isReady() { return _decoder != NULL; } + int32 isFading() { return _fading; } + + bool readyToRemove(); + int32 getTimeRemaining(); +}; + +class Sound : public AudioStream { +private: + Sword2Engine *_vm; + + Common::MutexRef _mutex; + + struct FxQueueEntry { + PlayingSoundHandle handle; // sound handle + uint32 resource; // resource id of sample + byte *data; // pointer to WAV data + uint32 len; // WAV data length + uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM) + uint8 volume; // sound volume + int8 pan; // sound panning + uint8 type; // FX_SPOT, FX_RANDOM, FX_LOOP + }; + + FxQueueEntry _fxQueue[FXQ_LENGTH]; + + void triggerFx(uint8 i); + + bool _reverseStereo; + + bool _speechMuted; + bool _fxMuted; + bool _musicMuted; + + bool _speechPaused; + bool _fxPaused; + bool _musicPaused; + + int32 _loopingMusicId; + + PlayingSoundHandle _soundHandleSpeech; + + MusicInputStream *_music[MAXMUS]; + int16 *_mixBuffer; + int _mixBufferLen; + +public: + Sound(Sword2Engine *vm); + ~Sound(); + + // AudioStream API + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const; + bool endOfData() const; + int getRate() const; + + // End of AudioStream API + + void clearFxQueue(); + void processFxQueue(); + + void setReverseStereo(bool reverse) { _reverseStereo = reverse; } + bool isReverseStereo() const { return _reverseStereo; } + + void muteSpeech(bool mute); + bool isSpeechMute() const { return _speechMuted; } + + void muteFx(bool mute); + bool isFxMute() const { return _fxMuted; } + + void muteMusic(bool mute) { _musicMuted = mute; } + bool isMusicMute() const { return _musicMuted; } + + void setLoopingMusicId(int32 id) { _loopingMusicId = id; } + int32 getLoopingMusicId() const { return _loopingMusicId; } + + void pauseSpeech(); + void unpauseSpeech(); + + void pauseFx(); + void unpauseFx(); + + void pauseMusic(); + void unpauseMusic(); + + void pauseAllSound(); + void unpauseAllSound(); + + void queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan); + int32 playFx(FxQueueEntry *fx); + int32 playFx(PlayingSoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, SoundMixer::SoundType soundType); + int32 stopFx(int32 i); + int32 setFxIdVolumePan(int32 id, int vol, int pan = -1); + + int32 getSpeechStatus(); + int32 amISpeaking(); + int32 playCompSpeech(uint32 speechId, uint8 vol, int8 pan); + uint32 preFetchCompSpeech(uint32 speechId, uint16 **buf); + int32 stopSpeech(); + + int32 streamCompMusic(uint32 musicId, bool loop); + void stopMusic(); + int32 musicTimeRemaining(); +}; + } // End of namespace Sword2 #endif diff --git a/sword2/speech.cpp b/sword2/speech.cpp index de1b335d04..dcd3651a9e 100644 --- a/sword2/speech.cpp +++ b/sword2/speech.cpp @@ -31,667 +31,15 @@ #include "sword2/memory.h" #include "sword2/resman.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" namespace Sword2 { -int32 Logic::fnAddSubject(int32 *params) { - // params: 0 id - // 1 daves reference number - - if (_scriptVars[IN_SUBJECT] == 0) { - // This is the start of the new subject list. Set the default - // repsonse id to zero in case we're never passed one. - _defaultResponseId = 0; - } - - if (params[0] == -1) { - // Id -1 is used for setting the default response, i.e. the - // response when someone uses an object on a person and he - // doesn't know anything about it. See fnChoose() below. - - _defaultResponseId = params[1]; - } else { - debug(5, "fnAddSubject res %d, uid %d", params[0], params[1]); - _subjectList[_scriptVars[IN_SUBJECT]].res = params[0]; - _subjectList[_scriptVars[IN_SUBJECT]].ref = params[1]; - _scriptVars[IN_SUBJECT]++; - } - - return IR_CONT; -} - -int32 Logic::fnChoose(int32 *params) { - // params: none - - // This opcode is used to open the conversation menu. The human is - // switched off so there will be no normal mouse engine. - - // The player's choice is piggy-backed on the standard opcode return - // values, to be used with the CP_JUMP_ON_RETURNED opcode. As far as I - // can tell, this is the only function that uses that feature. - - uint i; - - _scriptVars[AUTO_SELECTED] = 0; - - if (_scriptVars[OBJECT_HELD]) { - // The player used an object on a person. In this case it - // triggered a conversation menu. Act as if the user tried to - // talk to the person about that object. If the person doesn't - // know anything about it, use the default response. - - uint32 response = _defaultResponseId; - - for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { - if (_subjectList[i].res == _scriptVars[OBJECT_HELD]) { - response = _subjectList[i].ref; - break; - } - } - - // The user won't be holding the object any more, and the - // conversation menu will be closed. - - _scriptVars[OBJECT_HELD] = 0; - _scriptVars[IN_SUBJECT] = 0; - return IR_CONT | (response << 3); - } - - if (_scriptVars[CHOOSER_COUNT_FLAG] == 0 && _scriptVars[IN_SUBJECT] == 1 && _subjectList[0].res == EXIT_ICON) { - // This is the first time the chooser is coming up in this - // conversation, there is only one subject and that's the - // EXIT icon. - // - // In other words, the player doesn't have anything to talk - // about. Skip it. - - // The conversation menu will be closed. We set AUTO_SELECTED - // because the speech script depends on it. - - _scriptVars[AUTO_SELECTED] = 1; - _scriptVars[IN_SUBJECT] = 0; - return IR_CONT | (_subjectList[0].ref << 3); - } - - byte *icon; - - if (!_choosing) { - // This is a new conversation menu. - - if (!_scriptVars[IN_SUBJECT]) - error("fnChoose with no subjects"); - - for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { - icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader) + RDMENU_ICONWIDE * RDMENU_ICONDEEP; - _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); - _vm->_resman->closeResource(_subjectList[i].res); - } - - for (; i < 15; i++) - _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, (uint8) i, NULL); - - _vm->_graphics->showMenu(RDMENU_BOTTOM); - _vm->setMouse(NORMAL_MOUSE_ID); - _choosing = true; - return IR_REPEAT; - } - - // The menu is there - we're just waiting for a click. We only care - // about left clicks. - - MouseEvent *me = _vm->mouseEvent(); - - if (!me || !(me->buttons & RD_LEFTBUTTONDOWN) || _vm->_mouseY < 400) - return IR_REPEAT; - - // Check for click on a menu. - - int hit = _vm->menuClick(_scriptVars[IN_SUBJECT]); - if (hit < 0) - return IR_REPEAT; - - // Hilight the clicked icon by greying the others. - - for (i = 0; i < _scriptVars[IN_SUBJECT]; i++) { - if ((int) i != hit) { - icon = _vm->_resman->openResource(_subjectList[i].res) + sizeof(StandardHeader); - _vm->_graphics->setMenuIcon(RDMENU_BOTTOM, i, icon); - _vm->_resman->closeResource(_subjectList[i].res); - } - } - - // For non-speech scripts that manually call the chooser - _scriptVars[RESULT] = _subjectList[hit].res; - - // The conversation menu will be closed - - _choosing = false; - _scriptVars[IN_SUBJECT] = 0; - _vm->setMouse(0); - - return IR_CONT | (_subjectList[hit].ref << 3); -} - -/** - * Start a conversation. - * - * Note that fnStartConversation() might accidentally be called every time the - * script loops back for another chooser, but we only want to reset the chooser - * count flag the first time this function is called, i.e. when the talk flag - * is zero. - */ - -int32 Logic::fnStartConversation(int32 *params) { - // params: none - - if (_scriptVars[TALK_FLAG] == 0) { - // See fnChooser & speech scripts - _scriptVars[CHOOSER_COUNT_FLAG] = 0; - } - - fnNoHuman(params); - return IR_CONT; -} - -/** - * End a conversation. - */ - -int32 Logic::fnEndConversation(int32 *params) { - // params: none - - _vm->_graphics->hideMenu(RDMENU_BOTTOM); - - if (_vm->_mouseY > 399) { - // Will wait for cursor to move off the bottom menu - _vm->_mouseMode = MOUSE_holding; - } - - // In case DC forgets - _scriptVars[TALK_FLAG] = 0; - - return IR_CONT; -} - // To request the status of a target, we run its 4th script, get-speech-state. // This will cause RESULT to be set to either 1 (target is waiting) or 0 // (target is busy). -/** - * Wait for a target to become waiting, i.e. not busy, then send a command to - * it. - */ - -int32 Logic::fnTheyDo(int32 *params) { - // params: 0 target - // 1 command - // 2 ins1 - // 3 ins2 - // 4 ins3 - // 5 ins4 - // 6 ins5 - - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); - assert (head->fileType == GAME_OBJECT); - - // Run the target's get-speech-state script - - int32 target = params[0]; - char *raw_script_ad = (char *) head; - uint32 null_pc = 5; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(target); - - if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND]) { - // The target is waiting, i.e. not busy, and there is no other - // command queued. Send the command. - - debug(5, "fnTheyDo: sending command to %d", target); - - _vm->_debugger->_speechScriptWaiting = 0; - - _scriptVars[SPEECH_ID] = params[0]; - _scriptVars[INS_COMMAND] = params[1]; - _scriptVars[INS1] = params[2]; - _scriptVars[INS2] = params[3]; - _scriptVars[INS3] = params[4]; - _scriptVars[INS4] = params[5]; - _scriptVars[INS5] = params[6]; - - return IR_CONT; - } - - // The target is busy. Come back again next cycle. - - _vm->_debugger->_speechScriptWaiting = target; - return IR_REPEAT; -} - -/** - * Wait for a target to become waiting, i.e. not busy, send a command to it, - * then wait for it to finish. - */ - -int32 Logic::fnTheyDoWeWait(int32 *params) { - // params: 0 pointer to ob_logic - // 1 target - // 2 command - // 3 ins1 - // 4 ins2 - // 5 ins3 - // 6 ins4 - // 7 ins5 - - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); - assert(head->fileType == GAME_OBJECT); - - // Run the target's get-speech-state script - - int32 target = params[1]; - char *raw_script_ad = (char *) head; - uint32 null_pc = 5; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(target); - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - if (_scriptVars[RESULT] == 1 && !_scriptVars[INS_COMMAND] && ob_logic->looping == 0) { - // The target is waiting, i.e. not busy, and there is no other - // command queued. We haven't sent the command yet, so do it. - - debug(5, "fnTheyDoWeWait: sending command to %d", target); - - _vm->_debugger->_speechScriptWaiting = target; - ob_logic->looping = 1; - - _scriptVars[SPEECH_ID] = params[1]; - _scriptVars[INS_COMMAND] = params[2]; - _scriptVars[INS1] = params[3]; - _scriptVars[INS2] = params[4]; - _scriptVars[INS3] = params[5]; - _scriptVars[INS4] = params[6]; - _scriptVars[INS5] = params[7]; - - return IR_REPEAT; - } - - if (ob_logic->looping == 0) { - // The command has not been sent yet. Keep waiting. - _vm->_debugger->_speechScriptWaiting = target; - return IR_REPEAT; - } - - if (_scriptVars[RESULT] == 0) { - // The command has been sent, and the target is busy doing it. - // Wait for it to finish. - - debug(5, "fnTheyDoWeWait: Waiting for %d to finish", target); - - _vm->_debugger->_speechScriptWaiting = target; - return IR_REPEAT; - } - - debug(5, "fnTheyDoWeWait: %d finished", target); - - ob_logic->looping = 0; - _vm->_debugger->_speechScriptWaiting = 0; - return IR_CONT; -} - -/** - * Wait for a target to become waiting, i.e. not busy. - */ - -int32 Logic::fnWeWait(int32 *params) { - // params: 0 target - - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[0]); - assert(head->fileType == GAME_OBJECT); - - // Run the target's get-speech-state script - - int32 target = params[0]; - char *raw_script_ad = (char *) head; - uint32 null_pc = 5; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(target); - - if (_scriptVars[RESULT] == 0) { - // The target is busy. Try again. - _vm->_debugger->_speechScriptWaiting = target; - return IR_REPEAT; - } - - // The target is waiting, i.e. not busy. - - _vm->_debugger->_speechScriptWaiting = 0; - return IR_CONT; -} - -/** - * Wait for a target to become waiting, i.e. not busy, or until we time out. - * This is useful when clicking on a target to talk to it, and it doesn't - * reply. This way, we won't lock up. - * - * If the target becomes waiting, RESULT is set to 0. If we time out, RESULT is - * set to 1. - */ - -int32 Logic::fnTimedWait(int32 *params) { - // params: 0 ob_logic - // 1 target - // 2 number of cycles before give up - - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[1]); - assert(head->fileType == GAME_OBJECT); - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - if (!ob_logic->looping) { - // This is the first time, so set up the time-out. - ob_logic->looping = params[2]; - } - - // Run the target's get-speech-state script - - int32 target = params[1]; - char *raw_script_ad = (char *) head; - uint32 null_pc = 5; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(target); - - if (_scriptVars[RESULT] == 1) { - // The target is waiting, i.e. not busy - - _vm->_debugger->_speechScriptWaiting = 0; - - ob_logic->looping = 0; - _scriptVars[RESULT] = 0; - return IR_CONT; - } - - ob_logic->looping--; - - if (!ob_logic->looping) { - // Time's up. - - debug(5, "fnTimedWait: Timed out waiting for %d", target); - _vm->_debugger->_speechScriptWaiting = 0; - - // Clear the event that hasn't been picked up - in theory, - // none of this should ever happen. - - killAllIdsEvents(target); - _scriptVars[RESULT] = 1; - return IR_CONT; - } - - // Target is busy. Keep trying. - - _vm->_debugger->_speechScriptWaiting = target; - return IR_REPEAT; -} - -enum { - INS_talk = 1, - INS_anim = 2, - INS_reverse_anim = 3, - INS_walk = 4, - INS_turn = 5, - INS_face = 6, - INS_trace = 7, - INS_no_sprite = 8, - INS_sort = 9, - INS_foreground = 10, - INS_background = 11, - INS_table_anim = 12, - INS_reverse_table_anim = 13, - INS_walk_to_anim = 14, - INS_set_frame = 15, - INS_stand_after_anim = 16, - INS_quit = 42 -}; - -/** - * Receive and sequence the commands sent from the conversation script. We have - * to do this in a slightly tweeky manner as we can no longer have generic - * scripts. - */ - -int32 Logic::fnSpeechProcess(int32 *params) { - // params: 0 pointer to ob_graphic - // 1 pointer to ob_speech - // 2 pointer to ob_logic - // 3 pointer to ob_mega - // 4 pointer to ob_walkdata - - ObjectSpeech *ob_speech = (ObjectSpeech *) _vm->_memory->decodePtr(params[1]); - - while (1) { - int32 pars[9]; - - // Check which command we're waiting for, and call the - // appropriate function. Once we're done, clear the command - // and set wait_state to 1. - // - // Note: we could save a var and ditch wait_state and check - // 'command' for non zero means busy - // - // Note: I can't see that we ever check the value of wait_state - // but perhaps it accesses that memory location directly? - - switch (ob_speech->command) { - case 0: - break; - case INS_talk: - pars[0] = params[0]; // ob_graphic - pars[1] = params[1]; // ob_speech - pars[2] = params[2]; // ob_logic - pars[3] = params[3]; // ob_mega - pars[4] = ob_speech->ins1; // encoded text number - pars[5] = ob_speech->ins2; // wav res id - pars[6] = ob_speech->ins3; // anim res id - pars[7] = ob_speech->ins4; // anim table res id - pars[8] = ob_speech->ins5; // animation mode - 0 lip synced, 1 just straight animation - - if (fnISpeak(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_turn: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = params[4]; // ob_walkdata - pars[4] = ob_speech->ins1; // direction to turn to - - if (fnTurn(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_face: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = params[4]; // ob_walkdata - pars[4] = ob_speech->ins1; // target - - if (fnFaceMega(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_anim: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = ob_speech->ins1; // anim res - - if (fnAnim(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_reverse_anim: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = ob_speech->ins1; // anim res - - if (fnReverseAnim(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_table_anim: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = ob_speech->ins1; // pointer to anim table - - if (fnMegaTableAnim(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_reverse_table_anim: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = ob_speech->ins1; // pointer to anim table - - if (fnReverseMegaTableAnim(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_no_sprite: - fnNoSprite(params); // ob_graphic - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT ; - case INS_sort: - fnSortSprite(params); // ob_graphic - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT; - case INS_foreground: - fnForeSprite(params); // ob_graphic - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT; - case INS_background: - fnBackSprite(params); // ob_graphic - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT; - case INS_walk: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = params[4]; // ob_walkdata - pars[4] = ob_speech->ins1; // target x - pars[5] = ob_speech->ins2; // target y - pars[6] = ob_speech->ins3; // target direction - - if (fnWalk(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_walk_to_anim: - pars[0] = params[2]; // ob_logic - pars[1] = params[0]; // ob_graphic - pars[2] = params[3]; // ob_mega - pars[3] = params[4]; // ob_walkdata - pars[4] = ob_speech->ins1; // anim resource - - if (fnWalkToAnim(pars) != IR_REPEAT) { - ob_speech->command = 0; - ob_speech->wait_state = 1; - } - - return IR_REPEAT; - case INS_stand_after_anim: - pars[0] = params[0]; // ob_graphic - pars[1] = params[3]; // ob_mega - pars[2] = ob_speech->ins1; // anim resource - - fnStandAfterAnim(pars); - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT; - case INS_set_frame: - pars[0] = params[0]; // ob_graphic - pars[1] = ob_speech->ins1; // anim_resource - pars[2] = ob_speech->ins2; // FIRST_FRAME or LAST_FRAME - fnSetFrame(pars); - - ob_speech->command = 0; - ob_speech->wait_state = 1; - return IR_REPEAT; - case INS_quit: - // That's it - we're finished with this - ob_speech->command = 0; - // ob_speech->wait_state = 0; - return IR_CONT; - default: - // Unimplemented command - just cancel - ob_speech->command = 0; - ob_speech->wait_state = 1; - break; - } - - if (_scriptVars[SPEECH_ID] == _scriptVars[ID]) { - // There's a new command for us! Grab the command - - // potentially we only have this cycle to do this - and - // set things up so that the command will be picked up - // on the next iteration of the while loop. - - debug(5, "fnSpeechProcess: Received new command %d", _scriptVars[INS_COMMAND]); - - _scriptVars[SPEECH_ID] = 0; - - ob_speech->command = _scriptVars[INS_COMMAND]; - ob_speech->ins1 = _scriptVars[INS1]; - ob_speech->ins2 = _scriptVars[INS2]; - ob_speech->ins3 = _scriptVars[INS3]; - ob_speech->ins4 = _scriptVars[INS4]; - ob_speech->ins5 = _scriptVars[INS5]; - ob_speech->wait_state = 0; - - _scriptVars[INS_COMMAND] = 0; - } else { - // No new command. We could run a blink anim (or - // something) here. - - ob_speech->wait_state = 1; - return IR_REPEAT; - } - } -} +// Distance kept above talking sprite +#define GAP_ABOVE_HEAD 20 enum { S_OB_GRAPHIC = 0, @@ -707,365 +55,6 @@ enum { }; /** - * It's the super versatile fnSpeak. Text and wavs can be selected in any - * combination. - * - * @note We can assume no human - there should be no human, at least! - */ - -int32 Logic::fnISpeak(int32 *params) { - // params: 0 pointer to ob_graphic - // 1 pointer to ob_speech - // 2 pointer to ob_logic - // 3 pointer to ob_mega - // 4 encoded text number - // 5 wav res id - // 6 anim res id - // 7 anim table res id - // 8 animation mode 0 lip synced, - // 1 just straight animation - - static bool cycle_skip = false; - static bool speechRunning; - - // Set up the pointers which we know we'll always need - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[S_OB_LOGIC]); - ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->decodePtr(params[S_OB_GRAPHIC]); - - // FIRST TIME ONLY: create the text, load the wav, set up the anim, - // etc. - - if (!ob_logic->looping) { - // New fudge to wait for smacker samples to finish - // since they can over-run into the game - - if (_vm->_sound->getSpeechStatus() != RDSE_SAMPLEFINISHED) - return IR_REPEAT; - - // New fudge for 'fx' subtitles: If subtitles switched off, and - // we don't want to use a wav for this line either, then just - // quit back to script right now! - - if (!_vm->_gui->_subtitles && !wantSpeechForLine(params[S_WAV])) - return IR_CONT; - - // Drop out for 1st cycle to allow walks/anims to end and - // display last frame before system locks while speech loaded - - if (!cycle_skip) { - cycle_skip = true; - return IR_REPEAT; - } - - cycle_skip = false; - - _vm->_debugger->_textNumber = params[S_TEXT]; - - // Pull out the text line to get the official text number - // (for wav id). Once the wav id's go into all script text - // commands, we'll only need this for debugging. - - uint32 text_res = params[S_TEXT] / SIZE; - uint32 local_text = params[S_TEXT] & 0xffff; - - // For testing all text & speech! - // - // A script loop can send any text number to fnISpeak and it - // will only run the valid ones or return with 'result' equal - // to '1' or '2' to mean 'invalid text resource' and 'text - // number out of range' respectively - // - // See 'testing_routines' object in George's Player Character - // section of linc - - if (_scriptVars[SYSTEM_TESTING_TEXT]) { - if (!_vm->_resman->checkValid(text_res)) { - // Not a valid resource number - invalid (null - // resource) - _scriptVars[RESULT] = 1; - return IR_CONT; - } - - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(text_res); - - if (head->fileType != TEXT_FILE) { - // Invalid - not a text resource - _vm->_resman->closeResource(text_res); - _scriptVars[RESULT] = 1; - return IR_CONT; - } - - if (!_vm->checkTextLine((byte *) head, local_text)) { - // Line number out of range - _vm->_resman->closeResource(text_res); - _scriptVars[RESULT] = 2; - return IR_CONT; - } - - _vm->_resman->closeResource(text_res); - _scriptVars[RESULT] = 0; - } - - byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text); - _officialTextNumber = READ_LE_UINT16(text); - _vm->_resman->closeResource(text_res); - - // Prevent dud lines from appearing while testing text & speech - // since these will not occur in the game anyway - - if (_scriptVars[SYSTEM_TESTING_TEXT]) { - // If actor number is 0 and text line is just a 'dash' - // character - if (_officialTextNumber == 0 && text[2] == '-' && text[3] == 0) { - _scriptVars[RESULT] = 3; - return IR_CONT; - } - } - - // Set the 'looping_flag' and the text-click-delays. We can - // left-click past the text after half a second, and - // right-click past it after a quarter of a second. - - ob_logic->looping = 1; - _leftClickDelay = 6; - _rightClickDelay = 3; - - if (_scriptVars[PLAYER_ID] != CUR_PLAYER_ID) - debug(5, "(%d) Nico: %s", _officialTextNumber, text + 2); - else { - byte buf[NAME_LEN]; - - debug(5, "(%d) %s: %s", _officialTextNumber, _vm->fetchObjectName(_scriptVars[ID], buf), text + 2); - } - - // Set up the speech animation - - if (params[S_ANIM]) { - // Just a straight anim. - _animId = params[6]; - } else if (params[S_DIR_TABLE]) { - // Use this direction table to derive the anim - // NB. ASSUMES WE HAVE A MEGA OBJECT!! - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[S_OB_MEGA]); - int32 *anim_table = (int32 *) _vm->_memory->decodePtr(params[S_DIR_TABLE]); - - _animId = anim_table[ob_mega->current_dir]; - } else { - // No animation choosen - _animId = 0; - } - - if (_animId) { - // Set the talker's graphic to the first frame of this - // speech anim for now. - - _speechAnimType = _scriptVars[SPEECHANIMFLAG]; - ob_graphic->anim_resource = _animId; - ob_graphic->anim_pc = 0; - } - - // Default back to looped lip synced anims. - _scriptVars[SPEECHANIMFLAG] = 0; - - // Set up _textX and _textY for speech panning and/or text - // sprite position. - - locateTalker(params); - - // Is it to be speech or subtitles or both? - - // Assume not running until know otherwise - speechRunning = false; - - // New fudge for 'fx' subtitles: If speech is selected, and - // this line is allowed speech (not if it's an fx subtitle!) - - if (!_vm->_sound->isSpeechMute() && wantSpeechForLine(_officialTextNumber)) { - // If the wavId parameter is zero because not yet - // compiled into speech command, we can still get it - // from the 1st 2 chars of the text line. - - if (!params[S_WAV]) - params[S_WAV] = (int32) _officialTextNumber; - - // Panning goes from -16 (left) to 16 (right) - int8 speech_pan = ((_textX - 320) * 16) / 320; - - if (speech_pan < -16) - speech_pan = -16; - else if (speech_pan > 16) - speech_pan = 16; - - uint32 rv = _vm->_sound->playCompSpeech(params[S_WAV], 16, speech_pan); - - if (rv == RD_OK) { - // Ok, we've got something to play. Set it - // playing now. (We might want to do this the - // next cycle, don't know yet.) - - speechRunning = true; - _vm->_sound->unpauseSpeech(); - } else { - debug(5, "ERROR: PlayCompSpeech(wav=%d (res=%d pos=%d)) returned %.8x", params[S_WAV], text_res, local_text, rv); - } - } - - if (_vm->_gui->_subtitles || !speechRunning) { - // We want subtitles, or the speech failed to load. - // Either way, we're going to show the text so create - // the text sprite. - - formText(params); - } - } - - // EVERY TIME: run a cycle of animation, if there is one - - if (_animId) { - // There is an animation - Increment the anim frame number. - ob_graphic->anim_pc++; - - byte *anim_file = _vm->_resman->openResource(ob_graphic->anim_resource); - AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); - - if (!_speechAnimType) { - // ANIM IS TO BE LIP-SYNC'ED & REPEATING - - if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames)) { - // End of animation - restart from frame 0 - ob_graphic->anim_pc = 0; - } else if (speechRunning && _vm->_sound->amISpeaking() == RDSE_QUIET) { - // The speech is running, but we're at a quiet - // bit. Restart from frame 0 (closed mouth). - ob_graphic->anim_pc = 0; - } - } else { - // ANIM IS TO PLAY ONCE ONLY - if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames) - 1) { - // Reached the last frame of the anim. Hold - // anim on this last frame - _animId = 0; - } - } - - _vm->_resman->closeResource(ob_graphic->anim_resource); - } else if (_speechAnimType) { - // Placed here so we actually display the last frame of the - // anim. - _speechAnimType = 0; - } - - // EVERY TIME: FIND OUT IF WE NEED TO STOP THE SPEECH NOW... - - // If there is a wav then we're using that to end the speech naturally - - bool speechFinished = false; - - // If playing a sample - - if (speechRunning) { - // Has it finished? - if (_vm->_sound->getSpeechStatus() == RDSE_SAMPLEFINISHED) - speechFinished = true; - } else if (!speechRunning && _speechTime) { - // Counting down text time because there is no sample - this - // ends the speech - - // if no sample then we're using _speechTime to end speech - // naturally - - _speechTime--; - if (!_speechTime) - speechFinished = true; - } - - // Ok, all is running along smoothly - but a click means stop - // unnaturally - - // So that we can go to the options panel while text & speech is - // being tested - if (_scriptVars[SYSTEM_TESTING_TEXT] == 0 || _vm->_mouseY > 0) { - MouseEvent *me = _vm->mouseEvent(); - - // Note that we now have TWO click-delays - one for LEFT - // button, one for RIGHT BUTTON - - if ((!_leftClickDelay && me && (me->buttons & RD_LEFTBUTTONDOWN)) || - (!_rightClickDelay && me && (me->buttons & RD_RIGHTBUTTONDOWN))) { - // Mouse click, after click_delay has expired -> end - // the speech. - - // if testing text & speech - if (_scriptVars[SYSTEM_TESTING_TEXT]) { - // and RB used to click past text - if (me->buttons & RD_RIGHTBUTTONDOWN) { - // then we want the previous line again - _scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 1; - } else { - // LB just want next line again - _scriptVars[SYSTEM_WANT_PREVIOUS_LINE] = 0; - } - } - - speechFinished = true; - - // if speech sample playing, halt it prematurely - if (speechRunning) - _vm->_sound->stopSpeech(); - } - } - - // If we are finishing the speech this cycle, do the business - - // !speechAnimType, as we want an anim which is playing once to have - // finished. - - if (speechFinished && !_speechAnimType) { - // If there is text, kill it - if (_speechTextBlocNo) { - _vm->_fontRenderer->killTextBloc(_speechTextBlocNo); - _speechTextBlocNo = 0; - } - - // if there is a speech anim, end it on closed mouth frame - if (_animId) { - _animId = 0; - ob_graphic->anim_pc = 0; - } - - speechRunning = false; - - // no longer in a script function loop - ob_logic->looping = 0; - - _vm->_debugger->_textNumber = 0; - - // reset to zero, in case text line not even extracted (since - // this number comes from the text line) - _officialTextNumber = 0; - - _scriptVars[RESULT] = 0; - return IR_CONT; - } - - // Speech still going, so decrement the click_delay if it's still - // active - - if (_leftClickDelay) - _leftClickDelay--; - - if (_rightClickDelay) - _rightClickDelay--; - - return IR_REPEAT; -} - -// Distance kept above talking sprite -#define GAP_ABOVE_HEAD 20 - -/** * Sets _textX and _textY for position of text sprite. Note that _textX is * also used to calculate speech pan. */ diff --git a/sword2/startup.cpp b/sword2/startup.cpp index 1c11bbefca..d7ae9419ea 100644 --- a/sword2/startup.cpp +++ b/sword2/startup.cpp @@ -30,13 +30,13 @@ #include "sword2/memory.h" #include "sword2/resman.h" #include "sword2/router.h" -#include "sword2/driver/d_sound.h" +#include "sword2/sound.h" -#define Debug_Printf _vm->_debugger->DebugPrintf +#define Debug_Printf _debugger->DebugPrintf namespace Sword2 { -bool Logic::initStartMenu(void) { +bool Sword2Engine::initStartMenu() { // Print out a list of all the start points available. // There should be a linc produced file called startup.txt. // This file should contain ascii numbers of all the resource game @@ -49,6 +49,7 @@ bool Logic::initStartMenu(void) { // ok, load in the master screen manager file _totalStartups = 0; + _totalScreenManagers = 0; if (!fp.open("startup.inf")) { warning("Cannot open startup.inf - the debugger won't have a start menu"); @@ -119,12 +120,12 @@ bool Logic::initStartMenu(void) { // - need to check in case un-built sections included in // start list - if (_vm->_resman->checkValid(_startRes)) { - char *raw_script = (char *) _vm->_resman->openResource(_startRes); + if (_resman->checkValid(_startRes)) { + char *raw_script = (char *) _resman->openResource(_startRes); uint32 null_pc = 0; - runScript(raw_script, raw_script, &null_pc); - _vm->_resman->closeResource(_startRes); + _logic->runScript(raw_script, raw_script, &null_pc); + _resman->closeResource(_startRes); } else warning("Start menu resource %d invalid", _startRes); } @@ -132,22 +133,16 @@ bool Logic::initStartMenu(void) { return 1; } -int32 Logic::fnRegisterStartPoint(int32 *params) { - // params: 0 id of startup script to call - key - // 1 pointer to ascii message - +void Sword2Engine::registerStartPoint(int32 key, char *name) { assert(_totalStartups < MAX_starts); - char *name = (char *) _vm->_memory->decodePtr(params[1]); - _startList[_totalStartups].start_res_id = _startRes; - _startList[_totalStartups].key = params[0]; + _startList[_totalStartups].key = key; strncpy(_startList[_totalStartups].description, name, MAX_description); _startList[_totalStartups].description[MAX_description - 1] = 0; _totalStartups++; - return IR_CONT; } /** @@ -155,7 +150,7 @@ int32 Logic::fnRegisterStartPoint(int32 *params) { * start points in the game. */ -void Logic::conPrintStartMenu(void) { +void Sword2Engine::conPrintStartMenu() { if (!_totalStartups) { Debug_Printf("Sorry - no startup positions registered?\n"); @@ -170,7 +165,7 @@ void Logic::conPrintStartMenu(void) { Debug_Printf("%d (%s)\n", i, _startList[i].description); } -void Logic::conStart(int start) { +void Sword2Engine::conStart(int start) { if (!_totalStartups) { Debug_Printf("Sorry - there are no startups!\n"); return; @@ -183,44 +178,44 @@ void Logic::conStart(int start) { // Restarting - stop sfx, music & speech! - _vm->clearFxQueue(); - fnStopMusic(NULL); - _vm->_sound->unpauseSpeech(); - _vm->_sound->stopSpeech(); + _sound->clearFxQueue(); + _logic->fnStopMusic(NULL); + _sound->unpauseSpeech(); + _sound->stopSpeech(); // Remove all resources from memory, including player object and global // variables - _vm->_resman->removeAll(); + _resman->removeAll(); // Reopen global variables resource and player object - _vm->setupPersistentResources(); + setupPersistentResources(); // Free all the route memory blocks from previous game - _router->freeAllRouteMem(); + _logic->_router->freeAllRouteMem(); // If there was speech text, kill the text block - if (_speechTextBlocNo) { - _vm->_fontRenderer->killTextBloc(_speechTextBlocNo); - _speechTextBlocNo = 0; + if (_logic->_speechTextBlocNo) { + _fontRenderer->killTextBloc(_logic->_speechTextBlocNo); + _logic->_speechTextBlocNo = 0; } // Open George - char *raw_data_ad = (char *) _vm->_resman->openResource(CUR_PLAYER_ID); - char *raw_script = (char *) _vm->_resman->openResource(_startList[start].start_res_id); + char *raw_data_ad = (char *) _resman->openResource(CUR_PLAYER_ID); + char *raw_script = (char *) _resman->openResource(_startList[start].start_res_id); // Denotes script to run uint32 null_pc = _startList[start].key & 0xffff; Debug_Printf("Running start %d\n", start); - runScript(raw_script, raw_data_ad, &null_pc); + _logic->runScript(raw_script, raw_data_ad, &null_pc); - _vm->_resman->closeResource(_startList[start].start_res_id); - _vm->_resman->closeResource(CUR_PLAYER_ID); + _resman->closeResource(_startList[start].start_res_id); + _resman->closeResource(CUR_PLAYER_ID); // Make sure there's a mouse, in case restarting while mouse not // available - fnAddHuman(NULL); + _logic->fnAddHuman(NULL); } } // End of namespace Sword2 diff --git a/sword2/startup.h b/sword2/startup.h deleted file mode 100644 index 8486d81a63..0000000000 --- a/sword2/startup.h +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright (C) 1994-1998 Revolution Software Ltd. - * Copyright (C) 2003-2005 The ScummVM project - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * $Header$ - */ - -#ifndef _STARTUP -#define _STARTUP - -#define MAX_starts 100 -#define MAX_description 100 - -namespace Sword2 { -} // End of namespace Sword2 - -#endif diff --git a/sword2/sword2.cpp b/sword2/sword2.cpp index 0afc0b3141..9bc65fcb38 100644 --- a/sword2/sword2.cpp +++ b/sword2/sword2.cpp @@ -39,7 +39,6 @@ #include "sword2/resman.h" #include "sword2/sound.h" #include "sword2/driver/d_draw.h" -#include "sword2/driver/d_sound.h" #ifdef _WIN32_WCE extern bool isSmartphone(void); @@ -190,8 +189,6 @@ Sword2Engine::Sword2Engine(GameDetector *detector, OSystem *syst) : Engine(syst) } Sword2Engine::~Sword2Engine() { - killMusic(); - delete _debugger; delete _graphics; delete _sound; @@ -261,13 +258,14 @@ int Sword2Engine::init(GameDetector &detector) { _mixer->setVolumeForSoundType(SoundMixer::kSpeechAudioDataType, ConfMan.getInt("speech_volume")); _mixer->setVolumeForSoundType(SoundMixer::kSFXAudioDataType, ConfMan.getInt("sfx_volume")); + initStartMenu(); + // During normal gameplay, we care neither about mouse button releases // nor the scroll wheel. setEventFilter(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP | RD_WHEELUP | RD_WHEELDOWN); setupPersistentResources(); initialiseFontResourceFlags(); - initFxQueue(); if (_features & GF_DEMO) Logic::_scriptVars[DEMO] = 1; @@ -511,7 +509,7 @@ void Sword2Engine::gameCycle() { setScrolling(); mouseEngine(); - processFxQueue(); + _sound->processFxQueue(); } void Sword2Engine::startGame() { @@ -564,7 +562,7 @@ void Sword2Engine::pauseGame() { if (_graphics->getFadeStatus() != RDFADE_NONE) return; - pauseAllSound(); + _sound->pauseAllSound(); // Make the mouse cursor normal. This is the only place where we are // allowed to clear the luggage this way. @@ -599,7 +597,7 @@ void Sword2Engine::unpauseGame() { if (Logic::_scriptVars[OBJECT_HELD] && _realLuggageItem) setLuggage(_realLuggageItem); - unpauseAllSound(); + _sound->unpauseAllSound(); // Put back game screen palette; see build_display.cpp setFullPalette(-1); diff --git a/sword2/sword2.h b/sword2/sword2.h index cac3119f55..c51202b447 100644 --- a/sword2/sword2.h +++ b/sword2/sword2.h @@ -41,6 +41,9 @@ #include "sword2/object.h" #include "sword2/save_rest.h" +#define MAX_starts 100 +#define MAX_description 100 + class GameDetector; class OSystem; @@ -171,6 +174,24 @@ private: MenuObject _masterMenuList[TOTAL_engine_pockets]; uint32 _totalMasters; + uint32 _totalStartups; + uint32 _totalScreenManagers; + uint32 _startRes; + + struct StartUp { + char description[MAX_description]; + + // id of screen manager object + uint32 start_res_id; + + // tell the manager which startup you want (if there are more + // than 1) (i.e more than 1 entrance to a screen and/or + // separate game boots) + uint32 key; + }; + + StartUp _startList[MAX_starts]; + public: Sword2Engine(GameDetector *detector, OSystem *syst); ~Sword2Engine(); @@ -378,25 +399,6 @@ public: void setScrolling(); - // used to store id of tunes that loop, for save & restore - uint32 _loopingMusicId; - - // to be called during system initialisation - void initFxQueue(); - - // to be called from the main loop, once per cycle - void processFxQueue(); - - // stops all fx & clears the queue - eg. when leaving a location - void clearFxQueue(); - - void pauseAllSound(); - void unpauseAllSound(); - - void killMusic(); - - void triggerFx(uint8 j); - bool _gamePaused; bool _graphicsLevelFudged; @@ -410,6 +412,10 @@ public: void initialiseFontResourceFlags(); void initialiseFontResourceFlags(uint8 language); + bool initStartMenu(); + void registerStartPoint(int32 key, char *name); + void conPrintStartMenu(); + void conStart(int start); // Convenience alias for OSystem::getMillis(). // This is a bit hackish, of course :-). diff --git a/sword2/sync.cpp b/sword2/sync.cpp index bfb5aa7edd..473bc93217 100644 --- a/sword2/sync.cpp +++ b/sword2/sync.cpp @@ -26,26 +26,6 @@ namespace Sword2 { -int32 Logic::fnSendSync(int32 *params) { - // params: 0 sync's recipient - // 1 sync value - - for (int i = 0; i < MAX_syncs; i++) { - if (_syncList[i].id == 0) { - debug(5, "%d sends sync %d to %d", _scriptVars[ID], params[1], params[0]); - _syncList[i].id = params[0]; - _syncList[i].sync = params[1]; - return IR_CONT; - } - } - - // The original code didn't even check for this condition, so maybe - // it should be a fatal error? - - warning("No free sync slot"); - return IR_CONT; -} - /** * Clear any syncs registered for this id. Call this just after the id has been * processed. Theoretically there could be more than one sync waiting for us, @@ -75,38 +55,4 @@ int Logic::getSync(void) { return -1; } -/** - * Like getSync(), but called from scripts. Sets the RESULT variable to - * the sync value, or 0 if none is found. - */ - -int32 Logic::fnGetSync(int32 *params) { - // params: none - - int slot = getSync(); - - _scriptVars[RESULT] = (slot != -1) ? _syncList[slot].sync : 0; - return IR_CONT; -} - -/** - * Wait for sync to happen. Sets the RESULT variable to the sync value, once - * it has been found. - */ - -int32 Logic::fnWaitSync(int32 *params) { - // params: none - - debug(6, "fnWaitSync: %d waits", _scriptVars[ID]); - - int slot = getSync(); - - if (slot == -1) - return IR_REPEAT; - - debug(5, "fnWaitSync: %d got sync %d", _scriptVars[ID], _syncList[slot].sync); - _scriptVars[RESULT] = _syncList[slot].sync; - return IR_CONT; -} - } // End of namespace Sword2 diff --git a/sword2/walker.cpp b/sword2/walker.cpp index da1c2b5057..d0238b4df7 100644 --- a/sword2/walker.cpp +++ b/sword2/walker.cpp @@ -36,405 +36,6 @@ namespace Sword2 { /** - * Walk mega to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set - * RESULT to 1. - */ - -int32 Logic::fnWalk(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 target x-coord - // 5 target y-coord - // 6 target direction (8 means end walk on ANY direction) - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[1]); - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); - - int16 target_x = (int16) params[4]; - int16 target_y = (int16) params[5]; - uint8 target_dir = (uint8) params[6]; - - ObjectWalkdata *ob_walkdata; - - // If this is the start of the walk, calculate the route. - - if (!ob_logic->looping) { - // If we're already there, don't even bother allocating - // memory and calling the router, just quit back & continue - // the script! This avoids an embarassing mega stand frame - // appearing for one cycle when we're already in position for - // an anim eg. repeatedly clicking on same object to repeat - // an anim - no mega frame will appear in between runs of the - // anim. - - if (ob_mega->feet_x == target_x && ob_mega->feet_y == target_y && ob_mega->current_dir == target_dir) { - _scriptVars[RESULT] = 0; - return IR_CONT; - } - - assert(params[6] >= 0 && params[6] <= 8); - - ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); - - ob_mega->walk_pc = 0; - - // Set up mem for _walkData in route_slots[] & set mega's - // 'route_slot_id' accordingly - - _router->allocateRouteMem(); - - int32 route = _router->routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir); - - // 0 = can't make route to target - // 1 = created route - // 2 = zero route but may need to turn - - if (route == 1 || route == 2) { - // so script fnWalk loop continues until end of - // walk-anim - - ob_logic->looping = 1; - - // need to animate the route now, so don't set result - // or return yet! - - // started walk - ob_mega->currently_walking = 1; - - // (see fnGetPlayerSaveData() in save_rest.cpp - } else { - _router->freeRouteMem(); - _scriptVars[RESULT] = 1; - return IR_CONT; - } - - // Walk is about to start, so set the mega's graphic resource - ob_graph->anim_resource = ob_mega->megaset_res; - } else if (_scriptVars[EXIT_FADING] && _vm->_graphics->getFadeStatus() == RDFADE_BLACK) { - // Double clicked an exit so quit the walk when screen is black - // ok, thats it - back to script and change screen - - ob_logic->looping = 0; - _router->freeRouteMem(); - - // Must clear in-case on the new screen there's a walk - // instruction (which would get cut short) - _scriptVars[EXIT_CLICK_ID] = 0; - - // finished walk - ob_mega->currently_walking = 0; - - // see fnGetPlayerSaveData() in save_rest.cpp - - _scriptVars[RESULT] = 0; - - // continue the script so that RESULT can be checked! - return IR_CONT; - } - - // get pointer to walkanim & current frame position - - WalkData *walkAnim = _router->getRouteMem(); - int32 walk_pc = ob_mega->walk_pc; - - // If stopping the walk early, overwrite the next step with a - // slow-out, then finish - - if (checkEventWaiting()) { - if (walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) { - // At the beginning of a step - ob_walkdata = (ObjectWalkdata *) _vm->_memory->decodePtr(params[3]); - _router->earlySlowOut(ob_mega, ob_walkdata); - } - } - - // Get new frame of walk - - ob_graph->anim_pc = walkAnim[walk_pc].frame; - ob_mega->current_dir = walkAnim[walk_pc].dir; - ob_mega->feet_x = walkAnim[walk_pc].x; - ob_mega->feet_y = walkAnim[walk_pc].y; - - // Check if NEXT frame is in fact the end-marker of the walk sequence - // so we can return to script just as the final (stand) frame of the - // walk is set - so that if followed by an anim, the anim's first - // frame replaces the final stand-frame of the walk (see below) - - // '512' is end-marker - if (walkAnim[walk_pc + 1].frame == 512) { - ob_logic->looping = 0; - _router->freeRouteMem(); - - // finished walk - ob_mega->currently_walking = 0; - - // (see fnGetPlayerSaveData() in save_rest.cpp - - // if George's walk has been interrupted to run a new action - // script for instance or Nico's walk has been interrupted by - // player clicking on her to talk - - // There used to be code here for checking if two megas were - // colliding, but that code had been commented out, and it - // was only run if a function that always returned zero - // returned non-zero. - - if (checkEventWaiting()) { - startEvent(); - _scriptVars[RESULT] = 1; - return IR_TERMINATE; - } else { - _scriptVars[RESULT] = 0; - - // CONTINUE the script so that RESULT can be checked! - // Also, if an anim command follows the fnWalk command, - // the 1st frame of the anim (which is always a stand - // frame itself) can replace the final stand frame of - // the walk, to hide the slight difference between the - // shrinking on the mega frames and the pre-shrunk anim - // start-frame. - - return IR_CONT; - } - } - - // Increment the walkanim frame number and come back next cycle - - ob_mega->walk_pc++; - return IR_REPEAT; -} - -/** - * Walk mega to start position of anim - */ - -int32 Logic::fnWalkToAnim(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 anim resource id - - int32 pars[7]; - - // Walkdata is needed for earlySlowOut if player clicks elsewhere - // during the walk. - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = params[2]; - pars[3] = params[3]; - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - // If this is the start of the walk, read anim file to get start coords - - if (!ob_logic->looping) { - byte *anim_file = _vm->_resman->openResource(params[4]); - AnimHeader *anim_head = _vm->fetchAnimHeader( anim_file ); - - pars[4] = anim_head->feetStartX; - pars[5] = anim_head->feetStartY; - pars[6] = anim_head->feetStartDir; - - _vm->_resman->closeResource(params[4]); - - // If start coords not yet set in anim header, use the standby - // coords (which should be set beforehand in the script). - - if (pars[4] == 0 && pars[5] == 0) { - byte buf[NAME_LEN]; - - pars[4] = _standbyX; - pars[5] = _standbyY; - pars[6] = _standbyDir; - - debug(3, "WARNING: fnWalkToAnim(%s) used standby coords", _vm->fetchObjectName(params[4], buf)); - } - - assert(pars[6] >= 0 && pars[6] <= 7); - } - - return fnWalk(pars); -} - -/** - * Turn mega to the specified direction. Just needs to call fnWalk() with - * current feet coords, so router can produce anim of turn frames. - */ - -int32 Logic::fnTurn(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 target direction - - int32 pars[7]; - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = params[2]; - pars[3] = params[3]; - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - // If this is the start of the turn, get the mega's current feet - // coords + the required direction - - if (!ob_logic->looping) { - assert(params[4] >= 0 && params[4] <= 7); - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); - - pars[4] = ob_mega->feet_x; - pars[5] = ob_mega->feet_y; - pars[6] = params[4]; - } - - return fnWalk(pars); -} - -/** - * Stand mega at (x,y,dir) - * Sets up the graphic object, but also needs to set the new 'current_dir' in - * the mega object, so the router knows in future - */ - -int32 Logic::fnStandAt(int32 *params) { - // params: 0 pointer to object's graphic structure - // 1 pointer to object's mega structure - // 2 target x-coord - // 3 target y-coord - // 4 target direction - - assert(params[4] >= 0 && params[4] <= 7); - - ObjectGraphic *ob_graph = (ObjectGraphic *) _vm->_memory->decodePtr(params[0]); - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); - - // set up the stand frame & set the mega's new direction - - ob_mega->feet_x = params[2]; - ob_mega->feet_y = params[3]; - ob_mega->current_dir = params[4]; - - // mega-set animation file - ob_graph->anim_resource = ob_mega->megaset_res; - - // dir + first stand frame (always frame 96) - ob_graph->anim_pc = params[4] + 96; - - return IR_CONT; -} - -/** - * Stand mega into the specified direction at current feet coords. - * Just needs to call fnStandAt() with current feet coords. - */ - -int32 Logic::fnStand(int32 *params) { - // params: 0 pointer to object's graphic structure - // 1 pointer to object's mega structure - // 2 target direction - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[1]); - - int32 pars[5]; - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = ob_mega->feet_x; - pars[3] = ob_mega->feet_y; - pars[4] = params[2]; - - return fnStandAt(pars); -} - -/** - * stand mega at end position of anim - */ - -int32 Logic::fnStandAfterAnim(int32 *params) { - // params: 0 pointer to object's graphic structure - // 1 pointer to object's mega structure - // 2 anim resource id - - byte *anim_file = _vm->_resman->openResource(params[2]); - AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); - - int32 pars[5]; - - pars[0] = params[0]; - pars[1] = params[1]; - - pars[2] = anim_head->feetEndX; - pars[3] = anim_head->feetEndY; - pars[4] = anim_head->feetEndDir; - - // If start coords not available either use the standby coords (which - // should be set beforehand in the script) - - if (pars[2] == 0 && pars[3] == 0) { - byte buf[NAME_LEN]; - - pars[2] = _standbyX; - pars[3] = _standbyY; - pars[4] = _standbyDir; - - debug(3, "WARNING: fnStandAfterAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); - } - - assert(pars[4] >= 0 && pars[4] <= 7); - - _vm->_resman->closeResource(params[2]); - return fnStandAt(pars); -} - -/** - * Stand mega at start position of anim - */ - -int32 Logic::fnStandAtAnim(int32 *params) { - // params: 0 pointer to object's graphic structure - // 1 pointer to object's mega structure - // 2 anim resource id - - byte *anim_file = _vm->_resman->openResource(params[2]); - AnimHeader *anim_head = _vm->fetchAnimHeader(anim_file); - - int32 pars[5]; - - pars[0] = params[0]; - pars[1] = params[1]; - - pars[2] = anim_head->feetStartX; - pars[3] = anim_head->feetStartY; - pars[4] = anim_head->feetStartDir; - - // If start coords not available use the standby coords (which should - // be set beforehand in the script) - - if (pars[2] == 0 && pars[3] == 0) { - byte buf[NAME_LEN]; - - pars[2] = _standbyX; - pars[3] = _standbyY; - pars[4] = _standbyDir; - - debug(3, "WARNING: fnStandAtAnim(%s) used standby coords", _vm->fetchObjectName(params[2], buf)); - } - - assert(pars[4] >= 0 && pars[4] <= 7); - - _vm->_resman->closeResource(params[2]); - return fnStandAt(pars); -} - -/** * Work out direction from start to dest. */ @@ -468,244 +69,4 @@ int Logic::whatTarget(int startX, int startY, int destX, int destY) { return (deltaY > 0) ? 5 : 7; } -/** - * Turn mega to face point (x,y) on the floor - * Just needs to call fnWalk() with current feet coords & direction computed - * by whatTarget() - */ - -int32 Logic::fnFaceXY(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 target x-coord - // 5 target y-coord - - int32 pars[7]; - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = params[2]; - pars[3] = params[3]; - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - // If this is the start of the turn, get the mega's current feet - // coords + the required direction - - if (!ob_logic->looping) { - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); - - pars[4] = ob_mega->feet_x; - pars[5] = ob_mega->feet_y; - pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, params[4], params[5]); - } - - return fnWalk(pars); -} - -int32 Logic::fnFaceMega(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 id of target mega to face - - int32 pars[7]; - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = params[2]; - pars[3] = params[3]; - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - // If this is the start of the walk, decide where to walk to. - - if (!ob_logic->looping) { - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); - - assert(head->fileType == GAME_OBJECT); - - // Call the base script. This is the graphic/mouse service - // call, and will set _engineMega to the ObjectMega of mega we - // want to turn to face. - - char *raw_script_ad = (char *) head; - uint32 null_pc = 3; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(params[4]); - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); - - pars[3] = params[3]; - pars[4] = ob_mega->feet_x; - pars[5] = ob_mega->feet_y; - pars[6] = whatTarget(ob_mega->feet_x, ob_mega->feet_y, _engineMega.feet_x, _engineMega.feet_y); - } - - return fnWalk(pars); -} - -/** - * Route to the left or right hand side of target id, if possible. - */ - -int32 Logic::fnWalkToTalkToMega(int32 *params) { - // params: 0 pointer to object's logic structure - // 1 pointer to object's graphic structure - // 2 pointer to object's mega structure - // 3 pointer to object's walkdata structure - // 4 id of target mega to face - // 5 distance - - int32 pars[7]; - - pars[0] = params[0]; - pars[1] = params[1]; - pars[2] = params[2]; - pars[3] = params[3]; - - ObjectLogic *ob_logic = (ObjectLogic *) _vm->_memory->decodePtr(params[0]); - - // If this is the start of the walk, calculate the route. - - if (!ob_logic->looping) { - StandardHeader *head = (StandardHeader *) _vm->_resman->openResource(params[4]); - - assert(head->fileType == GAME_OBJECT); - - // Call the base script. This is the graphic/mouse service - // call, and will set _engineMega to the ObjectMega of mega we - // want to route to. - - char *raw_script_ad = (char *) head; - uint32 null_pc = 3; - - runScript(raw_script_ad, raw_script_ad, &null_pc); - - _vm->_resman->closeResource(params[4]); - - // Stand exactly beside the mega, ie. at same y-coord - pars[5] = _engineMega.feet_y; - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[2]); - - // Apply scale factor to walk distance. Ay+B gives 256 * scale - // ie. 256 * 256 * true_scale for even better accuracy, ie. - // scale = (Ay + B) / 256 - - int scale = (ob_mega->scale_a * ob_mega->feet_y + ob_mega->scale_b) / 256; - int mega_separation = (params[5] * scale) / 256; - - debug(4, "Target is at (%d, %d), separation %d", _engineMega.feet_x, _engineMega.feet_y, mega_separation); - - if (_engineMega.feet_x < ob_mega->feet_x) { - // Target is left of us, so aim to stand to their - // right. Face down_left - - pars[4] = _engineMega.feet_x + mega_separation; - pars[6] = 5; - } else { - // Ok, must be right of us so aim to stand to their - // left. Face down_right. - - pars[4] = _engineMega.feet_x - mega_separation; - pars[6] = 3; - } - } - - return fnWalk(pars); -} - -int32 Logic::fnSetWalkGrid(int32 *params) { - // params: none - - error("fnSetWalkGrid() is no longer a valid opcode"); - return IR_CONT; -} - -/** - * Add this walkgrid resource to the list of those used for routing in this - * location. Note that this is ignored if the resource is already in the list. - */ - -int32 Logic::fnAddWalkGrid(int32 *params) { - // params: 0 id of walkgrid resource - - // All objects that add walkgrids must be restarted whenever we - // re-enter a location. - - // DON'T EVER KILL GEORGE! - if (_scriptVars[ID] != 8) { - // Need to call this in case it wasn't called in script! - fnAddToKillList(NULL); - } - - _router->addWalkGrid(params[0]); - fnPreLoad(params); - return IR_CONT; -} - -/** - * Remove this walkgrid resource from the list of those used for routing in - * this location. Note that this is ignored if the resource isn't actually - * in the list. - */ - -int32 Logic::fnRemoveWalkGrid(int32 *params) { - // params: 0 id of walkgrid resource - - _router->removeWalkGrid(params[0]); - return IR_CONT; -} - -int32 Logic::fnRegisterWalkGrid(int32 *params) { - // params: none - - error("fnRegisterWalkGrid() is no longer a valid opcode"); - return IR_CONT; -} - -int32 Logic::fnSetScaling(int32 *params) { - // params: 0 pointer to object's mega structure - // 1 scale constant A - // 2 scale constant B - - // 256 * s = A * y + B - - // Where s is system scale, which itself is (256 * actual_scale) ie. - // s == 128 is half size - - ObjectMega *ob_mega = (ObjectMega *) _vm->_memory->decodePtr(params[0]); - - ob_mega->scale_a = params[1]; - ob_mega->scale_b = params[2]; - - return IR_CONT; -} - -/** - * Set the standby walk coords to be used by fnWalkToAnim() and - * fnStandAfterAnim() when the anim header's start/end coords are zero. - * Useful during development; can stay in final game anyway. - */ - -int32 Logic::fnSetStandbyCoords(int32 *params) { - // params: 0 x-coord - // 1 y-coord - // 2 direction (0..7) - - assert(params[2] >= 0 && params[2] <= 7); - - _standbyX = (int16) params[0]; - _standbyY = (int16) params[1]; - _standbyDir = (uint8) params[2]; - - return IR_CONT; -} - } // End of namespace Sword2 |