diff options
-rw-r--r-- | engines/draci/animation.cpp | 225 | ||||
-rw-r--r-- | engines/draci/animation.h | 20 | ||||
-rw-r--r-- | engines/draci/draci.cpp | 12 | ||||
-rw-r--r-- | engines/draci/game.cpp | 821 | ||||
-rw-r--r-- | engines/draci/game.h | 88 | ||||
-rw-r--r-- | engines/draci/mouse.cpp | 6 | ||||
-rw-r--r-- | engines/draci/mouse.h | 3 | ||||
-rw-r--r-- | engines/draci/script.cpp | 109 | ||||
-rw-r--r-- | engines/draci/script.h | 5 | ||||
-rw-r--r-- | engines/draci/walking.cpp | 17 |
10 files changed, 630 insertions, 676 deletions
diff --git a/engines/draci/animation.cpp b/engines/draci/animation.cpp index 6e1a4e4ad8..1b6cafed4e 100644 --- a/engines/draci/animation.cpp +++ b/engines/draci/animation.cpp @@ -29,13 +29,13 @@ namespace Draci { -Animation::Animation(DraciEngine *vm, int index) : _vm(vm) { - _id = kUnused; - _index = index; - _z = 0; +Animation::Animation(DraciEngine *vm, int id, uint z, bool playing) : _vm(vm) { + _id = id; + _index = kIgnoreIndex; + _z = z; clearShift(); _displacement = kNoDisplacement; - _playing = false; + _playing = playing; _looping = false; _paused = false; _canBeQuick = false; @@ -239,10 +239,6 @@ void Animation::deleteFrames() { _samples.clear(); } -void Animation::stopAnimation() { - _vm->_anims->stop(_id); -} - void Animation::exitGameLoop() { _vm->_game->setExitLoop(true); } @@ -251,73 +247,38 @@ void Animation::tellWalkingState() { _vm->_game->heroAnimationFinished(); } -Animation *AnimationManager::addAnimation(int id, uint z, bool playing) { - // Increment animation index - ++_lastIndex; - - Animation *anim = new Animation(_vm, _lastIndex); +void Animation::play() { + if (isPlaying()) { + return; + } - anim->setID(id); - anim->setZ(z); - anim->setPlaying(playing); + // Mark the first frame dirty so it gets displayed + markDirtyRect(_vm->_screen->getSurface()); - insertAnimation(anim); + setPlaying(true); - return anim; + debugC(3, kDraciAnimationDebugLevel, "Playing animation %d...", getID()); } -Animation *AnimationManager::addItem(int id, bool playing) { - Animation *anim = new Animation(_vm, kIgnoreIndex); - - anim->setID(id); - anim->setZ(256); - anim->setPlaying(playing); - - insertAnimation(anim); - - return anim; -} +void Animation::stop() { + if (!isPlaying()) { + return; + } -Animation *AnimationManager::addText(int id, bool playing) { - Animation *anim = new Animation(_vm, kIgnoreIndex); + // Clean up the last frame that was drawn before stopping + markDirtyRect(_vm->_screen->getSurface()); - anim->setID(id); - anim->setZ(257); - anim->setPlaying(playing); + setPlaying(false); - insertAnimation(anim); + // Reset the animation to the beginning + setCurrentFrame(0); + clearShift(); - return anim; + debugC(3, kDraciAnimationDebugLevel, "Stopping animation %d...", getID()); } -void AnimationManager::play(int id) { - Animation *anim = getAnimation(id); - - if (anim && !anim->isPlaying()) { - // Mark the first frame dirty so it gets displayed - anim->markDirtyRect(_vm->_screen->getSurface()); - - anim->setPlaying(true); - - debugC(3, kDraciAnimationDebugLevel, "Playing animation %d...", id); - } -} - -void AnimationManager::stop(int id) { - Animation *anim = getAnimation(id); - - if (anim && anim->isPlaying()) { - // Clean up the last frame that was drawn before stopping - anim->markDirtyRect(_vm->_screen->getSurface()); - - anim->setPlaying(false); - - // Reset the animation to the beginning - anim->setCurrentFrame(0); - anim->clearShift(); - - debugC(3, kDraciAnimationDebugLevel, "Stopping animation %d...", id); - } +void Animation::del() { + _vm->_anims->deleteAnimation(this); } void AnimationManager::pauseAnimations() { @@ -358,7 +319,10 @@ Animation *AnimationManager::getAnimation(int id) { return NULL; } -void AnimationManager::insertAnimation(Animation *anim) { +void AnimationManager::insert(Animation *anim, bool allocateIndex) { + if (allocateIndex) + anim->setIndex(++_lastIndex); + Common::List<Animation *>::iterator it; for (it = _animations.begin(); it != _animations.end(); ++it) { @@ -369,20 +333,6 @@ void AnimationManager::insertAnimation(Animation *anim) { _animations.insert(it, anim); } -void AnimationManager::addOverlay(Drawable *overlay, uint z) { - // Since this is an overlay, we don't need it to be deleted - // when the GPL Release command is invoked so we pass the index - // as kIgnoreIndex - Animation *anim = new Animation(_vm, kIgnoreIndex); - - anim->setID(kOverlayImage); - anim->setZ(z); - anim->setPlaying(true); - anim->addFrame(overlay, NULL); - - insertAnimation(anim); -} - void AnimationManager::drawScene(Surface *surf) { // Fill the screen with colour zero since some rooms may rely on the screen being black _vm->_screen->getSurface()->fill(0); @@ -431,7 +381,7 @@ void AnimationManager::sortAnimations() { Animation *anim = *next; next = _animations.reverse_erase(next); - insertAnimation(anim); + insert(anim, false); hasChanged = true; } @@ -441,21 +391,24 @@ void AnimationManager::sortAnimations() { } while (hasChanged); } -void AnimationManager::deleteAnimation(int id) { +void AnimationManager::deleteAnimation(Animation *anim) { + if (!anim) { + return; + } Common::List<Animation *>::iterator it; int index = -1; // Iterate for the first time to delete the animation for (it = _animations.begin(); it != _animations.end(); ++it) { - if ((*it)->getID() == id) { + if (*it == anim) { // Remember index of the deleted animation index = (*it)->getIndex(); delete *it; _animations.erase(it); - debugC(3, kDraciAnimationDebugLevel, "Deleting animation %d...", id); + debugC(3, kDraciAnimationDebugLevel, "Deleting animation %d...", anim->getID()); break; } @@ -515,12 +468,10 @@ void AnimationManager::deleteAfterIndex(int index) { _lastIndex = index; } -int AnimationManager::getTopAnimationID(int x, int y) const { +const Animation *AnimationManager::getTopAnimation(int x, int y) const { Common::List<Animation *>::const_iterator it; - // The default return value if no animations were found on these coordinates (not even overlays) - // i.e. the black background shows through so treat it as an overlay - int retval = kOverlayImage; + Animation *retval = NULL; // Get transparent colour for the current screen const int transparent = _vm->_screen->getSurface()->getTransparentColour(); @@ -540,24 +491,112 @@ int AnimationManager::getTopAnimationID(int x, int y) const { continue; } + bool matches = false; if (frame->getRect(anim->getCurrentFrameDisplacement()).contains(x, y)) { if (frame->getType() == kDrawableText) { - retval = anim->getID(); + matches = true; } else if (frame->getType() == kDrawableSprite && reinterpret_cast<const Sprite *>(frame)->getPixel(x, y, anim->getCurrentFrameDisplacement()) != transparent) { - retval = anim->getID(); + matches = true; } } - // Found an animation - if (retval != kOverlayImage) - break; + // Return the top-most animation object, unless it is an + // overlay sprite and there is an actual object underneath it. + if (matches) { + if (anim->getID() != kOverlayImage) { + return anim; + } else if (retval == NULL) { + retval = anim; + } + } } + // The default return value if no animations were found on these coordinates (not even overlays) return retval; } +Animation *AnimationManager::load(uint animNum) { + // Make double-sure that an animation isn't loaded more than twice, + // otherwise horrible things happen in the AnimationManager, because + // they use a simple link-list without duplicate checking. This should + // never happen unless there is a bug in the game, because all GPL2 + // commands are guarded. + assert(!getAnimation(animNum)); + + const BAFile *animFile = _vm->_animationsArchive->getFile(animNum); + Common::MemoryReadStream animationReader(animFile->_data, animFile->_length); + + uint numFrames = animationReader.readByte(); + + // The following two flags are ignored by the played. Memory logic was + // a hint to the old player whether it should cache the sprites or load + // them on demand. We have 1 memory manager and ignore these hints. + animationReader.readByte(); + // The disable erasing field is just a (poor) optimization flag that + // turns of drawing the background underneath the sprite. By reading + // the source code of the old player, I'm not sure if that would ever + // have worked. There are only 6 animations in the game with this flag + // true. All of them have just 1 animation phase and they are used to + // patch a part of the original background by a new sprite. This + // should work with the default logic as well---just play this + // animation on top of the background. Since the only meaning of the + // flag was optimization, ignoring should be OK even without dipping + // into details. + animationReader.readByte(); + const bool cyclic = animationReader.readByte(); + const bool relative = animationReader.readByte(); + + Animation *anim = new Animation(_vm, animNum, 0, false); + insert(anim, true); + + anim->setLooping(cyclic); + + for (uint i = 0; i < numFrames; ++i) { + uint spriteNum = animationReader.readUint16LE() - 1; + int x = animationReader.readSint16LE(); + int y = animationReader.readSint16LE(); + uint scaledWidth = animationReader.readUint16LE(); + uint scaledHeight = animationReader.readUint16LE(); + byte mirror = animationReader.readByte(); + int sample = animationReader.readUint16LE() - 1; + uint freq = animationReader.readUint16LE(); + uint delay = animationReader.readUint16LE(); + + // _spritesArchive is flushed when entering a room. All + // scripts in a room are responsible for loading their animations. + const BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum); + Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length, + relative ? 0 : x, relative ? 0 : y, true); + + // Some frames set the scaled dimensions to 0 even though other frames + // from the same animations have them set to normal values + // We work around this by assuming it means no scaling is necessary + if (scaledWidth == 0) { + scaledWidth = sp->getWidth(); + } + + if (scaledHeight == 0) { + scaledHeight = sp->getHeight(); + } + + sp->setScaled(scaledWidth, scaledHeight); + + if (mirror) + sp->setMirrorOn(); + + sp->setDelay(delay * 10); + + anim->addFrame(sp, _vm->_soundsArchive->getSample(sample, freq)); + if (relative) { + anim->makeLastFrameRelative(x, y); + } + } + + return anim; +} + } diff --git a/engines/draci/animation.h b/engines/draci/animation.h index aff2680c37..b669d53272 100644 --- a/engines/draci/animation.h +++ b/engines/draci/animation.h @@ -62,7 +62,7 @@ class Animation { typedef void (Animation::* AnimationCallback)(); public: - Animation(DraciEngine *v, int index); + Animation(DraciEngine *v, int id, uint z, bool playing); ~Animation(); uint getZ() const { return _z; } @@ -120,10 +120,13 @@ public: void registerCallback(AnimationCallback callback) { _callback = callback; } void doNothing() {} - void stopAnimation(); void exitGameLoop(); void tellWalkingState(); + void play(); + void stop(); + void del(); + private: uint nextFrameNum() const; void deleteFrames(); @@ -174,17 +177,13 @@ public: AnimationManager(DraciEngine *vm) : _vm(vm), _lastIndex(-1) {} ~AnimationManager() { deleteAll(); } - Animation *addAnimation(int id, uint z, bool playing); - Animation *addText(int id, bool playing); - Animation *addItem(int id, bool playing); - void addOverlay(Drawable *overlay, uint z); + void insert(Animation *anim, bool allocateIndex); + Animation *load(uint animNum); - void play(int id); - void stop(int id); void pauseAnimations(); void unpauseAnimations(); - void deleteAnimation(int id); + void deleteAnimation(Animation *anim); void deleteOverlays(); void deleteAll(); @@ -195,11 +194,10 @@ public: int getLastIndex() const { return _lastIndex; } void deleteAfterIndex(int index); - int getTopAnimationID(int x, int y) const; + const Animation *getTopAnimation(int x, int y) const; private: void sortAnimations(); - void insertAnimation(Animation *anim); DraciEngine *_vm; diff --git a/engines/draci/draci.cpp b/engines/draci/draci.cpp index d9720b77fd..f759cad4a3 100644 --- a/engines/draci/draci.cpp +++ b/engines/draci/draci.cpp @@ -242,7 +242,7 @@ void DraciEngine::handleEvents() { ? _game->getEscRoom() : _game->getPreviousRoomNum(); // Check if there is an escape room defined for the current room - if (escRoom != kNoEscRoom) { + if (escRoom >= 0) { // Schedule room change // TODO: gate 0 is not always the best one for returning from the map @@ -266,15 +266,7 @@ void DraciEngine::handleEvents() { case Common::KEYCODE_w: // Show walking map toggle _showWalkingMap = !_showWalkingMap; - if (_showWalkingMap) { - _anims->play(kWalkingMapOverlay); - _anims->play(kWalkingShortestPathOverlay); - _anims->play(kWalkingObliquePathOverlay); - } else { - _anims->stop(kWalkingMapOverlay); - _anims->stop(kWalkingShortestPathOverlay); - _anims->stop(kWalkingObliquePathOverlay); - } + _game->switchWalkingAnimations(_showWalkingMap); break; case Common::KEYCODE_q: _game->setWantQuickHero(!_game->getWantQuickHero()); diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp index f9eb55fb0e..1f3245a31b 100644 --- a/engines/draci/game.cpp +++ b/engines/draci/game.cpp @@ -135,6 +135,8 @@ Game::Game(DraciEngine *vm) : _vm(vm), _walkingState(vm) { // Set object location _objects[i]._location = (~(1 << 7) & tmp) - 1; + + _objects[i]._playingAnim = -1; } assert(numDialogues == _info._numDialogues); @@ -154,6 +156,7 @@ void Game::start() { loop(kOuterLoop, false); } } + } void Game::init() { @@ -168,61 +171,63 @@ void Game::init() { setLoopStatus(kStatusGate); setLoopSubstatus(kOuterLoop); - _animUnderCursor = kOverlayImage; + _animUnderCursor = NULL; - _currentItem = kNoItem; - _itemUnderCursor = kNoItem; + _currentItem = _itemUnderCursor = NULL; _vm->_mouse->setCursorType(kHighlightedCursor); // anything different from kNormalCursor - _objUnderCursor = kOverlayImage; + _objUnderCursor = NULL; // Set the inventory to empty initially - memset(_inventory, kNoItem, kInventorySlots * sizeof(int)); + memset(_inventory, 0, kInventorySlots * sizeof(GameItem *)); // Initialize animation for object / room titles - Animation *titleAnim = _vm->_anims->addText(kTitleText, true); - Text *title = new Text("", _vm->_smallFont, kTitleColour, 0, 0, 0); - titleAnim->addFrame(title, NULL); + _titleAnim = new Animation(_vm, kTitleText, 257, true); + _titleAnim->addFrame(new Text("", _vm->_smallFont, kTitleColour, 0, 0, 0), NULL); + _vm->_anims->insert(_titleAnim, false); // Initialize animation for speech text - Animation *speechAnim = _vm->_anims->addText(kSpeechText, true); - Text *speech = new Text("", _vm->_bigFont, kFontColour1, 0, 0, 0); - speechAnim->addFrame(speech, NULL); + Animation *speechAnim = new Animation(_vm, kSpeechText, 257, true); + speechAnim->addFrame(new Text("", _vm->_bigFont, kFontColour1, 0, 0, 0), NULL); + _vm->_anims->insert(speechAnim, false); // Initialize inventory animation. _iconsArchive is never flushed. const BAFile *f = _vm->_iconsArchive->getFile(13); - Animation *inventoryAnim = _vm->_anims->addAnimation(kInventorySprite, 255, false); + _inventoryAnim = new Animation(_vm, kInventorySprite, 255, false); Sprite *inventorySprite = new Sprite(f->_data, f->_length, 0, 0, true); - inventoryAnim->addFrame(inventorySprite, NULL); - inventoryAnim->setRelative((kScreenWidth - inventorySprite->getWidth()) / 2, + _inventoryAnim->addFrame(inventorySprite, NULL); + _inventoryAnim->setRelative((kScreenWidth - inventorySprite->getWidth()) / 2, (kScreenHeight - inventorySprite->getHeight()) / 2); + _vm->_anims->insert(_inventoryAnim, true); for (uint i = 0; i < kDialogueLines; ++i) { - _dialogueAnims[i] = _vm->_anims->addText(kDialogueLinesID - i, true); - Text *dialogueLine = new Text("", _vm->_smallFont, kLineInactiveColour, 0, 0, 0); - _dialogueAnims[i]->addFrame(dialogueLine, NULL); + _dialogueAnims[i] = new Animation(_vm, kDialogueLinesID - i, 254, true); + _dialogueAnims[i]->addFrame(new Text("", _vm->_smallFont, kLineInactiveColour, 0, 0, 0), NULL); - _dialogueAnims[i]->setZ(254); _dialogueAnims[i]->setRelative(1, kScreenHeight - (i + 1) * _vm->_smallFont->getFontHeight()); + _vm->_anims->insert(_dialogueAnims[i], false); Text *text = reinterpret_cast<Text *>(_dialogueAnims[i]->getCurrentFrame()); text->setText(""); } for (uint i = 0; i < _info._numItems; ++i) { - loadItem(i); + _items[i].load(i, _vm->_itemsArchive); } - loadObject(kDragonObject); + _objects[kDragonObject].load(kDragonObject, _vm->_objectsArchive); const GameObject *dragon = getObject(kDragonObject); debugC(4, kDraciLogicDebugLevel, "Running init program for the dragon object..."); _vm->_script->run(dragon->_program, dragon->_init); + // Add overlays for the walking map and shortest/obliqued paths. + initWalkingOverlays(); + // Make sure we enter the right room in start(). - setRoomNum(kNoEscRoom); + setRoomNum(-1); rememberRoomNumAsPrevious(); scheduleEnteringRoomUsingGate(_info._startRoom, 0); _pushedNewRoom = _pushedNewGate = -1; @@ -238,23 +243,21 @@ void Game::handleOrdinaryLoop(int x, int y) { if (_vm->_mouse->lButtonPressed()) { _vm->_mouse->lButtonSet(false); - if (_currentItem != kNoItem) { + if (_currentItem) { putItem(_currentItem, 0); - _currentItem = kNoItem; + _currentItem = NULL; updateOrdinaryCursor(); } else { - if (_objUnderCursor != kObjectNotFound) { - const GameObject *obj = &_objects[_objUnderCursor]; + if (_objUnderCursor) { + _walkingState.setCallback(&_objUnderCursor->_program, _objUnderCursor->_look); - _walkingState.setCallback(&obj->_program, obj->_look); - - if (obj->_imLook || !_currentRoom._heroOn) { + if (_objUnderCursor->_imLook || !_currentRoom._heroOn) { _walkingState.callback(); } else { - if (obj->_lookDir == kDirectionLast) { - walkHero(x, y, obj->_lookDir); + if (_objUnderCursor->_lookDir == kDirectionLast) { + walkHero(x, y, _objUnderCursor->_lookDir); } else { - walkHero(obj->_lookX, obj->_lookY, obj->_lookDir); + walkHero(_objUnderCursor->_lookX, _objUnderCursor->_lookY, _objUnderCursor->_lookDir); } } } else { @@ -267,19 +270,17 @@ void Game::handleOrdinaryLoop(int x, int y) { if (_vm->_mouse->rButtonPressed()) { _vm->_mouse->rButtonSet(false); - if (_objUnderCursor != kObjectNotFound) { - const GameObject *obj = &_objects[_objUnderCursor]; - - if (_vm->_script->testExpression(obj->_program, obj->_canUse)) { - _walkingState.setCallback(&obj->_program, obj->_use); + if (_objUnderCursor) { + if (_vm->_script->testExpression(_objUnderCursor->_program, _objUnderCursor->_canUse)) { + _walkingState.setCallback(&_objUnderCursor->_program, _objUnderCursor->_use); - if (obj->_imUse || !_currentRoom._heroOn) { + if (_objUnderCursor->_imUse || !_currentRoom._heroOn) { _walkingState.callback(); } else { - if (obj->_useDir == kDirectionLast) { - walkHero(x, y, obj->_useDir); + if (_objUnderCursor->_useDir == kDirectionLast) { + walkHero(x, y, _objUnderCursor->_useDir); } else { - walkHero(obj->_useX, obj->_useY, obj->_useDir); + walkHero(_objUnderCursor->_useX, _objUnderCursor->_useY, _objUnderCursor->_useDir); } } } else { @@ -309,14 +310,15 @@ void Game::handleInventoryLoop() { // If we are in inventory mode, all the animations except game items' // images will necessarily be paused so we can safely assume that any // animation under the cursor (a value returned by - // AnimationManager::getTopAnimationID()) will be an item animation or + // AnimationManager::getTopAnimation()) will be an item animation or // an overlay, for which we check. Item animations have their IDs // calculated by offseting their itemID from the ID of the last "special" // animation ID. In this way, we obtain its itemID. - if (_animUnderCursor != kOverlayImage && _animUnderCursor != kInventorySprite) { - _itemUnderCursor = kInventoryItemsID - _animUnderCursor; + if (_animUnderCursor != NULL && _animUnderCursor != _inventoryAnim && _animUnderCursor->getID() != kOverlayImage) { + _itemUnderCursor = getItem(kInventoryItemsID - _animUnderCursor->getID()); + assert(_itemUnderCursor->_anim == _animUnderCursor); } else { - _itemUnderCursor = kNoItem; + _itemUnderCursor = NULL; } // If the user pressed the left mouse button @@ -325,13 +327,11 @@ void Game::handleInventoryLoop() { // If there is an inventory item under the cursor and we aren't // holding any item, run its look GPL program - if (_itemUnderCursor != kNoItem && _currentItem == kNoItem) { - const GameItem *item = &_items[_itemUnderCursor]; - - _vm->_script->run(item->_program, item->_look); + if (_itemUnderCursor && !_currentItem) { + _vm->_script->run(_itemUnderCursor->_program, _itemUnderCursor->_look); // Otherwise, if we are holding an item, try to place it inside the // inventory - } else if (_currentItem != kNoItem) { + } else if (_currentItem) { const int column = scummvm_lround( (_vm->_mouse->getPosX() - kInventoryX + kInventoryItemWidth / 2.) / kInventoryItemWidth) - 1; @@ -342,22 +342,22 @@ void Game::handleInventoryLoop() { putItem(_currentItem, index); // Remove it from our hands - _currentItem = kNoItem; + _currentItem = NULL; } } else if (_vm->_mouse->rButtonPressed()) { _vm->_mouse->rButtonSet(false); // If we right-clicked outside the inventory, close it - if (_animUnderCursor != kInventorySprite && _itemUnderCursor == kNoItem) { + if (_animUnderCursor != _inventoryAnim && !_itemUnderCursor) { inventoryDone(); // If there is an inventory item under our cursor - } else if (_itemUnderCursor != kNoItem) { + } else if (_itemUnderCursor) { // Again, we have two possibilities: // The first is that there is no item in our hands. // In that case, just take the inventory item from the inventory. - if (_currentItem == kNoItem) { + if (!_currentItem) { _currentItem = _itemUnderCursor; removeItem(_itemUnderCursor); @@ -366,10 +366,8 @@ void Game::handleInventoryLoop() { // which will check if the two items are combinable and, finally, // run the use script for the item. } else { - const GameItem *item = &_items[_itemUnderCursor]; - - if (_vm->_script->testExpression(item->_program, item->_canUse)) { - _vm->_script->run(item->_program, item->_use); + if (_vm->_script->testExpression(_itemUnderCursor->_program, _itemUnderCursor->_canUse)) { + _vm->_script->run(_itemUnderCursor->_program, _itemUnderCursor->_use); } } updateInventoryCursor(); @@ -386,7 +384,7 @@ void Game::handleDialogueLoop() { for (int i = 0; i < kDialogueLines; ++i) { text = reinterpret_cast<Text *>(_dialogueAnims[i]->getCurrentFrame()); - if (_animUnderCursor == _dialogueAnims[i]->getID()) { + if (_animUnderCursor == _dialogueAnims[i]) { text->setColour(kLineActiveColour); } else { text->setColour(kLineInactiveColour); @@ -509,9 +507,9 @@ void Game::loop(LoopSubstatus substatus, bool shouldExit) { // corresponding to it int x = _vm->_mouse->getPosX(); int y = _vm->_mouse->getPosY(); - _animUnderCursor = _vm->_anims->getTopAnimationID(x, y); + _animUnderCursor = _vm->_anims->getTopAnimation(x, y); _objUnderCursor = getObjectWithAnimation(_animUnderCursor); - debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor); + debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor ? _animUnderCursor->getID() : -1); switch (_loopStatus) { case kStatusOrdinary: @@ -547,9 +545,9 @@ void Game::updateOrdinaryCursor() { bool mouseChanged = false; // If there is no game object under the cursor, try using the room itself - if (_objUnderCursor == kObjectNotFound) { + if (!_objUnderCursor) { if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) { - if (_currentItem == kNoItem) { + if (!_currentItem) { _vm->_mouse->setCursorType(kHighlightedCursor); } else { _vm->_mouse->loadItemCursor(_currentItem, true); @@ -558,14 +556,12 @@ void Game::updateOrdinaryCursor() { } // If there *is* a game object under the cursor, update the cursor image } else { - const GameObject *obj = &_objects[_objUnderCursor]; - // If there is no walking direction set on the object (i.e. the object // is not a gate / exit), test whether it can be used and, if so, // update the cursor image (highlight it). - if (obj->_walkDir == 0) { - if (_vm->_script->testExpression(obj->_program, obj->_canUse)) { - if (_currentItem == kNoItem) { + if (_objUnderCursor->_walkDir == 0) { + if (_vm->_script->testExpression(_objUnderCursor->_program, _objUnderCursor->_canUse)) { + if (!_currentItem) { _vm->_mouse->setCursorType(kHighlightedCursor); } else { _vm->_mouse->loadItemCursor(_currentItem, true); @@ -575,14 +571,14 @@ void Game::updateOrdinaryCursor() { // If the walking direction *is* set, the game object is a gate, so update // the cursor image to the appropriate arrow. } else { - _vm->_mouse->setCursorType((CursorType)obj->_walkDir); + _vm->_mouse->setCursorType((CursorType)_objUnderCursor->_walkDir); mouseChanged = true; } } // Load the appropriate cursor (item image if an item is held or ordinary cursor // if not) if (!mouseChanged) { - if (_currentItem == kNoItem) { + if (!_currentItem) { _vm->_mouse->setCursorType(kNormalCursor); } else { _vm->_mouse->loadItemCursor(_currentItem, false); @@ -594,11 +590,9 @@ void Game::updateInventoryCursor() { // Fetch mouse coordinates bool mouseChanged = false; - if (_itemUnderCursor != kNoItem) { - const GameItem *item = &_items[_itemUnderCursor]; - - if (_vm->_script->testExpression(item->_program, item->_canUse)) { - if (_currentItem == kNoItem) { + if (_itemUnderCursor) { + if (_vm->_script->testExpression(_itemUnderCursor->_program, _itemUnderCursor->_canUse)) { + if (!_currentItem) { _vm->_mouse->setCursorType(kHighlightedCursor); } else { _vm->_mouse->loadItemCursor(_currentItem, true); @@ -607,7 +601,7 @@ void Game::updateInventoryCursor() { } } if (!mouseChanged) { - if (_currentItem == kNoItem) { + if (!_currentItem) { _vm->_mouse->setCursorType(kNormalCursor); } else { _vm->_mouse->loadItemCursor(_currentItem, false); @@ -621,101 +615,85 @@ void Game::updateTitle(int x, int y) { const int smallFontHeight = _vm->_smallFont->getFontHeight(); // Fetch the dedicated objects' title animation / current frame - Animation *titleAnim = _vm->_anims->getAnimation(kTitleText); - Text *title = reinterpret_cast<Text *>(titleAnim->getCurrentFrame()); + Text *title = reinterpret_cast<Text *>(_titleAnim->getCurrentFrame()); // Mark dirty rectangle to delete the previous text - titleAnim->markDirtyRect(surface); + _titleAnim->markDirtyRect(surface); if (_loopStatus == kStatusInventory) { // If there is no item under the cursor, delete the title. // Otherwise, show the item's title. - if (_itemUnderCursor == kNoItem) { - title->setText(""); - } else { - const GameItem *item = &_items[_itemUnderCursor]; - title->setText(item->_title); - } + title->setText(_itemUnderCursor ? _itemUnderCursor->_title : ""); } else { // If there is no object under the cursor, delete the title. // Otherwise, show the object's title. - if (_objUnderCursor == kObjectNotFound) { - title->setText(""); - } else { - const GameObject *obj = &_objects[_objUnderCursor]; - title->setText(obj->_title); - } + title->setText(_objUnderCursor ? _objUnderCursor->_title : ""); } // Move the title to the correct place (just above the cursor) int newX = surface->centerOnX(x, title->getWidth()); int newY = surface->putAboveY(y - smallFontHeight / 2, title->getHeight()); - titleAnim->setRelative(newX, newY); + _titleAnim->setRelative(newX, newY); // If we are currently playing the title, mark it dirty so it gets updated. // Otherwise, start playing the title animation. - if (titleAnim->isPlaying()) { - titleAnim->markDirtyRect(surface); + if (_titleAnim->isPlaying()) { + _titleAnim->markDirtyRect(surface); } else { - _vm->_anims->play(titleAnim->getID()); + _titleAnim->play(); } } -int Game::getObjectWithAnimation(int animID) const { +const GameObject *Game::getObjectWithAnimation(const Animation *anim) const { for (uint i = 0; i < _info._numObjects; ++i) { GameObject *obj = &_objects[i]; - - for (uint j = 0; j < obj->_anim.size(); ++j) { - if (obj->_anim[j] == animID) { - return i; - } + if (obj->_playingAnim >= 0 && obj->_anim[obj->_playingAnim] == anim) { + return obj; } } - return kObjectNotFound; + return NULL; } -void Game::removeItem(int itemID) { +void Game::removeItem(GameItem *item) { for (uint i = 0; i < kInventorySlots; ++i) { - if (_inventory[i] == itemID) { - _inventory[i] = kNoItem; - _vm->_anims->stop(kInventoryItemsID - itemID); + if (_inventory[i] == item) { + _inventory[i] = NULL; + item->_anim->stop(); break; } } } -void Game::putItem(int itemID, int position) { - if (itemID == kNoItem) +void Game::loadItemAnimation(GameItem *item) { + if (item->_anim) return; + item->_anim = new Animation(_vm, kInventoryItemsID - item->_absNum, 256, false); + _vm->_anims->insert(item->_anim, false); + // _itemImagesArchive is never flushed. + const BAFile *img = _vm->_itemImagesArchive->getFile(2 * item->_absNum); + item->_anim->addFrame(new Sprite(img->_data, img->_length, 0, 0, true), NULL); +} - if (position >= 0 && - position < kInventorySlots && - (_inventory[position] == kNoItem || _inventory[position] == itemID)) { - _inventory[position] = itemID; - } else { - for (int i = 0; i < kInventorySlots; ++i) { - int pos = (position + i) % kInventorySlots; - if (_inventory[pos] == kNoItem) { - _inventory[pos] = itemID; - position = pos; - break; - } +void Game::putItem(GameItem *item, int position) { + if (!item) + return; + assert(position >= 0); + + for (int i = 0; i < kInventorySlots; ++i) { + int pos = (position + i) % kInventorySlots; + if (!_inventory[pos] || _inventory[pos] == item) { + _inventory[pos] = item; + position = pos; + break; } } const int line = position / kInventoryColumns + 1; const int column = position % kInventoryColumns + 1; - const int anim_id = kInventoryItemsID - itemID; - Animation *anim = _vm->_anims->getAnimation(anim_id); - if (!anim) { - anim = _vm->_anims->addItem(anim_id, false); - // _itemImagesArchive is never flushed. - const BAFile *img = _vm->_itemImagesArchive->getFile(2 * itemID); - Sprite *sp = new Sprite(img->_data, img->_length, 0, 0, true); - anim->addFrame(sp, NULL); - } + loadItemAnimation(item); + Animation *anim = item->_anim; Drawable *frame = anim->getCurrentFrame(); const int x = kInventoryX + @@ -728,7 +706,7 @@ void Game::putItem(int itemID, int position) { (kInventoryItemHeight / 2) - (frame->getHeight() / 2); - debug(2, "itemID: %d position: %d line: %d column: %d x: %d y: %d", itemID, position, line, column, x, y); + debug(2, "itemID: %d position: %d line: %d column: %d x: %d y: %d", item->_absNum, position, line, column, x, y); anim->setRelative(x, y); @@ -736,7 +714,7 @@ void Game::putItem(int itemID, int position) { // upon returning it to its slot but *not* in other modes because it should be // invisible then (along with the inventory) if (_loopStatus == kStatusInventory && _loopSubstatus == kOuterLoop) { - _vm->_anims->play(anim_id); + anim->play(); } } @@ -764,24 +742,24 @@ void Game::inventoryDone() { _vm->_anims->unpauseAnimations(); - _vm->_anims->stop(kInventorySprite); + _inventoryAnim->stop(); for (uint i = 0; i < kInventorySlots; ++i) { - if (_inventory[i] != kNoItem) { - _vm->_anims->stop(kInventoryItemsID - _inventory[i]); + if (_inventory[i]) { + _inventory[i]->_anim->stop(); } } // Reset item under cursor - _itemUnderCursor = kNoItem; + _itemUnderCursor = NULL; } void Game::inventoryDraw() { - _vm->_anims->play(kInventorySprite); + _inventoryAnim->play(); for (uint i = 0; i < kInventorySlots; ++i) { - if (_inventory[i] != kNoItem) { - _vm->_anims->play(kInventoryItemsID - _inventory[i]); + if (_inventory[i]) { + _inventory[i]->_anim->play(); } } } @@ -834,7 +812,7 @@ void Game::dialogueMenu(int dialogueID) { } while (!_dialogueExit); dialogueDone(); - _currentDialogue = kNoDialogue; + _currentDialogue = -1; } int Game::dialogueDraw() { @@ -878,7 +856,7 @@ int Game::dialogueDraw() { bool notDialogueAnim = true; for (uint j = 0; j < kDialogueLines; ++j) { - if (_dialogueAnims[j]->getID() == _animUnderCursor) { + if (_dialogueAnims[j] == _animUnderCursor) { notDialogueAnim = false; break; } @@ -887,7 +865,7 @@ int Game::dialogueDraw() { if (notDialogueAnim) { ret = -1; } else { - ret = _dialogueAnims[0]->getID() - _animUnderCursor; + ret = kDialogueLinesID - _animUnderCursor->getID(); } } else { ret = _dialogueLinesNum - 1; @@ -932,7 +910,7 @@ void Game::dialogueInit(int dialogID) { } for (uint i = 0; i < kDialogueLines; ++i) { - _vm->_anims->play(_dialogueAnims[i]->getID()); + _dialogueAnims[i]->play(); } setLoopStatus(kStatusDialogue); @@ -942,7 +920,7 @@ void Game::dialogueInit(int dialogID) { void Game::dialogueDone() { for (uint i = 0; i < kDialogueLines; ++i) { - _vm->_anims->stop(_dialogueAnims[i]->getID()); + _dialogueAnims[i]->stop(); } delete _dialogueArchive; @@ -963,28 +941,26 @@ void Game::runDialogueProg(GPL2Program prog, int offset) { } int Game::playHeroAnimation(int anim_index) { - const GameObject *dragon = getObject(kDragonObject); - const int current_anim_index = playingObjectAnimation(dragon); - const int animID = dragon->_anim[anim_index]; - Animation *anim = _vm->_anims->getAnimation(animID); + GameObject *dragon = getObject(kDragonObject); + const int current_anim_index = dragon->_playingAnim; + Animation *anim = dragon->_anim[anim_index]; if (anim_index == current_anim_index) { anim->markDirtyRect(_vm->_screen->getSurface()); } else { - stopObjectAnimations(dragon); + dragon->stopAnim(); } positionAnimAsHero(anim); if (anim_index == current_anim_index) { anim->markDirtyRect(_vm->_screen->getSurface()); } else { - _vm->_anims->play(animID); + dragon->playAnim(anim_index); } return anim->currentFrameNum(); } -void Game::redrawWalkingPath(int id, byte colour, const WalkingPath &path) { - Animation *anim = _vm->_anims->getAnimation(id); +void Game::redrawWalkingPath(Animation *anim, byte colour, const WalkingPath &path) { Sprite *ov = _walkingMap.newOverlayFromPath(path, colour); delete anim->getFrame(0); anim->replaceFrame(0, ov, NULL); @@ -1016,8 +992,8 @@ void Game::walkHero(int x, int y, SightDirection dir) { _walkingMap.obliquePath(shortestPath, &obliquePath); debugC(2, kDraciWalkingDebugLevel, "Walking path lengths: shortest=%d oblique=%d", shortestPath.size(), obliquePath.size()); if (_vm->_showWalkingMap) { - redrawWalkingPath(kWalkingShortestPathOverlay, kWalkingShortestPathOverlayColour, shortestPath); - redrawWalkingPath(kWalkingObliquePathOverlay, kWalkingObliquePathOverlayColour, obliquePath); + redrawWalkingPath(_walkingShortestPathOverlay, kWalkingShortestPathOverlayColour, shortestPath); + redrawWalkingPath(_walkingObliquePathOverlay, kWalkingObliquePathOverlayColour, obliquePath); } // Start walking. Walking will be gradually advanced by @@ -1030,123 +1006,30 @@ void Game::walkHero(int x, int y, SightDirection dir) { _walkingMap.getDelta(), obliquePath); } -void Game::loadItem(int itemID) { - const BAFile *f = _vm->_itemsArchive->getFile(itemID * 3); - Common::MemoryReadStream itemReader(f->_data, f->_length); - - GameItem *item = _items + itemID; - - item->_init = itemReader.readSint16LE(); - item->_look = itemReader.readSint16LE(); - item->_use = itemReader.readSint16LE(); - item->_canUse = itemReader.readSint16LE(); - item->_imInit = itemReader.readByte(); - item->_imLook = itemReader.readByte(); - item->_imUse = itemReader.readByte(); - - f = _vm->_itemsArchive->getFile(itemID * 3 + 1); - - // The first byte is the length of the string - item->_title = Common::String((const char *)f->_data + 1, f->_length - 1); - assert(f->_data[0] == item->_title.size()); - - f = _vm->_itemsArchive->getFile(itemID * 3 + 2); - - item->_program._bytecode = f->_data; - item->_program._length = f->_length; -} - -void Game::loadRoom(int roomNum) { - const BAFile *f; - f = _vm->_roomsArchive->getFile(roomNum * 4); - Common::MemoryReadStream roomReader(f->_data, f->_length); - - roomReader.readUint32LE(); // Pointer to room program, not used - roomReader.readUint16LE(); // Program length, not used - roomReader.readUint32LE(); // Pointer to room title, not used - - // Music will be played by the GPL2 command startMusic when needed. - setMusicTrack(roomReader.readByte()); - - _currentRoom._mapID = roomReader.readByte() - 1; - _currentRoom._palette = roomReader.readByte() - 1; - _currentRoom._numOverlays = roomReader.readSint16LE(); - _currentRoom._init = roomReader.readSint16LE(); - _currentRoom._look = roomReader.readSint16LE(); - _currentRoom._use = roomReader.readSint16LE(); - _currentRoom._canUse = roomReader.readSint16LE(); - _currentRoom._imInit = roomReader.readByte(); - _currentRoom._imLook = roomReader.readByte(); - _currentRoom._imUse = roomReader.readByte(); - _currentRoom._mouseOn = roomReader.readByte(); - _currentRoom._heroOn = roomReader.readByte(); - - // Read in pers0 and persStep (stored as 6-byte Pascal reals) - byte real[6]; - - for (int i = 5; i >= 0; --i) { - real[i] = roomReader.readByte(); - } +void Game::initWalkingOverlays() { + _walkingMapOverlay = new Animation(_vm, kWalkingMapOverlay, 256, _vm->_showWalkingMap); + _walkingMapOverlay->addFrame(NULL, NULL); // rewritten below by loadWalkingMap() + _vm->_anims->insert(_walkingMapOverlay, true); - _currentRoom._pers0 = real_to_double(real); - - for (int i = 5; i >= 0; --i) { - real[i] = roomReader.readByte(); - } - - _currentRoom._persStep = real_to_double(real); - - _currentRoom._escRoom = roomReader.readByte() - 1; - _currentRoom._numGates = roomReader.readByte(); - - debugC(4, kDraciLogicDebugLevel, "Music: %d", getMusicTrack()); - debugC(4, kDraciLogicDebugLevel, "Map: %d", getMapID()); - debugC(4, kDraciLogicDebugLevel, "Palette: %d", _currentRoom._palette); - debugC(4, kDraciLogicDebugLevel, "Overlays: %d", _currentRoom._numOverlays); - debugC(4, kDraciLogicDebugLevel, "Init: %d", _currentRoom._init); - debugC(4, kDraciLogicDebugLevel, "Look: %d", _currentRoom._look); - debugC(4, kDraciLogicDebugLevel, "Use: %d", _currentRoom._use); - debugC(4, kDraciLogicDebugLevel, "CanUse: %d", _currentRoom._canUse); - debugC(4, kDraciLogicDebugLevel, "ImInit: %d", _currentRoom._imInit); - debugC(4, kDraciLogicDebugLevel, "ImLook: %d", _currentRoom._imLook); - debugC(4, kDraciLogicDebugLevel, "ImUse: %d", _currentRoom._imUse); - debugC(4, kDraciLogicDebugLevel, "MouseOn: %d", _currentRoom._mouseOn); - debugC(4, kDraciLogicDebugLevel, "HeroOn: %d", _currentRoom._heroOn); - debugC(4, kDraciLogicDebugLevel, "Pers0: %f", _currentRoom._pers0); - debugC(4, kDraciLogicDebugLevel, "PersStep: %f", _currentRoom._persStep); - debugC(4, kDraciLogicDebugLevel, "EscRoom: %d", _currentRoom._escRoom); - debugC(4, kDraciLogicDebugLevel, "Gates: %d", _currentRoom._numGates); - - // Read in the gates' numbers - _currentRoom._gates.clear(); - for (uint i = 0; i < _currentRoom._numGates; ++i) { - _currentRoom._gates.push_back(roomReader.readSint16LE()); - } - - // Add overlays for the walking map and shortest/obliqued paths. - Animation *map = _vm->_anims->addAnimation(kWalkingMapOverlay, 256, _vm->_showWalkingMap); - map->addFrame(NULL, NULL); // rewritten below by loadWalkingMap() - - Animation *sPath = _vm->_anims->addAnimation(kWalkingShortestPathOverlay, 257, _vm->_showWalkingMap); - Animation *oPath = _vm->_anims->addAnimation(kWalkingObliquePathOverlay, 258, _vm->_showWalkingMap); + _walkingShortestPathOverlay = new Animation(_vm, kWalkingShortestPathOverlay, 257, _vm->_showWalkingMap); + _walkingObliquePathOverlay = new Animation(_vm, kWalkingObliquePathOverlay, 258, _vm->_showWalkingMap); WalkingPath emptyPath; - Sprite *ov = _walkingMap.newOverlayFromPath(emptyPath, 0); - sPath->addFrame(ov, NULL); - ov = _walkingMap.newOverlayFromPath(emptyPath, 0); - oPath->addFrame(ov, NULL); - - // Load the walking map - loadWalkingMap(getMapID()); + _walkingShortestPathOverlay->addFrame(_walkingMap.newOverlayFromPath(emptyPath, 0), NULL); + _walkingObliquePathOverlay->addFrame(_walkingMap.newOverlayFromPath(emptyPath, 0), NULL); + _vm->_anims->insert(_walkingShortestPathOverlay, true); + _vm->_anims->insert(_walkingObliquePathOverlay, true); +} +void Game::loadRoomObjects() { // Load the room's objects for (uint i = 0; i < _info._numObjects; ++i) { debugC(7, kDraciLogicDebugLevel, "Checking if object %d (%d) is at the current location (%d)", i, - _objects[i]._location, roomNum); + _objects[i]._location, getRoomNum()); - if (_objects[i]._location == roomNum) { - debugC(6, kDraciLogicDebugLevel, "Loading object %d from room %d", i, roomNum); - loadObject(i); + if (_objects[i]._location == getRoomNum()) { + debugC(6, kDraciLogicDebugLevel, "Loading object %d from room %d", i, getRoomNum()); + _objects[i].load(i, _vm->_objectsArchive); } } @@ -1154,7 +1037,7 @@ void Game::loadRoom(int roomNum) { // We can't do this in the above loop because some objects' scripts reference // other objects that may not yet be loaded for (uint i = 0; i < _info._numObjects; ++i) { - if (_objects[i]._location == roomNum) { + if (_objects[i]._location == getRoomNum()) { const GameObject *obj = getObject(i); debugC(6, kDraciLogicDebugLevel, "Running init program for object %d (offset %d)", i, obj->_init); @@ -1162,138 +1045,9 @@ void Game::loadRoom(int roomNum) { } } - // Load the room's GPL program and run the init part - f = _vm->_roomsArchive->getFile(roomNum * 4 + 3); - _currentRoom._program._bytecode = f->_data; - _currentRoom._program._length = f->_length; - + // Run the init part of the GPL program debugC(4, kDraciLogicDebugLevel, "Running room init program..."); _vm->_script->run(_currentRoom._program, _currentRoom._init); - - // Set room palette - f = _vm->_paletteArchive->getFile(_currentRoom._palette); - _vm->_screen->setPalette(f->_data, 0, kNumColours); -} - -int Game::loadAnimation(uint animNum, uint z) { - // Make double-sure that an animation isn't loaded more than twice, - // otherwise horrible things happen in the AnimationManager, because - // they use a simple link-list without duplicate checking. This should - // never happen unless there is a bug in the game, because all GPL2 - // commands are guarded. - assert(!_vm->_anims->getAnimation(animNum)); - - const BAFile *animFile = _vm->_animationsArchive->getFile(animNum); - Common::MemoryReadStream animationReader(animFile->_data, animFile->_length); - - uint numFrames = animationReader.readByte(); - - // The following two flags are ignored by the played. Memory logic was - // a hint to the old player whether it should cache the sprites or load - // them on demand. We have 1 memory manager and ignore these hints. - animationReader.readByte(); - // The disable erasing field is just a (poor) optimization flag that - // turns of drawing the background underneath the sprite. By reading - // the source code of the old player, I'm not sure if that would ever - // have worked. There are only 6 animations in the game with this flag - // true. All of them have just 1 animation phase and they are used to - // patch a part of the original background by a new sprite. This - // should work with the default logic as well---just play this - // animation on top of the background. Since the only meaning of the - // flag was optimization, ignoring should be OK even without dipping - // into details. - animationReader.readByte(); - const bool cyclic = animationReader.readByte(); - const bool relative = animationReader.readByte(); - - Animation *anim = _vm->_anims->addAnimation(animNum, z, false); - - anim->setLooping(cyclic); - - for (uint i = 0; i < numFrames; ++i) { - uint spriteNum = animationReader.readUint16LE() - 1; - int x = animationReader.readSint16LE(); - int y = animationReader.readSint16LE(); - uint scaledWidth = animationReader.readUint16LE(); - uint scaledHeight = animationReader.readUint16LE(); - byte mirror = animationReader.readByte(); - int sample = animationReader.readUint16LE() - 1; - uint freq = animationReader.readUint16LE(); - uint delay = animationReader.readUint16LE(); - - // _spritesArchive is flushed when entering a room. All - // scripts in a room are responsible for loading their animations. - const BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum); - Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length, - relative ? 0 : x, relative ? 0 : y, true); - - // Some frames set the scaled dimensions to 0 even though other frames - // from the same animations have them set to normal values - // We work around this by assuming it means no scaling is necessary - if (scaledWidth == 0) { - scaledWidth = sp->getWidth(); - } - - if (scaledHeight == 0) { - scaledHeight = sp->getHeight(); - } - - sp->setScaled(scaledWidth, scaledHeight); - - if (mirror) - sp->setMirrorOn(); - - sp->setDelay(delay * 10); - - const SoundSample *sam = _vm->_soundsArchive->getSample(sample, freq); - - anim->addFrame(sp, sam); - if (relative) { - anim->makeLastFrameRelative(x, y); - } - } - - return animNum; -} - -void Game::loadObject(uint objNum) { - const BAFile *file; - - file = _vm->_objectsArchive->getFile(objNum * 3); - Common::MemoryReadStream objReader(file->_data, file->_length); - - GameObject *obj = _objects + objNum; - - obj->_init = objReader.readUint16LE(); - obj->_look = objReader.readUint16LE(); - obj->_use = objReader.readUint16LE(); - obj->_canUse = objReader.readUint16LE(); - obj->_imInit = objReader.readByte(); - obj->_imLook = objReader.readByte(); - obj->_imUse = objReader.readByte(); - obj->_walkDir = objReader.readByte() - 1; - obj->_z = objReader.readByte(); - objReader.readUint16LE(); // idxSeq field, not used - objReader.readUint16LE(); // numSeq field, not used - obj->_lookX = objReader.readUint16LE(); - obj->_lookY = objReader.readUint16LE(); - obj->_useX = objReader.readUint16LE(); - obj->_useY = objReader.readUint16LE(); - obj->_lookDir = static_cast<SightDirection> (objReader.readByte()); - obj->_useDir = static_cast<SightDirection> (objReader.readByte()); - - obj->_absNum = objNum; - - file = _vm->_objectsArchive->getFile(objNum * 3 + 1); - - // The first byte of the file is the length of the string (without the length) - assert(file->_length - 1 == file->_data[0]); - - obj->_title = Common::String((char *)(file->_data+1), file->_length-1); - - file = _vm->_objectsArchive->getFile(objNum * 3 + 2); - obj->_program._bytecode = file->_data; - obj->_program._length = file->_length; } void Game::loadWalkingMap(int mapID) { @@ -1301,11 +1055,22 @@ void Game::loadWalkingMap(int mapID) { f = _vm->_walkingMapsArchive->getFile(mapID); _walkingMap.load(f->_data, f->_length); - Animation *anim = _vm->_anims->getAnimation(kWalkingMapOverlay); Sprite *ov = _walkingMap.newOverlayFromMap(kWalkingMapOverlayColour); - delete anim->getFrame(0); - anim->replaceFrame(0, ov, NULL); - anim->markDirtyRect(_vm->_screen->getSurface()); + delete _walkingMapOverlay->getFrame(0); + _walkingMapOverlay->replaceFrame(0, ov, NULL); + _walkingMapOverlay->markDirtyRect(_vm->_screen->getSurface()); +} + +void Game::switchWalkingAnimations(bool enabled) { + if (enabled) { + _walkingMapOverlay->play(); + _walkingShortestPathOverlay->play(); + _walkingObliquePathOverlay->play(); + } else { + _walkingMapOverlay->stop(); + _walkingShortestPathOverlay->stop(); + _walkingObliquePathOverlay->stop(); + } } void Game::loadOverlays() { @@ -1328,7 +1093,11 @@ void Game::loadOverlays() { overlayFile = _vm->_overlaysArchive->getFile(num); Sprite *sp = new Sprite(overlayFile->_data, overlayFile->_length, x, y, true); - _vm->_anims->addOverlay(sp, z); + Animation *anim = new Animation(_vm, kOverlayImage, z, true); + anim->addFrame(sp, NULL); + // Since this is an overlay, we don't need it to be deleted + // when the GPL Release command is invoked + _vm->_anims->insert(anim, false); } _vm->_screen->getSurface()->markDirty(); @@ -1337,27 +1106,12 @@ void Game::loadOverlays() { void Game::deleteObjectAnimations() { for (uint i = 0; i < _info._numObjects; ++i) { GameObject *obj = &_objects[i]; - if (i != 0 && (obj->_location == getPreviousRoomNum())) { - for (uint j = 0; j < obj->_anim.size(); ++j) { - _vm->_anims->deleteAnimation(obj->_anim[j]); - } - obj->_anim.clear(); + obj->deleteAnims(); } } } -int Game::playingObjectAnimation(const GameObject *obj) const { - for (uint i = 0; i < obj->_anim.size(); ++i) { - const int animID = obj->_anim[i]; - const Animation *anim = _vm->_anims->getAnimation(animID); - if (anim && anim->isPlaying()) { - return i; - } - } - return -1; -} - bool Game::enterNewRoom() { if (_newRoom == getRoomNum() && !isReloaded()) { // If the game has been reloaded, force reloading all animations. @@ -1386,22 +1140,13 @@ bool Game::enterNewRoom() { _vm->_anims->deleteOverlays(); - // Delete walking map testing overlay - _vm->_anims->deleteAnimation(kWalkingMapOverlay); - _vm->_anims->deleteAnimation(kWalkingShortestPathOverlay); - _vm->_anims->deleteAnimation(kWalkingObliquePathOverlay); - - // TODO: Make objects capable of stopping their own animations - const GameObject *dragon = getObject(kDragonObject); - stopObjectAnimations(dragon); + GameObject *dragon = getObject(kDragonObject); + dragon->stopAnim(); // Remember the previous room for returning back from the map. rememberRoomNumAsPrevious(); deleteObjectAnimations(); - // Set the current room to the new value - _currentRoom._roomNum = _newRoom; - // Before setting these variables we have to convert the values to 1-based indexing // because this is how everything is stored in the data files _variables[0] = _newGate + 1; @@ -1425,16 +1170,22 @@ bool Game::enterNewRoom() { // on by pressing Escape in the intro or in the map room. _vm->_script->endCurrentProgram(false); - loadRoom(_newRoom); + _currentRoom.load(_newRoom, _vm->_roomsArchive); + loadWalkingMap(getMapID()); + loadRoomObjects(); loadOverlays(); + // Set room palette + const BAFile *f; + f = _vm->_paletteArchive->getFile(_currentRoom._palette); + _vm->_screen->setPalette(f->_data, 0, kNumColours); + // Clean the mouse and animation title. It gets first updated in // loop(), hence if the hero walks during the initialization scripts, // the old values would remain otherwise. _vm->_mouse->setCursorType(kNormalCursor); - Animation *titleAnim = _vm->_anims->getAnimation(kTitleText); - titleAnim->markDirtyRect(_vm->_screen->getSurface()); - Text *title = reinterpret_cast<Text *>(titleAnim->getCurrentFrame()); + _titleAnim->markDirtyRect(_vm->_screen->getSurface()); + Text *title = reinterpret_cast<Text *>(_titleAnim->getCurrentFrame()); title->setText(""); // Run the program for the gate the dragon came through @@ -1557,24 +1308,20 @@ void Game::deleteAnimationsAfterIndex(int lastAnimIndex) { for (uint i = 0; i < getNumObjects(); ++i) { GameObject *obj = &_objects[i]; - for (uint j = 0; j < obj->_anim.size(); ++j) { - Animation *anim; - - anim = _vm->_anims->getAnimation(obj->_anim[j]); - if (anim != NULL && anim->getIndex() > lastAnimIndex) - obj->_anim.remove_at(j--); + for (int j = obj->_anim.size() - 1; j >= 0; --j) { + Animation *anim = obj->_anim[j]; + if (anim->getIndex() > lastAnimIndex) { + obj->_anim.remove_at(j); + if (j == obj->_playingAnim) { + obj->_playingAnim = -1; + } + } } } _vm->_anims->deleteAfterIndex(lastAnimIndex); } -void Game::stopObjectAnimations(const GameObject *obj) { - for (uint i = 0; i < obj->_anim.size(); ++i) { - _vm->_anims->stop(obj->_anim[i]); - } -} - Game::~Game() { delete[] _persons; delete[] _variables; @@ -1599,7 +1346,14 @@ void Game::DoSync(Common::Serializer &s) { } for (int i = 0; i < kInventorySlots; ++i) { - s.syncAsSint16LE(_inventory[i]); + if (s.isSaving()) { + int itemID = _inventory[i] ? _inventory[i]->_absNum : -1; + s.syncAsSint16LE(itemID); + } else { + int itemID; + s.syncAsSint16LE(itemID); + _inventory[i] = getItem(itemID); + } } for (int i = 0; i < _info._numVariables; ++i) { @@ -1648,4 +1402,189 @@ static double real_to_double(byte real[6]) { return ldexp(mantissa, exp); } +int GameObject::getAnim(int animID) const { + for (uint i = 0; i < _anim.size(); ++i) { + if (_anim[i]->getID() == animID) { + return i; + } + } + return -1; +} + +int GameObject::addAnim(Animation *anim) { + anim->setZ(_z); + _anim.push_back(anim); + int index = _anim.size() - 1; + if (_absNum == kDragonObject && index <= kLastTurning) { + // Index to _anim is the Movement type. All walking and + // turning movements can be accelerated. + anim->supportsQuickAnimation(true); + } + return index; +} + +void GameObject::playAnim(int i) { + _anim[i]->play(); + _playingAnim = i; +} + +void GameObject::stopAnim() { + if (_playingAnim >= 0) { + _anim[_playingAnim]->stop(); + _playingAnim = -1; + } +} + +void GameObject::deleteAnims() { + for (uint j = 0; j < _anim.size(); ++j) { + _anim[j]->del(); + } + _anim.clear(); + _playingAnim = -1; +} + +void GameObject::load(uint objNum, BArchive *archive) { + const BAFile *file; + + file = archive->getFile(objNum * 3); + Common::MemoryReadStream objReader(file->_data, file->_length); + + _init = objReader.readUint16LE(); + _look = objReader.readUint16LE(); + _use = objReader.readUint16LE(); + _canUse = objReader.readUint16LE(); + _imInit = objReader.readByte(); + _imLook = objReader.readByte(); + _imUse = objReader.readByte(); + _walkDir = objReader.readByte() - 1; + _z = objReader.readByte(); + objReader.readUint16LE(); // idxSeq field, not used + objReader.readUint16LE(); // numSeq field, not used + _lookX = objReader.readUint16LE(); + _lookY = objReader.readUint16LE(); + _useX = objReader.readUint16LE(); + _useY = objReader.readUint16LE(); + _lookDir = static_cast<SightDirection> (objReader.readByte()); + _useDir = static_cast<SightDirection> (objReader.readByte()); + + _absNum = objNum; + + file = archive->getFile(objNum * 3 + 1); + + // The first byte of the file is the length of the string (without the length) + assert(file->_length - 1 == file->_data[0]); + + _title = Common::String((char *)(file->_data+1), file->_length-1); + + file = archive->getFile(objNum * 3 + 2); + _program._bytecode = file->_data; + _program._length = file->_length; + + _playingAnim = -1; + _anim.clear(); +} + +void GameItem::load(int itemID, BArchive *archive) { + const BAFile *f = archive->getFile(itemID * 3); + Common::MemoryReadStream itemReader(f->_data, f->_length); + + _init = itemReader.readSint16LE(); + _look = itemReader.readSint16LE(); + _use = itemReader.readSint16LE(); + _canUse = itemReader.readSint16LE(); + _imInit = itemReader.readByte(); + _imLook = itemReader.readByte(); + _imUse = itemReader.readByte(); + + _absNum = itemID; + + f = archive->getFile(itemID * 3 + 1); + + // The first byte is the length of the string + _title = Common::String((const char *)f->_data + 1, f->_length - 1); + assert(f->_data[0] == _title.size()); + + f = archive->getFile(itemID * 3 + 2); + + _program._bytecode = f->_data; + _program._length = f->_length; + + _anim = NULL; +} + +void Room::load(int roomNum, BArchive *archive) { + const BAFile *f; + f = archive->getFile(roomNum * 4); + Common::MemoryReadStream roomReader(f->_data, f->_length); + + roomReader.readUint32LE(); // Pointer to room program, not used + roomReader.readUint16LE(); // Program length, not used + roomReader.readUint32LE(); // Pointer to room title, not used + + // Set the current room to the new value + _roomNum = roomNum; + + // Music will be played by the GPL2 command startMusic when needed. + _music = roomReader.readByte(); + _mapID = roomReader.readByte() - 1; + _palette = roomReader.readByte() - 1; + _numOverlays = roomReader.readSint16LE(); + _init = roomReader.readSint16LE(); + _look = roomReader.readSint16LE(); + _use = roomReader.readSint16LE(); + _canUse = roomReader.readSint16LE(); + _imInit = roomReader.readByte(); + _imLook = roomReader.readByte(); + _imUse = roomReader.readByte(); + _mouseOn = roomReader.readByte(); + _heroOn = roomReader.readByte(); + + // Read in pers0 and persStep (stored as 6-byte Pascal reals) + byte real[6]; + + for (int i = 5; i >= 0; --i) { + real[i] = roomReader.readByte(); + } + + _pers0 = real_to_double(real); + + for (int i = 5; i >= 0; --i) { + real[i] = roomReader.readByte(); + } + + _persStep = real_to_double(real); + + _escRoom = roomReader.readByte() - 1; + _numGates = roomReader.readByte(); + + debugC(4, kDraciLogicDebugLevel, "Music: %d", _music); + debugC(4, kDraciLogicDebugLevel, "Map: %d", _mapID); + debugC(4, kDraciLogicDebugLevel, "Palette: %d", _palette); + debugC(4, kDraciLogicDebugLevel, "Overlays: %d", _numOverlays); + debugC(4, kDraciLogicDebugLevel, "Init: %d", _init); + debugC(4, kDraciLogicDebugLevel, "Look: %d", _look); + debugC(4, kDraciLogicDebugLevel, "Use: %d", _use); + debugC(4, kDraciLogicDebugLevel, "CanUse: %d", _canUse); + debugC(4, kDraciLogicDebugLevel, "ImInit: %d", _imInit); + debugC(4, kDraciLogicDebugLevel, "ImLook: %d", _imLook); + debugC(4, kDraciLogicDebugLevel, "ImUse: %d", _imUse); + debugC(4, kDraciLogicDebugLevel, "MouseOn: %d", _mouseOn); + debugC(4, kDraciLogicDebugLevel, "HeroOn: %d", _heroOn); + debugC(4, kDraciLogicDebugLevel, "Pers0: %f", _pers0); + debugC(4, kDraciLogicDebugLevel, "PersStep: %f", _persStep); + debugC(4, kDraciLogicDebugLevel, "EscRoom: %d", _escRoom); + debugC(4, kDraciLogicDebugLevel, "Gates: %d", _numGates); + + // Read in the gates' numbers + _gates.clear(); + for (uint i = 0; i < _numGates; ++i) { + _gates.push_back(roomReader.readSint16LE()); + } + + // Load the room's GPL program + f = archive->getFile(roomNum * 4 + 3); + _program._bytecode = f->_data; + _program._length = f->_length; +} + } diff --git a/engines/draci/game.h b/engines/draci/game.h index 08c4307c41..8d219850d2 100644 --- a/engines/draci/game.h +++ b/engines/draci/game.h @@ -42,26 +42,7 @@ enum { kDragonObject = 0 }; -// Used as a return value for Game::getObjectWithAnimation() if no object -// owns the animation in question enum { - kObjectNotFound = -1 -}; - -// Used as the value of the _escRoom field of the current room if there is -// no escape room defined -enum { - kNoEscRoom = -1 -}; - -// Used as a value to Game::_currentIcon and means there is no item selected -// and a "real" cursor image is used -enum { - kNoItem = -1 -}; - -enum { - kNoDialogue = -1, kDialogueLines = 4 }; @@ -94,19 +75,30 @@ enum InventoryConstants { kInventorySlots = kInventoryLines * kInventoryColumns }; -struct GameObject { +class GameObject { +public: + int _absNum; uint _init, _look, _use, _canUse; bool _imInit, _imLook, _imUse; int _walkDir; byte _z; uint _lookX, _lookY, _useX, _useY; SightDirection _lookDir, _useDir; - uint _absNum; - Common::Array<int> _anim; GPL2Program _program; Common::String _title; int _location; bool _visible; + + Common::Array<Animation *> _anim; + int _playingAnim; + + int getAnim(int animID) const; + int addAnim(Animation *anim); + int playingAnim() const { return _playingAnim; } + void playAnim(int i); + void stopAnim(); + void deleteAnims(); + void load(uint objNum, BArchive *archive); }; struct GameInfo { @@ -123,11 +115,17 @@ struct GameInfo { uint _numDialogueBlocks; }; -struct GameItem { +class GameItem { +public: + int _absNum; uint _init, _look, _use, _canUse; bool _imInit, _imLook, _imUse; GPL2Program _program; Common::String _title; + + Animation *_anim; + + void load(int itemID, BArchive *archive); }; struct Person { @@ -142,7 +140,8 @@ struct Dialogue { GPL2Program _program; }; -struct Room { +class Room { +public: int _roomNum; byte _music; int _mapID; @@ -156,6 +155,8 @@ struct Room { byte _numGates; Common::Array<int> _gates; GPL2Program _program; + + void load(int roomNum, BArchive *archive); }; enum LoopStatus { @@ -223,19 +224,15 @@ public: // unless the animation hasn't changed). int playHeroAnimation(int anim_index); - int loadAnimation(uint animNum, uint z); void loadOverlays(); - void loadObject(uint numObj); void loadWalkingMap(int mapID); // but leaves _currentRoom._mapID untouched - void loadItem(int itemID); + void switchWalkingAnimations(bool enabled); uint getNumObjects() const { return _info._numObjects; } GameObject *getObject(uint objNum) { return _objects + objNum; } - int getObjectWithAnimation(int animID) const; + const GameObject *getObjectWithAnimation(const Animation *anim) const; void deleteObjectAnimations(); void deleteAnimationsAfterIndex(int lastAnimIndex); - void stopObjectAnimations(const GameObject *obj); - int playingObjectAnimation(const GameObject *obj) const; int getVariable(int varNum) const { return _variables[varNum]; } void setVariable(int varNum, int value) { _variables[varNum] = value; } @@ -257,10 +254,12 @@ public: int getItemStatus(int itemID) const { return _itemStatus[itemID]; } void setItemStatus(int itemID, int status) { _itemStatus[itemID] = status; } - int getCurrentItem() const { return _currentItem; } - void setCurrentItem(int itemID) { _currentItem = itemID; } - void removeItem(int itemID); - void putItem(int itemID, int position); + GameItem *getItem(int id) { return id >= 0 ? &_items[id] : NULL; } + GameItem *getCurrentItem() const { return _currentItem; } + void setCurrentItem(GameItem *item) { _currentItem = item; } + void removeItem(GameItem *item); + void loadItemAnimation(GameItem *item); + void putItem(GameItem *item, int position); void addItem(int itemID); int getEscRoom() const { return _currentRoom._escRoom; } @@ -337,9 +336,10 @@ private: void advanceAnimationsAndTestLoopExit(); bool enterNewRoom(); // Returns false if another room change has been triggered and therefore loop() shouldn't be called yet. - void loadRoom(int roomNum); + void initWalkingOverlays(); + void loadRoomObjects(); void runGateProgram(int gate); - void redrawWalkingPath(int id, byte colour, const WalkingPath &path); + void redrawWalkingPath(Animation *anim, byte colour, const WalkingPath &path); DraciEngine *_vm; @@ -353,10 +353,10 @@ private: byte *_itemStatus; GameItem *_items; - int _currentItem; - int _itemUnderCursor; + GameItem *_currentItem; + GameItem *_itemUnderCursor; - int _inventory[kInventorySlots]; + GameItem *_inventory[kInventorySlots]; bool _inventoryExit; Room _currentRoom; @@ -390,8 +390,8 @@ private: uint _speechTick; uint _speechDuration; - int _objUnderCursor; - int _animUnderCursor; + const GameObject *_objUnderCursor; + const Animation *_animUnderCursor; int _markedAnimationIndex; ///< Used by the Mark GPL command @@ -406,6 +406,12 @@ private: WalkingMap _walkingMap; WalkingState _walkingState; + + Animation *_titleAnim; + Animation *_inventoryAnim; + Animation *_walkingMapOverlay; + Animation *_walkingShortestPathOverlay; + Animation *_walkingObliquePathOverlay; }; } // End of namespace Draci diff --git a/engines/draci/mouse.cpp b/engines/draci/mouse.cpp index ae80775898..4d15d2e3f6 100644 --- a/engines/draci/mouse.cpp +++ b/engines/draci/mouse.cpp @@ -24,6 +24,7 @@ */ #include "draci/draci.h" +#include "draci/game.h" #include "draci/mouse.h" #include "draci/barchive.h" @@ -104,8 +105,9 @@ void Mouse::setCursorType(CursorType cur) { sp.getWidth() / 2, sp.getHeight() / 2); } -void Mouse::loadItemCursor(int itemID, bool highlighted) { - int archiveIndex = 2 * itemID + highlighted; +void Mouse::loadItemCursor(const GameItem *item, bool highlighted) { + const int itemID = item->_absNum; + const int archiveIndex = 2 * itemID + highlighted; CursorType newCursor = static_cast<CursorType> (kItemCursor + archiveIndex); if (newCursor == getCursorType()) { return; diff --git a/engines/draci/mouse.h b/engines/draci/mouse.h index 90c5b919ad..87af2a3993 100644 --- a/engines/draci/mouse.h +++ b/engines/draci/mouse.h @@ -45,6 +45,7 @@ enum CursorType { }; class DraciEngine; +class GameItem; class Mouse { public: @@ -58,7 +59,7 @@ public: void setPosition(uint16 x, uint16 y); CursorType getCursorType() const { return _cursorType; } void setCursorType(CursorType cur); - void loadItemCursor(int itemID, bool highlighted); + void loadItemCursor(const GameItem *item, bool highlighted); bool lButtonPressed() const { return _lButton; } bool rButtonPressed() const { return _rButton; } void lButtonSet(bool state) { _lButton = state; } diff --git a/engines/draci/script.cpp b/engines/draci/script.cpp index 079e1c5a5a..f7cf015c24 100644 --- a/engines/draci/script.cpp +++ b/engines/draci/script.cpp @@ -270,7 +270,9 @@ int Script::funcIcoStat(int itemID) const { int Script::funcIsIcoAct(int itemID) const { itemID -= 1; - return _vm->_game->getCurrentItem() == itemID; + const GameItem *item = _vm->_game->getCurrentItem(); + const int currentID = item ? item->_absNum : -1; + return currentID == itemID; } int Script::funcActIco(int itemID) const { @@ -279,7 +281,8 @@ int Script::funcActIco(int itemID) const { // implemented in such a way that they had to have a single parameter so this is only // passed as a dummy. - return _vm->_game->getCurrentItem(); + const GameItem *item = _vm->_game->getCurrentItem(); + return item ? item->_absNum + 1 : 0; } int Script::funcIsObjOn(int objID) const { @@ -340,10 +343,9 @@ int Script::funcActPhase(int objID) const { bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); if (objID == kDragonObject || visible) { - const int i = _vm->_game->playingObjectAnimation(obj); + const int i = obj->playingAnim(); if (i >= 0) { - int animID = obj->_anim[i]; - Animation *anim = _vm->_anims->getAnimation(animID); + Animation *anim = obj->_anim[i]; ret = anim->currentFrameNum(); } } @@ -363,18 +365,6 @@ void Script::play(const Common::Array<int> ¶ms) { _vm->_game->loop(kInnerUntilExit, true); } -Animation *Script::loadObjectAnimation(int objID, GameObject *obj, int animID) { - _vm->_game->loadAnimation(animID, obj->_z); - obj->_anim.push_back(animID); - Animation *anim = _vm->_anims->getAnimation(animID); - if (objID == kDragonObject && obj->_anim.size() - 1 <= kLastTurning) { - // obj->_anim.size() is the Movement type. All walking and - // turning movements can be accelerated. - anim->supportsQuickAnimation(true); - } - return anim; -} - void Script::load(const Common::Array<int> ¶ms) { if (_vm->_game->getLoopStatus() == kStatusInventory) { return; @@ -383,21 +373,17 @@ void Script::load(const Common::Array<int> ¶ms) { int objID = params[0] - 1; int animID = params[1] - 1; - uint i; - GameObject *obj = _vm->_game->getObject(objID); - // If the animation is already loaded, return - for (i = 0; i < obj->_anim.size(); ++i) { - if (obj->_anim[i] == animID) { - return; - } + GameObject *obj = _vm->_game->getObject(objID); + if (obj->getAnim(animID) >= 0) { + return; } // We don't test here whether an animation is loaded in the // AnimationManager while not being registered in the object's array of // animations. This cannot legally happen and an assertion will be - // thrown by loadAnimation(). - loadObjectAnimation(objID, obj, animID); + // thrown by AnimationManager::load(). + obj->addAnim(_vm->_anims->load(animID)); } void Script::start(const Common::Array<int> ¶ms) { @@ -409,10 +395,10 @@ void Script::start(const Common::Array<int> ¶ms) { int animID = params[1] - 1; GameObject *obj = _vm->_game->getObject(objID); - _vm->_game->stopObjectAnimations(obj); + obj->stopAnim(); - Animation *anim = _vm->_anims->getAnimation(animID); - if (!anim) { + int index = obj->getAnim(animID); + if (index < 0) { // WORKAROUND: // // The original game files seem to contain errors, which I have @@ -432,20 +418,21 @@ void Script::start(const Common::Array<int> ¶ms) { // to apply the hedgehog, but there is no way that the game // player would load the requested animation by itself. // See objekty:5077 and parezy.txt:27. - anim = loadObjectAnimation(objID, obj, animID); + index = obj->addAnim(_vm->_anims->load(animID)); debugC(1, kDraciBytecodeDebugLevel, "start(%d=%s) cannot find animation %d. Loading.", objID, obj->_title.c_str(), animID); } + Animation *anim = obj->_anim[index]; if (objID == kDragonObject) _vm->_game->positionAnimAsHero(anim); - anim->registerCallback(&Animation::stopAnimation); + anim->registerCallback(&Animation::stop); bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); if (objID == kDragonObject || visible) { - _vm->_anims->play(animID); + obj->playAnim(index); } } @@ -458,14 +445,15 @@ void Script::startPlay(const Common::Array<int> ¶ms) { int animID = params[1] - 1; GameObject *obj = _vm->_game->getObject(objID); - _vm->_game->stopObjectAnimations(obj); + obj->stopAnim(); - Animation *anim = _vm->_anims->getAnimation(animID); - if (!anim) { - anim = loadObjectAnimation(objID, obj, animID); + int index = obj->getAnim(animID); + if (index < 0) { + index = obj->addAnim(_vm->_anims->load(animID)); debugC(1, kDraciBytecodeDebugLevel, "startPlay(%d=%s) cannot find animation %d. Loading.", objID, obj->_title.c_str(), animID); } + Animation *anim = obj->_anim[index]; if (objID == kDragonObject) _vm->_game->positionAnimAsHero(anim); @@ -474,37 +462,27 @@ void Script::startPlay(const Common::Array<int> ¶ms) { bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); if (objID == kDragonObject || visible) { - _vm->_anims->play(animID); + obj->playAnim(index); } // Runs an inner loop until the animation ends. _vm->_game->loop(kInnerUntilExit, false); - _vm->_anims->stop(animID); + obj->stopAnim(); anim->registerCallback(&Animation::doNothing); } void Script::justTalk(const Common::Array<int> ¶ms) { const GameObject *dragon = _vm->_game->getObject(kDragonObject); - const int last_anim = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); - int new_anim; - if (last_anim == kSpeakRight || last_anim == kStopRight) { - new_anim = kSpeakRight; - } else { - new_anim = kSpeakLeft; - } + const int last_anim = static_cast<Movement> (dragon->playingAnim()); + const int new_anim = (last_anim == kSpeakRight || last_anim == kStopRight) ? kSpeakRight : kSpeakLeft; _vm->_game->playHeroAnimation(new_anim); } void Script::justStay(const Common::Array<int> ¶ms) { const GameObject *dragon = _vm->_game->getObject(kDragonObject); - const int last_anim = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); - int new_anim; - if (last_anim == kSpeakRight || last_anim == kStopRight) { - new_anim = kStopRight; - } else { - new_anim = kStopLeft; - } + const int last_anim = static_cast<Movement> (dragon->playingAnim()); + const int new_anim = (last_anim == kSpeakRight || last_anim == kStopRight) ? kStopRight : kStopLeft; _vm->_game->playHeroAnimation(new_anim); } @@ -557,18 +535,20 @@ void Script::release(const Common::Array<int> ¶ms) { void Script::icoStat(const Common::Array<int> ¶ms) { int status = params[0]; int itemID = params[1] - 1; + GameItem *item = _vm->_game->getItem(itemID); _vm->_game->setItemStatus(itemID, status == 1); if (_vm->_game->getItemStatus(itemID) == 0) { - if (itemID != kNoItem) { - _vm->_anims->deleteAnimation(kInventoryItemsID - itemID); + if (item) { + item->_anim->del(); + item->_anim = NULL; } - _vm->_game->removeItem(itemID); + _vm->_game->removeItem(item); - if (_vm->_game->getCurrentItem() == itemID) { - _vm->_game->setCurrentItem(kNoItem); + if (_vm->_game->getCurrentItem() == item) { + _vm->_game->setCurrentItem(NULL); } if (_vm->_mouse->getCursorType() == kNormalCursor) { @@ -580,16 +560,13 @@ void Script::icoStat(const Common::Array<int> ¶ms) { } if (_vm->_game->getItemStatus(itemID) == 1) { - if (itemID != kNoItem) { - Animation *itemAnim = _vm->_anims->addItem(kInventoryItemsID - itemID, false); - const BAFile *f = _vm->_itemImagesArchive->getFile(2 * itemID); - Sprite *sp = new Sprite(f->_data, f->_length, 0, 0, true); - itemAnim->addFrame(sp, NULL); + if (item) { + _vm->_game->loadItemAnimation(item); } - _vm->_game->setCurrentItem(itemID); + _vm->_game->setCurrentItem(item); - _vm->_mouse->loadItemCursor(itemID, false); + _vm->_mouse->loadItemCursor(item, false); // TODO: This is probably not needed but I'm leaving it to be sure for now // The original engine needed to turn off the mouse temporarily when changing @@ -625,7 +602,7 @@ void Script::objStat(const Common::Array<int> ¶ms) { obj->_location = -1; } - _vm->_game->stopObjectAnimations(obj); + obj->stopAnim(); } void Script::execInit(const Common::Array<int> ¶ms) { @@ -674,7 +651,7 @@ void Script::stayOn(const Common::Array<int> ¶ms) { Common::Point heroPos(_vm->_game->findNearestWalkable(x, y)); Common::Point mousePos(_vm->_mouse->getPosX(), _vm->_mouse->getPosY()); const GameObject *dragon = _vm->_game->getObject(kDragonObject); - Movement startingDirection = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); + Movement startingDirection = static_cast<Movement> (dragon->playingAnim()); _vm->_game->stopWalking(); _vm->_game->setHeroPosition(heroPos); diff --git a/engines/draci/script.h b/engines/draci/script.h index a7be243387..5fd63517c1 100644 --- a/engines/draci/script.h +++ b/engines/draci/script.h @@ -90,7 +90,7 @@ struct GPL2Program { }; class Animation; -struct GameObject; +class GameObject; class Script { @@ -193,9 +193,6 @@ private: int handleMathExpression(Common::MemoryReadStream *reader) const; DraciEngine *_vm; - - // Auxilliary functions - Animation *loadObjectAnimation(int objID, GameObject *obj, int animID); }; } // End of namespace Draci diff --git a/engines/draci/walking.cpp b/engines/draci/walking.cpp index 932259d213..7998126699 100644 --- a/engines/draci/walking.cpp +++ b/engines/draci/walking.cpp @@ -458,7 +458,7 @@ void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2 // Remember the initial dragon's direction. const GameObject *dragon = _vm->_game->getObject(kDragonObject); - _startingDirection = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); + _startingDirection = static_cast<Movement> (dragon->playingAnim()); // Going to start with the first segment. _segment = 0; @@ -503,7 +503,7 @@ bool WalkingState::continueWalkingOrClearPath() { bool WalkingState::continueWalking() { const GameObject *dragon = _vm->_game->getObject(kDragonObject); - const Movement movement = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); + const Movement movement = static_cast<Movement> (dragon->playingAnim()); if (_turningFinished) { // When a turning animation has finished, heroAnimationFinished() callback @@ -526,8 +526,7 @@ bool WalkingState::continueWalking() { // Read the dragon's animation's current phase. Determine if it has // changed from the last time. If not, wait until it has. - const int animID = dragon->_anim[movement]; - Animation *anim = _vm->_anims->getAnimation(animID); + Animation *anim = dragon->_anim[movement]; const int animPhase = anim->currentFrameNum(); const bool wasUpdated = animPhase != _lastAnimPhase; if (!wasUpdated) { @@ -589,6 +588,11 @@ bool WalkingState::continueWalking() { bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, const Common::Point &prevHero, Common::Point *hero) { const Movement movement = animationForDirection(p1, p2); const Common::Point p2Diff(p2.x - p1.x, p2.y - p1.y); + if (p2Diff.x == 0 && p2Diff.y == 0) { + debugC(2, kDraciWalkingDebugLevel, "Adjusted walking edge has zero length"); + // Due to changing the path vertices on the fly, this can happen. + return true; + } bool reachedEnd; if (movement == kMoveLeft || movement == kMoveRight) { reachedEnd = movement == kMoveLeft ? hero->x <= p2.x : hero->x >= p2.x; @@ -602,7 +606,7 @@ bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point bool WalkingState::turnForTheNextSegment() { const GameObject *dragon = _vm->_game->getObject(kDragonObject); - const Movement currentAnim = static_cast<Movement> (_vm->_game->playingObjectAnimation(dragon)); + const Movement currentAnim = static_cast<Movement> (dragon->playingAnim()); const Movement wantAnim = directionForNextPhase(); Movement transition = transitionBetweenAnimations(currentAnim, wantAnim); @@ -617,8 +621,7 @@ bool WalkingState::turnForTheNextSegment() { // to calling walkOnNextEdge() in the next phase. assert(isTurningMovement(transition)); _lastAnimPhase = _vm->_game->playHeroAnimation(transition); - const int animID = dragon->_anim[transition]; - Animation *anim = _vm->_anims->getAnimation(animID); + Animation *anim = dragon->_anim[transition]; anim->registerCallback(&Animation::tellWalkingState); debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d with phase %d", transition, _lastAnimPhase); |