scummvm/engines/bbvs/walk.cpp
johndoe123 1800f9d8dc BBVS: Fix bug #6954: Pathfinding bug in Prison
The bug was caused by a check introduced by me to avoid division-by-zero errors
when the source and dest x values are equal.
This had the side effect that it didn't work well in this case outlined in the
bug report, maybe also in other places.
I'm not sure how to handle a DBZ correctly here so I'm setting the x delta to
1.0 if it would normally be 0.0, which seems to work after walking around
in some scenes.
2015-11-23 13:22:51 +01:00

463 lines
15 KiB
C++

/* 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.
*
*/
#include "bbvs/bbvs.h"
#include "bbvs/gamemodule.h"
namespace Bbvs {
static const int8 kTurnInfo[8][8] = {
{ 0, 1, 1, 1, 1, -1, -1, -1},
{-1, 0, 1, 1, 1, 1, -1, -1},
{-1, -1, 0, 1, 1, 1, 1, -1},
{-1, -1, -1, 0, 1, 1, 1, 1},
{ 1, -1, -1, -1, 0, 1, 1, 1},
{ 1, 1, -1, -1, -1, 0, 1, 1},
{ 1, 1, 1, -1, -1, -1, 0, 1},
{ 1, 1, 1, 1, -1, -1, -1, 0}
};
static const int8 kWalkAnimTbl[32] = {
3, 0, 0, 0, 2, 1, 1, 1,
15, 12, 14, 13, 0, 0, 0, 0,
7, 9, 4, 8, 6, 10, 5, 11,
3, 0, 2, 1, 15, 12, 14, 13
};
void BbvsEngine::startWalkObject(SceneObject *sceneObject) {
if (_buttheadObject != sceneObject && _beavisObject != sceneObject)
return;
initWalkAreas(sceneObject);
_sourceWalkAreaPt.x = sceneObject->x >> 16;
_sourceWalkAreaPt.y = sceneObject->y >> 16;
_sourceWalkArea = getWalkAreaAtPos(_sourceWalkAreaPt);
if (!_sourceWalkArea)
return;
_destWalkAreaPt = sceneObject->walkDestPt;
_destWalkArea = getWalkAreaAtPos(_destWalkAreaPt);
if (!_destWalkArea)
return;
if (_sourceWalkArea != _destWalkArea) {
_currWalkDistance = kMaxDistance;
walkFindPath(_sourceWalkArea, 0);
_destWalkAreaPt = _currWalkDistance == kMaxDistance ? _sourceWalkAreaPt : _finalWalkPt;
}
walkObject(sceneObject, _destWalkAreaPt, sceneObject->sceneObjectDef->walkSpeed);
}
void BbvsEngine::updateWalkObject(SceneObject *sceneObject) {
int animIndex;
if (sceneObject->walkCount > 0 && (sceneObject->xIncr != 0 || sceneObject->yIncr != 0)) {
if (ABS(sceneObject->xIncr) <= ABS(sceneObject->yIncr))
sceneObject->turnValue = sceneObject->yIncr >= 0 ? 0 : 4;
else
sceneObject->turnValue = sceneObject->xIncr >= 0 ? 6 : 2;
animIndex = sceneObject->sceneObjectDef->animIndices[kWalkAnimTbl[sceneObject->turnValue]];
sceneObject->turnCount = 0;
sceneObject->turnTicks = 0;
} else {
animIndex = sceneObject->sceneObjectDef->animIndices[kWalkTurnTbl[sceneObject->turnValue]];
}
Animation *anim = 0;
if (animIndex > 0)
anim = _gameModule->getAnimation(animIndex);
if (sceneObject->anim != anim) {
if (anim) {
sceneObject->anim = anim;
sceneObject->animIndex = animIndex;
sceneObject->frameTicks = 1;
sceneObject->frameIndex = anim->frameCount - 1;
} else {
sceneObject->anim = 0;
sceneObject->animIndex = 0;
sceneObject->frameTicks = 0;
sceneObject->frameIndex = 0;
}
}
}
void BbvsEngine::walkObject(SceneObject *sceneObject, const Common::Point &destPt, int walkSpeed) {
int deltaX = destPt.x - (sceneObject->x >> 16);
int deltaY = destPt.y - (sceneObject->y >> 16);
float distance = (float)sqrt((double)(deltaX * deltaX + deltaY * deltaY));
// NOTE The original doesn't have this check but without it the whole pathfinding breaks
if (distance > 0.0f) {
sceneObject->walkCount = (int)(distance / ((((float)ABS(deltaX) / distance) + 1.0f) * ((float)walkSpeed / 120)));
sceneObject->xIncr = (int)(((float)deltaX / sceneObject->walkCount) * 65536.0f);
sceneObject->yIncr = (int)(((float)deltaY / sceneObject->walkCount) * 65536.0f);
sceneObject->x = (sceneObject->x & 0xFFFF0000) | 0x8000;
sceneObject->y = (sceneObject->y & 0xFFFF0000) | 0x8000;
} else
sceneObject->walkCount = 0;
}
void BbvsEngine::turnObject(SceneObject *sceneObject) {
if (sceneObject->turnTicks > 0) {
--sceneObject->turnTicks;
} else {
int turnDir = kTurnInfo[sceneObject->turnValue][sceneObject->turnCount & 0x7F];
if (turnDir) {
sceneObject->turnValue = (sceneObject->turnValue + turnDir) & 7;
int turnAnimIndex = sceneObject->sceneObjectDef->animIndices[kWalkTurnTbl[sceneObject->turnValue]];
if (turnAnimIndex) {
Animation *anim = _gameModule->getAnimation(turnAnimIndex);
if (anim) {
sceneObject->anim = anim;
sceneObject->animIndex = turnAnimIndex;
sceneObject->turnTicks = 4;
sceneObject->frameTicks = 1;
sceneObject->frameIndex = anim->frameCount - 1;
}
}
} else {
sceneObject->turnCount = 0;
}
}
}
int BbvsEngine::rectSubtract(const Common::Rect &rect1, const Common::Rect &rect2, Common::Rect *outRects) {
int count = 0;
Common::Rect workRect = rect1.findIntersectingRect(rect2);
if (!workRect.isEmpty()) {
count = 0;
outRects[count] = Common::Rect(rect2.width(), workRect.top - rect2.top);
if (!outRects[count].isEmpty()) {
outRects[count].translate(rect2.left, rect2.top);
++count;
}
outRects[count] = Common::Rect(workRect.left - rect2.left, workRect.height());
if (!outRects[count].isEmpty()) {
outRects[count].translate(rect2.left, workRect.top);
++count;
}
outRects[count] = Common::Rect(rect2.right - workRect.right, workRect.height());
if (!outRects[count].isEmpty()) {
outRects[count].translate(workRect.right, workRect.top);
++count;
}
outRects[count] = Common::Rect(rect2.width(), rect2.bottom - workRect.bottom);
if (!outRects[count].isEmpty()) {
outRects[count].translate(rect2.left, workRect.bottom);
++count;
}
} else {
outRects[0] = rect2;
count = 1;
}
return count;
}
WalkInfo *BbvsEngine::addWalkInfo(int16 x, int16 y, int delta, int direction, int16 midPtX, int16 midPtY, int walkAreaIndex) {
WalkInfo *walkInfo = &_walkInfos[_walkInfosCount++];
walkInfo->walkAreaIndex = walkAreaIndex;
walkInfo->direction = direction;
walkInfo->x = x;
walkInfo->y = y;
walkInfo->delta = delta;
walkInfo->midPt.x = midPtX;
walkInfo->midPt.y = midPtY;
return walkInfo;
}
void BbvsEngine::initWalkAreas(SceneObject *sceneObject) {
int16 objX = sceneObject->x >> 16;
int16 objY = sceneObject->y >> 16;
Common::Rect rect;
bool doRect = false;
Common::Rect *workWalkableRects;
if (_buttheadObject == sceneObject && _beavisObject->anim) {
rect = _beavisObject->anim->frameRects2[_beavisObject->frameIndex];
rect.translate(_beavisObject->x >> 16, 1 + (_beavisObject->y >> 16));
doRect = !rect.isEmpty();
} else if (_buttheadObject->anim) {
rect = _buttheadObject->anim->frameRects2[_buttheadObject->frameIndex];
rect.translate(_buttheadObject->x >> 16, 1 + (_buttheadObject->y >> 16));
doRect = !rect.isEmpty();
}
workWalkableRects = _walkableRects;
_walkAreasCount = _walkableRectsCount;
if (doRect && !rect.contains(objX, objY)) {
_walkAreasCount = 0;
for (int i = 0; i < _walkableRectsCount; ++i)
_walkAreasCount += rectSubtract(rect, _walkableRects[i], &_tempWalkableRects1[_walkAreasCount]);
workWalkableRects = _tempWalkableRects1;
}
for (int i = 0; i < _walkAreasCount; ++i) {
_walkAreas[i].x = workWalkableRects[i].left;
_walkAreas[i].y = workWalkableRects[i].top;
_walkAreas[i].width = workWalkableRects[i].width();
_walkAreas[i].height = workWalkableRects[i].height();
_walkAreas[i].checked = false;
_walkAreas[i].linksCount = 0;
}
_walkInfosCount = 0;
// Find connections between the walkRects
for (int i = 0; i < _walkAreasCount; ++i) {
WalkArea *walkArea1 = &_walkAreas[i];
int xIter = walkArea1->x + walkArea1->width;
int yIter = walkArea1->y + walkArea1->height;
for (int j = 0; j < _walkAreasCount; ++j) {
WalkArea *walkArea2 = &_walkAreas[j];
if (i == j)
continue;
if (walkArea2->y == yIter) {
int wa1x = MAX(walkArea1->x, walkArea2->x);
int wa2x = MIN(walkArea2->x + walkArea2->width, xIter);
if (wa2x > wa1x) {
debug(5, "WalkArea %d connected to %d by Y", i, j);
WalkInfo *walkInfo1 = addWalkInfo(wa1x, yIter - 1, wa2x - wa1x, 0, wa1x + (wa2x - wa1x) / 2, yIter - 1, i);
WalkInfo *walkInfo2 = addWalkInfo(wa1x, yIter, wa2x - wa1x, 0, wa1x + (wa2x - wa1x) / 2, yIter, j);
walkArea1->linksD1[walkArea1->linksCount] = walkInfo1;
walkArea1->linksD2[walkArea1->linksCount] = walkInfo2;
walkArea1->links[walkArea1->linksCount++] = walkArea2;
walkArea2->linksD1[walkArea2->linksCount] = walkInfo2;
walkArea2->linksD2[walkArea2->linksCount] = walkInfo1;
walkArea2->links[walkArea2->linksCount++] = walkArea1;
}
}
if (walkArea2->x == xIter) {
int wa1y = MAX(walkArea1->y, walkArea2->y);
int wa2y = MIN(walkArea2->y + walkArea2->height, yIter);
if (wa2y > wa1y) {
debug(5, "WalkArea %d connected to %d by X", i, j);
WalkInfo *walkInfo1 = addWalkInfo(xIter - 1, wa1y, wa2y - wa1y, 1, xIter - 1, wa1y + (wa2y - wa1y) / 2, i);
WalkInfo *walkInfo2 = addWalkInfo(xIter, wa1y, wa2y - wa1y, 1, xIter, wa1y + (wa2y - wa1y) / 2, j);
walkArea1->linksD1[walkArea1->linksCount] = walkInfo1;
walkArea1->linksD2[walkArea1->linksCount] = walkInfo2;
walkArea1->links[walkArea1->linksCount++] = walkArea2;
walkArea2->linksD1[walkArea2->linksCount] = walkInfo2;
walkArea2->linksD2[walkArea2->linksCount] = walkInfo1;
walkArea2->links[walkArea2->linksCount++] = walkArea1;
}
}
}
}
}
WalkArea *BbvsEngine::getWalkAreaAtPos(const Common::Point &pt) {
for (int i = 0; i < _walkAreasCount; ++i) {
WalkArea *walkArea = &_walkAreas[i];
if (walkArea->contains(pt))
return walkArea;
}
return 0;
}
bool BbvsEngine::canButtheadWalkToDest(const Common::Point &destPt) {
Common::Point srcPt;
_walkReachedDestArea = false;
initWalkAreas(_buttheadObject);
srcPt.x = _buttheadObject->x >> 16;
srcPt.y = _buttheadObject->y >> 16;
_sourceWalkArea = getWalkAreaAtPos(srcPt);
if (_sourceWalkArea) {
_destWalkArea = getWalkAreaAtPos(destPt);
if (_destWalkArea)
canWalkToDest(_sourceWalkArea, 0);
}
return _walkReachedDestArea;
}
void BbvsEngine::canWalkToDest(WalkArea *walkArea, int infoCount) {
if (_destWalkArea == walkArea) {
_walkReachedDestArea = true;
return;
}
if (_gameModule->getFieldC() <= 320 || infoCount <= 20) {
walkArea->checked = true;
for (int linkIndex = 0; linkIndex < walkArea->linksCount; ++linkIndex) {
if (!walkArea->links[linkIndex]->checked) {
canWalkToDest(walkArea->links[linkIndex], infoCount + 2);
if (_walkReachedDestArea)
break;
}
}
walkArea->checked = false;
}
}
bool BbvsEngine::walkTestLineWalkable(const Common::Point &sourcePt, const Common::Point &destPt, WalkInfo *walkInfo) {
const float ptDeltaX = MAX<float>(destPt.x - sourcePt.x, 1.0f);
const float ptDeltaY = destPt.y - sourcePt.y;
const float wDeltaX = walkInfo->x - sourcePt.x;
const float wDeltaY = walkInfo->y - sourcePt.y;
if (walkInfo->direction) {
const float nDeltaY = wDeltaX * ptDeltaY / ptDeltaX + (float)sourcePt.y - (float)walkInfo->y;
return (nDeltaY >= 0.0f) && (nDeltaY < (float)walkInfo->delta);
} else {
const float nDeltaX = wDeltaY / ptDeltaX * ptDeltaY + (float)sourcePt.x - (float)walkInfo->x;
return (nDeltaX >= 0.0f) && (nDeltaX < (float)walkInfo->delta);
}
return false;
}
void BbvsEngine::walkFindPath(WalkArea *sourceWalkArea, int infoCount) {
if (_destWalkArea == sourceWalkArea) {
walkFoundPath(infoCount);
} else if (_gameModule->getFieldC() <= 320 || infoCount <= 20) {
sourceWalkArea->checked = true;
for (int linkIndex = 0; linkIndex < sourceWalkArea->linksCount; ++linkIndex) {
if (!sourceWalkArea->links[linkIndex]->checked) {
_walkInfoPtrs[infoCount + 0] = sourceWalkArea->linksD1[linkIndex];
_walkInfoPtrs[infoCount + 1] = sourceWalkArea->linksD2[linkIndex];
walkFindPath(sourceWalkArea->links[linkIndex], infoCount + 2);
}
}
sourceWalkArea->checked = false;
}
}
int BbvsEngine::calcDistance(const Common::Point &pt1, const Common::Point &pt2) {
return (int)sqrt((double)(pt1.x - pt2.x) * (pt1.x - pt2.x) + (pt1.y - pt2.y) * (pt1.y - pt2.y));
}
void BbvsEngine::walkFoundPath(int count) {
debug(5, "BbvsEngine::walkFoundPath(%d)", count);
Common::Point midPt = _sourceWalkAreaPt;
int totalMidPtDistance = 0;
if (count > 0) {
Common::Point lastMidPt;
int halfCount = (count + 1) >> 1;
for (int i = 0; i < halfCount; ++i) {
lastMidPt = midPt;
midPt = _walkInfoPtrs[i * 2]->midPt;
totalMidPtDistance += calcDistance(midPt, lastMidPt);
}
}
int distance = calcDistance(midPt, _destWalkAreaPt) + totalMidPtDistance;
debug(5, "BbvsEngine::walkFoundPath() distance: %d; _currWalkDistance: %d", distance, _currWalkDistance);
if (distance >= _currWalkDistance)
return;
debug(5, "BbvsEngine::walkFoundPath() distance smaller");
_currWalkDistance = distance;
Common::Point destPt = _destWalkAreaPt, newDestPt;
while (1) {
int index = 0;
if (count > 0) {
do {
if (!walkTestLineWalkable(_sourceWalkAreaPt, destPt, _walkInfoPtrs[index]))
break;
++index;
} while (index < count);
}
if (index == count)
break;
WalkInfo *walkInfo = _walkInfoPtrs[--count];
destPt.x = walkInfo->x;
destPt.y = walkInfo->y;
if (walkInfo->direction) {
newDestPt.x = walkInfo->x;
newDestPt.y = walkInfo->y + walkInfo->delta - 1;
} else {
newDestPt.x = walkInfo->x + walkInfo->delta - 1;
newDestPt.y = walkInfo->y;
}
if ((newDestPt.x - _destWalkAreaPt.x) * (newDestPt.x - _destWalkAreaPt.x) +
(newDestPt.y - _destWalkAreaPt.y) * (newDestPt.y - _destWalkAreaPt.y) <
(destPt.x - _destWalkAreaPt.x) * (destPt.x - _destWalkAreaPt.x) +
(destPt.y - _destWalkAreaPt.y) * (destPt.y - _destWalkAreaPt.y))
destPt = newDestPt;
}
debug(5, "BbvsEngine::walkFoundPath() destPt: (%d, %d)", destPt.x, destPt.y);
_finalWalkPt = destPt;
debug(5, "BbvsEngine::walkFoundPath() OK");
}
void BbvsEngine::updateWalkableRects() {
// Go through all walkable rects and subtract all scene object rects
Common::Rect *rectsList1 = _tempWalkableRects1;
Common::Rect *rectsList2 = _gameModule->getWalkRects();
_walkableRectsCount = _gameModule->getWalkRectsCount();
for (int i = 0; i < _gameModule->getSceneObjectDefsCount(); ++i) {
SceneObject *sceneObject = &_sceneObjects[i];
Animation *anim = sceneObject->anim;
if (anim && _buttheadObject != sceneObject && _beavisObject != sceneObject) {
Common::Rect rect = sceneObject->anim->frameRects2[sceneObject->frameIndex];
rect.translate(sceneObject->x >> 16, sceneObject->y >> 16);
int count = _walkableRectsCount;
_walkableRectsCount = 0;
for (int j = 0; j < count; ++j)
_walkableRectsCount += rectSubtract(rect, rectsList2[j], &rectsList1[_walkableRectsCount]);
if (rectsList1 == _tempWalkableRects1) {
rectsList1 = _tempWalkableRects2;
rectsList2 = _tempWalkableRects1;
} else {
rectsList1 = _tempWalkableRects1;
rectsList2 = _tempWalkableRects2;
}
}
}
for (int i = 0; i < _walkableRectsCount; ++i)
_walkableRects[i] = rectsList2[i];
}
} // End of namespace Bbvs