scummvm/engines/draci/walking.cpp
2021-12-26 18:48:43 +01:00

773 lines
25 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/memstream.h"
#include "draci/draci.h"
#include "draci/animation.h"
#include "draci/game.h"
#include "draci/walking.h"
#include "draci/sprite.h"
namespace Draci {
void WalkingMap::load(const byte *data, uint length) {
Common::MemoryReadStream mapReader(data, length);
_realWidth = mapReader.readUint16LE();
_realHeight = mapReader.readUint16LE();
_deltaX = mapReader.readUint16LE();
_deltaY = mapReader.readUint16LE();
_mapWidth = mapReader.readUint16LE();
_mapHeight = mapReader.readUint16LE();
_byteWidth = mapReader.readUint16LE();
// Set the data pointer to raw map data
_data = data + mapReader.pos();
}
bool WalkingMap::getPixel(int x, int y) const {
const byte *pMapByte = _data + _byteWidth * y + x / 8;
return *pMapByte & (1 << x % 8);
}
bool WalkingMap::isWalkable(const Common::Point &p) const {
// Convert to map pixels
return getPixel(p.x / _deltaX, p.y / _deltaY);
}
Sprite *WalkingMap::newOverlayFromMap(byte color) const {
// HACK: Create a visible overlay from the walking map so we can test it
byte *wlk = new byte[_realWidth * _realHeight];
memset(wlk, 255, _realWidth * _realHeight);
for (int i = 0; i < _mapWidth; ++i) {
for (int j = 0; j < _mapHeight; ++j) {
if (getPixel(i, j)) {
drawOverlayRectangle(Common::Point(i, j), color, wlk);
}
}
}
Sprite *ov = new Sprite(_realWidth, _realHeight, wlk, 0, 0, false);
// ov has taken the ownership of wlk.
return ov;
}
/**
* @brief For a given point, find a nearest walkable point on the walking map
*
* @param startX x coordinate of the point
* @param startY y coordinate of the point
*
* @return A Common::Point representing the nearest walkable point
*/
Common::Point WalkingMap::findNearestWalkable(int startX, int startY) const {
// The dimension of the screen.
const Common::Rect searchRect(0, 0, _realWidth, _realHeight);
// Consider circles with radii gradually rising from 0 to the length of
// the longest edge on the screen. For each radius, probe all points
// on the circle and return the first walkable one. Go through angles
// [0, 45 degrees] and probe all 8 reflections of each point.
for (int radius = 0; radius < searchRect.width() + searchRect.height(); radius += _deltaX) {
// The position of the point on the circle.
int x = 0;
int y = radius;
// Variables for computing the points on the circle
int prediction = 1 - radius;
int dx = 3;
int dy = 2 * radius - 2;
// Walk until we reach the 45-degree angle.
while (x <= y) {
// The place where, eventually, the result coordinates will be stored
Common::Point finalPos;
// Auxilliary array of multiplicative coefficients for reflecting points.
static const int kSigns[] = { 1, -1 };
// Check all 8 reflections of the basic point.
for (uint i = 0; i < 2; ++i) {
finalPos.y = startY + y * kSigns[i];
for (uint j = 0; j < 2; ++j) {
finalPos.x = startX + x * kSigns[j];
// If the current point is walkable, return it
if (searchRect.contains(finalPos.x, finalPos.y) && isWalkable(finalPos)) {
return finalPos;
}
}
}
for (uint i = 0; i < 2; ++i) {
finalPos.y = startY + x * kSigns[i];
for (uint j = 0; j < 2; ++j) {
finalPos.x = startX + y * kSigns[j];
// If the current point is walkable, return it
if (searchRect.contains(finalPos.x, finalPos.y) && isWalkable(finalPos)) {
return finalPos;
}
}
}
// Walk along the circle to the next point: the
// X-coordinate moves to the right, and the
// Y-coordinate may move to the bottom if the predictor
// says so.
if (prediction >= 0) {
prediction -= dy;
dy -= 2 * _deltaX;
y -= _deltaX;
}
prediction += dx;
dx += 2 * _deltaX;
x += _deltaX;
}
}
// The destination point is unreachable.
return Common::Point(-1, -1);
}
// We don't use Common::Point due to using static initialization.
const int WalkingMap::kDirections[][2] = { {0, -1}, {0, +1}, {-1, 0}, {+1, 0} };
bool WalkingMap::findShortestPath(Common::Point p1, Common::Point p2, WalkingPath *path) const {
// Round the positions to map squares.
p1.x /= _deltaX;
p2.x /= _deltaX;
p1.y /= _deltaY;
p2.y /= _deltaY;
// Allocate buffers for breadth-first search. The buffer of points for
// exploration should be large enough.
const int bufSize = 4 * _realHeight;
int8 *cameFrom = new int8[_mapWidth * _mapHeight];
Common::Point *toSearch = new Common::Point[bufSize];
// Insert the starting point as a single seed.
int toRead = 0, toWrite = 0;
memset(cameFrom, -1, _mapWidth * _mapHeight); // -1 = not found yet
cameFrom[p1.y * _mapWidth + p1.x] = 0;
toSearch[toWrite++] = p1;
// Search until we empty the whole buffer (not found) or find the
// destination point.
while (toRead != toWrite) {
const Common::Point &here = toSearch[toRead];
const int from = cameFrom[here.y * _mapWidth + here.x];
if (here == p2) {
break;
}
// Look into all 4 directions in a particular order depending
// on the direction we came to this point from. This is to
// ensure that among many paths of the same length, the one
// with the smallest number of turns is preferred.
for (int addDir = 0; addDir < 4; ++addDir) {
const int probeDirection = (from + addDir) % 4;
const int x = here.x + kDirections[probeDirection][0];
const int y = here.y + kDirections[probeDirection][1];
if (x < 0 || x >= _mapWidth || y < 0 || y >= _mapHeight) {
continue;
}
// If this point is walkable and we haven't seen it
// yet, record how we have reached it and insert it
// into the round buffer for exploration.
if (getPixel(x, y) && cameFrom[y * _mapWidth + x] == -1) {
cameFrom[y * _mapWidth + x] = probeDirection;
toSearch[toWrite++] = Common::Point(x, y);
toWrite %= bufSize;
}
}
++toRead;
toRead %= bufSize;
}
// The path doesn't exist.
if (toRead == toWrite) {
delete[] cameFrom;
delete[] toSearch;
return false;
}
// Trace the path back and store it. Count the path length, resize the
// output array, and then track the pack from the end.
path->clear();
for (int pass = 0; pass < 2; ++pass) {
Common::Point p = p2;
int index = 0;
while (1) {
++index;
if (pass == 1) {
(*path)[path->size() - index] = p;
}
if (p == p1) {
break;
}
const int from = cameFrom[p.y * _mapWidth + p.x];
p.x -= kDirections[from][0];
p.y -= kDirections[from][1];
}
if (pass == 0) {
path->resize(index);
}
}
delete[] cameFrom;
delete[] toSearch;
return true;
}
void WalkingMap::obliquePath(const WalkingPath& path, WalkingPath *obliquedPath) {
// Prune the path to only contain vertices where the direction is changing.
obliquedPath->clear();
if (path.empty()) {
return;
}
obliquedPath->push_back(path[0]);
uint index = 1;
while (index < path.size()) {
// index1 points to the last vertex inserted into the
// simplified path.
uint index1 = index - 1;
// Probe the vertical direction. Notice that the shortest path
// never turns by 180 degrees and therefore it is sufficient to
// test that the X coordinates are equal.
while (index < path.size() && path[index].x == path[index1].x) {
++index;
}
if (index - 1 > index1) {
index1 = index - 1;
obliquedPath->push_back(path[index1]);
}
// Probe the horizontal direction.
while (index < path.size() && path[index].y == path[index1].y) {
++index;
}
if (index - 1 > index1) {
index1 = index - 1;
obliquedPath->push_back(path[index1]);
}
}
// Repeatedly oblique the path until it cannot be improved. This
// process is finite, because after each success the number of vertices
// goes down.
while (managedToOblique(obliquedPath)) {}
}
Sprite *WalkingMap::newOverlayFromPath(const WalkingPath &path, byte color) const {
// HACK: Create a visible overlay from the walking map so we can test it
byte *wlk = new byte[_realWidth * _realHeight];
memset(wlk, 255, _realWidth * _realHeight);
for (uint segment = 1; segment < path.size(); ++segment) {
const Common::Point &v1 = path[segment-1];
const Common::Point &v2 = path[segment];
const int steps = pointsBetween(v1, v2);
// Draw only points in the interval [v1, v2). These half-open
// half-closed intervals connect all the way to the last point.
for (int step = 0; step < steps; ++step) {
drawOverlayRectangle(interpolate(v1, v2, step, steps), color, wlk);
}
}
// Draw the last point. This works also when the path has no segment,
// but just one point.
if (path.size() > 0) {
const Common::Point &vLast = path[path.size()-1];
drawOverlayRectangle(vLast, color, wlk);
}
Sprite *ov = new Sprite(_realWidth, _realHeight, wlk, 0, 0, false);
// ov has taken the ownership of wlk.
return ov;
}
void WalkingMap::drawOverlayRectangle(const Common::Point &p, byte color, byte *buf) const {
for (int i = 0; i < _deltaX; ++i) {
for (int j = 0; j < _deltaY; ++j) {
buf[(p.y * _deltaY + j) * _realWidth + (p.x * _deltaX + i)] = color;
}
}
}
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 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);
}
bool WalkingMap::lineIsCovered(const Common::Point &p1, const Common::Point &p2) const {
const int steps = pointsBetween(p1, p2);
for (int step = 0; step <= steps; ++step) {
Common::Point p = interpolate(p1, p2, step, steps);
if (!getPixel(p.x, p.y)) {
return false;
}
}
return true;
}
bool WalkingMap::managedToOblique(WalkingPath *path) const {
bool improved = false;
// Making the path oblique works as follows. If the path has at least
// 3 remaining vertices, we try to oblique the L-shaped path between
// them. First we try to connect the 1st and 3rd vertex directly (if
// all points on the line between them are walkable) and then we try to
// walk on both edges towards the 2nd vertex in parallel and try to
// find a shortcut (replacing the 2nd vertex by this mid-point). If
// either of those attempts succeeds, we have shortned the path. We
// update the path vertices and continue with the next segment.
for (uint head = 2; head < path->size(); ++head) {
const Common::Point &v1 = (*path)[head-2];
const Common::Point &v2 = (*path)[head-1];
const Common::Point &v3 = (*path)[head];
const int points12 = pointsBetween(v1, v2);
const int points32 = pointsBetween(v3, v2);
// Find the first point p on each edge [v1, v2] and [v3, v2]
// such that the edge [p, the third vertex] is covered.
// Ideally we would like p \in {v1, v3} and the closer the
// better. The last point p = v2 should always succeed.
int first12, first32;
for (first12 = 0; first12 < points12; ++first12) {
Common::Point midPoint = interpolate(v1, v2, first12, points12);
if (lineIsCovered(midPoint, v3)) {
break;
}
}
if (first12 == 0) {
// Can completely remove the vertex. Head stays the
// same after -- and ++.
path->remove_at(--head);
improved = true;
continue;
}
for (first32 = 0; first32 < points32; ++first32) {
Common::Point midPoint = interpolate(v3, v2, first32, points32);
if (lineIsCovered(midPoint, v1)) {
break;
}
}
if (first12 < points12 && first32 >= points32 + MIN(first12 - points12, 0)) {
// There is such a point on the first edge and the
// second edge has either not succeeded or we gain more
// by cutting this edge than the other one.
(*path)[head-1] = interpolate(v1, v2, first12, points12);
// After replacing the 2nd vertex, let head move on.
} else if (first32 < points32) {
(*path)[head-1] = interpolate(v3, v2, first32, points32);
}
}
return improved;
}
void WalkingState::stopWalking() {
_path.clear();
_callback = nullptr;
}
void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2,
const Common::Point &mouse, SightDirection dir,
const Common::Point &delta, const WalkingPath& path) {
_path = path;
_mouse = mouse;
_dir = dir;
if (!_path.size()) {
_path.push_back(p1);
}
if (_path.size() == 1 && p2 != p1) {
// Although the first and last point belong to the same
// rectangle and therefore the computed path is of length 1,
// they are different pixels.
_path.push_back(p2);
}
debugC(2, kDraciWalkingDebugLevel, "Starting walking [%d,%d] -> [%d,%d] with %d vertices",
p1.x, p1.y, p2.x, p2.y, _path.size());
// The first and last point are available with pixel accurracy.
_path[0] = p1;
_path[_path.size() - 1] = p2;
// The intermediate points are given with map granularity; convert them
// to pixels.
for (uint i = 1; i < _path.size() - 1; ++i) {
_path[i].x *= delta.x;
_path[i].y *= delta.y;
}
// Remember the initial dragon's direction.
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
_startingDirection = static_cast<Movement> (dragon->playingAnim());
// Going to start with the first segment.
_segment = 0;
_lastAnimPhase = -1;
_turningFinished = false;
turnForTheNextSegment();
}
void WalkingState::setCallback(const GPL2Program *program, uint16 offset) {
_callback = _callbackLast = program;
_callbackOffset = _callbackOffsetLast = offset;
}
void WalkingState::callback() {
if (!_callback) {
return;
}
debugC(2, kDraciWalkingDebugLevel, "Calling walking callback");
const GPL2Program &originalCallback = *_callback;
_callback = nullptr;
_vm->_script->runWrapper(originalCallback, _callbackOffset, true, false);
_callbackLast = nullptr;
_callbackOffset = 0;
}
void WalkingState::callbackLast() {
setCallback(_callbackLast, _callbackOffsetLast);
}
bool WalkingState::continueWalkingOrClearPath() {
const bool stillWalking = continueWalking();
if (!stillWalking) {
_path.clear();
}
return stillWalking;
}
bool WalkingState::continueWalking() {
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
const Movement movement = static_cast<Movement> (dragon->playingAnim());
if (_turningFinished) {
// When a turning animation has finished, heroAnimationFinished() callback
// gets called, which sets this flag to true. It's important
// to not start walking right away in the callback, because
// that would disturb the data structures of the animation
// manager.
_turningFinished = false;
return walkOnNextEdge();
}
// If the current segment is the last one, we have reached the
// destination and are already facing in the right direction ===>
// return false. The code should, however, get here only if the path
// has just 1 vertex and startWalking() leaves the path open.
// Finishing and nontrivial path will get caught earlier.
if (_segment >= _path.size()) {
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.
Animation *anim = dragon->_anim[movement];
const int animPhase = anim->currentFrameNum();
const bool wasUpdated = animPhase != _lastAnimPhase;
if (!wasUpdated) {
debugC(4, kDraciWalkingDebugLevel, "Waiting for an animation phase change: still %d", animPhase);
return true;
}
if (isTurningMovement(movement)) {
// If the current animation is a turning animation, wait a bit more.
debugC(3, kDraciWalkingDebugLevel, "Continuing turning for edge %d with phase %d", _segment, animPhase);
_lastAnimPhase = animPhase;
return true;
}
// We are walking in the middle of an edge. The animation phase has
// just changed.
// Read the position of the hero from the animation object, and adjust
// it to the current edge.
const Common::Point prevHero = _vm->_game->getHeroPosition();
_vm->_game->positionHeroAsAnim(anim);
const Common::Point curHero = _vm->_game->getHeroPosition();
Common::Point adjustedHero = curHero;
const bool reachedEnd = alignHeroToEdge(_path[_segment-1], _path[_segment], prevHero, &adjustedHero);
if (reachedEnd && _segment >= _path.size() - 1) {
// We don't want the dragon to jump around if we repeatedly
// click on the same pixel. Let him always end where desired.
debugC(2, kDraciWalkingDebugLevel, "Adjusting position to the final node");
adjustedHero = _path[_segment];
}
debugC(3, kDraciWalkingDebugLevel, "Continuing walking on edge %d: phase %d and position+=[%d,%d]->[%d,%d] adjusted to [%d,%d]",
_segment-1, animPhase, curHero.x - prevHero.x, curHero.y - prevHero.y, curHero.x, curHero.y, adjustedHero.x, adjustedHero.y);
// Update the hero position to the adjusted one. The animation number
// is not changing, so this will just move the sprite and return the
// current frame number.
_vm->_game->setHeroPosition(adjustedHero);
_lastAnimPhase = _vm->_game->playHeroAnimation(movement);
// 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 the hero has arrived at a slightly different point due to
// animated sprites, adjust the path so that the animation can smoothly
// continue.
if (reachedEnd) {
if (adjustedHero != _path[_segment]) {
debugC(2, kDraciWalkingDebugLevel, "Adjusting node %d of the path [%d,%d]->[%d,%d]",
_segment, _path[_segment].x, _path[_segment].y, adjustedHero.x, adjustedHero.y);
_path[_segment] = adjustedHero;
}
return turnForTheNextSegment();
}
return true;
}
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) {
if (p2Diff.x == 0) {
error("Wrong value for horizontal movement");
}
reachedEnd = movement == kMoveLeft ? hero->x <= p2.x : hero->x >= p2.x;
hero->y += hero->x * p2Diff.y / p2Diff.x - prevHero.x * p2Diff.y / p2Diff.x;
} else {
if (p2Diff.y == 0) {
error("Wrong value for vertical movement");
}
reachedEnd = movement == kMoveUp ? hero->y <= p2.y : hero->y >= p2.y;
hero->x += hero->y * p2Diff.x / p2Diff.y - prevHero.y * p2Diff.x / p2Diff.y;
}
return reachedEnd;
}
bool WalkingState::turnForTheNextSegment() {
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
const Movement currentAnim = static_cast<Movement> (dragon->playingAnim());
const Movement wantAnim = directionForNextPhase();
Movement transition = transitionBetweenAnimations(currentAnim, wantAnim);
debugC(2, kDraciWalkingDebugLevel, "Turning for edge %d", _segment);
if (transition == kMoveUndefined) {
// Start the next segment right away as if the turning has just finished.
return walkOnNextEdge();
} else {
// Otherwise start the transition and wait until the Animation
// class calls heroAnimationFinished() as a callback, leading
// to calling walkOnNextEdge() in the next phase.
assert(isTurningMovement(transition));
_lastAnimPhase = _vm->_game->playHeroAnimation(transition);
Animation *anim = dragon->_anim[transition];
anim->registerCallback(&Animation::tellWalkingState);
debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d with phase %d", transition, _lastAnimPhase);
return true;
}
}
void WalkingState::heroAnimationFinished() {
debugC(2, kDraciWalkingDebugLevel, "Turning callback called");
_turningFinished = true;
// We don't need to clear the callback to safer doNothing, because
// nobody ever plays this animation directly. It is only played by
// turnForTheNextSegment() and then the same callback needs to be
// activated again.
}
bool WalkingState::walkOnNextEdge() {
// The hero is turned well for the next line segment or for facing the
// target direction. It is also standing on the right spot thanks to
// the entry condition for turnForTheNextSegment().
// Start the desired next animation and retrieve the current animation
// phase.
// 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();
_lastAnimPhase = _vm->_game->playHeroAnimation(nextAnim);
debugC(2, kDraciWalkingDebugLevel, "Turned for edge %d, starting animation %d with phase %d", _segment, nextAnim, _lastAnimPhase);
if (++_segment < _path.size()) {
// We are on an edge: track where the hero is on this edge.
int length = WalkingMap::pointsBetween(_path[_segment-1], _path[_segment]);
debugC(2, kDraciWalkingDebugLevel, "Next edge %d has length %d", _segment-1, length);
return true;
} else {
// Otherwise we are done. continueWalking() will return false next time.
debugC(2, kDraciWalkingDebugLevel, "We have walked the whole path");
return false;
}
}
Movement WalkingState::animationForDirection(const Common::Point &here, const Common::Point &there) {
const int dx = there.x - here.x;
const int dy = there.y - here.y;
if (ABS(dx) >= ABS(dy)) {
return dx >= 0 ? kMoveRight : kMoveLeft;
} else {
return dy >= 0 ? kMoveDown : kMoveUp;
}
}
Movement WalkingState::directionForNextPhase() const {
if (_segment >= _path.size() - 1) {
return animationForSightDirection(_dir, _path[_path.size()-1], _mouse, _path, _startingDirection);
} else {
return animationForDirection(_path[_segment], _path[_segment+1]);
}
}
Movement WalkingState::transitionBetweenAnimations(Movement previous, Movement next) {
switch (next) {
case kMoveUp:
switch (previous) {
case kMoveLeft:
case kStopLeft:
case kSpeakLeft:
return kMoveLeftUp;
case kMoveRight:
case kStopRight:
case kSpeakRight:
return kMoveRightUp;
default:
return kMoveUndefined;
}
case kMoveDown:
switch (previous) {
case kMoveLeft:
case kStopLeft:
case kSpeakLeft:
return kMoveLeftDown;
case kMoveRight:
case kStopRight:
case kSpeakRight:
return kMoveRightDown;
default:
return kMoveUndefined;
}
case kMoveLeft:
switch (previous) {
case kMoveDown:
return kMoveDownLeft;
case kMoveUp:
return kMoveUpLeft;
case kMoveRight:
case kStopRight:
case kSpeakRight:
return kMoveRightLeft;
default:
return kMoveUndefined;
}
case kMoveRight:
switch (previous) {
case kMoveDown:
return kMoveDownRight;
case kMoveUp:
return kMoveUpRight;
case kMoveLeft:
case kStopLeft:
case kSpeakLeft:
return kMoveLeftRight;
default:
return kMoveUndefined;
}
case kStopLeft:
switch (previous) {
case kMoveUp:
return kMoveUpStopLeft;
case kMoveRight:
case kStopRight:
case kSpeakRight:
return kMoveRightLeft;
default:
return kMoveUndefined;
}
case kStopRight:
switch (previous) {
case kMoveUp:
return kMoveUpStopRight;
case kMoveLeft:
case kStopLeft:
case kSpeakLeft:
return kMoveLeftRight;
default:
return kMoveUndefined;
}
default:
return kMoveUndefined;
}
}
Movement WalkingState::animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path, Movement startingDirection) {
switch (dir) {
case kDirectionLeft:
return kStopLeft;
case kDirectionRight:
return kStopRight;
case kDirectionMouse:
if (mouse.x < hero.x) {
return kStopLeft;
} else if (mouse.x > hero.x) {
return kStopRight;
}
// fall through
default: {
// Find the last horizontal direction on the path.
int i = path.size() - 1;
while (i >= 0 && path[i].x == hero.x) {
--i;
}
if (i >= 0) {
return path[i].x < hero.x ? kStopRight : kStopLeft;
} else {
// Avoid changing the direction when no walking has
// been done. Preserve the original direction.
return (startingDirection == kMoveLeft || startingDirection == kStopLeft || startingDirection == kSpeakLeft)
? kStopLeft : kStopRight;
}
}
}
}
} // End of namespace Draci