/* Copyright (C) 1994-2004 Revolution Software Ltd * * 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$ */ // --------------------------------------------------------------------------- // A more intelligent version of the old ANIMS.C // All this stuff by James // DON'T TOUCH! // --------------------------------------------------------------------------- #include "common/stdafx.h" #include "common/file.h" #include "sword2/sword2.h" #include "sword2/defs.h" #include "sword2/controls.h" #include "sword2/interpreter.h" #include "sword2/logic.h" #include "sword2/maketext.h" #include "sword2/resman.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 // 2 resource id of animation file ObjectLogic *ob_logic; ObjectGraphic *ob_graphic; uint8 *anim_file; AnimHeader *anim_head; int32 res = params[2]; #ifdef _SWORD2_DEBUG // for animation testing & checking for correct file type StandardHeader *head; #endif // read the main parameters ob_logic = (ObjectLogic *) _vm->_memory->intToPtr(params[0]); ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[1]); if (ob_logic->looping == 0) { // This is the start of the anim - set up the first frame #ifdef _SWORD2_DEBUG // For testing all anims! // A script loop can send every resource number to the anim // function & it will only run the valid ones. See // 'testing_routines' object in George's Player Character // section of linc if (SYSTEM_TESTING_ANIMS) { // if the resource number is within range & it's not // a null resource if (_resman->Res_check_valid(res)) { // Open the resource. Can close it immediately. // We've got a pointer to the header. head = (StandardHeader *) _resman->openResource(res); _resman->closeResource(res); // if it's not an animation file if (head->fileType != ANIMATION_FILE) { // switch off the sprite // don't animate - just continue // script next cycle fnNoSprite(params + 1); return IR_STOP; } } else { // Not a valid resource number. Switch off // the sprite. Don't animate - just continue // script next cycle. fnNoSprite(params + 1); return IR_STOP; } // switch on the sprite fnSortSprite(params + 1); } #endif #ifdef _SWORD2_DEBUG // check that we haven't been passed a zero resource number if (res == 0) error("animate: %s (id %d) passed zero anim resource", _vm->fetchObjectName(ID), ID); #endif // open anim file anim_file = _vm->_resman->openResource(res); #ifdef _SWORD2_DEBUG // check this this resource is actually an animation file! head = (StandardHeader *) anim_file; if (head->fileType != ANIMATION_FILE) error("animate: %s (%d) is not an anim!", _vm->fetchObjectName(res), res); #endif // point to anim header anim_head = _vm->fetchAnimHeader(anim_file); /* #ifdef _SWORD2_DEBUG // check there's at least one frame if (anim_head->noAnimFrames == 0) error("animate: %s (%d) has zero frame count!", _vm->fetchObjectName(res), res); #endif */ // now running an anim, looping back to this 'FN' call again ob_logic->looping = 1; ob_graphic->anim_resource = res; if (reverse) ob_graphic->anim_pc = anim_head->noAnimFrames - 1; else ob_graphic->anim_pc = 0; } else if (getSync()) { // We've received a sync - return to script immediately debug(5, "**sync stopped %d**", ID); // If sync received, anim finishes right now (remaining on // last frame). Quit animation, but continue script. ob_logic->looping = 0; return IR_CONT; } else { // Not first frame, and no sync received - set up the next // frame of the anim. // open anim file and point to anim header anim_file = _vm->_resman->openResource(ob_graphic->anim_resource); anim_head = _vm->fetchAnimHeader(anim_file); if (reverse) ob_graphic->anim_pc--; else ob_graphic->anim_pc++; } // check for end of anim if (reverse) { if (ob_graphic->anim_pc == 0) ob_logic->looping = 0; } else { if (ob_graphic->anim_pc == (int32) (anim_head->noAnimFrames - 1)) ob_logic->looping = 0; } // close the anim file _vm->_resman->closeResource(ob_graphic->anim_resource); // check if we want the script to loop back & call this function again return ob_logic->looping ? IR_REPEAT : IR_STOP; } int32 Logic::megaTableAnimate(int32 *params, bool reverse) { // 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 ObjectLogic *ob_logic; ObjectMega *ob_mega; uint32 *anim_table; int32 pars[5]; // if this is the start of the anim, read the anim table to get the // appropriate anim resource ob_logic = (ObjectLogic *) _vm->_memory->intToPtr(params[0]); if (ob_logic->looping == 0) { ob_mega = (ObjectMega *) _vm->_memory->intToPtr(params[2]); anim_table = (uint32 *) _vm->_memory->intToPtr(params[3]); // appropriate anim resource is in 'table[direction]' pars[2] = anim_table[ob_mega->current_dir]; } // set up the rest of the parameters for fnAnim() pars[0] = params[0]; pars[1] = params[1]; // pars[2] only needed setting at the start of the anim // call animate() with these params 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) ObjectGraphic *ob_graphic; uint8 *anim_file; AnimHeader *anim_head; int32 res = params[1]; #ifdef _SWORD2_DEBUG // for checking for correct file type StandardHeader *head; #endif #ifdef _SWORD2_DEBUG // check that we haven't been passed a zero resource number if (res == 0) error("fnSetFrame: %s (id %d) passed zero anim resource", _vm->fetchObjectName(ID), ID); #endif // open the resource (& check it's valid) anim_file = _vm->_resman->openResource(res); #ifdef _SWORD2_DEBUG // check this this resource is actually an animation file! head = (StandardHeader *) anim_file; if (head->fileType != ANIMATION_FILE) error("fnSetFrame: %s (%d) is not an anim!", _vm->fetchObjectName(res), res); #endif // set up pointer to the animation header anim_head = _vm->fetchAnimHeader(anim_file); /* #ifdef _SWORD2_DEBUG // check there's at least one frame if (anim_head->noAnimFrames == 0) error("fnSetFrame: %s (%d) has zero frame count!", _vm->fetchObjectName(res), res); #endif */ // set up anim resource in graphic object ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); ob_graphic->anim_resource = res; if (params[2]) ob_graphic->anim_pc = anim_head->noAnimFrames - 1; else ob_graphic->anim_pc = 0; // Close the anim file and drop out of script _vm->_resman->closeResource(ob_graphic->anim_resource); return IR_CONT; } int32 Logic::fnNoSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= NO_SPRITE; return IR_CONT; } int32 Logic::fnBackPar0Sprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= BGP0_SPRITE; return IR_CONT; } int32 Logic::fnBackPar1Sprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= BGP1_SPRITE; return IR_CONT; } int32 Logic::fnBackSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= BACK_SPRITE; return IR_CONT; } int32 Logic::fnSortSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= SORT_SPRITE; return IR_CONT; } int32 Logic::fnForeSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= FORE_SPRITE; return IR_CONT; } int32 Logic::fnForePar0Sprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= FGP0_SPRITE; return IR_CONT; } int32 Logic::fnForePar1Sprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0xffff0000; ob_graphic->type |= FGP1_SPRITE; return IR_CONT; } int32 Logic::fnShadedSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) // note that drivers may still shade mega frames automatically, even // when not sent 'RDSPR_SHADOW' ob_graphic->type &= 0x0000ffff; ob_graphic->type |= SHADED_SPRITE; return IR_CONT; } int32 Logic::fnUnshadedSprite(int32 *params) { // params: 0 pointer to object's graphic structure ObjectGraphic *ob_graphic = (ObjectGraphic *) _vm->_memory->intToPtr(params[0]); // remove previous status (but don't affect the shading upper-word) ob_graphic->type &= 0x0000ffff; ob_graphic->type |= 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 = (uint16) params[2]; _sequenceTextLines++; return IR_CONT; } void Logic::createSequenceSpeech(MovieTextObject *sequenceText[]) { uint32 line; FrameHeader *frame; uint32 local_text; uint32 text_res; uint8 *text; uint32 wavId; // ie. offical text number (actor text number) bool speechRunning; char speechFile[256]; // for each sequence text line that's been logged for (line = 0; line < _sequenceTextLines; line++) { // allocate this structure sequenceText[line] = new MovieTextObject; sequenceText[line]->startFrame = _sequenceTextList[line].startFrame; sequenceText[line]->endFrame = _sequenceTextList[line].endFrame; // pull out the text line to get the official text number // (for wav id) text_res = _sequenceTextList[line].textNumber / SIZE; local_text = _sequenceTextList[line].textNumber & 0xffff; // open text resource & get the line text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text); wavId = (int32) READ_LE_UINT16(text); // now ok to close the text file _vm->_resman->closeResource(text_res); // 1st word of text line is the official line number debug(5,"(%d) SEQUENCE TEXT: %s", READ_LE_UINT16(text), text + 2); // is it to be speech or subtitles or both? // assume speech is not running until know otherwise speechRunning = false; _sequenceTextList[line].speech_mem = NULL; sequenceText[line]->speech = NULL; if (!_vm->_sound->isSpeechMute()) { // speech is selected, so try that first // set up path to speech cluster // first checking if we have speech1.clu or // speech2.clu in current directory (for translators // to test) File fp; sprintf(speechFile, "speech%d.clu", _vm->_resman->whichCd()); if (fp.open(speechFile)) fp.close(); else strcpy(speechFile, "speech.clu"); _sequenceTextList[line].speechBufferSize = _vm->_sound->preFetchCompSpeech((char *) speechFile, wavId, &_sequenceTextList[line].speech_mem); if (_sequenceTextList[line].speechBufferSize) { // ok, we've got speech! speechRunning = true; } } // if we want subtitles, or speech failed to load if (_vm->_gui->_subtitles || !speechRunning) { // open text resource & get the line text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text); // make the sprite // 'text+2' to skip the first 2 bytes which form the // line reference number // NB. The mem block containing the text sprite is // currently FLOATING! // When rendering text over a sequence we need a // different colour for the border. _sequenceTextList[line].text_mem = _vm->_fontRenderer->makeTextSprite(text + 2, 600, 255, _vm->_speechFontId, 1); // ok to close the text resource now _vm->_resman->closeResource(text_res); } else { _sequenceTextList[line].text_mem = NULL; sequenceText[line]->textSprite = NULL; } } // for drivers: NULL-terminate the array of pointers to // MovieTextObject's sequenceText[_sequenceTextLines] = NULL; // now lock all the memory blocks containing text sprites & speech // samples and set up the pointers to them, etc, for the drivers for (line = 0; line < _sequenceTextLines; line++) { // if we've made a text sprite for this line... if (_sequenceTextList[line].text_mem) { _vm->_memory->lockMemory(_sequenceTextList[line].text_mem); // now fill out the SpriteInfo structure in the // MovieTextObjectStructure frame = (FrameHeader *) _sequenceTextList[line].text_mem->ad; sequenceText[line]->textSprite = new SpriteInfo; // center text at bottom of screen sequenceText[line]->textSprite->x = 320 - frame->width / 2; sequenceText[line]->textSprite->y = 440 - frame->height; sequenceText[line]->textSprite->w = frame->width; sequenceText[line]->textSprite->h = frame->height; sequenceText[line]->textSprite->type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION; sequenceText[line]->textSprite->data = _sequenceTextList[line].text_mem->ad + sizeof(FrameHeader); } // if we've loaded a speech sample for this line... if (_sequenceTextList[line].speech_mem) { // for drivers: set up pointer to decompressed wav in // memory sequenceText[line]->speechBufferSize = _sequenceTextList[line].speechBufferSize; sequenceText[line]->speech = _sequenceTextList[line].speech_mem; } } } void Logic::clearSequenceSpeech(MovieTextObject *sequenceText[]) { uint32 line; for (line = 0; line < _sequenceTextLines; line++) { // free up the memory used by this MovieTextObject delete sequenceText[line]; // free up the mem block containing this text sprite if (_sequenceTextList[line].text_mem) _vm->_memory->freeMemory(_sequenceTextList[line].text_mem); // free up the mem block containing this speech sample if (_sequenceTextList[line].speech_mem) free(_sequenceTextList[line].speech_mem); } // IMPORTANT! Reset the line count ready for the next sequence! _sequenceTextLines = 0; } int32 Logic::fnSmackerLeadIn(int32 *params) { uint8 *leadIn; uint32 rv; #ifdef _SWORD2_DEBUG StandardHeader *header; #endif // params: 0 id of lead-in music leadIn = _vm->_resman->openResource(params[0]); #ifdef _SWORD2_DEBUG header = (StandardHeader *) leadIn; if (header->fileType != WAV_FILE) error("fnSmackerLeadIn() given invalid resource"); #endif leadIn += sizeof(StandardHeader); // wav data gets copied to sound memory rv = _vm->_sound->playFx(0, 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]; uint32 rv; MovieTextObject *sequenceSpeechArray[MAX_SEQUENCE_TEXT_LINES + 1]; uint8 *leadOut = NULL; #ifdef _SWORD2_DEBUG StandardHeader *header; #endif // 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->intToPtr(params[0])); #ifdef _SWORD2_DEBUG // check that the name paseed from script is 8 chars or less if (strlen((const char *) _vm->_memory->intToPtr(params[0])) > 8) error("Sequence filename too long"); #endif // add the appropriate file extension & play it strcpy(filename, (const char *) _vm->_memory->intToPtr(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); #ifdef _SWORD2_DEBUG header = (StandardHeader *) leadOut; if (header->fileType != WAV_FILE) error("fnSmackerLeadOut() given invalid resource"); #endif leadOut += sizeof(StandardHeader); } // play the smacker // 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); if (_sequenceTextLines && !DEMO) rv = player.play(filename, sequenceSpeechArray, leadOut); else rv = player.play(filename, NULL, leadOut); // 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; } // check the error return-value if (rv) debug(5, "MoviePlayer.play(\"%s\") returned 0x%.8x", filename, rv); // 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! PalEntry pal[256]; memset(pal, 0, 256 * sizeof(PalEntry)); _vm->_graphics->setPalette(0, 256, (uint8 *) pal, RDPAL_INSTANT); debug(5, "fnPlaySequence FINISHED"); return IR_CONT; } } // End of namespace Sword2