/* ScummVM - Scumm Interpreter * Copyright (C) 2003-2005 The ScummVM project * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ #include "stdafx.h" #include "queen/walk.h" #include "queen/bankman.h" #include "queen/input.h" #include "queen/logic.h" #include "queen/graphics.h" #include "queen/grid.h" #include "queen/queen.h" namespace Queen { const MovePersonData Walk::_moveData[] = { { "COMPY", -1, -6, 1, 6, 0, 0, 0, 0, 12, 12, 1, 14 }, { "DEINO", -1, -8, 1, 8, 0, 0, 0, 0, 11, 11, 1, 10 }, { "FAYE", -1, -6, 1, 6, 13, 18, 7, 12, 19, 22, 2, 5 }, { "GUARDS", -1, -6, 1, 6, 0, 0, 0, 0, 7, 7, 2, 5 }, { "PRINCESS1", -1, -6, 1, 6, 13, 18, 7, 12, 19, 21, 2, 5 }, { "PRINCESS2", -1, -6, 1, 6, 13, 18, 7, 12, 19, 21, 2, 5 }, { "AMGUARD", -1, -6, 1, 6, 13, 18, 7, 12, 19, 21, 2, 5 }, { "SPARKY", -1, -6, 1, 6, 13, 18, 7, 12, 21, 20, 2, 5 }, { "LOLA_SHOWER", -1, -6, 55, 60, 0, 0, 0, 0, 7, 7, 2, 5 }, { "LOLA", -24, -29, 24, 29, 0, 0, 0, 0, 30, 30, 2, 5 }, { "BOB", -15, -20, 15, 20, 21, 26, 0, 0, 27, 29, 2, 5 }, { "CHEF", -1, -4, 1, 4, 0, 0, 0, 0, 1, 5, 2, 4 }, { "HENRY", -1, -6, 1, 6, 0, 0, 0, 0, 7, 7, 2, 6 }, { "ANDERSON", -1, -6, 1, 6, 0, 0, 0, 0, 7, 7, 2, 5 }, { "JASPAR", -4, -9, 4, 9, 16, 21, 10, 15, 1, 3, 1, 10 }, { "PYGMY", -7, -12, 7, 12, 0, 0, 0, 0, 27, 27, 2, 5 }, { "FRANK", 7, 12, 1, 6, 0, 0, 0, 0, 13, 13, 2, 4 }, { "WEDGEWOOD", -20, -25, 20, 25, 0, 0, 0, 0, 1, 1, 1, 5 }, { "TMPD", -1, -6, 1, 6, 13, 18, 7, 12, 19, 21, 2, 5 }, { "IAN", -1, -6, 1, 6, 0, 0, 0, 0, 7, 7, 2, 6 }, { "*", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; Walk::Walk(QueenEngine *vm) : _vm(vm) { } void Walk::animateJoePrepare() { // queen.c l.2748-2788 uint16 i; for (i = 1; i <= _walkDataCount; ++i) { WalkData *pwd = &_walkData[i]; if (pwd->dx < 0) { pwd->anim.set(11, 18, DIR_LEFT); } else { pwd->anim.set(11, 18, DIR_RIGHT); } int16 k = ABS(pwd->dy); int16 ds = pwd->area->scaleDiff(); if (ds > 0) { k *= ((k * ds) / pwd->area->box.yDiff()) / 2; } if (ABS(pwd->dx) < k) { if (pwd->dy < 0) { if (ds < 0) { pwd->anim.set(19, 24, DIR_FRONT); } else { pwd->anim.set(25, 30, DIR_BACK); } } else if (pwd->dy > 0) { if (ds < 0) { pwd->anim.set(25, 30, DIR_BACK); } else { pwd->anim.set(19, 24, DIR_FRONT); } } } } } void Walk::animateJoe() { // queen.c l.2789-2835 uint16 lastDirection = 0; uint16 i; BobSlot *pbs = _vm->graphics()->bob(0); _vm->logic()->joeFacing(_walkData[1].anim.facing); _vm->logic()->joeScale(_walkData[1].area->calcScale(pbs->y)); _vm->logic()->joeFace(); for (i = 1; i <= _walkDataCount && !_joeInterrupted; ++i) { WalkData *pwd = &_walkData[i]; // area has been turned off, see if we should execute a cutaway if (pwd->area->mapNeighbours < 0) { // queen.c l.2838-2911 _vm->logic()->handleSpecialArea(pwd->anim.facing, pwd->areaNum, i); _joeMoveBlock = true; return; } if (lastDirection != pwd->anim.facing) { pbs->animNormal(pwd->anim.firstFrame, pwd->anim.lastFrame, 1, false, false); } uint16 moveSpeed = _vm->grid()->findScale(pbs->x, pbs->y) * 6 / 100; pbs->move(pbs->x + pwd->dx, pbs->y + pwd->dy, moveSpeed); pbs->xflip = (pbs->xdir < 0); while (pbs->moving) { // adjust Joe's movespeed according to scale pbs->scale = pwd->area->calcScale(pbs->y); _vm->logic()->joeScale(pbs->scale); pbs->scaleWalkSpeed(6); _vm->update(true); if (_vm->input()->cutawayQuit() || _vm->logic()->joeWalk() == JWM_EXECUTE) { stopJoe(); break; } } lastDirection = pwd->anim.facing; } _vm->logic()->joeFacing(lastDirection); } void Walk::animatePersonPrepare(const MovePersonData *mpd, int direction) { // queen.c l.2469-2572 int i; for (i = 1; i <= _walkDataCount; ++i) { WalkData *pwd = &_walkData[i]; if (pwd->dx < 0) { pwd->anim.set(mpd->walkLeft1, mpd->walkLeft2, DIR_LEFT); } else if (pwd->dx > 0) { pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_RIGHT); } else { if (ABS(mpd->walkLeft1) == ABS(mpd->walkRight1)) { pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_RIGHT); } else { // we have specific moves for this actor, see what direction they were last facing if (direction == -3) { // previously facing right pwd->anim.set(mpd->walkLeft1, mpd->walkLeft2, DIR_LEFT); } else { // previously facing left pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_RIGHT); } } } int16 k = ABS(pwd->dy); int16 ds = pwd->area->scaleDiff(); if (ds > 0) { k *= ((k * ds) / pwd->area->box.yDiff()) / 2; } if (ABS(pwd->dx) < k) { if (pwd->dy < 0) { if (mpd->walkBack1 > 0) { pwd->anim.set(mpd->walkBack1, mpd->walkBack2, DIR_BACK); } else if (pwd->dx < 0) { pwd->anim.set(mpd->walkLeft1, mpd->walkLeft2, DIR_BACK); } else { pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_BACK); } } else if (pwd->dy > 0) { if (mpd->walkFront1 > 0) { pwd->anim.set(mpd->walkFront1, mpd->walkFront2, DIR_FRONT); } else if (ABS(mpd->walkLeft1) == ABS(mpd->walkRight1)) { if (pwd->dx < 0) { pwd->anim.set(mpd->walkLeft1, mpd->walkLeft2, DIR_FRONT); } else { pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_FRONT); } } else { // we have a special move for left/right, so select that instead! if (direction == -3) { // previously facing right pwd->anim.set(mpd->walkLeft1, mpd->walkLeft2, DIR_FRONT); } else { // previously facing left pwd->anim.set(mpd->walkRight1, mpd->walkRight2, DIR_FRONT); } } } } } } void Walk::animatePerson(const MovePersonData *mpd, uint16 image, uint16 bobNum, uint16 bankNum, int direction) { // queen.c l.2572-2651 BobSlot *pbs = _vm->graphics()->bob(bobNum); // check to see which way person should be facing if (mpd->walkLeft1 == mpd->walkRight1) { pbs->xflip = (direction == -3); } else { // they have special walk for left and right, so don't flip pbs->xflip = false; } uint16 i; for (i = 1; i <= _walkDataCount; ++i) { WalkData *pwd = &_walkData[i]; // unpack necessary frames for bob animation uint16 dstFrame = image; uint16 srcFrame = ABS(pwd->anim.firstFrame); while (srcFrame <= ABS(pwd->anim.lastFrame)) { _vm->bankMan()->unpack(srcFrame, dstFrame, bankNum); ++dstFrame; ++srcFrame; } // pass across bobs direction ONLY if walk is a mirror flip! if (ABS(mpd->walkLeft1) == ABS(mpd->walkRight1)) { pbs->animNormal(image, dstFrame - 1, mpd->animSpeed, false, pbs->xflip); } else { pbs->animNormal(image, dstFrame - 1, mpd->animSpeed, false, false); } // move other actors at correct speed relative to scale uint16 moveSpeed = _vm->grid()->findScale(pbs->x, pbs->y) * mpd->moveSpeed / 100; pbs->move(pbs->x + pwd->dx, pbs->y + pwd->dy, moveSpeed); // flip if one set of frames for actor if (mpd->walkLeft1 < 0 || ABS(mpd->walkLeft1) == ABS(mpd->walkRight1)) { pbs->xflip = pwd->dx < 0; } while (pbs->moving) { _vm->update(); pbs->scale = pwd->area->calcScale(pbs->y); pbs->scaleWalkSpeed(mpd->moveSpeed); if (_vm->input()->cutawayQuit()) { stopPerson(bobNum); break; } } } } int16 Walk::moveJoe(int direction, int16 endx, int16 endy, bool inCutaway) { _joeInterrupted = false; _joeMoveBlock = false; int16 can = 0; initWalkData(); uint16 oldx = _vm->graphics()->bob(0)->x; uint16 oldy = _vm->graphics()->bob(0)->y; _vm->logic()->joeWalk(JWM_MOVE); uint16 oldPos = _vm->grid()->findAreaForPos(GS_ROOM, oldx, oldy); uint16 newPos = _vm->grid()->findAreaForPos(GS_ROOM, endx, endy); debug(9, "Walk::moveJoe(%d, %d, %d, %d, %d) - old = %d, new = %d", direction, oldx, oldy, endx, endy, oldPos, newPos); // if in cutaway, allow Joe to walk anywhere if (newPos == 0 && inCutaway) { incWalkData(oldx, oldy, endx, endy, oldPos); } else { if (calc(oldPos, newPos, oldx, oldy, endx, endy)) { if (_walkDataCount > 0) { animateJoePrepare(); animateJoe(); if (_joeInterrupted) { can = -1; } } } else { // path has been blocked, make Joe say so _vm->logic()->makeJoeSpeak(4); can = -1; } } _vm->graphics()->bob(0)->animating = false; if (_joeMoveBlock) { can = -2; _joeMoveBlock = false; } else if (direction > 0) { _vm->logic()->joeFacing(direction); } _vm->logic()->joePrevFacing(_vm->logic()->joeFacing()); _vm->logic()->joeFace(); return can; } int16 Walk::movePerson(const Person *pp, int16 endx, int16 endy, uint16 curImage, int direction) { if (endx == 0 && endy == 0) { warning("Walk::movePerson() - endx == 0 && endy == 0"); return 0; } int16 can = 0; initWalkData(); uint16 bobNum = pp->actor->bobNum; uint16 bankNum = pp->actor->bankNum; uint16 oldx = _vm->graphics()->bob(bobNum)->x; uint16 oldy = _vm->graphics()->bob(bobNum)->y; uint16 oldPos = _vm->grid()->findAreaForPos(GS_ROOM, oldx, oldy); uint16 newPos = _vm->grid()->findAreaForPos(GS_ROOM, endx, endy); debug(9, "Walk::movePerson(%d, %d, %d, %d, %d) - old = %d, new = %d", direction, oldx, oldy, endx, endy, oldPos, newPos); // find MovePersonData associated to Person const MovePersonData *mpd = _moveData; while (mpd->name[0] != '*') { if (scumm_stricmp(mpd->name, pp->name) == 0) { break; } ++mpd; } if (calc(oldPos, newPos, oldx, oldy, endx, endy)) { if (_walkDataCount > 0) { animatePersonPrepare(mpd, direction); animatePerson(mpd, curImage, bobNum, bankNum, direction); } } else { can = -1; } uint16 standingFrame = 31 + bobNum; // make other person face the right direction BobSlot *pbs = _vm->graphics()->bob(bobNum); pbs->endx = endx; pbs->endy = endy; pbs->animating = false; pbs->scale = _walkData[_walkDataCount].area->calcScale(endy); if (_walkData[_walkDataCount].anim.facing == DIR_BACK) { _vm->bankMan()->unpack(mpd->backStandingFrame, standingFrame, bankNum); } else { _vm->bankMan()->unpack(mpd->frontStandingFrame, standingFrame, bankNum); } uint16 obj = _vm->logic()->objectForPerson(bobNum); if (_walkData[_walkDataCount].dx < 0) { _vm->logic()->objectData(obj)->image = -3; pbs->xflip = true; } else { _vm->logic()->objectData(obj)->image = -4; pbs->xflip = false; } pbs->frameNum = standingFrame; return can; } void Walk::stopJoe() { BobSlot *pbs = _vm->graphics()->bob(0); pbs->moving = false; _joeInterrupted = true; } void Walk::stopPerson(uint16 bobNum) { BobSlot *pbs = _vm->graphics()->bob(bobNum); pbs->x = pbs->endx; pbs->y = pbs->endy; pbs->moving = false; } bool Walk::calc(uint16 oldPos, uint16 newPos, int16 oldx, int16 oldy, int16 x, int16 y) { // if newPos is outside of an AREA then traverse Y axis until an AREA is found if (newPos == 0) { newPos = findAreaPosition(&x, &y, true); } // do the same for oldPos in case Joe somehow sits on the border of an AREA // and does not register if (oldPos == 0) { oldPos = findAreaPosition(&oldx, &oldy, false); } if (oldPos == newPos) { incWalkData(oldx, oldy, x, y, newPos); return true; } else if (calcPath(oldPos, newPos)) { uint16 i; int16 px = oldx; int16 py = oldy; for (i = 2; i <= _areaListCount; ++i) { uint16 a1 = _areaList[i - 1]; uint16 a2 = _areaList[i]; const Area *pa1 = &_roomArea[a1]; const Area *pa2 = &_roomArea[a2]; uint16 x1 = calcC(pa1->box.x1, pa1->box.x2, pa2->box.x1, pa2->box.x2, px); uint16 y1 = calcC(pa1->box.y1, pa1->box.y2, pa2->box.y1, pa2->box.y2, py); incWalkData(px, py, x1, y1, a1); px = x1; py = y1; } incWalkData(px, py, x, y, newPos); return true; } return false; } int16 Walk::calcC(int16 c1, int16 c2, int16 c3, int16 c4, int16 lastc) { int16 s1 = MAX(c1, c3); int16 s2 = MIN(c2, c4); int16 c; if ((lastc >= s1 && lastc <= s2) || (lastc >= s2 && lastc <= s1)) { c = lastc; } else { c = (s1 + s2) / 2; } return c; } int16 Walk::findAreaPosition(int16 *x, int16 *y, bool recalibrate) { // FIXME - in order to locate the nearest available area, the original // algorithm computes the X (or Y) closest face distance for each available // area. We simply added the case where the pointer is neither lying in the // X range nor in the Y one. // To get an example of this in action, in the room D1, make Joe walking // to the wall at the right of the window (just above the radiator). On the // original game, Joe will go to the left door... uint16 i; uint16 pos = 1; uint32 minDist = (uint32)~0; const Box *b = &_roomArea[1].box; for (i = 1; i <= _roomAreaCount; ++i) { b = &_roomArea[i].box; uint16 dx1 = ABS(b->x1 - *x); uint16 dx2 = ABS(b->x2 - *x); uint16 dy1 = ABS(b->y1 - *y); uint16 dy2 = ABS(b->y2 - *y); uint16 csx = MIN(dx1, dx2); uint16 csy = MIN(dy1, dy2); bool inX = (*x >= b->x1) && (*x <= b->x2); bool inY = (*y >= b->y1) && (*y <= b->y2); uint32 dist = minDist; if (!inX && !inY) { dist = csx * csx + csy * csy; } else if (inX) { dist = csy * csy; } else if (inY) { dist = csx * csx; } if (dist < minDist) { minDist = dist; pos = i; } } // we now have the closest area near X,Y, so we can recalibrate // the X,Y coord to be in this area if (recalibrate) { b = &_roomArea[pos].box; if (*x < b->x1) *x = b->x1; if (*x > b->x2) *x = b->x2; if (*y < b->y1) *y = b->y1; if (*y > b->y2) *y = b->y2; } return pos; } uint16 Walk::findFreeArea(uint16 area) const { uint16 testArea; uint16 freeArea = 0; uint16 map = ABS(_roomArea[area].mapNeighbours); for (testArea = 1; testArea <= _roomAreaCount; ++testArea) { int b = _roomAreaCount - testArea; if (map & (1 << b)) { // connecting area, check if it's been struck off if (!isAreaStruck(testArea)) { // legitimate connecting area, keep it freeArea = testArea; break; } } } return freeArea; } bool Walk::isAreaStruck(uint16 area) const { uint16 i; bool found = false; for (i = 1; i <= _areaStrikeCount; ++i) { if (_areaStrike[i] == area) { found = true; break; } } return found; } bool Walk::calcPath(uint16 oldArea, uint16 newArea) { debug(9, "Walk::calcPath(%d, %d)", oldArea, newArea); _areaList[1] = _areaStrike[1] = oldArea; _areaListCount = _areaStrikeCount = 1; uint16 area = oldArea; while (_areaListCount > 0 && area != newArea) { area = findFreeArea(area); if (!area) { // wrong path, rolling back _areaList[_areaListCount] = 0; --_areaListCount; area = _areaList[_areaListCount]; } else { ++_areaListCount; assert(_areaListCount < MAX_WALK_DATA); _areaList[_areaListCount] = area; if (!isAreaStruck(area)) { ++_areaStrikeCount; assert(_areaStrikeCount < MAX_WALK_DATA); _areaStrike[_areaStrikeCount] = area; } } } return _areaList[1] != 0; } void Walk::initWalkData() { uint16 curRoom = _vm->logic()->currentRoom(); _roomArea = _vm->grid()->area(curRoom, 0); _roomAreaCount = _vm->grid()->areaMax(curRoom); _walkDataCount = 0; memset(_walkData, 0, sizeof(_walkData)); _areaStrikeCount = 0; memset(_areaStrike, 0, sizeof(_areaStrike)); _areaListCount = 0; memset(_areaList, 0, sizeof(_areaList)); } void Walk::incWalkData(int16 px, int16 py, int16 x, int16 y, uint16 areaNum) { debug(9, "Walk::incWalkData(%d, %d, %d)", (x - px), (y - py), areaNum); if (px != x || py != y) { ++_walkDataCount; assert(_walkDataCount < MAX_WALK_DATA); WalkData *pwd = &_walkData[_walkDataCount]; pwd->dx = x - px; pwd->dy = y - py; pwd->area = &_roomArea[areaNum]; pwd->areaNum = areaNum; } } } // End of namespace Queen