Implemented and debugged the walking framework.

The hero does not walk yet (it still teleports to the target immediately),
but that is just because the actual walking algorithm is left trivial first.
However, the main game loop, callbacks, and waiting all already work with
the general framework.

svn-id: r45648
This commit is contained in:
Robert Špalek 2009-11-04 00:42:37 +00:00
parent 692aea8e8d
commit 14f2685134
7 changed files with 102 additions and 37 deletions

View File

@ -84,6 +84,7 @@ DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc)
Common::addDebugChannel(kDraciLogicDebugLevel, "logic", "Game logic debug info");
Common::addDebugChannel(kDraciAnimationDebugLevel, "animation", "Animation debug info");
Common::addDebugChannel(kDraciSoundDebugLevel, "sound", "Sound debug info");
Common::addDebugChannel(kDraciWalkingDebugLevel, "walking", "Walking debug info");
// Don't forget to register your random source
g_eventRec.registerRandomSource(_rnd, "draci");

View File

@ -105,7 +105,8 @@ enum {
kDraciArchiverDebugLevel = 1 << 2,
kDraciLogicDebugLevel = 1 << 3,
kDraciAnimationDebugLevel = 1 << 4,
kDraciSoundDebugLevel = 1 << 5
kDraciSoundDebugLevel = 1 << 5,
kDraciWalkingDebugLevel = 1 << 6
};
} // End of namespace Draci

View File

@ -248,16 +248,17 @@ void Game::handleOrdinaryLoop(int x, int y) {
_walkingState.setCallback(&obj->_program, obj->_look);
if (!obj->_imLook) {
if (obj->_imLook || !_currentRoom._heroOn) {
_walkingState.callback();
} else {
if (obj->_lookDir == kDirectionLast) {
walkHero(x, y, obj->_lookDir);
} else {
walkHero(obj->_lookX, obj->_lookY, obj->_lookDir);
}
}
_walkingState.callback();
} else {
_walkingState.setCallback(NULL, 0);
walkHero(x, y, kDirectionLast);
}
}
@ -272,16 +273,17 @@ void Game::handleOrdinaryLoop(int x, int y) {
if (_vm->_script->testExpression(obj->_program, obj->_canUse)) {
_walkingState.setCallback(&obj->_program, obj->_use);
if (!obj->_imUse) {
if (obj->_imUse || !_currentRoom._heroOn) {
_walkingState.callback();
} else {
if (obj->_useDir == kDirectionLast) {
walkHero(x, y, obj->_useDir);
} else {
walkHero(obj->_useX, obj->_useY, obj->_useDir);
}
}
_walkingState.callback();
} else {
_walkingState.setCallback(NULL, 0);
walkHero(x, y, kDirectionLast);
}
} else {
@ -289,6 +291,7 @@ void Game::handleOrdinaryLoop(int x, int y) {
_walkingState.setCallback(&_currentRoom._program, _currentRoom._use);
_walkingState.callback();
} else {
_walkingState.setCallback(NULL, 0);
walkHero(x, y, kDirectionLast);
}
}
@ -436,6 +439,29 @@ void Game::advanceAnimationsAndTestLoopExit() {
setExitLoop(true);
}
// Walk the hero. The WalkingState class handles everything including
// proper timing.
if (_walkingState.isActive()) {
if (!_walkingState.continueWalking()) {
// Walking has finished.
bool exitLoop = false;
if (_loopSubstatus == kInnerUntilExit) {
// The callback may run another inner loop (for
// example, a dialogue). Reset the loop
// substatus temporarily to the outer one.
exitLoop = true;
setLoopSubstatus(kOuterLoop);
}
debugC(2, kDraciWalkingDebugLevel, "Finished walking");
_walkingState.callback(); // clears callback pointer first
if (exitLoop) {
debugC(3, kDraciWalkingDebugLevel, "Exiting from the inner loop");
setExitLoop(true);
setLoopSubstatus(kInnerUntilExit);
}
}
}
// Advance animations (this may also call setExitLoop(true) in the
// callbacks) and redraw screen
_vm->_anims->drawScene(_vm->_screen->getSurface());
@ -942,7 +968,7 @@ void Game::redrawWalkingPath(int id, byte colour, const WalkingPath &path) {
}
void Game::positionHero(const Common::Point &p, SightDirection dir) {
debugC(3, kDraciLogicDebugLevel, "Jump to x: %d y: %d", p.x, p.y);
debugC(3, kDraciWalkingDebugLevel, "Jump to x: %d y: %d", p.x, p.y);
_hero = p;
Movement movement = kStopRight;
@ -965,33 +991,38 @@ void Game::positionHero(const Common::Point &p, SightDirection dir) {
playHeroAnimation(movement);
}
void Game::walkHero(int x, int y, SightDirection dir) {
// Needed for the map room with empty walking map. For some reason,
// findNearestWalkable() takes several seconds with 100% CPU to finish
// (correctly).
if (!_currentRoom._heroOn)
return;
Common::Point target;
Common::Point Game::findNearestWalkable(int x, int y) const {
Surface *surface = _vm->_screen->getSurface();
target = _walkingMap.findNearestWalkable(x, y, surface->getDimensions());
debugC(3, kDraciLogicDebugLevel, "Walk to x: %d y: %d", target.x, target.y);
return _walkingMap.findNearestWalkable(x, y, surface->getDimensions());
}
void Game::walkHero(int x, int y, SightDirection dir) {
if (!_currentRoom._heroOn) {
// Nothing to do. Happens for example in the map.
return;
}
Common::Point target = findNearestWalkable(x, y);
// Compute the shortest and obliqued path.
WalkingPath shortestPath, obliquePath;
_walkingMap.findShortestPath(_hero, target, &shortestPath);
// TODO: test reachability and react
_walkingMap.obliquePath(shortestPath, &obliquePath);
debugC(2, kDraciWalkingDebugLevel, "Walking path lengths: shortest=%d oblique=%d", shortestPath.size(), obliquePath.size());
if (_vm->_showWalkingMap) {
redrawWalkingPath(kWalkingShortestPathOverlay, kWalkingShortestPathOverlayColour, shortestPath);
redrawWalkingPath(kWalkingObliquePathOverlay, kWalkingObliquePathOverlayColour, obliquePath);
}
_walkingState.setPath(_hero, target, Common::Point(x, y),
// Start walking. Walking will be gradually advanced by
// advanceAnimationsAndTestLoopExit(), which also handles calling the
// callback and stopping the walk at the end. If the hero is already
// walking at this point, this command will cancel the previous path
// and replace it by the current one (the callback has already been
// reset by our caller).
_walkingState.startWalking(_hero, target, Common::Point(x, y), dir,
_walkingMap.getDelta(), obliquePath);
// FIXME: Need to add proper walking (this only warps the dragon to position)
positionHero(target, dir);
}
void Game::loadItem(int itemID) {

View File

@ -206,9 +206,10 @@ public:
return n;
}
void clearPath() { _walkingState.clearPath(); }
void positionHero(const Common::Point &p, SightDirection dir);
void walkHero(int x, int y, SightDirection dir);
Common::Point findNearestWalkable(int x, int y) const;
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
int getHeroX() const { return _hero.x; }
int getHeroY() const { return _hero.y; }
void positionAnimAsHero(Animation *anim);

View File

@ -660,8 +660,8 @@ void Script::stayOn(Common::Queue<int> &params) {
SightDirection dir = static_cast<SightDirection> (params.pop());
// Jumps into the given position regardless of the walking map.
_vm->_game->positionHero(Common::Point(x, y), dir);
_vm->_game->clearPath();
_vm->_game->stopWalking();
_vm->_game->positionHero(_vm->_game->findNearestWalkable(x, y), dir);
}
void Script::walkOn(Common::Queue<int> &params) {
@ -675,6 +675,7 @@ void Script::walkOn(Common::Queue<int> &params) {
// Constructs an optimal path and starts walking there. No callback
// will be called at the end nor will the loop-body exit.
_vm->_game->stopWalking();
_vm->_game->walkHero(x, y, dir);
}
@ -687,12 +688,12 @@ void Script::walkOnPlay(Common::Queue<int> &params) {
int y = params.pop();
SightDirection dir = static_cast<SightDirection> (params.pop());
_vm->_game->stopWalking();
_vm->_game->walkHero(x, y, dir);
// HACK: This (shouldExit==true) should be an onDest action when hero
// walking is properly implemented For now, we just go throught the
// loop-body once to redraw the screen.
_vm->_game->loop(kInnerUntilExit, true);
// Walk in an inner loop until the hero has arrived at the target
// point. Then the loop-body will exit.
_vm->_game->loop(kInnerUntilExit, false);
}
void Script::newRoom(Common::Queue<int> &params) {

View File

@ -422,14 +422,17 @@ bool WalkingMap::managedToOblique(WalkingPath *path) const {
return improved;
}
void WalkingState::clearPath() {
void WalkingState::stopWalking() {
_path.clear();
_callback = NULL;
}
void WalkingState::setPath(const Common::Point &p1, const Common::Point &p2, const Common::Point &mouse, const Common::Point &delta, const WalkingPath& path) {
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()) {
return;
@ -440,6 +443,8 @@ void WalkingState::setPath(const Common::Point &p1, const Common::Point &p2, con
// they are different pixels.
_path.push_back(p2);
}
debugC(2, kDraciWalkingDebugLevel, "Starting walking [%d,%d] -> [%d,%d] in %d segments",
p1.x, p1.y, p2.x, p2.y, _path.size());
// The first and last point are available with pixel accurracy.
_path[0] = p1;
@ -461,6 +466,7 @@ void WalkingState::callback() {
if (!_callback) {
return;
}
debugC(2, kDraciWalkingDebugLevel, "Calling walking callback");
// Fetch the dedicated objects' title animation / current frame
Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
@ -470,8 +476,21 @@ void WalkingState::callback() {
titleAnim->markDirtyRect(_vm->_screen->getSurface());
title->setText("");
_vm->_script->run(*_callback, _callbackOffset);
const GPL2Program *originalCallback = _callback;
_callback = NULL;
_vm->_script->run(*originalCallback, _callbackOffset);
_vm->_mouse->cursorOn();
}
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
}
}

View File

@ -99,21 +99,32 @@ struct GPL2Program;
class WalkingState {
public:
explicit WalkingState(DraciEngine *vm) : _vm(vm) { clearPath(); }
explicit WalkingState(DraciEngine *vm) : _vm(vm) { stopWalking(); }
~WalkingState() {}
void clearPath();
void setPath(const Common::Point &p1, const Common::Point &p2, const Common::Point &mouse, const Common::Point &delta, const WalkingPath& path);
void stopWalking();
void startWalking(const Common::Point &p1, const Common::Point &p2,
const Common::Point &mouse, SightDirection dir,
const Common::Point &delta, const WalkingPath& path);
const WalkingPath& getPath() const { return _path; }
void setCallback(const GPL2Program *program, uint16 offset);
void callback();
bool isActive() const { return _path.size() > 0; }
// Advances the hero along the path and changes animation accordingly.
// Walking MUST be active when calling this method. When the hero has
// arrived to the target, clears the path and returns false, but leaves
// the callback untouched (the caller must call it).
bool continueWalking();
private:
DraciEngine *_vm;
WalkingPath _path;
Common::Point _mouse;
SightDirection _dir;
const GPL2Program *_callback;
uint16 _callbackOffset;