From f8a19bb4a9be128de321522b92037774e1625b69 Mon Sep 17 00:00:00 2001 From: Robert Špalek Date: Thu, 5 Nov 2009 14:22:39 +0000 Subject: Implemented proper walking. First shot, not debugged yet, but seems to work (even though a bit hairy)! svn-id: r45688 --- engines/draci/animation.cpp | 4 ++ engines/draci/animation.h | 1 + engines/draci/game.cpp | 31 ++++------ engines/draci/game.h | 2 + engines/draci/walking.cpp | 143 +++++++++++++++++++++++++++++++++++++++++--- engines/draci/walking.h | 34 ++++++++++- 6 files changed, 183 insertions(+), 32 deletions(-) diff --git a/engines/draci/animation.cpp b/engines/draci/animation.cpp index 560f03ffcf..2bbb960fb1 100644 --- a/engines/draci/animation.cpp +++ b/engines/draci/animation.cpp @@ -210,6 +210,10 @@ void Animation::exitGameLoop() { _vm->_game->setExitLoop(true); } +void Animation::tellWalkingState() { + _vm->_game->heroAnimationFinished(); +} + Animation *AnimationManager::addAnimation(int id, uint z, bool playing) { // Increment animation index ++_lastIndex; diff --git a/engines/draci/animation.h b/engines/draci/animation.h index e7ffc9d761..473e875941 100644 --- a/engines/draci/animation.h +++ b/engines/draci/animation.h @@ -114,6 +114,7 @@ public: void doNothing() {} void stopAnimation(); void exitGameLoop(); + void tellWalkingState(); private: uint nextFrameNum() const; diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp index 11e9546b39..8539751c87 100644 --- a/engines/draci/game.cpp +++ b/engines/draci/game.cpp @@ -956,6 +956,11 @@ void Game::runDialogueProg(GPL2Program prog, int offset) { void Game::playHeroAnimation(int anim_index) { const GameObject *dragon = getObject(kDragonObject); + const int current_anim_index = playingObjectAnimation(dragon); + if (anim_index == current_anim_index) { + return; + } + const int animID = dragon->_anim[anim_index]; Animation *anim = _vm->_anims->getAnimation(animID); stopObjectAnimations(dragon); @@ -971,28 +976,14 @@ void Game::redrawWalkingPath(int id, byte colour, const WalkingPath &path) { anim->markDirtyRect(_vm->_screen->getSurface()); } -void Game::positionHero(const Common::Point &p, SightDirection dir) { +void Game::setHeroPosition(const Common::Point &p) { debugC(3, kDraciWalkingDebugLevel, "Jump to x: %d y: %d", p.x, p.y); - _hero = p; - Movement movement = kStopRight; - switch (dir) { - case kDirectionLeft: - movement = kStopLeft; - break; - case kDirectionRight: - movement = kStopRight; - break; - default: { - const GameObject *dragon = getObject(kDragonObject); - const int anim_index = playingObjectAnimation(dragon); - if (anim_index >= 0) { - movement = static_cast (anim_index); - } - break; - } - } - playHeroAnimation(movement); +} + +void Game::positionHero(const Common::Point &p, SightDirection dir) { + setHeroPosition(p); + playHeroAnimation(_walkingState.animationForSightDirection(dir)); } Common::Point Game::findNearestWalkable(int x, int y) const { diff --git a/engines/draci/game.h b/engines/draci/game.h index bb4d97e6af..f57b917e91 100644 --- a/engines/draci/game.h +++ b/engines/draci/game.h @@ -207,9 +207,11 @@ public: } Common::Point findNearestWalkable(int x, int y) const; + void heroAnimationFinished() { _walkingState.heroAnimationFinished(); } void stopWalking() { _walkingState.stopWalking(); } // and clear callback void positionHero(const Common::Point &p, SightDirection dir); // teleport the dragon void walkHero(int x, int y, SightDirection dir); // start walking and leave callback as is + void setHeroPosition(const Common::Point &p); int getHeroX() const { return _hero.x; } int getHeroY() const { return _hero.y; } void positionAnimAsHero(Animation *anim); diff --git a/engines/draci/walking.cpp b/engines/draci/walking.cpp index 0f2dda4b88..1400471f04 100644 --- a/engines/draci/walking.cpp +++ b/engines/draci/walking.cpp @@ -347,11 +347,11 @@ void WalkingMap::drawOverlayRectangle(const Common::Point &p, byte colour, byte } } -int WalkingMap::pointsBetween(const Common::Point &p1, const Common::Point &p2) const { +int WalkingMap::pointsBetween(const Common::Point &p1, const Common::Point &p2) { return MAX(abs(p2.x - p1.x), abs(p2.y - p1.y)); } -Common::Point WalkingMap::interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n) const { +Common::Point WalkingMap::interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n) { const int x = (p1.x * (n-i) + p2.x * i + n/2) / n; const int y = (p1.y * (n-i) + p2.y * i + n/2) / n; return Common::Point(x, y); @@ -455,6 +455,10 @@ void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2 _path[i].x *= delta.x; _path[i].y *= delta.y; } + + // Going to start with the first segment. + _segment = _lastAnimPhase = _position = _length = -1; + turnForTheNextSegment(); } void WalkingState::setCallback(const GPL2Program *program, uint16 offset) { @@ -484,13 +488,106 @@ void WalkingState::callback() { } bool WalkingState::continueWalking() { - // FIXME: do real walking instead of immediately exiting. Compare the - // current dragon's animation phase with the stored one, and if they - // differ, walk another step. - debugC(2, kDraciWalkingDebugLevel, "Continuing walking"); - _vm->_game->positionHero(_path[_path.size() - 1], _dir); - _path.clear(); - return false; // finished + const GameObject *dragon = _vm->_game->getObject(kDragonObject); + const Movement anim_index = static_cast (_vm->_game->playingObjectAnimation(dragon)); + + // If the current animation is a turning animation, wait a bit more. + // When this animation has finished, heroAnimationFinished() callback + // will be called, which starts a new scheduled one, so the code never + // gets here if it hasn't finished yet. + if (isTurningMovement(anim_index)) { + return true; + } + + // If the current segment is the last one, we have reached the + // destination and are already facing in the right direction ===> + // return false. + if (_segment >= (int) (_path.size() - 1)) { + _path.clear(); + return false; + } + + // 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[anim_index]; + Animation *anim = _vm->_anims->getAnimation(animID); + const int animPhase = anim->currentFrameNum(); + const bool wasUpdated = animPhase != _lastAnimPhase; + if (!wasUpdated) { + return true; + } + + debugC(3, kDraciWalkingDebugLevel, "Continuing walking in segment %d and position %d/%d", _segment, _position, _length); + + // We are walking in the middle of an edge. The animation phase has + // just changed. Update the position of the hero. + Common::Point newPos = WalkingMap::interpolate( + _path[_segment], _path[_segment+1], ++_position, _length); + _vm->_game->setHeroPosition(newPos); + _vm->_game->positionAnimAsHero(anim); + + // If the hero has reached the end of the edge, start transition to the + // next phase. This will increment _segment, either immediately (if no + // transition is needed) or in the callback (after the transition is + // done). + if (_position >= _length) { + turnForTheNextSegment(); + } + + return true; +} + +void WalkingState::turnForTheNextSegment() { + const GameObject *dragon = _vm->_game->getObject(kDragonObject); + const Movement currentAnim = static_cast (_vm->_game->playingObjectAnimation(dragon)); + const Movement wantAnim = directionForNextPhase(); + Movement transition = transitionBetweenAnimations(currentAnim, wantAnim); + + debugC(2, kDraciWalkingDebugLevel, "Turning for segment %d", _segment+1); + + if (transition == kMoveUndefined) { + // Start the next segment right away as if the turning has just finished. + heroAnimationFinished(); + } else { + // Otherwise start the transition and wait until the Animation + // class calls heroAnimationFinished() as a callback. + assert(isTurningMovement(transition)); + _vm->_game->playHeroAnimation(transition); + const int animID = dragon->_anim[transition]; + Animation *anim = _vm->_anims->getAnimation(animID); + anim->registerCallback(&Animation::tellWalkingState); + + debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d", transition); + } +} + +void WalkingState::heroAnimationFinished() { + // The hero is turned well for the next line segment or for facing the + // target direction. + + // Start the desired next animation. playHeroAnimation() takes care of + // stopping the current animation. + // Don't use any callbacks, because continueWalking() will decide the + // end on its own and after walking is done callbacks shouldn't be + // called either. It wouldn't make much sense anyway, since the + // walking/staying/talking animations are cyclic. + Movement nextAnim = directionForNextPhase(); + _vm->_game->playHeroAnimation(nextAnim); + _lastAnimPhase = 0; + + debugC(2, kDraciWalkingDebugLevel, "Turned for segment %d, starting animation %d", _segment+1, nextAnim); + + if (++_segment < (int) (_path.size() - 1)) { + // We are on an edge: track where the hero is on this edge. + _position = 0; + _length = WalkingMap::pointsBetween(_path[_segment], _path[_segment+1]); + debugC(2, kDraciWalkingDebugLevel, "Next segment %d has length %d", _segment, _length); + } else { + // Otherwise we are done. continueWalking() will return false next time. + debugC(2, kDraciWalkingDebugLevel, "We have walked the whole path"); + } + + // TODO: do we need to clear this callback for the animation? } Movement WalkingState::animationForDirection(const Common::Point &here, const Common::Point &there) { @@ -503,6 +600,14 @@ Movement WalkingState::animationForDirection(const Common::Point &here, const Co } } +Movement WalkingState::directionForNextPhase() const { + if (_segment >= (int) (_path.size() - 2)) { + return animationForSightDirection(_dir); + } else { + return animationForDirection(_path[_segment+1], _path[_segment+2]); + } +} + Movement WalkingState::transitionBetweenAnimations(Movement previous, Movement next) { switch (next) { case kMoveUp: @@ -584,4 +689,24 @@ Movement WalkingState::transitionBetweenAnimations(Movement previous, Movement n } } +Movement WalkingState::animationForSightDirection(SightDirection dir) const { + switch (dir) { + case kDirectionLeft: + return kStopLeft; + case kDirectionRight: + return kStopRight; + default: { + const GameObject *dragon = _vm->_game->getObject(kDragonObject); + const int anim_index = _vm->_game->playingObjectAnimation(dragon); + if (anim_index >= 0) { + return static_cast (anim_index); + } else { + return kStopRight; // TODO + } + break; + } + } + // TODO: implement all needed functionality +} + } diff --git a/engines/draci/walking.h b/engines/draci/walking.h index b85b77d53d..180c3cf855 100644 --- a/engines/draci/walking.h +++ b/engines/draci/walking.h @@ -53,6 +53,9 @@ public: Sprite *newOverlayFromPath(const WalkingPath &path, byte colour) const; Common::Point getDelta() const { return Common::Point(_deltaX, _deltaY); } + static int pointsBetween(const Common::Point &p1, const Common::Point &p2); + static Common::Point interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n); + private: int _realWidth, _realHeight; int _deltaX, _deltaY; @@ -66,8 +69,6 @@ private: static int kDirections[][2]; void drawOverlayRectangle(const Common::Point &p, byte colour, byte *buf) const; - int pointsBetween(const Common::Point &p1, const Common::Point &p2) const; - Common::Point interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n) const; bool lineIsCovered(const Common::Point &p1, const Common::Point &p2) const; // Returns true if the number of vertices on the path was decreased. @@ -88,9 +89,13 @@ enum SightDirection { enum Movement { kMoveUndefined = -1, kMoveDown, kMoveUp, kMoveRight, kMoveLeft, - kMoveRightDown, kMoveRightUp, kMoveLeftDown, kMoveLeftUp, + + kFirstTurning, + kMoveRightDown = kFirstTurning, kMoveRightUp, kMoveLeftDown, kMoveLeftUp, kMoveDownRight, kMoveUpRight, kMoveDownLeft, kMoveUpLeft, kMoveLeftRight, kMoveRightLeft, kMoveUpStopLeft, kMoveUpStopRight, + kLastTurning = kMoveUpStopRight, + kSpeakRight, kSpeakLeft, kStopRight, kStopLeft }; @@ -119,6 +124,13 @@ public: // the callback untouched (the caller must call it). bool continueWalking(); + // Called when the hero's turning animation has finished. Starts + // scheduled animation. + void heroAnimationFinished(); + + // Returns the hero's animation corresponding to looking into given direction. + Movement animationForSightDirection(SightDirection dir) const; + private: DraciEngine *_vm; @@ -126,17 +138,33 @@ private: Common::Point _mouse; SightDirection _dir; + int _segment; + int _position, _length; + int _lastAnimPhase; + const GPL2Program *_callback; uint16 _callbackOffset; + // Initiates turning of the dragon into the direction for the next segment / after walking. + void turnForTheNextSegment(); + // Return one of the 4 animations kMove{Down,Up,Right,Left} // corresponding to the walking from here to there. static Movement animationForDirection(const Common::Point &here, const Common::Point &there); + // Returns the desired facing direction to begin the next phase of the + // walk. It's either a direction for the given edge or the desired + // final direction. + Movement directionForNextPhase() const; + // Returns either animation that needs to be played between given two // animations (e.g., kMoveRightDown after kMoveRight and before // kMoveDown), or kMoveUndefined if none animation is to be played. static Movement transitionBetweenAnimations(Movement previous, Movement next); + + static bool isTurningMovement(Movement m) { + return m >= kFirstTurning && m <= kLastTurning; + } }; } // End of namespace Draci -- cgit v1.2.3