Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

puzzle.cpp

/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.      See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-1/engines/saga/puzzle.cpp $
 * $Id: puzzle.cpp 30944 2008-02-23 22:50:18Z sev $
 *
 */

#include "saga/saga.h"

#include "saga/actor.h"
#include "saga/interface.h"
#include "saga/scene.h"
#include "saga/sprite.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "saga/sagaresnames.h"

#include "common/system.h"
#include "common/timer.h"

namespace Saga {

#define PUZZLE_X_OFFSET       72
#define PUZZLE_Y_OFFSET       46

#define PUZZLE_FIT                  0x01   // 1 when in correct position
#define PUZZLE_MOVED          0x04   // 1 when somewhere in the box
#define PUZZLE_ALL_SET        PUZZLE_FIT | PUZZLE_MOVED


enum rifOptions {
      kROLater = 0,
      kROAccept = 1,
      kRODecline = 2,
      kROHint = 3
};

Puzzle::Puzzle(SagaEngine *vm) : _vm(vm), _solved(false), _active(false) {
      _lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0;

      _hintRqState = kRQNoHint;
      _hintOffer = 0;
      _hintCount = 0;
      _helpCount = 0;
      _puzzlePiece = -1;
      _newPuzzle = true;
      _sliding = false;
      _hintBox.left = 70;
      _hintBox.top = 105;
      _hintBox.setWidth(240);
      _hintBox.setHeight(30);

      initPieceInfo( 0, 268,  18,  0, 0,  0 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 3,
              Point(0, 1),  Point(0, 62), Point(15, 31), Point(0, 0), Point(0, 0), Point(0,0));
      initPieceInfo( 1, 270,  52,  0, 0,  0 + PUZZLE_X_OFFSET,  32 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 31), Point(0, 47), Point(39, 47), Point(15, 1), Point(0, 0), Point(0, 0));
      initPieceInfo( 2, 19,  51,  0, 0,  0 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 0), Point(23, 46), Point(39, 15), Point(31, 0), Point(0, 0), Point(0, 0));
      initPieceInfo( 3, 73,   0,  0, 0,   32 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 6,
              Point(0, 0), Point(8, 16), Point(0, 31), Point(31, 31), Point(39, 15), Point(31, 0));
      initPieceInfo( 4, 0,  35,  0, 0,   64 + PUZZLE_X_OFFSET,  16 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 15), Point(15, 46), Point(23, 32), Point(7, 1), Point(0, 0), Point(0, 0));
      initPieceInfo( 5, 215,   0,  0, 0,   24 + PUZZLE_X_OFFSET,  32 + PUZZLE_Y_OFFSET, 0, 6,
              Point(0, 15), Point(8, 31), Point(39, 31), Point(47, 16), Point(39, 0), Point(8, 0));
      initPieceInfo( 6, 159,   0,  0, 0,   32 + PUZZLE_X_OFFSET,  48 + PUZZLE_Y_OFFSET, 0, 5,
              Point(0, 16), Point(8, 31), Point(55, 31), Point(39, 1), Point(32, 15), Point(0, 0));
      initPieceInfo( 7, 9,  70,  0, 0,   80 + PUZZLE_X_OFFSET,  32 + PUZZLE_Y_OFFSET, 0, 5,
              Point(0, 31), Point(8, 47), Point(23, 47), Point(31, 31), Point(15, 1), Point(0, 0));
      initPieceInfo( 8, 288,  18,  0, 0,   96 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 31), Point(15, 62), Point(31, 32), Point(15, 1), Point(0, 0), Point(0, 0));
      initPieceInfo( 9, 112,   0,  0, 0,  112 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 0), Point(16, 31), Point(47, 31), Point(31, 0), Point(0, 0), Point(0, 0));
      initPieceInfo(10, 27,  89,  0, 0,  104 + PUZZLE_X_OFFSET,  32 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 47), Point(31, 47), Point(31, 0), Point(24, 0), Point(0, 0), Point(0, 0));
      initPieceInfo(11, 43,   0,  0, 0,  136 + PUZZLE_X_OFFSET,  32 + PUZZLE_Y_OFFSET, 0, 6,
              Point(0, 0), Point(0, 47), Point(15, 47), Point(15, 15), Point(31, 15), Point(23, 0));
      initPieceInfo(12, 0,   0,  0, 0,  144 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 0), Point(24, 47), Point(39, 47), Point(39, 0), Point(0, 0), Point(0, 0));
      initPieceInfo(13, 262,   0,  0, 0,   64 + PUZZLE_X_OFFSET,   0 + PUZZLE_Y_OFFSET, 0, 3,
              Point(0, 0), Point(23, 46), Point(47, 0), Point(0, 0), Point(0, 0), Point(0, 0));
      initPieceInfo(14, 271, 103,  0, 0,  152 + PUZZLE_X_OFFSET,  48 + PUZZLE_Y_OFFSET, 0, 4,
              Point(0, 0), Point(0, 31), Point(31, 31), Point(31, 0), Point(0, 0), Point(0, 0));
}

void Puzzle::initPieceInfo(int i, int16 curX, int16 curY, byte offX, byte offY, int16 trgX,
                                       int16 trgY, uint8 flag, uint8 count, Point point0, Point point1,
                                       Point point2, Point point3, Point point4, Point point5) {
      _pieceInfo[i].curX = curX;
      _pieceInfo[i].curY = curY;
      _pieceInfo[i].offX = offX;
      _pieceInfo[i].offY = offY;
      _pieceInfo[i].trgX = trgX;
      _pieceInfo[i].trgY = trgY;
      _pieceInfo[i].flag = flag;
      _pieceInfo[i].count = count;
      _pieceInfo[i].point[0] = point0;
      _pieceInfo[i].point[1] = point1;
      _pieceInfo[i].point[2] = point2;
      _pieceInfo[i].point[3] = point3;
      _pieceInfo[i].point[4] = point4;
      _pieceInfo[i].point[5] = point5;
}


void Puzzle::execute(void) {
      _active = true;
      _vm->_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);

      initPieces();

      showPieces();

      _vm->_interface->setMode(kPanelConverse);
      clearHint();
      //_solved = true; // Cheat
      //exitPuzzle();
}

void Puzzle::exitPuzzle(void) {
      _active = false;

      _vm->_timer->removeTimerProc(&hintTimerCallback);

      _vm->_scene->changeScene(ITE_SCENE_LODGE, 0, kTransitionNoFade);
      _vm->_interface->setMode(kPanelMain);
}

void Puzzle::initPieces(void) {
      SpriteInfo *spI;
      ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
      int frameNumber;
      SpriteList *spriteList;
      _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
      
      for (int i = 0; i < PUZZLE_PIECES; i++) {
            spI = &(spriteList->infoList[i]);
            _pieceInfo[i].offX = (byte)(spI->width >> 1);
            _pieceInfo[i].offY = (byte)(spI->height >> 1);

            if (_newPuzzle) {
                  _pieceInfo[i].curX = pieceOrigins[i].x;
                  _pieceInfo[i].curY = pieceOrigins[i].y;
            }
            _piecePriority[i] = i;
      }
      _newPuzzle = false;
}

void Puzzle::showPieces(void) {
      ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
      int frameNumber;
      SpriteList *spriteList;
      Surface *backBuffer = _vm->_gfx->getBackBuffer();
      _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);

      for (int j = PUZZLE_PIECES - 1 ; j >= 0; j--) {
            int num = _piecePriority[j];

            if (_puzzlePiece != num) {
                  _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), *spriteList, num, Point(_pieceInfo[num].curX, _pieceInfo[num].curY), 256);
            }
      }
}

void Puzzle::drawCurrentPiece() {
      ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
      Surface *backBuffer = _vm->_gfx->getBackBuffer();
      int frameNumber;
      SpriteList *spriteList;
      _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);

      _vm->_sprite->draw(backBuffer, _vm->_scene->getSceneClip(), *spriteList, _puzzlePiece,
                     Point(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY), 256);
}

void Puzzle::movePiece(Point mousePt) {
      int newx, newy;

      showPieces();

      if (_puzzlePiece == -1)
            return;

      if (_sliding) {
            newx = _slidePointX;
            newy = _slidePointY;
      } else {
            if (mousePt.y >= 137)
                  return;

            newx = mousePt.x;
            newy = mousePt.y;
      }

      newx -= _pieceInfo[_puzzlePiece].offX;
      newy -= _pieceInfo[_puzzlePiece].offY;

      _pieceInfo[_puzzlePiece].curX = newx;
      _pieceInfo[_puzzlePiece].curY = newy;

      drawCurrentPiece();
}

void Puzzle::handleClick(Point mousePt) {
      if (_puzzlePiece != -1) {
            dropPiece(mousePt);

            if (!_active)
                  return; // we won

            drawCurrentPiece();
            _puzzlePiece = -1;

            return;
      }

      for (int j = 0; j < PUZZLE_PIECES; j++)   {
            int i = _piecePriority[j];
            int adjX = mousePt.x - _pieceInfo[i].curX;
            int adjY = mousePt.y - _pieceInfo[i].curY;

            if (hitTestPoly(&_pieceInfo[i].point[0], _pieceInfo[i].count, Point(adjX, adjY))) {
                  _puzzlePiece = i;
                  break;
            }
      }

      if (_puzzlePiece == -1)
            return;

      alterPiecePriority();

      // Display scene background
      _vm->_scene->draw();
      showPieces();

      int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
      int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;

      if (newx != _pieceInfo[_puzzlePiece].curX
            || newy != _pieceInfo[_puzzlePiece].curY) {
            _pieceInfo[_puzzlePiece].curX = newx;
            _pieceInfo[_puzzlePiece].curY = newy;
      }
      _vm->_interface->setStatusText(pieceNames[_lang][_puzzlePiece]);
}

void Puzzle::alterPiecePriority(void) {
      for (int i = 1; i < PUZZLE_PIECES; i++) {
            if (_puzzlePiece == _piecePriority[i]) {
                  for (int j = i - 1; j >= 0; j--)
                        _piecePriority[j+1] = _piecePriority[j];
                  _piecePriority[0] = _puzzlePiece;
                  break;
            }
      }
}

void Puzzle::slidePiece(int x1, int y1, int x2, int y2) {
      int count;
      Point slidePoints[320];

      x1 += _pieceInfo[_puzzlePiece].offX;
      y1 += _pieceInfo[_puzzlePiece].offY;

      count = pathLine(&slidePoints[0], Point(x1, y1),
             Point(x2 + _pieceInfo[_puzzlePiece].offX, y2 + _pieceInfo[_puzzlePiece].offY));

      if (count > 1) {
            int factor = count / 4;
            _sliding = true;

            if (!factor)
                  factor++;

            for (int i = 1; i < count; i += factor) {
                  _slidePointX = slidePoints[i].x;
                  _slidePointY = slidePoints[i].y;
                  _vm->_render->drawScene();
                  _vm->_system->delayMillis(10);
            }
            _sliding = false;
      }

      _pieceInfo[_puzzlePiece].curX = x2;
      _pieceInfo[_puzzlePiece].curY = y2;
}

void Puzzle::dropPiece(Point mousePt) {
      int boxx = PUZZLE_X_OFFSET;
      int boxy = PUZZLE_Y_OFFSET;
      int boxw = boxx + 184;
      int boxh = boxy + 80;

      // if the center is within the box quantize within
      // else move it back to its original start point
      if (mousePt.x >= boxx && mousePt.x < boxw && mousePt.y >= boxy && mousePt.y <= boxh) {
            ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
            SpriteInfo *spI;
            int frameNumber;
            SpriteList *spriteList;
            _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);

            int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
            int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;

            if (newx < boxx)
                  newx = PUZZLE_X_OFFSET;
            if (newy < boxy)
                  newy = PUZZLE_Y_OFFSET;

            spI = &(spriteList->infoList[_puzzlePiece]);

            if (newx + spI->width > boxw)
                  newx = boxw - spI->width ;
            if (newy + spI->height > boxh)
                  newy = boxh - spI->height ;

            int x1 = ((newx - PUZZLE_X_OFFSET) & ~7) + PUZZLE_X_OFFSET;
            int y1 = ((newy - PUZZLE_Y_OFFSET) & ~7) + PUZZLE_Y_OFFSET;
            int x2 = x1 + 8;
            int y2 = y1 + 8;
            newx = (x2 - newx < newx - x1) ? x2 : x1;
            newy = (y2 - newy < newy - y1) ? y2 : y1;

            // if any part of the puzzle piece falls outside the box
            // force it back in

            // is the piece at the target location
            if (newx == _pieceInfo[_puzzlePiece].trgX
                        && newy == _pieceInfo[_puzzlePiece].trgY) {
                  _pieceInfo[_puzzlePiece].flag |= (PUZZLE_MOVED | PUZZLE_FIT);
            } else {
                  _pieceInfo[_puzzlePiece].flag &= ~PUZZLE_FIT;
                  _pieceInfo[_puzzlePiece].flag |= PUZZLE_MOVED;
            }
            _pieceInfo[_puzzlePiece].curX = newx;
            _pieceInfo[_puzzlePiece].curY = newy;
      } else {
            int newx = pieceOrigins[_puzzlePiece].x;
            int newy = pieceOrigins[_puzzlePiece].y;
            _pieceInfo[_puzzlePiece].flag &= ~(PUZZLE_FIT | PUZZLE_MOVED);

            // slide piece from current position to new position
            slidePiece(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY,
                              newx, newy);
      }

      // is the puzzle completed?

      _solved = true;
      for (int i = 0; i < PUZZLE_PIECES; i++)
            if ((_pieceInfo[i].flag & PUZZLE_FIT) == 0)     {
                  _solved = false;
                  break;
            }

      if (_solved)
            exitPuzzle();
}

void Puzzle::hintTimerCallback(void *refCon) {
      ((Puzzle *)refCon)->solicitHint();
}

void Puzzle::solicitHint(void) {
      int i;

      _vm->_actor->setSpeechColor(1, kITEColorBlack);

      _vm->_timer->removeTimerProc(&hintTimerCallback);

      switch (_hintRqState) {
      case kRQSpeaking:
            if (_vm->_actor->isSpeaking()) {
                  _vm->_timer->installTimerProc(&hintTimerCallback, 50000, this);
                  break;
            }

            _hintRqState = _hintNextRqState;
            _vm->_timer->installTimerProc(&hintTimerCallback, 333333, this);
            break;

      case kRQNoHint:
            //    Pick a random hint request.
            i = _hintOffer++;
            if (_hintOffer >= NUM_SOLICIT_REPLIES)
                  _hintOffer = 0;

            //    Determine which of the journeymen will offer then
            //    hint, and then show that character's portrait.
            _hintGiver = portraitList[i];
            _hintSpeaker = _hintGiver - RID_ITE_JFERRET_SERIOUS;
            _vm->_interface->setRightPortrait(_hintGiver);

            _vm->_actor->nonActorSpeech(_hintBox, &solicitStr[_lang][i], 1, PUZZLE_SOLICIT_SOUNDS + i * 3 + _hintSpeaker, 0);

            //    Add Rif's reply to the list.
            clearHint();

            //    Roll to see if Sakka scolds
            if (_vm->_rnd.getRandomNumber(1)) {
                  _hintRqState = kRQSakkaDenies;
                  _vm->_timer->installTimerProc(&hintTimerCallback, 200000, this);
            } else {
                  _hintRqState = kRQSpeaking;
                  _hintNextRqState = kRQHintRequested;
                  _vm->_timer->installTimerProc(&hintTimerCallback, 50000, this);
            }

            break;

      case kRQHintRequested:
            i = _vm->_rnd.getRandomNumber(NUM_SAKKA - 1);
            _vm->_actor->nonActorSpeech(_hintBox, &sakkaStr[_lang][i], 1, PUZZLE_SAKKA_SOUNDS + i, 0);

            _vm->_interface->setRightPortrait(RID_ITE_SAKKA_APPRAISING);

            _hintRqState = kRQSpeaking;
            _hintNextRqState = kRQHintRequestedStage2;
            _vm->_timer->installTimerProc(&hintTimerCallback, 50000, this);

            _vm->_interface->converseClear();
            _vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 0, 1, 0, 0 );
            _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 0, 2, 0, 0 );
            _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0 );
            _vm->_interface->converseDisplayText();
            break;

      case kRQHintRequestedStage2:
            if (_vm->_rnd.getRandomNumber(1)) {             //    Skip Reply part
                  i = _vm->_rnd.getRandomNumber(NUM_WHINES - 1);
                  _vm->_actor->nonActorSpeech(_hintBox, &whineStr[_lang][i], 1, PUZZLE_WHINE_SOUNDS + i * 3 + _hintSpeaker, 0);
            }

            _vm->_interface->setRightPortrait(_hintGiver);

            _hintRqState = kRQSakkaDenies;
            break;

      case kRQSakkaDenies:
            _vm->_interface->converseClear();
            _vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 0, 1, 0, 0);
            _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 0, 2, 0, 0);
            _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
            _vm->_interface->converseDisplayText();

            _vm->_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);

            _hintRqState = kRQSkipEverything;
            break;

      default:
            break;
      }
}

void Puzzle::handleReply(int reply) {
      switch (reply) {
      case 0:           // Quit the puzzle
            exitPuzzle();
            break;

      case 1:           // Accept the hint
            giveHint();
            break;

      case 2:           // Decline the hint
            _vm->_actor->abortSpeech();
            _hintRqState = kRQNoHint;
            _vm->_timer->removeTimerProc(&hintTimerCallback);
            _vm->_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime * 2, this);
            clearHint();
            break;
      }
}

void Puzzle::giveHint(void) {
      int i, total = 0;

      _vm->_interface->converseClear();

      _vm->_actor->abortSpeech();
      _vm->_interface->setRightPortrait(_hintGiver);

      for (i = 0; i < PUZZLE_PIECES; i++)
            total += _pieceInfo[i].flag & PUZZLE_FIT;

      if (_hintCount == 0 && (_pieceInfo[1].flag & PUZZLE_FIT
                  || _pieceInfo[12].flag & PUZZLE_FIT))
            _hintCount++;
      if (_hintCount == 1 && _pieceInfo[14].flag & PUZZLE_FIT)
            _hintCount++;

      if (_hintCount == 2 && total > 3)
            _hintCount++;

      _vm->_actor->setSpeechColor(1, kITEColorBlack);

      if (_hintCount < 3) {
            _vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][_hintCount], 1, PUZZLE_HINT_SOUNDS + _hintCount * 3 + _hintSpeaker, 0);
      } else {
            int piece = 0;

            for (i = PUZZLE_PIECES - 1; i >= 0; i--) {
                  piece = _piecePriority[i];
                  if (_pieceInfo[piece].flag & PUZZLE_MOVED
                              && !(_pieceInfo[piece].flag & PUZZLE_FIT)) {
                        if (_helpCount < 12)
                              _helpCount++;
                        break;
                  }
            }

            if (i >= 0) {
                  static char hintBuf[64];
                  static const char *hintPtr = hintBuf;
                  sprintf(hintBuf, optionsStr[_lang][kROHint], pieceNames[_lang][piece]);

                  _vm->_actor->nonActorSpeech(_hintBox, &hintPtr, 1, PUZZLE_TOOL_SOUNDS + _hintSpeaker + piece * 3, 0);
            }
            else {
                        //    If no pieces are in the wrong place
                  _vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][3], 1, PUZZLE_HINT_SOUNDS + 3 * 3 + _hintSpeaker, 0);
            }
      }
      _hintCount++;

      _hintRqState = kRQNoHint;

      _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
      _vm->_interface->converseDisplayText();

      _vm->_timer->removeTimerProc(&hintTimerCallback);
      _vm->_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this);
}

void Puzzle::clearHint(void) {
      _vm->_interface->converseClear();
      _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
      _vm->_interface->converseDisplayText();
      _vm->_interface->setStatusText(" ");
}

} // End of namespace Saga

Generated by  Doxygen 1.6.0   Back to index