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

hotspots.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/lure/hotspots.cpp $
 * $Id: hotspots.cpp 30944 2008-02-23 22:50:18Z sev $
 *
 */

#include "lure/hotspots.h"
#include "lure/decode.h"
#include "lure/palette.h"
#include "lure/disk.h"
#include "lure/res.h"
#include "lure/scripts.h"
#include "lure/room.h"
#include "lure/strings.h"
#include "lure/res_struct.h"
#include "lure/events.h"
#include "lure/game.h"
#include "lure/fights.h"
#include "lure/sound.h"
#include "lure/lure.h"
#include "common/endian.h"

namespace Lure {

Hotspot::Hotspot(HotspotData *res): _pathFinder(this) {
      Resources &resources = Resources::getReference();

      _data = res;
      _anim = NULL;
      _frames = NULL;
      _numFrames = 0;
      _persistant = false;

      _hotspotId = res->hotspotId;
      _originalId = res->hotspotId;
      _roomNumber = res->roomNumber;
      _startX = res->startX;
      _startY = res->startY;
      _destX = res->startX;
      _destY = res->startY;
      _destHotspotId = 0;
      _frameWidth = res->width;
      _frameStartsUsed = false;
      _height = res->height;
      _width = res->width;
      _heightCopy = res->heightCopy;
      _widthCopy = res->widthCopy;
      _yCorrection = res->yCorrection;
      _talkX = res->talkX;
      _talkY = res->talkY;
      _layer = res->layer;
      _hotspotScriptOffset = res->hotspotScriptOffset;
      _frameCtr = res->tickTimeout;
      _colourOffset = res->colourOffset;
      _tempDest.counter = 0;

      _override = resources.getHotspotOverride(res->hotspotId);
      setAnimation(_data->animRecordId);
      _tickHandler = HotspotTickHandlers::getHandler(_data->tickProcId);
      _nameBuffer[0] = '\0';

      _skipFlag = false;
      _charRectY = 0;
      _voiceCtr = 0;
      _blockedOffset = 0;
      _exitCtr = 0;
      _walkFlag = false;
      _startRoomNumber = 0;
      _supportValue = 0;

      if (_data->npcSchedule != 0) {
            CharacterScheduleEntry *entry = resources.charSchedules().getEntry(_data->npcSchedule);
            _currentActions.addFront(DISPATCH_ACTION, entry, _roomNumber);
      }
}

// Special constructor used to create a voice hotspot

Hotspot::Hotspot(Hotspot *character, uint16 objType): _pathFinder(this) {
      assert(character);

      _originalId = objType;
      _data = NULL;
      _anim = NULL;
      _frames = NULL;
      _numFrames = 0;
      _persistant = false;
      _hotspotId = 0xffff;
      _override = NULL;
      _colourOffset = 0;
      _destHotspotId = character->hotspotId();
      _blockedOffset = 0;
      _exitCtr = 0;
      _voiceCtr = 0;
      _walkFlag = false;
      _skipFlag = false;

      switch (objType) {
      case VOICE_ANIM_IDX:
            _roomNumber = character->roomNumber();
            _destHotspotId = character->hotspotId();
            _startX = character->x() + character->talkX() + 12;
            _startY = character->y() + character->talkY() - 18;
            _destX = _startX;
            _destY = _startY;
            _layer = 1;
            _height = 18;
            _width = 32;
            _heightCopy = character->height() + 14;
            _widthCopy = 24;
            _yCorrection = 1;

            _frameCtr = 0;
            _voiceCtr = 40;

            _tickHandler = HotspotTickHandlers::getHandler(VOICE_TICK_PROC_ID);
            setAnimationIndex(VOICE_ANIM_INDEX);
            break;

      case PUZZLED_ANIM_IDX:
      case EXCLAMATION_ANIM_IDX:
            _roomNumber = character->roomNumber();
            _hotspotId = 0xfffe;
            _startX = character->x() + character->talkX() + 12;
            _startY = character->y() + character->talkY() - 20;
            _width = 32;
            _height = 18;
            _widthCopy = 19;
            _heightCopy = 18 + character->heightCopy();
            _layer = 1;
            _persistant = false;
            _yCorrection = 1;
            _voiceCtr = CONVERSE_COUNTDOWN_SIZE;

            _destHotspotId = character->hotspotId();
            _tickHandler = HotspotTickHandlers::getHandler(PUZZLED_TICK_PROC_ID);
            setAnimationIndex(VOICE_ANIM_INDEX);
            setFrameNumber(objType == PUZZLED_ANIM_IDX ? 1 : 2);
            
            character->setFrameCtr(_voiceCtr);
            break;

      default:
            break;
      }

      _frameWidth = _width;
      _frameStartsUsed = false;
      _nameBuffer[0] = '\0';
}

Hotspot::Hotspot(): _pathFinder(NULL) {
      _data = NULL;
      _anim = NULL;
      _frames = NULL;
      _numFrames = 0;
      _persistant = false;
      _hotspotId = 0xffff;
      _override = NULL;
      _colourOffset = 0;
      _destHotspotId = 0;
      _blockedOffset = 0;
      _exitCtr = 0;
      _voiceCtr = 0;
      _walkFlag = false;
      _skipFlag = false;
      _roomNumber = 0;
      _destHotspotId = 0;
      _startX = 0;
      _startY = 0;
      _destX = 0;
      _destY = 0;
      _layer = 0;
      _height = 0;
      _width = 0;
      _heightCopy = 0;
      _widthCopy = 0;
      _yCorrection = 0;
      _frameCtr = 0;
      _tickHandler = NULL;
      _frameWidth = _width;
      _frameStartsUsed = false;
      _tempDest.counter = 0;
}

Hotspot::~Hotspot() {
      Resources &res = Resources::getReference();

      // WORKAROUND: If Blacksmith is being deactivated, make sure his animation is
      // reset back to his standard movement set
      if (_hotspotId == BLACKSMITH_ID) {
            HotspotAnimData *tempAnim = res.animRecords()[BLACKSMITH_DEFAULT_ANIM_INDEX];
            assert(tempAnim);
            _data->animRecordId = tempAnim->animRecordId;
      }

      // WORKAROUND: Goewin is initially deactivated when the player faces the dragon. When she's later
      // reactivated, make sure she doesn't have a schedule set (so standard follower logic will apply)
      if (_hotspotId == GOEWIN_ID) {
            HotspotData *goewinHotspot = res.getHotspot(GOEWIN_ID);
            goewinHotspot->npcSchedule = 0;
      }

      if (_frames) delete _frames;
}

void Hotspot::setAnimation(uint16 newAnimId) {
      Resources &r = Resources::getReference();
      HotspotAnimData *tempAnim;
      _animId = newAnimId;
      if (_data)
            _data->animRecordId = newAnimId;

      if (newAnimId == 0) 
            tempAnim = NULL;
      else {
            tempAnim = r.getAnimation(newAnimId); 
            if (tempAnim == NULL)
                  error("Hotspot %xh tried to set non-existant Animation Id: %xh", _hotspotId, newAnimId);
      }
      
      setAnimation(tempAnim);
}

void Hotspot::setAnimationIndex(int animIndex) {
      Resources &r = Resources::getReference();

      // Get the animation specified
      HotspotAnimData *tempAnim = r.animRecords()[animIndex];
      
      _animId = tempAnim->animRecordId;
      if (_data)
            _data->animRecordId = tempAnim->animRecordId;

      setAnimation(tempAnim);
}

struct SizeOverrideEntry {
      int animIndex;
      uint16 width, height;
};

static const SizeOverrideEntry sizeOverrides[] = {
      {BLACKSMITH_DEFAULT_ANIM_INDEX, 32, 48},
      {BLACKSMITH_HAMMERING_ANIM_INDEX, 48, 47},
      {0, 0, 0}
};

void Hotspot::setAnimation(HotspotAnimData *newRecord) {
      Disk &disk = Disk::getReference();
      Resources &res = Resources::getReference();
      uint16 tempWidth, tempHeight;
      int16 xStart;
      int animIndex = res.getAnimationIndex(newRecord);

      if (_frames) {
            delete _frames;
            _frames = NULL;
      }
      _anim = NULL;
      _numFrames = 0;
      _frameNumber = 0;
      if (!newRecord) return;
      if (!disk.exists(newRecord->animId)) return;

      // Scan for any size overrides - some animations get their size set after decoding, but
      // we want it in advance so we can decode the animation straight to a graphic surface
      const SizeOverrideEntry *p = &sizeOverrides[0];
      while ((p->animIndex != 0) && (p->animIndex != animIndex)) ++p;
      if (p->animIndex != 0)
            setSize(p->width, p->height);

      _anim = newRecord;
      MemoryBlock *src = Disk::getReference().getEntry(_anim->animId);
      
      uint16 numEntries = READ_LE_UINT16(src->data());
      uint16 *headerEntry = (uint16 *) (src->data() + 2);
      assert((numEntries >= 1) && (numEntries < 100));

      // Calculate total needed size for output and create memory block to hold it
      uint32 totalSize = 0;
      for (uint16 ctr = 0; ctr < numEntries; ++ctr, ++headerEntry) {
            totalSize += (READ_LE_UINT16(headerEntry) + 31) / 32;
      }
      totalSize = (totalSize + 0x81) << 4;
      MemoryBlock *dest = Memory::allocate(totalSize);

      uint32 srcStart = (numEntries + 1) * sizeof(uint16) + 6;
      AnimationDecoder::decode_data(src, dest, srcStart);

      _numFrames = numEntries;
      _frameNumber = 0;
      
      // Special handling need
      if (_hotspotId == RACK_SERF_ID) {
            _frameStartsUsed = true;
            _frames = new Surface(416, 27);
      } else {
            _frames = new Surface(_width * _numFrames, _height);
            _frameStartsUsed = false;
      }
      _frames->data().setBytes(_colourOffset, 0, _frames->data().size());

      byte *pSrc = dest->data() + 0x40;
      byte *pDest;
      headerEntry = (uint16 *) (src->data() + 2);
      MemoryBlock &mDest = _frames->data();
      uint16 frameOffset = 0x40; 
      uint16 *offsetPtr = (uint16 *) src->data();

      tempWidth = _width;
      tempHeight = _height;

      for (uint16 frameNumCtr = 0; frameNumCtr < _numFrames; ++frameNumCtr, ++headerEntry) {

            if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0) {
                  // For animations with an offset table, set the source pointer
                  pSrc = dest->data() + frameOffset;
            }
            
            if (_hotspotId == RACK_SERF_ID) {
                  // Save the start of each frame for serf, since the size varies 
                  xStart = (frameNumCtr == 0) ? 0 : _frameStarts[frameNumCtr - 1] + tempWidth;
                  _frameStarts[frameNumCtr] = xStart;

                  // Switch statement to handle varying size for different frames
                  switch (frameNumCtr) {
                  case 3:
                        tempWidth = 48;
                        tempHeight = 25;
                        break;
                  case 4:
                        tempHeight = 26;
                        break;
                  case 5:
                        tempWidth = 32;
                        break;
                  case 6:
                        tempHeight = 27;
                        break;
                  case 7:
                        tempWidth = 16;
                        break;
                  default:
                        break;
                  }
            } else {
                  // Set the X Start based on the frame size
                  xStart = frameNumCtr * _width;
            }

            // Copy over the frame, applying the colour offset to each nibble
            for (uint16 yPos = 0; yPos < tempHeight; ++yPos) {
                  pDest = mDest.data() + yPos * _frames->width() + xStart;

                  for (uint16 xPos = 0; xPos < tempWidth / 2; ++xPos) {
                        *pDest++ = _colourOffset + (*pSrc >> 4);
                        *pDest++ = _colourOffset + (*pSrc & 0xf);
                        ++pSrc;
                  }
            }

            if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0) 
                  frameOffset += (READ_LE_UINT16(++offsetPtr) >> 1);
      }

      delete src;
      delete dest;
}

void Hotspot::copyTo(Surface *dest) {
      int16 xPos = _startX;
      int16 yPos = _startY;
      uint16 hWidth = _frameWidth;
      uint16 hHeight = _height;

      Rect r(_frameNumber * hWidth, 0, (_frameNumber + 1) * hWidth - 1, hHeight - 1);
      if (_frameStartsUsed) {
            assert(_frameNumber < MAX_NUM_FRAMES);
            r.left = _frameStarts[_frameNumber];
            r.right = (_frameNumber == _numFrames - 1) ? _frames->width() - 1 :
                  _frameStarts[_frameNumber + 1] - 1;
            r.bottom = _height - 1;
      }

      // Handle clipping for X position
      if (xPos < 0) {
            if (xPos + hWidth <= 0)
                  // Completely off screen, so don't display
                  return;

            // Reduce the source rectangle to only the on-screen portion
            r.left += -xPos;
            xPos = 0;
      }
      else if (xPos >= FULL_SCREEN_WIDTH) 
            return;
      else if (xPos + hWidth > FULL_SCREEN_WIDTH)
            r.right = r.left + (FULL_SCREEN_WIDTH - xPos - 1);

      // Handle clipping for Y position
      if (yPos < 0) {
            if (yPos + hHeight <= MENUBAR_Y_SIZE) 
                  // Completely off screen, so don't display
                  return;

            // Reduce the source rectangle to only the on-screen portion
            r.top += -yPos + MENUBAR_Y_SIZE;
            yPos = MENUBAR_Y_SIZE;
      }
      else if (yPos >= FULL_SCREEN_HEIGHT)
            return;
      else if (yPos + hHeight > FULL_SCREEN_HEIGHT)
            r.bottom = r.top + (FULL_SCREEN_HEIGHT - yPos - 1);

      // Final check to make sure there is anything to display
      if ((r.top >= r.bottom) || (r.left >= r.right))
            return;

      _frames->copyTo(dest, r, (uint16) xPos, (uint16) yPos, _colourOffset);
}

void Hotspot::incFrameNumber() {
      ++_frameNumber;
      if (_frameNumber >= _numFrames) 
            _frameNumber = 0;
}

bool Hotspot::isActiveAnimation() {
      return ((_numFrames != 0) && (_layer != 0));
}

uint16 Hotspot::nameId() {
      if (_data == NULL)
            return 0;
      else 
            return _data->nameId;
}

const char *Hotspot::getName() {
      // If name hasn't been loaded yet, then do so
      if (!_nameBuffer[0] && (nameId() != 0))
            StringData::getReference().getString(nameId(), _nameBuffer);

      return &_nameBuffer[0];
}

void Hotspot::setPosition(int16 newX, int16 newY) {
      _startX = newX;
      _startY = newY;
      if (_data) {
            _data->startX = newX;
            _data->startY = newY;
      }
}

void Hotspot::setSize(uint16 newWidth, uint16 newHeight) {
      _width = newWidth;
      _frameWidth = newWidth;
      _height = newHeight;
}

bool Hotspot::executeScript() {
      if (_data->hotspotScriptOffset == 0xffff)
            return false;
      else
            return HotspotScript::execute(this);
}

void Hotspot::tick() {
      uint16 id = _hotspotId;
      debugC(ERROR_BASIC, kLureDebugAnimations, "Hotspot %xh tick begin", id);
      _tickHandler(*this);
      debugC(ERROR_BASIC, kLureDebugAnimations, "Hotspot %xh tick end", id);
}

void Hotspot::setTickProc(uint16 newVal) {
      if (_data)
            _data->tickProcId = newVal;

      _tickHandler = HotspotTickHandlers::getHandler(newVal);     
}

void Hotspot::walkTo(int16 endPosX, int16 endPosY, uint16 destHotspot) {
      if ((hotspotId() == PLAYER_ID) && (PATHFIND_COUNTDOWN != 0)) {
            // Show the clock cursor whilst pathfinding will be calculated
            Mouse &mouse = Mouse::getReference();
            mouse.setCursorNum(CURSOR_TIME_START, 0, 0);
      }

      _destX = endPosX;
      _destY = endPosY;
      _destHotspotId = destHotspot;
      _currentActions.addFront(START_WALKING, _roomNumber);
}

void Hotspot::stopWalking() {
      _voiceCtr = 0;
      setActionCtr(0);
      _currentActions.clear();
      Room::getReference().setCursorState(CS_NONE);
}

void Hotspot::endAction() {
      Room &room = Room::getReference();

      _voiceCtr = 0;
      setActionCtr(0);
      if (_hotspotId == PLAYER_ID)
            room.setCursorState((CursorState) ((int) room.cursorState() & 2));

      if (_currentActions.top().hasSupportData()) {
            CharacterScheduleEntry *rec = _currentActions.top().supportData().next();
            _currentActions.top().setSupportData(rec);
      }
}

void Hotspot::setDirection(Direction dir) {
      if ((_numFrames == 0) || (_direction == dir)) return;
      uint8 newFrameNumber = 0;

      switch (dir) {
      case UP:
            newFrameNumber = _anim->upFrame;
            _charRectY = 4;
            break;
      case DOWN:
            newFrameNumber = _anim->downFrame;
            _charRectY = 4;
            break;
      case LEFT:
            newFrameNumber = _anim->leftFrame;
            _charRectY = 0;
            break;
      case RIGHT:
            newFrameNumber = _anim->rightFrame;
            _charRectY = 0;
            break;
      default:
            // No need to change
            return;
      }

      setFrameNumber(newFrameNumber);
      _direction = dir;
}

// Makes the character face the given hotspot

void Hotspot::faceHotspot(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      Room &room = Room::getReference();
      Screen &screen = Screen::getReference();

      if (hotspot->hotspotId >= START_NONVISUAL_HOTSPOT_ID) {
            // Non visual hotspot
            setDirection(hotspot->nonVisualDirection());

      } else {
            // Visual hotspot
            int xp, yp;
            
            HotspotOverrideData *hsEntry = res.getHotspotOverride(hotspot->hotspotId);
            if (hsEntry != NULL) {
                  xp = x() - hsEntry->xs;
                  yp = y() + heightCopy() - (hsEntry->ys + hotspot->heightCopy);
            } else {
                  xp = x() - hotspot->startX;
                  yp = y() + heightCopy() - (hotspot->startY + hotspot->heightCopy);
            }

            if (ABS(yp) >= ABS(xp)) {
                  if (yp < 0) setDirection(DOWN);
                  else setDirection(UP);
            } else {
                  if (xp < 0) setDirection(RIGHT);
                  else setDirection(LEFT);
            }
      }

      if (hotspotId() == PLAYER_ID) {
            room.update();
            screen.update();
      }
}

void Hotspot::faceHotspot(uint16 id) {
      HotspotData *hotspot = Resources::getReference().getHotspot(id);
      assert(hotspot != NULL);
      faceHotspot(hotspot);
}

// Sets a character walking to a random destination position

void Hotspot::setRandomDest() {
      Resources &res = Resources::getReference();
      RoomData *roomData = res.getRoom(roomNumber());
      Common::Rect &rect = roomData->walkBounds;
      Common::RandomSource rnd;
      int16 xp, yp;

      g_system->getEventManager()->registerRandomSource(rnd, "lureHotspots");

      if (_currentActions.isEmpty())
            _currentActions.addFront(START_WALKING, roomNumber());
      else
            _currentActions.top().setAction(START_WALKING);
      _walkFlag = true;

      // Try up to 20 times to find an unoccupied destination 
      for (int tryCtr = 0; tryCtr < 20; ++tryCtr) {
            xp = rect.left + rnd.getRandomNumber(rect.right - rect.left);
            yp = rect.top + rnd.getRandomNumber(rect.bottom - rect.top);
            setDestPosition(xp, yp);
            setDestHotspot(0);

            // Check if three sequential blocks at chosen destination are unoccupied
            if (!roomData->paths.isOccupied(xp, yp, 3))  
                  break;
      }
}

// Sets or clears the hotspot as occupying an area in its room's pathfinding data

void Hotspot::setOccupied(bool occupiedFlag) {
      if ((coveredFlag() != VB_INITIAL) &&
            (occupiedFlag == (coveredFlag() == VB_TRUE)))
            return;
      setCoveredFlag(occupiedFlag ? VB_TRUE : VB_FALSE);

      int xp = x() >> 3;
      int yp = (y() - 8 + heightCopy() - 4) >> 3;
      int widthVal = MAX(widthCopy() >> 3, 1);

      // Handle cropping for screen left
      if (xp < 0) {
            xp = -xp;
            widthVal -= xp;
            if (widthVal <= 0) return;
            xp = 0;
      }

      // Handle cropping for screen right
      int x2 = xp + widthVal - ROOM_PATHS_WIDTH - 1;
      if (x2 >= 0) {
            widthVal -= (x2 + 1);
            if (widthVal <= 0) return;
      }

      RoomPathsData &paths = Resources::getReference().getRoom(_roomNumber)->paths;
      if (occupiedFlag) {
            paths.setOccupied(xp, yp, widthVal);
      } else {
            paths.clearOccupied(xp, yp, widthVal);
      }
}

// walks the character a single step in a sequence defined by the walking list

bool Hotspot::walkingStep() {
      if (_pathFinder.isEmpty())    return true;

      // Check to see if the end of the next straight walking slice
      if (_pathFinder.stepCtr() >= _pathFinder.top().numSteps()) {
            // Move to next slice in walking sequence
            _pathFinder.stepCtr() = 0;
            _pathFinder.pop();
            if (_pathFinder.isEmpty())    return true;
      }

      if (_pathFinder.stepCtr() == 0)
            // At start of new slice, set the direction
            setDirection(_pathFinder.top().direction());

      MovementDataList *frameSet;
      switch (_pathFinder.top().direction()) {
      case UP:
            frameSet = &_anim->upFrames;
            break;
      case DOWN:
            frameSet = &_anim->downFrames;
            break;
      case LEFT:
            frameSet = &_anim->leftFrames;
            break;
      case RIGHT:
            frameSet = &_anim->rightFrames;
            break;
      default:
            return true;
      }

      int16 _xChange, _yChange;
      uint16 nextFrame;

      if (frameSet->getFrame(frameNumber(), _xChange, _yChange, nextFrame)) {
            setFrameNumber(nextFrame);
            setPosition(x() + _xChange, y() + _yChange);

            ++_pathFinder.stepCtr();
      } else {
            warning("Hotspot %xh dir frame not found: currentFrame=%d, dir=%s",
                  _hotspotId, frameNumber(), directionList[(int) _pathFinder.top().direction()]);
      }

      return false;
}

void Hotspot::updateMovement() {
      assert(_data != NULL);
      if (_currentActions.action() == EXEC_HOTSPOT_SCRIPT) {
            if (_data->coveredFlag) {
                  // Reset position and direction
                  resetPosition();
            } else {
                  // Make sure the cell occupied by character is covered
                  _data->coveredFlag = VB_TRUE;
                  setOccupied(true);
            }
      }

      resetDirection();
}

void Hotspot::updateMovement2(CharacterMode value) {
      setCharacterMode(value);
      updateMovement();
}

void Hotspot::resetPosition() {
      setPosition(x() & 0xf8 | 5, y());
      setDirection(direction());
}

void Hotspot::converse(uint16 destCharacterId, uint16 messageId, bool srcStandStill, 
                                 bool destStandStill) {
      assert(_data);
      _data->talkDestCharacterId = destCharacterId;
      _data->talkMessageId = messageId;
      _data->talkCountdown = CONVERSE_COUNTDOWN_SIZE;

      if ((destCharacterId != 0) && (destCharacterId != NOONE_ID)) {
            // Talking to a destination - add in any talk countdown from the destination,
            // in case the destination is already in process of talking
            HotspotData *hotspot = Resources::getReference().getHotspot(destCharacterId);
            _data->talkCountdown += hotspot->talkCountdown;

            if (destStandStill) {
                  hotspot->talkerId = _hotspotId;
                  hotspot->talkGate = 0;
            }
      }

      if (srcStandStill) {
            setDelayCtr(_data->talkCountdown);
            _data->characterMode = CHARMODE_CONVERSING;
      }
}

void Hotspot::showMessage(uint16 messageId, uint16 destCharacterId) {
      debugC(ERROR_DETAILED, kLureDebugStrings, "Hotspot::showMessage messageId=%xh srcChar=%xh, destChar=%xh",
            messageId, _hotspotId, destCharacterId);
      Resources &res = Resources::getReference();
      char nameBuffer[MAX_HOTSPOT_NAME_SIZE];
      MemoryBlock *data = res.messagesData();
      Hotspot *hotspot;
      uint16 *v = (uint16 *) data->data();
      uint16 v2, idVal;
      messageId &= 0x7fff;

      // Skip through header to find table for given character
      while (READ_LE_UINT16(v) != hotspotId()) v += 2;

      // Scan through secondary list
      ++v;
      v = (uint16 *) (data->data() + READ_LE_UINT16(v));
      v2 = 0;
      while ((idVal = READ_LE_UINT16(v)) != 0xffff) {
            ++v;
            if (READ_LE_UINT16(v) == messageId) break;
            ++v;
      }

      // default response if a specific response not found

      if (idVal == 0xffff) idVal = 0x8c4; 
      debugC(ERROR_DETAILED, kLureDebugStrings, "Hotspot::showMessage idVal=%xh", idVal);

      if (idVal == 0x76) {
            // Special code id for showing the puzzled talk bubble
            hotspot = new Hotspot(this, PUZZLED_ANIM_IDX);
            res.addHotspot(hotspot);

      } else if (idVal == 0x120) {
            // Special code id for showing the exclamation talk bubble
            hotspot = new Hotspot(this, EXCLAMATION_ANIM_IDX);
            res.addHotspot(hotspot);

      } else if (idVal >= 0x8000) {
            // Handle string display
            idVal &= 0x7fff;
            HotspotData *hotspotData = res.getHotspot(res.fieldList().getField(ACTIVE_HOTSPOT_ID));
            const char *itemName = NULL;
            if (hotspotData != NULL) {
                  StringData::getReference().getString(hotspotData->nameId, nameBuffer);
                  itemName = nameBuffer;
            }
                  
            Dialog::show(idVal, itemName, this->getName());
            
      } else if (idVal != 0) {
            // Handle message as a talking dialog 
            converse(destCharacterId, idVal, true, false);
      }
}

void Hotspot::handleTalkDialog() {
      assert(_data);
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      Room &room = Room::getReference();

      // Return if no talk dialog is necessary
      if (_data->talkCountdown == 0) return;
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk countdown = %d", _data->talkCountdown);

      if (_data->talkCountdown == CONVERSE_COUNTDOWN_SIZE) {
            // Check if there's already an active dialog - if so, wait until it's finished
            if (room.isDialogShowing() && (res.getTalkingCharacter() != _hotspotId)) {
                  ++_data->talkCountdown;
                  if (delayCtr() > 0)
                        setDelayCtr(delayCtr() + 2);

                  if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
                        Hotspot *destCharacter = res.getActiveHotspot(_data->talkDestCharacterId);
                        if (destCharacter->resource()->talkCountdown > CONVERSE_COUNTDOWN_SIZE) {
                              destCharacter->resource()->talkCountdown += 2;
                              if (destCharacter->delayCtr() > 0)
                                    destCharacter->setDelayCtr(destCharacter->delayCtr() + 2);
                        }
                  }
                  return;     
            }

            // Time to set up the dialog for the character
            --_data->talkCountdown;
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk dialog opening");
            startTalkDialog();

            if ((_data->talkDestCharacterId != NOONE_ID) && (_data->talkDestCharacterId != 0) &&
                  (_hotspotId < FIRST_NONCHARACTER_ID)) {
                  // Speaking to a hotspot
                  fields.setField(ACTIVE_HOTSPOT_ID, _data->talkDestCharacterId);

                  // Face the character to the hotspot
                  HotspotData *destHotspot = res.getHotspot(_data->talkDestCharacterId);
                  assert(destHotspot != NULL);
                  faceHotspot(destHotspot);

                  // If the hotspot is also a character, then face it to the speaker
                  if (_data->talkDestCharacterId < FIRST_NONCHARACTER_ID) {
                        Hotspot *charHotspot = res.getActiveHotspot(_data->talkDestCharacterId);
                        if (charHotspot != NULL)
                              charHotspot->faceHotspot(resource());
                  }
            }
/*
      } else if (game.fastTextFlag()) {
            // Fast text speed
            --_data->talkCountdown;
      } else if (fields.textCtr2() != 0) {
            fields.textCtr2() = 1;
            --_data->talkCountdown;
      } else {
            --_data->talkCountdown;
            --fields.textCtr2();
      }*/

      } else if ((room.talkDialog() != NULL) && (room.talkDialog()->isBuilding())) {
            return;

      } else if (_data->talkCountdown > 0) {
            --_data->talkCountdown;

            if (_data->talkCountdown == 0) {
                  // Talking is finish - stop talking and free voice animation
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk dialog close");
                  room.setTalkDialog(0, 0, 0, 0);
/*
                  if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
                        HotspotData *destChar = res.getHotspot(_data->talkDestCharacterId);
                        destChar->talkerId = 0;
                  }

                  _data->talkerId = 0;
                  _data->talkGate = 0;
*/
            }
      }

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk handler method end");
}

void Hotspot::startTalkDialog() {
      assert(_data);
      Room &room = Room::getReference();

      if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
//          HotspotData *hotspot = Resources::getReference().getHotspot(_data->talkDestCharacterId);
//          hotspot->talkerId = _hotspotId;
      }

      if (room.roomNumber() != roomNumber())
            return;

      room.setTalkDialog(hotspotId(), _data->talkDestCharacterId, _data->useHotspotId, 
            _data->talkMessageId);
}

/*-------------------------------------------------------------------------*/
/* Hotspot action handling                                                 */
/*                                                                         */
/*-------------------------------------------------------------------------*/

static const uint16 validRoomExitHotspots[] = {0x2711, 0x2712, 0x2714, 0x2715, 0x2716, 0x2717,
      0x2718, 0x2719, 0x271A, 0x271E, 0x271F, 0x2720, 0x2721, 0x2722, 0x2725, 0x2726,
      0x2729, 0x272A, 0x272B, 0x272C, 0x272D, 0x272E, 0x272F, 0};

bool Hotspot::isRoomExit(uint16 id) {
      for (const uint16 *p = &validRoomExitHotspots[0]; *p != 0; ++p) 
            if (*p == id) return true;
      return false;
}

HotspotPrecheckResult Hotspot::actionPrecheck(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();

      if ((hotspot->hotspotId == SID_ID) || (hotspot->hotspotId == EWAN_ID) ||
            (hotspot->hotspotId == NELLIE_ID)) {
            // Check for a bar place
            if (getBarPlace() == BP_KEEP_TRYING)
                  return PC_WAIT;
      } else if (hotspot->roomNumber != roomNumber()) {
            // Object is not in the same room
            if (actionCtr() == 0)
                  showMessage(0, hotspot->hotspotId);
            setActionCtr(0);
            return PC_NOT_IN_ROOM;
      } else if (actionCtr() != 0) {
            // loc_883
            setActionCtr(actionCtr() + 1);
            if (actionCtr() >= 6) {
                  warning("actionCtr exceeded");
                  setActionCtr(0);
                  showMessage(13, NOONE_ID);
                  return PC_EXCESS;
            } 

            if ((hotspot->hotspotId >= FIRST_NONCHARACTER_ID) || 
                  (hotspot->characterMode == CHARMODE_INTERACTING) ||
                  (hotspot->characterMode == CHARMODE_WAIT_FOR_PLAYER) ||
                  (hotspot->characterMode == CHARMODE_WAIT_FOR_INTERACT)) {
                  // loc_880
                  if (characterWalkingCheck(hotspot->hotspotId))
                        return PC_WAIT;
            } else {
                  // loc_886
                  setActionCtr(0);
                  showMessage(14, NOONE_ID);
                  return PC_FAILED;
            }
      } else {
            setActionCtr(1);

            if ((hotspot->hotspotId >= FIRST_NONCHARACTER_ID) ||
                  ((hotspot->actionHotspotId != _hotspotId) && 
                   (hotspot->characterMode == CHARMODE_WAIT_FOR_PLAYER))) {
                  // loc_880
                  if (characterWalkingCheck(hotspot->hotspotId))
                        return PC_WAIT;

            } else if (hotspot->actionHotspotId != _hotspotId) {
                  if (fields.getField(AREA_FLAG) != 2) {
                        showMessage(5, hotspot->hotspotId);
                        setDelayCtr(4);
                  }

                  hotspot->talkGate = GENERAL_MAGIC_ID;
                  hotspot->talkerId = _hotspotId;
                  return PC_WAIT;
            } 
      }

      // loc_888
      setActionCtr(0);
      if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
            hotspot->characterMode = CHARMODE_INTERACTING;
            hotspot->delayCtr = 30;
            hotspot->actionHotspotId = _hotspotId;
      }
      
      // If the player had called out to someone to wait, close down that talk dialog
      if ((_hotspotId == PLAYER_ID) && (res.getTalkingCharacter() == PLAYER_ID)) 
            Room::getReference().setTalkDialog(0, 0, 0, 0);

      return PC_EXECUTE;
}

BarPlaceResult Hotspot::getBarPlace() {
      Resources &res = Resources::getReference();
      BarEntry &barEntry = res.barmanLists().getDetails(roomNumber());

      if (actionCtr() != 0) {
            // Already at bar 
            // Find the character's slot in the bar entry list
            for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
                  if (barEntry.customers[index].hotspotId == hotspotId()) 
                        return ((barEntry.customers[index].serveFlags & 0x80) == 0) ? BP_GOT_THERE : BP_KEEP_TRYING;
            }

            setActionCtr(0);
            return BP_KEEP_TRYING;
      }

      // Try and find a bar place
      if (!findClearBarPlace())
            return BP_KEEP_TRYING;

      // First scan for any existing entry for the character
      int index = -1;
      while (++index < NUM_SERVE_CUSTOMERS) {
            if (barEntry.customers[index].hotspotId == hotspotId()) 
                  break;
      }
      if (index == NUM_SERVE_CUSTOMERS) {
            // Not already present - so scan for an empty slot
            index = -1;
            while (++index < NUM_SERVE_CUSTOMERS) {
                  if (barEntry.customers[index].hotspotId == 0) 
                  break;
            }

            if (index == NUM_SERVE_CUSTOMERS)
                  // No slots available, so flag to keep trying
                  return BP_KEEP_TRYING;
      }

      // Set up the slot entry for the character
      barEntry.customers[index].hotspotId = hotspotId();
      barEntry.customers[index].serveFlags = 0x82;
      setActionCtr(1);
      updateMovement();
      setDirection(UP);

      return BP_KEEP_TRYING;
}

bool Hotspot::findClearBarPlace() {
      // Check if character has reached the bar
      Resources &res = Resources::getReference();
      BarEntry &barEntry = res.barmanLists().getDetails(roomNumber());
      if ((y() + heightCopy()) < ((barEntry.gridLine << 3) + 24)) 
            return true;

      RoomPathsData &paths = res.getRoom(roomNumber())->paths;
      
      // Scan backwards from the right side for 4 free blocks along the bar line block 
      int numFree = 0;
      for (int xp = ROOM_PATHS_WIDTH - 1; xp >= 0; --xp) {
            if (paths.isOccupied(xp, barEntry.gridLine))
                  numFree = 0;
            else if (++numFree == 4) {
                  // Start character walking to the found position
                  walkTo(xp * 8, (barEntry.gridLine << 3) + 8);
                  return false;
            }
      }

      return false;
}

bool Hotspot::characterWalkingCheck(uint16 id) {
      Resources &res = Resources::getReference();
      int16 xp, yp;
      bool altFlag;
      HotspotData *hotspot;

      // Note that several invalid hotspot Ids are used to identify special walk to 
      // coordinates used throughout the game

      _walkFlag = true;
      altFlag = false;

      switch (id) {
      case 997:
            xp = 169; yp = 146;
            altFlag = true;
            break;

      case 998:
            xp = 124; yp = 169;
            break;

      case 999:
            xp = 78; yp = 162;
            break;

      default:
            hotspot = res.getHotspot(id);
            if (hotspot == NULL) {
                  // Should never come here, as all other constants are handled
                  warning("characterWalkingCheck done on unknown hotspot Id %xh", id);
                  xp = 78; yp = 162;
            } else if ((hotspot->walkX == 0) && (hotspot->walkY == 0)) {
                  // The hotspot doesn't have any walk co-ordinates
                  xp = hotspot->startX;
                  yp = hotspot->startY + hotspot->heightCopy - 4;
                  _walkFlag = false;
            } else {
                  xp = hotspot->walkX;
                  yp = hotspot->walkY & 0x7fff;
                  altFlag = (hotspot->walkY & 0x8000) != 0;
            }
            break;
      }

      if (altFlag) {
            // Alternate walking check
            if (((x() >> 3) != (xp >> 3)) || 
                  ((((y() + heightCopy()) >> 3) - 1) != (yp >> 3))) {
                  // Walk to the specified destination
                  walkTo(xp, yp);
                  return true;
            } else {
                  return false;
            }
      }

      // Default walking handling
      if ((ABS(x() - xp) >= 8) ||
            (ABS(y() + heightCopy() - yp - 1) >= 19)) {
            walkTo(xp, yp);
            return true;
      }

      return false;
}

bool Hotspot::doorCloseCheck(uint16 doorId) {
      Resources &res = Resources::getReference();
      Hotspot *doorHotspot = res.getActiveHotspot(doorId);
      if (!doorHotspot) {
            warning("Hotspot %xh is not currently active", doorId);
            return true;
      }

      Rect bounds(doorHotspot->x(), doorHotspot->y() + doorHotspot->heightCopy() 
            - doorHotspot->yCorrection() - doorHotspot->charRectY(),
            doorHotspot->x() + doorHotspot->widthCopy(),
            doorHotspot->y() + doorHotspot->heightCopy() + doorHotspot->charRectY());

      // Loop through active hotspots
      HotspotList::iterator i;
      HotspotList &lst = res.activeHotspots();
      for (i = lst.begin(); i != lst.end(); ++i) {
            Hotspot *hsCurrent = *i;

            // Skip entry if it's the door or the character
            if ((hsCurrent->hotspotId() == hotspotId()) ||
                  (hsCurrent->hotspotId() == doorHotspot->hotspotId())) 
                  continue;

            // Skip entry if it doesn't meet certain criteria
            if ((hsCurrent->layer() == 0) ||
                  (hsCurrent->roomNumber() != doorHotspot->roomNumber()) ||
                  (hsCurrent->hotspotId() < PLAYER_ID) ||
                  ((hsCurrent->hotspotId() >= 0x408) && (hsCurrent->hotspotId() < 0x2710))) 
                  continue;

            // Also skip entry if special Id
            if ((hsCurrent->hotspotId() == 0xfffe) || (hsCurrent->hotspotId() == 0xffff))
                  continue;

            // Check to see if the character is intersecting the door area
            int tempY = hsCurrent->y() + hsCurrent->heightCopy();
            if ((hsCurrent->x() >= bounds.right) ||
                  (hsCurrent->x() + hsCurrent->widthCopy() <= bounds.left) ||
                  (tempY + hsCurrent->charRectY() < bounds.top) ||
                  (tempY - hsCurrent->yCorrection() - hsCurrent->charRectY() > bounds.bottom))
                  continue;

            // At this point we know a character is blocking door, so return false
            return false;
      }

      // No blocking characters, so return true that the door can be closed
      return true;
}

void Hotspot::resetDirection() {
      uint16 newFrameNumber;
      switch (_direction) {
      case UP:
            newFrameNumber = _anim->upFrame;
            break;
      case DOWN:
            newFrameNumber = _anim->downFrame;
            break;
      case LEFT:
            newFrameNumber = _anim->leftFrame;
            break;
      case RIGHT:
            newFrameNumber = _anim->rightFrame;
            break;
      default:
            // No need to change
            return;
      }

      setFrameNumber(newFrameNumber);
}

/*-------------------------------------------------------------------------*/

typedef void (Hotspot::*ActionProcPtr)(HotspotData *hotspot);

void Hotspot::doAction() {
      CurrentActionEntry &entry = _currentActions.top();
      HotspotData *hotspot = NULL;

      if (!entry.hasSupportData() || (entry.supportData().action() == NONE)) {
            doAction(NONE, NULL);
      } else {
            if (entry.supportData().numParams() > 0)
                  hotspot = Resources::getReference().getHotspot(entry.supportData().param(
                        (entry.supportData().action() == USE) ? 1 : 0));
            doAction(entry.supportData().action(), hotspot);
      }
}

void Hotspot::doAction(Action action, HotspotData *hotspot) {
      StringList &stringList = Resources::getReference().stringList();
      debugC(ERROR_INTERMEDIATE, kLureDebugHotspots,  "Action charId=%xh Action=%d/%s", 
            _hotspotId, (int)action, (action > EXAMINE) ? NULL : stringList.getString((int)action));

      // Set the ACTIVE_HOTSPOT_ID and USE_HOTSPOT_ID fields
      if (hotspot != NULL) {
            ValueTableData &fields = Resources::getReference().fieldList();
            fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);

            if (action == USE) 
                  fields.setField(USE_HOTSPOT_ID, _currentActions.top().supportData().param(0));
            else if ((action == GIVE) || (action == ASK)) 
                  fields.setField(USE_HOTSPOT_ID, _currentActions.top().supportData().param(1));
             else 
                  fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
      }

      // Call the appropriate action method
      switch (action) {
      case GET:
            doGet(hotspot);
            break;
      case PUSH:
      case PULL:
      case OPERATE:
            doOperate(hotspot);
            break;
      case OPEN:
            doOpen(hotspot);
            break;
      case CLOSE:
            doClose(hotspot);
            break;
      case LOCK:
      case UNLOCK:
            doLockUnlock(hotspot);
            break;
      case USE:
            doUse(hotspot);
            break;
      case GIVE:
            doGive(hotspot);
            break;
      case TALK_TO:
            doTalkTo(hotspot);
            break;
      case TELL:
            doTell(hotspot);
            break;
      case LOOK:
            doLook(hotspot);
            break;
      case LOOK_AT:
            doLookAt(hotspot);
            break;
      case LOOK_THROUGH:
            doLookThrough(hotspot);
            break;
      case ASK:
            doAsk(hotspot);
            break;
      case DRINK:
            doDrink(hotspot);
            break;
      case STATUS:
            doStatus(hotspot);
            break;
      case GO_TO:
            doGoto(hotspot);
            break;
      case RETURN:
            doReturn(hotspot);
            break;
      case BRIBE:
            doBribe(hotspot);
            break;
      case EXAMINE:
            doExamine(hotspot);
            break;
      case NPC_SET_ROOM_AND_OFFSET:
            npcSetRoomAndBlockedOffset(hotspot);
            break;
      case NPC_TALK_TO_PLAYER:
            npcHeySir(hotspot);
            break;
      case NPC_EXEC_SCRIPT:
            npcExecScript(hotspot);
            break;
      case NPC_RESET_PAUSED_LIST:
            npcResetPausedList(hotspot);
            break;
      case NPC_SET_RAND_DEST:
            npcSetRandomDest(hotspot);
            break;
      case NPC_WALKING_CHECK:
            npcWalkingCheck(hotspot);
            break;
      case NPC_SET_SUPPORT_OFFSET:
            npcSetSupportOffset(hotspot);
            break;
      case NPC_SUPPORT_OFFSET_COND:
            npcSupportOffsetConditional(hotspot);
            break;
      case NPC_DISPATCH_ACTION:
            npcDispatchAction(hotspot);
            break;
      case NPC_TALK_NPC_TO_NPC:
            npcTalkNpcToNpc(hotspot);
            break;
      case NPC_PAUSE:
            npcPause(hotspot);
            break;
      case NPC_START_TALKING:
            npcStartTalking(hotspot);
            break;
      case NPC_JUMP_ADDRESS:
            npcJumpAddress(hotspot);
            break;
      default:
            doNothing(hotspot);
            break;
      }

      debugC(ERROR_DETAILED, kLureDebugHotspots,  "Action charId=%xh Action=%d/%s Complete", 
            _hotspotId, (int)action, (action > EXAMINE) ? NULL : stringList.getString((int)action));
}

void Hotspot::doNothing(HotspotData *hotspot) {
      if (!_currentActions.isEmpty()) {
            _currentActions.pop();
            if (!_currentActions.isEmpty()) {
                  setBlockedFlag(false);
                  currentActions().top().setAction(DISPATCH_ACTION);
                  return;
            }
      }

      if (hotspotId() == PLAYER_ID) 
            Room::getReference().setCursorState(CS_NONE);
}

void Hotspot::doGet(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      HotspotPrecheckResult result = actionPrecheck(hotspot);

      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      endAction();

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GET);
      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
            return;
      } 
      
      if (sequenceOffset != 0) {
            uint16 execResult = Script::execute(sequenceOffset);

            if (execResult == 1) return;
            else if (execResult != 0) {
                  showMessage(execResult);
                  return;
            }
      }

      // Move hotspot into characters's inventory
      hotspot->roomNumber = hotspotId();  

      if (hotspot->hotspotId < START_NONVISUAL_HOTSPOT_ID) {
            // Deactive hotspot animation
            Resources::getReference().deactivateHotspot(hotspot->hotspotId);
            // Remove any 'on the ground' description for the hotspot
            hotspot->descId2 = 0;
      }
}

void Hotspot::doOperate(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      Action action = _currentActions.top().supportData().action();

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      setActionCtr(0);
      faceHotspot(hotspot);
      endAction();

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else {
            sequenceOffset = Script::execute(sequenceOffset);
            if (sequenceOffset > 1)
                  showMessage(sequenceOffset);
      }
}

void Hotspot::doOpen(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      RoomExitJoinData *joinRec;

      if (isRoomExit(hotspot->hotspotId)) {
            joinRec = res.getExitJoin(hotspot->hotspotId);
            if (!joinRec->blocked) {
                  // Room exit is already open
                  showMessage(4);
                  endAction();
                  return;
            }
      }

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      setActionCtr(0);
      endAction();

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, OPEN);
      if (sequenceOffset >= 0x8000) {
            // Message to display
            showMessage(sequenceOffset);
            return;
      }

      if (sequenceOffset != 0) {
            sequenceOffset = Script::execute(sequenceOffset);

            if (sequenceOffset == 1) return;
            if (sequenceOffset != 0) {
                  if (_exitCtr != 0) 
                        _exitCtr = 4;
                  showMessage(sequenceOffset);
                  return;
            }
      }

      joinRec = res.getExitJoin(hotspot->hotspotId);
      if (joinRec->blocked) {
            joinRec->blocked = 0;

            if (hotspotId() != PLAYER_ID) {
                  setCharacterMode(CHARMODE_PAUSED);
                  setDelayCtr(4);
            }
      }
}

void Hotspot::doClose(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      RoomExitJoinData *joinRec;

      if (isRoomExit(hotspot->hotspotId)) {
            joinRec = res.getExitJoin(hotspot->hotspotId);
            if (joinRec->blocked) {
                  // Room exit is already closed/blocked
                  showMessage(3);
                  endAction();
                  return;
            }
      }

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      setActionCtr(0);
      endAction();

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, CLOSE);
      if (sequenceOffset >= 0x8000) {
            // Message to display
            showMessage(sequenceOffset);
            return;
      } else if (sequenceOffset != 0) {
            // Otherwise handle script
            sequenceOffset = Script::execute(sequenceOffset);

            if (sequenceOffset != 0) {
                  showMessage(sequenceOffset);
                  return;
            }
      }

      joinRec = res.getExitJoin(hotspot->hotspotId);
      if (!joinRec->blocked) {
            // Close the door
            if (!doorCloseCheck(joinRec->hotspots[0].hotspotId) ||
                  !doorCloseCheck(joinRec->hotspots[1].hotspotId)) {
                  // A character is preventing the door from closing
                  showMessage(2);
            } else {
                  // Flag the door as closed
                  joinRec->blocked = 1;
            }
      }
}

void Hotspot::doUse(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      uint16 usedId = _currentActions.top().supportData().param(0);
      HotspotData *usedHotspot = res.getHotspot(usedId);
      _data->useHotspotId = usedId;

      if (usedHotspot->roomNumber != hotspotId()) {
            // Item to be used is not in character's inventory - say "What???"
            endAction();
            showMessage(0xF);
            return;
      }

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      endAction();

      if (hotspotId() == RATPOUCH_ID) {
            _tempDest.position.x = 40;
            setFrameCtr(80);
      }

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, USE);

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else if (sequenceOffset == 0) {
            showMessage(17);
      } else {
            sequenceOffset = Script::execute(sequenceOffset);
            if (sequenceOffset != 0) 
                  showMessage(sequenceOffset);
      }
}

void Hotspot::doGive(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      uint16 usedId = _currentActions.top().supportData().param(1);
      HotspotData *usedHotspot = res.getHotspot(usedId);
      _data->useHotspotId = usedId;

      if (usedHotspot->roomNumber != hotspotId()) {
            // Item to be used is not in character's inventory - say "What???"
            endAction();
            showMessage(0xF);
            return;
      }

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      endAction();

      if ((hotspot->hotspotId != PRISONER_ID) || (usedId != BOTTLE_HOTSPOT_ID)) 
            showMessage(7, hotspot->hotspotId);

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GIVE);

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else if (sequenceOffset != 0) {
            sequenceOffset = Script::execute(sequenceOffset);
            if (sequenceOffset == NOONE_ID) {
                  // Start a conversation based on the index of field #6
                  uint16 index = res.fieldList().getField(GIVE_TALK_INDEX);
                  uint16 id = res.getGiveTalkId(index);
                  startTalk(hotspot, id);

            } else if (sequenceOffset == 0) {
                  // Move item into character's inventory
                  HotspotData *usedItem = res.getHotspot(usedId);
                  usedItem->roomNumber = hotspot->hotspotId;
            } else if (sequenceOffset > 1) {
                  Hotspot *destCharacter = res.getActiveHotspot(hotspot->hotspotId);
                  if (destCharacter != NULL)
                        destCharacter->showMessage(sequenceOffset, hotspotId());
            }
      }
}

void Hotspot::doTalkTo(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      if ((hotspot->hotspotId != SKORL_ID) && ((hotspot->roomNumber != 28) || 
            (hotspot->hotspotId != BLACKSMITH_ID))) {

            HotspotPrecheckResult result = actionPrecheck(hotspot);
            if (result == PC_WAIT) return;
            else if (result != PC_EXECUTE) {
                  endAction();
                  return;
            }
      }

      faceHotspot(hotspot);
      endAction();

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TALK_TO);
      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
            return;
      }

      if (sequenceOffset != 0) {
            uint16 result = Script::execute(sequenceOffset);

            if (result != 0) {
                  endAction();
                  return;
            }
      }

      // Start talking with character
      startTalk(hotspot, getTalkId(hotspot));
}

void Hotspot::doTell(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
      Hotspot *character = res.getActiveHotspot(hotspot->hotspotId);
      assert(character);

      HotspotPrecheckResult hsResult = actionPrecheck(hotspot);
      if (hsResult == PC_WAIT) return;
      else if (hsResult != PC_EXECUTE) {
            endAction();
            return;
      }

      converse(hotspot->hotspotId, 0x7C, true, false);

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TELL);
      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else if (sequenceOffset != 0) {
            uint16 result = Script::execute(sequenceOffset);

            if (result == 0) {
                  // Build up sequence of commands for character to follow
                  CharacterScheduleEntry &cmdData = _currentActions.top().supportData();
                  character->setStartRoomNumber(character->roomNumber());
                  character->currentActions().clear();
                  character->setBlockedFlag(false);

                  for (int index = 1; index < cmdData.numParams(); index += 3) {
                        character->currentActions().addBack((Action) cmdData.param(index), 0,
                              cmdData.param(index + 1), cmdData.param(index + 2));
                  }
            }
      }

      endAction();
}

void Hotspot::doLook(HotspotData *hotspot) {
      endAction();
      Dialog::show(Room::getReference().descId());
}

static const uint16 hotspotLookAtList[] = {0x411, 0x412, 0x41F, 0x420, 0x421, 0x422, 0x426, 
      0x427, 0x428, 0x429, 0x436, 0x437, 0};

void Hotspot::doLookAt(HotspotData *hotspot) {
      doLookAction(hotspot, LOOK_AT);
}

void Hotspot::doLookThrough(HotspotData *hotspot) {
      doLookAction(hotspot, LOOK_THROUGH);
}

void Hotspot::doLookAction(HotspotData *hotspot, Action action) {
      Resources &res = Resources::getReference();
      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);

      if (hotspot->hotspotId >= FIRST_NONCHARACTER_ID) {
            // Check if the hotspot appears in the list of hotspots that don't
            // need to be walked to before being looked at
            const uint16 *tempId = &hotspotLookAtList[0];
            while ((*tempId != 0) && (*tempId != hotspot->hotspotId))
                  ++tempId;
            if (!*tempId) {
                  // Hotspot wasn't in the list
                  HotspotPrecheckResult result = actionPrecheck(hotspot);
                  if (result == PC_WAIT)
                        return;
                  else if (result != PC_EXECUTE) {
                        endAction();
                        return;
                  }
            }
      }

      faceHotspot(hotspot);
      setActionCtr(0);
      endAction();

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else {
            if (sequenceOffset != 0) 
                  sequenceOffset = Script::execute(sequenceOffset);

            if (sequenceOffset == 0) {
                  uint16 descId = (hotspot->descId2 != 0) ? hotspot->descId2 : hotspot->descId;
                  Dialog::show(descId);
            }
      }
}

void Hotspot::doAsk(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      uint16 usedId = _currentActions.top().supportData().param(1);
      Hotspot *destCharacter = res.getActiveHotspot(hotspot->hotspotId);
      HotspotData *usedHotspot = res.getHotspot(usedId);
      _data->useHotspotId = usedId;

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      endAction();
      showMessage(9, hotspot->hotspotId); // CHARACTER, DO YOU HAVE ITEM?

      // Get the action and handle the reply
      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, ASK);

      if (sequenceOffset >= 0x8000) {
            if (destCharacter != NULL)
                  destCharacter->showMessage(sequenceOffset, hotspotId());
      } else if (sequenceOffset != 0) {
            sequenceOffset = Script::execute(sequenceOffset);

            if (sequenceOffset == 0) {
                  // Give item to character
                  usedHotspot->roomNumber = hotspotId();
                  if (destCharacter != NULL)
                        destCharacter->showMessage(32, hotspotId());
            } else if ((sequenceOffset != 1) && (destCharacter != NULL)) {
                  destCharacter->showMessage(sequenceOffset, hotspotId());
            }
      }
}

void Hotspot::doDrink(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      endAction();

      // Make sure item is in character's inventory
      if (hotspot->roomNumber != hotspotId()) {
            showMessage(0xF);
            return;
      }

      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, DRINK);

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else if (sequenceOffset == 0) {
            showMessage(22);
      } else {
            uint16 result = Script::execute(sequenceOffset);
            if (result == 0) {
                  // Item has been drunk, so remove item from game
                  hotspot->roomNumber = 0;
            } else if (result != 1) {
                  showMessage(result);
            }
      }
}

// doStatus
// Handle the status window

void Hotspot::doStatus(HotspotData *hotspot) {
      char buffer[MAX_DESC_SIZE];
      uint16 numItems = 0;
      Resources &res = Resources::getReference();
      StringList &stringList = res.stringList();
      StringData &strings = StringData::getReference();
      Room &room = Room::getReference();
      
      room.update();
      endAction();

      strings.getString(room.roomNumber(), buffer);
      strcat(buffer, "\n\n");
      strcat(buffer, stringList.getString(S_YOU_ARE_CARRYING));

      // Scan through the list and add in any items assigned to the player
      HotspotDataList &list = res.hotspotData();
      HotspotDataList::iterator i;
      for (i = list.begin(); i != list.end(); ++i) {
            HotspotData *rec = *i;

            if (rec->roomNumber == PLAYER_ID) {
                  if (numItems++ == 0) strcat(buffer, ": ");
                  else strcat(buffer, ", ");
                  strings.getString(rec->nameId, buffer + strlen(buffer));
            }
      }

      // If there were no items, add in the word 'nothing'
      if (numItems == 0) strcat(buffer, stringList.getString(S_INV_NOTHING));

      // If the player has money, add it in
      uint16 numGroats = res.fieldList().numGroats();
      if (numGroats > 0) {
            strcat(buffer, "\n\n");
            strcat(buffer, stringList.getString(S_YOU_HAVE));
            sprintf(buffer + strlen(buffer), "%d", numGroats);
            strcat(buffer, " ");
            strcat(buffer, stringList.getString((numGroats == 1) ? S_GROAT : S_GROATS));
      }

      // Display the dialog
      Screen &screen = Screen::getReference();
      Mouse &mouse = Mouse::getReference();
      mouse.cursorOff();

      Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, buffer);
      s->copyToScreen(INFO_DIALOG_X, (FULL_SCREEN_HEIGHT-s->height())/2);

      Events::getReference().waitForPress();
      screen.update();
      mouse.cursorOn();
}

// doGoto
// Sets the room for the character to go to

void Hotspot::doGoto(HotspotData *hotspot) {
      _exitCtr = 0;
      _blockedOffset = 0;
      _currentActions.top().setRoomNumber(_currentActions.top().supportData().param(0));
      endAction();
}

void Hotspot::doReturn(HotspotData *hotspot) {
      currentActions().top().setRoomNumber(startRoomNumber());
      endAction();
}

static const uint16 bribe_hotspot_list[] = {0x421, 0x879, 0x3E9, 0x8C7, 0x429, 0x8D1,
      0x422, 0x8D4, 0x420, 0x8D6, 0x42B, 0x956, 0x3F2, 0xBE6, 0};

void Hotspot::doBribe(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }
      
      const uint16 *tempId = &bribe_hotspot_list[0];
      uint16 sequenceOffset = 0x14B;     // Default sequence offset
      while (*tempId != 0) {
            if (*tempId++ == hotspotId()) {
                  sequenceOffset = *tempId;
                  if ((sequenceOffset & 0x8000) != 0)
                        sequenceOffset = Script::execute(sequenceOffset & 0x7fff);
                  break;
            }
            ++tempId;                      // Move over entry's sequence offset
      }
      
      faceHotspot(hotspot);
      setActionCtr(0);
      endAction();

      sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, BRIBE);
      if (sequenceOffset != 0) {
            sequenceOffset = Script::execute(sequenceOffset);
            if (sequenceOffset != 0) return;
      }

      uint16 talkIndex = res.fieldList().getField(TALK_INDEX);
      showMessage((talkIndex == 6) ? 0x30 : 0x29);
}

void Hotspot::doExamine(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      endAction();
      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, EXAMINE);

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else {
            if (sequenceOffset != 0) 
                  sequenceOffset = Script::execute(sequenceOffset);

            if (sequenceOffset == 0) {
                  Dialog::show(hotspot->descId);
            }
      }
}

void Hotspot::doLockUnlock(HotspotData *hotspot) {
      Action action = _currentActions.top().supportData().action();
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      faceHotspot(hotspot);
      endAction();
      
      uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);

      if (sequenceOffset >= 0x8000) {
            showMessage(sequenceOffset);
      } else {
            if (sequenceOffset != 0) 
                  Script::execute(sequenceOffset);
      }
}

void Hotspot::npcSetRoomAndBlockedOffset(HotspotData *hotspot) {
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      _exitCtr = 0;

      _blockedOffset = entry.param(1);
      _currentActions.top().setRoomNumber(entry.param(0));
      endAction();
}

void Hotspot::npcHeySir(HotspotData *hotspot) {
      Resources &res = Resources::getReference();

      // If player is performing an action, wait until it's done
      Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
      if (!playerHotspot->currentActions().isEmpty()) {
            setDelayCtr(12);
            setCharacterMode(CHARMODE_PAUSED);
            setActionCtr(0);
            return;
      }

      // TODO: Check storage of hotspot Id in talk_first=player/talk_second=0

      // Get the npc to say "Hey Sir" to player
      showMessage(0x22, PLAYER_ID);

      // Get the character to remain in place for a while
      setDelayCtr(130);
      setCharacterMode(CHARMODE_WAIT_FOR_PLAYER);

      // Set the talk override to the specified Id
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      _data->talkOverride = entry.param(0);

      doNothing(hotspot);
}

void Hotspot::npcExecScript(HotspotData *hotspot) {
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      uint16 offset = entry.param(0);
      endAction();
      Script::execute(offset);
}

void Hotspot::npcResetPausedList(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      setCharacterMode(CHARMODE_HESITATE);
      setDelayCtr(IDLE_COUNTDOWN_SIZE + 1);

      res.pausedList().reset(hotspotId());
      endAction();
}

void Hotspot::npcSetRandomDest(HotspotData *hotspot) {
      endAction();
      setRandomDest();
}

void Hotspot::npcWalkingCheck(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      uint16 hId = entry.param(0);

      endAction();
      fields.setField(USE_HOTSPOT_ID, hId);
      fields.setField(ACTIVE_HOTSPOT_ID, hId);

      if ((hId < PLAYER_ID) || (hotspot->roomNumber == _roomNumber)) {
            characterWalkingCheck(hId);
      }
}

void Hotspot::npcSetSupportOffset(HotspotData *hotspot) {
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      uint16 entryId = entry.param(0);

      CharacterScheduleEntry *newEntry = Resources::getReference().
            charSchedules().getEntry(entryId, entry.parent());
      _currentActions.top().setSupportData(newEntry);
}

void Hotspot::npcSupportOffsetConditional(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      CharacterScheduleEntry *newEntry;
      uint16 scriptOffset = entry.param(0);
      uint16 entryId = entry.param(1);

      if (Script::execute(scriptOffset) == 0) {
            // Not succeeded, get next entry
            newEntry = entry.next();
      } else {
            // Get entry specified by parameter 1
            newEntry = res.charSchedules().getEntry(entryId, entry.parent());
      }

      _currentActions.top().setSupportData(newEntry);
      HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL : res.getHotspot(
            (newEntry->action() == USE) ? newEntry->param(1) : newEntry->param(0));
      doAction(newEntry->action(), hotspotData);
}

void Hotspot::npcDispatchAction(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      CharacterScheduleEntry &entry = _currentActions.top().supportData();

      fields.setField(USE_HOTSPOT_ID, entry.param(0));
      fields.setField(ACTIVE_HOTSPOT_ID, entry.param(0));

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_EXECUTE) {
            endAction();
      } else if (result != PC_WAIT) {
            CharacterScheduleEntry *newEntry = Resources::getReference().
                  charSchedules().getEntry(entry.param(0), entry.parent());
            _currentActions.top().setSupportData(newEntry);
            
            HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL : 
                  res.getHotspot(newEntry->param((newEntry->action() == USE) ? 1 : 0));
            doAction(newEntry->action(), hotspotData);
      }
}

void Hotspot::npcTalkNpcToNpc(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
      fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);

      HotspotPrecheckResult result = actionPrecheck(hotspot);
      if (result == PC_WAIT) return;
      else if (result != PC_EXECUTE) {
            endAction();
            return;
      }

      // If dest is already talking, keep exiting until they're free
      if (hotspot->talkCountdown != 0) 
            return;

      // Handle the source's talk message
      if (entry.param(1) != 0) {
            converse(hotspot->hotspotId, entry.param(1), true, false);
            _data->talkCountdown += entry.param(2);
            setDelayCtr(delayCtr() + entry.param(2));
      }

      // Handle the destination's response message
      if (entry.param(3) != 0) {
            Hotspot *destHotspot = res.getActiveHotspot(hotspot->hotspotId);
            assert(destHotspot);
            destHotspot->converse(this->hotspotId(), entry.param(3), true, false);
      }

      endAction();
}

void Hotspot::npcPause(HotspotData *hotspot) {
      uint16 delayAmount = _currentActions.top().supportData().param(0);
      endAction();

      setCharacterMode(CHARMODE_PAUSED);
      setDelayCtr(delayAmount);
}

void Hotspot::npcStartTalking(HotspotData *hotspot) {
      CharacterScheduleEntry &entry = _currentActions.top().supportData();
      uint16 stringId = entry.param(0);
      uint16 destHotspot = entry.param(1);

      converse(destHotspot, stringId, false);
      endAction();
}

void Hotspot::npcJumpAddress(HotspotData *hotspot) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      int procIndex = _currentActions.top().supportData().param(0);
      Hotspot *player;
      CharacterScheduleEntry *entry;
      endAction();

      switch (procIndex) {
      case 0:
            if (fields.getField(OLD_ROOM_NUMBER) == 19) {
                  fields.setField(TALK_INDEX, 24);
                  res.getHotspot(0x3F1)->nameId = 0x154;
                  Dialog::show(0xAB9);
            }
            break;

      case 1:
            player = res.getActiveHotspot(PLAYER_ID);
            if (player->y() < 52) {
                  entry = res.charSchedules().getEntry(JUMP_ADDR_2_SUPPORT_ID, NULL);
                  assert(entry);

                  _currentActions.clear();
                  _currentActions.addFront(DISPATCH_ACTION, entry, ROOMNUM_CELLAR);
            }
            break;

      default:
            error("Hotspot::npcJumpAddress - invalid method index %d", procIndex);
            break;
      }
}

/*------------------------------------------------------------------------*/

uint16 Hotspot::getTalkId(HotspotData *charHotspot) {
      Resources &res = Resources::getReference();
      uint16 talkIndex;
      TalkHeaderData *headerEntry;
      bool isEnglish = LureEngine::getReference().getLanguage() == EN_ANY;

      // If the hotspot has a talk data override, return it
      if (charHotspot->talkOverride != 0) {
            // Has an override, so return it and reset back to zero
            uint16 result = charHotspot->talkOverride;
            charHotspot->talkOverride = 0;
            return result;
      }

      // Get offset of talk set to use
      headerEntry = res.getTalkHeader(charHotspot->hotspotId);

      // Check whether character is a stranger 
      if ((isEnglish && (charHotspot->nameId == 378)) ||
            (!isEnglish && ((charHotspot->nameId == 381) || (charHotspot->nameId == 382))))
            // Is a stranger, so force talk Index to be 0 (initial talk)
            talkIndex = 0;
      else
            // Set the talk index based on the current game-wide talk index
            talkIndex = res.fieldList().getField(TALK_INDEX) + 1;

      return headerEntry->getEntry(talkIndex);
}

void Hotspot::startTalk(HotspotData *charHotspot, uint16 id) {
      Resources &res = Resources::getReference();

      // Set for providing talk listing
      setTickProc(TALK_TICK_PROC_ID);    
      
      // Signal the character that they're being talked to
      charHotspot->talkerId = _hotspotId;
      charHotspot->talkGate = 0;
      charHotspot->talkDestCharacterId = _hotspotId;
      _data->talkDestCharacterId = charHotspot->hotspotId;
      _data->talkGate = 0;
      
      // Set the active talk data
      res.setTalkStartEntry(0);
      res.setTalkData(id);
      if (!res.getTalkData()) 
            error("Talk failed - invalid offset: Character=%xh, offset=%xh",
                  charHotspot->hotspotId, id);
}

void Hotspot::saveToStream(Common::WriteStream *stream) {
      _currentActions.saveToStream(stream);
      _pathFinder.saveToStream(stream);

      stream->writeUint16LE(_roomNumber);
      stream->writeSint16LE(_startX);
      stream->writeSint16LE(_startY);
      stream->writeSint16LE(_destX);
      stream->writeSint16LE(_destY);
      stream->writeUint16LE(_destHotspotId);
      stream->writeByte(_tempDest.counter);
      stream->writeSint16LE(_tempDest.position.x);
      stream->writeSint16LE(_tempDest.position.y);
      stream->writeUint16LE(_frameWidth);
      stream->writeUint16LE(_height);
      stream->writeUint16LE(_width);
      stream->writeUint16LE(_heightCopy);
      stream->writeUint16LE(_widthCopy);
      stream->writeUint16LE(_yCorrection);
      stream->writeUint16LE(_talkX);
      stream->writeUint16LE(_talkY);
      stream->writeByte(_layer);
      stream->writeUint16LE(_hotspotScriptOffset);
      stream->writeByte(_colourOffset);
      stream->writeByte((byte)_direction);
      stream->writeUint16LE(_animId);
      stream->writeUint16LE(_frameNumber);

      stream->writeUint16LE(_frameCtr);
      stream->writeByte(_skipFlag);
      stream->writeUint16LE(_charRectY);
      stream->writeUint16LE(_voiceCtr);
      stream->writeUint16LE(_blockedOffset);
      stream->writeUint16LE(_exitCtr);
      stream->writeByte(_walkFlag);
      stream->writeByte(_persistant);
      stream->writeUint16LE(_startRoomNumber);
      stream->writeUint16LE(_supportValue);
}

void Hotspot::loadFromStream(Common::ReadStream *stream) {
      _currentActions.loadFromStream(stream);
      _pathFinder.loadFromStream(stream);

      _roomNumber = stream->readUint16LE();
      _startX = stream->readSint16LE();
      _startY = stream->readSint16LE();
      _destX = stream->readSint16LE();
      _destY = stream->readSint16LE();
      _destHotspotId = stream->readUint16LE();
      _tempDest.counter = stream->readByte();
      _tempDest.position.x = stream->readSint16LE();
      _tempDest.position.y = stream->readSint16LE();
      _frameWidth = stream->readUint16LE();
      _height = stream->readUint16LE();
      _width = stream->readUint16LE();
      _heightCopy = stream->readUint16LE();
      _widthCopy = stream->readUint16LE();
      _yCorrection = stream->readUint16LE();
      _talkX = stream->readUint16LE();
      _talkY = stream->readUint16LE();
      _layer = stream->readByte();
      _hotspotScriptOffset = stream->readUint16LE();
      _colourOffset = stream->readByte();
      _direction = (Direction)stream->readByte();
      setAnimation(stream->readUint16LE());
      setFrameNumber(stream->readUint16LE());

      _frameCtr = stream->readUint16LE();
      _skipFlag = stream->readByte() != 0;
      _charRectY = stream->readUint16LE();
      _voiceCtr = stream->readUint16LE();
      _blockedOffset = stream->readUint16LE();
      _exitCtr = stream->readUint16LE();
      _walkFlag = stream->readByte() != 0;
      _persistant = stream->readByte() != 0;
      _startRoomNumber = stream->readUint16LE();
      _supportValue = stream->readUint16LE();
}

/*------------------------------------------------------------------------*/

HandlerMethodPtr HotspotTickHandlers::getHandler(uint16 procIndex) {
      switch (procIndex) {
      case 1:
            return defaultHandler;
      case STANDARD_CHARACTER_TICK_PROC:
            return standardCharacterAnimHandler;
      case PLAYER_TICK_PROC_ID:
            return playerAnimHandler;
      case VOICE_TICK_PROC_ID:
            return voiceBubbleAnimHandler;
      case PUZZLED_TICK_PROC_ID:
            return puzzledAnimHandler;
      case 6:
            return roomExitAnimHandler;
      case 7:
      case FOLLOWER_TICK_PROC_2:
            return followerAnimHandler;
      case JAILOR_TICK_PROC_ID:
      case 10:
            return jailorAnimHandler;
      case STANDARD_ANIM_2_TICK_PROC:
            return standardAnimHandler2;
      case STANDARD_ANIM_TICK_PROC:
            return standardAnimHandler;
      case 13:
            return sonicRatAnimHandler;
      case 14:
            return droppingTorchAnimHandler;
      case 15:
            return playerSewerExitAnimHandler;
      case 16:
            return fireAnimHandler;
      case 17:
            return sparkleAnimHandler;
      case 18:
            return teaAnimHandler;
      case 19:
            return goewinCaptiveAnimHandler;
      case 20:
            return prisonerAnimHandler;
      case 21:
            return catrionaAnimHandler;
      case 22:
            return morkusAnimHandler;
      case 23:
            return grubAnimHandler;
      case 24:
            return barmanAnimHandler;
      case 25:
            return skorlAnimHandler;
      case 26:
            return gargoyleAnimHandler;
      case GOEWIN_SHOP_TICK_PROC:
            return goewinShopAnimHandler;
      case 28:
      case 29:
      case 30:
      case 31:
      case 32:
      case 33:
            return skullAnimHandler;
      case 34:
            return dragonFireAnimHandler;
      case 35:
            return castleSkorlAnimHandler;
      case 36:
            return rackSerfAnimHandler;
      case TALK_TICK_PROC_ID:
            return talkAnimHandler;
      case 38:
            return fighterAnimHandler;
      case PLAYER_FIGHT_TICK_PROC_ID:
            return playerFightAnimHandler;
      default:
            error("Unknown tick proc Id %xh for hotspot", procIndex);
      }
}

void HotspotTickHandlers::defaultHandler(Hotspot &h) {
      // No handling done
}

void HotspotTickHandlers::standardAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();

      if (h.frameCtr() > 0) 
            h.decrFrameCtr();
      else {
            if (h.executeScript()) {
                  // Script is finished - deactivate hotspot and move it to an out of range room
                  HotspotData *data = h.resource();
                  res.deactivateHotspot(&h);
                  data->roomNumber |= 0x8000;
            }
      }
}

void HotspotTickHandlers::standardAnimHandler2(Hotspot &h) {
      h.handleTalkDialog();
      standardAnimHandler(h);
}

void HotspotTickHandlers::standardCharacterAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
      PathFinder &pathFinder = h.pathFinder();
      CurrentActionStack &actions = h.currentActions();
      Hotspot *player = res.getActiveHotspot(PLAYER_ID);
      uint16 impingingList[MAX_NUM_IMPINGING];
      int numImpinging;
      bool bumpedPlayer;

      if (h.currentActions().action() != WALKING) {
            char buffer[MAX_DESC_SIZE];
            h.currentActions().list(buffer);
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character p=(%d,%d,%d) bs=%d\n%s", 
                  h.x(), h.y(), h.roomNumber(), h.blockedState(), buffer);
      }

      // Handle any active talk dialog
      h.handleTalkDialog();

      // If someone is talking to the character, this stops them from moving for the duration)
      if (h.resource()->talkerId != 0) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Talker Id = %xh, talk_gate = %d",
                  h.resource()->talkerId, h.talkGate());
            if (h.talkGate() == GENERAL_MAGIC_ID) {
                  fields.setField(ACTIVE_HOTSPOT_ID, h.talkGate());
                  fields.setField(USE_HOTSPOT_ID, h.resource()->talkerId);
                  Script::execute(h.talkScript());
                  h.resource()->talkerId = 0;
            } else {
                  h.updateMovement();
                  return;
            }
      }

      // If a frame countdown is in progress, then decrement and exit
      if (h.frameCtr() > 0) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Frame ctr = %d", h.frameCtr());
            h.decrFrameCtr();
            return;
      }

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 2");
      numImpinging = Support::findIntersectingCharacters(h, impingingList);
      bumpedPlayer = (numImpinging == 0) ? false :
            Support::isCharacterInList(impingingList, numImpinging, PLAYER_ID);

      // Check for character having just changed room
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 3");
      if (h.skipFlag()) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Skip flag was set");

            if (numImpinging > 0) {
                  // Scan to check if the character has bumped into player
                  if (bumpedPlayer && (player->characterMode() == CHARMODE_IDLE)) {
                        // Signal the player to move out of the way automatically
                        player->setBlockedState(BS_INITIAL);
                        player->setDestHotspot(0);
                        player->setRandomDest();

                        Room::getReference().setCursorState(CS_BUMPED);
                        debugC(ERROR_DETAILED, kLureDebugAnimations, "Player bumped code starting");

                  } else {
                        // Signal the character to pause briefly to allow bumped
                        // character time to start moving out of the way
                        h.setDelayCtr(10);
                        h.setCharacterMode(CHARMODE_PAUSED);
                        debugC(ERROR_DETAILED, kLureDebugAnimations, "Pausing after bumping into character");
                  }
                  return;
            }

            h.setSkipFlag(false);
      }

      if (h.resource()->scriptHotspotId != 0) {
            // Character bumped against another
            fields.setField(USE_HOTSPOT_ID, h.resource()->scriptHotspotId);
            Script::execute(h.resource()->tickScriptOffset);
            h.resource()->scriptHotspotId = 0;
      }

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 4");
      if (h.pauseCtr() != 0) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "pause ctr = %d", h.pauseCtr());
            h.updateMovement();
            h.pathFinder().clear();
            if (h.pauseCtr() > 1) {
                  res.pausedList().scan(h);
                  return;
            } else {
                  h.setPauseCtr(0);
                  if (h.characterMode() == CHARMODE_NONE) {
                        h.pathFinder().clear();
                        return;
                  }
            }
      }

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 5");
      if (h.characterMode() != CHARMODE_NONE) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "char mode = %d, delay ctr = %d", 
                  h.characterMode(), h.delayCtr());

            if (h.characterMode() == CHARMODE_PLAYER_WAIT) {
                  h.updateMovement();
                  if (bumpedPlayer) return;
            } else {
                  // All other character modes
                  if (h.delayCtr() > 0) {
                        // There is some countdown left to do
                        h.updateMovement();

                        bool decrementFlag = (h.resource()->actionHotspotId == 0);
                        if (!decrementFlag) {
                              HotspotData *hotspot = res.getHotspot(h.resource()->actionHotspotId);
                              assert(hotspot);
                              decrementFlag = (hotspot->roomNumber != h.roomNumber()) ? false :
                                    Support::charactersIntersecting(hotspot, h.resource());
                        }

                        if (decrementFlag) {
                              h.setDelayCtr(h.delayCtr() - 1);
                              return;
                        }
                  }
            }

            h.resource()->actionHotspotId = 0;
            CharacterMode currentMode = h.characterMode();
            h.setCharacterMode(CHARMODE_NONE);
            h.pathFinder().clear();

            if ((currentMode == CHARMODE_WAIT_FOR_PLAYER) || (currentMode == CHARMODE_WAIT_FOR_INTERACT)) {
                  h.resource()->talkOverride = 0;
                  h.showMessage(1);
            }
            return;
      }

      /* interactHotspotId never seems to be set 
      if ((h.resource()->interactHotspotId != 0) && !player->currentActions().isEmpty())  {
            h.setActionCtr(99);
            if (!actions.isEmpty()) 
                  actions.top().setAction(DISPATCH_ACTION);
      }
      */

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 6");
      CurrentAction action = actions.action();
      PathFinderResult pfResult;

      switch (action) {
      case NO_ACTION:
            h.updateMovement2(CHARMODE_IDLE);
            break;

      case DISPATCH_ACTION:
            // Dispatch an action
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character dispatch action");

            if (actions.top().roomNumber() == 0)
                  actions.top().setRoomNumber(h.roomNumber());
            if (actions.top().roomNumber() == h.roomNumber()) {
                  // NPC in correct room for action
                  h.setSkipFlag(false);
                  h.doAction();
            } else {
                  // NPC in wrong room for action
                  npcRoomChange(h);
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character change room request");
            }
            break;

      case EXEC_HOTSPOT_SCRIPT:
            // A hotspot script is in progress for the player, so don't interrupt
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character exec hotspot script");
            if (h.executeScript()) {
                  // Script is finished
                  actions.top().setAction(DISPATCH_ACTION);
            }
            break;

      case START_WALKING:
            // Start the player walking to the given destination
            
            debugC(ERROR_DETAILED, kLureDebugAnimations, 
                  "Hotspot standard character exec start walking => (%d,%d)",
                  h.destX(), h.destY());
            h.setOccupied(false);  
            pathFinder.reset(paths);
            h.currentActions().top().setAction(PROCESSING_PATH);

            // Deliberate fall through to processing walking path

      case PROCESSING_PATH:
            // Handle processing pathfinding
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character processing path");
            res.pausedList().scan(h);
            
            pfResult = pathFinder.process();
            if (pfResult == PF_UNFINISHED) break;

            debugC(ERROR_DETAILED, kLureDebugAnimations, 
                  "pathFinder done: result = %d", pfResult);

            // Post-processing checks
            if ((pfResult == PF_OK) || 
                  ((h.destHotspotId() == 0) && (pfResult == PF_DEST_OCCUPIED))) {
                  // Standard processing
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Standard result handling");

                  h.setBlockedState(BS_NONE);
                  if (h.pathFinder().isEmpty()) {
                        // No path was defined
                        h.currentActions().top().setAction(DISPATCH_ACTION);
                        return;
                  }
                  h.currentActions().top().setAction(WALKING);
                  h.setPosition(h.x(), h.y() & 0xfff8);
            } else if (h.blockedState() == BS_FINAL) {
                  // If this point is reached, the character twice hasn't found a walking path
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Character is hopelessly blocked");

                  res.pausedList().reset(h.hotspotId());
                  h.updateMovement();

                  assert(!h.currentActions().isEmpty());
                  h.currentActions().pop();

                  h.setBlockedFlag(false);
                  h.setBlockedState(BS_NONE);
                  h.setCharacterMode(CHARMODE_PAUSED);
                  h.setDelayCtr(2);

                  if (h.currentActions().isEmpty() || 
                        (h.currentActions().top().roomNumber() != h.roomNumber()))
                        h.setDestHotspot(0xffff);

                  if (bumpedPlayer)
                        h.setCharacterMode(CHARMODE_PLAYER_WAIT);

            } else {
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Character is blocked from moving");
                  CharacterScheduleEntry *newEntry = res.charSchedules().getEntry(RETURN_SUPPORT_ID);
                  assert(newEntry);
                  
                  // Increment the blocked state 
                  h.setBlockedState((BlockedState) ((int) h.blockedState() + 1));
                  if (!h.blockedFlag()) {
                        // Not already handling blocked, so add a new dummy action so that the new
                        // action set below will not replace the existing one
                        h.currentActions().addFront(DISPATCH_ACTION, 0);
                        h.setBlockedFlag(true);
                  }

                  // Set the current action
                  CurrentActionEntry &entry = h.currentActions().top();
                  entry.setAction(DISPATCH_ACTION);
                  entry.setSupportData(newEntry);
                  entry.setRoomNumber(h.roomNumber());
            } 
            
            // If the top action is now walking, deliberately fall through to the case entry;
            // otherwise break out to exit method
            if (h.currentActions().isEmpty() || h.currentActions().top().action() != WALKING)
                  break;

      case WALKING:
            // The character is currently moving
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character walking");
            h.setOccupied(false);  

            // If the character is walking to an exit hotspot, make sure it's still open
            if ((h.destHotspotId() != 0) && (h.destHotspotId() != 0xffff)) {
                  // Player is walking to a room exit hotspot
                  RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
                  if (joinRec->blocked) {
                        // Exit now blocked, so stop walking
                        actions.top().setAction(DISPATCH_ACTION);
                        h.setOccupied(true);
                        break;
                  }
            }

            if (res.pausedList().check(h.hotspotId(), numImpinging, impingingList) == 0) {
                  if (h.walkingStep()) 
                        // Walking done
                        h.currentActions().top().setAction(DISPATCH_ACTION);
                   
                  if (h.destHotspotId() != 0) {
                        // Walking to an exit, check for any required room change
                        if (Support::checkRoomChange(h))
                              break;
                  }
            }

            h.setOccupied(true);
            break;
      }
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 7");
}

void HotspotTickHandlers::voiceBubbleAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      debugC(ERROR_DETAILED, kLureDebugAnimations, 
            "Voice Bubble anim handler: char = %xh, ctr = %d, char speaking ctr = %d", 
            h.hotspotId(), h.voiceCtr(), 
            res.getHotspot(res.getTalkingCharacter())->talkCountdown);

      if (h.voiceCtr() != 0) 
            h.setVoiceCtr(h.voiceCtr() - 1);

      if (h.voiceCtr() != 0) {
            // Countdown not yet ended
            HotspotData *charHotspot = res.getHotspot(res.getTalkingCharacter());
            if (charHotspot->roomNumber == h.roomNumber()) {
                  // Character is still in the same room as when it began speaking
                  if (charHotspot->talkCountdown != 0) {
                        // Character still talking
                        if (!res.checkHotspotExtent(charHotspot)) {
                              // Set voice bubble off screen to hide it
                              h.setPosition(h.x(), -100);
                        } else {
                              // Keep voice bubble in track with character
                              h.setPosition(charHotspot->startX + charHotspot->talkX + 12,
                                    charHotspot->startY + charHotspot->talkY - 18);
                        }
                        return;
                  }
            }
      }

      // End of voice time, so unload
      res.deactivateHotspot(&h);
      return;
}

void HotspotTickHandlers::puzzledAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      HotspotData *charHotspot = res.getHotspot(h.destHotspotId());
      assert(charHotspot);

      h.setVoiceCtr(h.voiceCtr() - 1);
      if ((charHotspot->roomNumber != h.roomNumber()) || (h.voiceCtr() == 0) ||
            !res.checkHotspotExtent(charHotspot)) {
            // Remove the animation
            res.deactivateHotspot(&h);
            return;
      }

      h.setPosition(charHotspot->startX + charHotspot->talkX + 12,
            charHotspot->startY + charHotspot->talkY - 20);
}

void HotspotTickHandlers::roomExitAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      Room &room = Room::getReference();

      RoomExitJoinData *rec = res.getExitJoin(h.hotspotId());
      if (!rec) return;
      RoomExitJoinStruct &rs = (rec->hotspots[0].hotspotId == h.hotspotId()) ? 
            rec->hotspots[0] : rec->hotspots[1];

      if ((rec->blocked != 0) && (rs.currentFrame != rs.destFrame)) {
            // Closing the door
            h.setOccupied(true);

            ++rs.currentFrame;
            if ((rs.currentFrame == rs.destFrame) && (h.hotspotId() == room.roomNumber()))
                  Sound.addSound(rs.closeSound);

      } else if ((rec->blocked == 0) && (rs.currentFrame != 0)) {
            // Opening the door
            h.setOccupied(false);

            --rs.currentFrame;
            if ((rs.currentFrame == rs.destFrame) && (h.hotspotId() == room.roomNumber())) {
                  Sound.addSound(rs.openSound);
                  
                  // If in the outside village, trash reverb
                  if (fields.getField(AREA_FLAG) == 1)
                        Sound.musicInterface_TrashReverb();
            }
      }

      h.setFrameNumber(rs.currentFrame);
}

void HotspotTickHandlers::playerAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      Room &room = Room::getReference();
      Mouse &mouse = Mouse::getReference();
      RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
      PathFinder &pathFinder = h.pathFinder();
      CurrentActionStack &actions = h.currentActions();
      uint16 impingingList[MAX_NUM_IMPINGING];
      int numImpinging;
      Action hsAction;
      uint16 hotspotId;
      HotspotData *hotspot;
      char buffer[MAX_DESC_SIZE];

      h.currentActions().list(buffer);
      debugC(ERROR_DETAILED, kLureDebugAnimations, 
            "Hotspot player anim handler p=(%d,%d,%d) bs=%d\n%s", 
            h.x(), h.y(), h.roomNumber(), h.blockedState(), buffer);

      h.handleTalkDialog();

      // If a frame countdown is in progress, then decrement and exit
      if (h.frameCtr() > 0) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Frame countdown = %d", h.frameCtr());
            h.decrFrameCtr();
            return;
      }

      numImpinging = Support::findIntersectingCharacters(h, impingingList);

      if (h.skipFlag()) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Skip flag set: numImpinging = %d", numImpinging);
            if (numImpinging > 0) 
                  return;
            h.setSkipFlag(false);
      }

      /* interactHotspotId never seems to be set 
      if (h.resource()->interactHotspotId != 0) {
            h.resource()->interactHotspotId = 0;
            Hotspot *hotspot = res.getActiveHotspot(h.resource()->interactHotspotId);
            assert(hotspot);
            if ((hotspot->characterMode() != CHARMODE_WAIT_FOR_INTERACT) &&
                  !actions.isEmpty())
                  actions.top().setAction(ACTION_NONE);
      }
      */

      if (h.pauseCtr() > 0) {
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Pause countdown = %d", h.pauseCtr());
            h.updateMovement();
            h.pathFinder().clear();

            if (h.pauseCtr() == 1) {
                  h.setPauseCtr(0);
                  if (h.characterMode() == 0) {
                        h.setOccupied(false);
                        return;
                  }
            } else {
                  res.pausedList().scan(h);
                  return;
            }
      }

      if ((h.characterMode() != CHARMODE_NONE) && (h.characterMode() != CHARMODE_IDLE)) {
            if (h.delayCtr() != 0) {
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Delay countdown = %d", h.delayCtr());
                  h.updateMovement();
                  h.pathFinder().clear();
                  h.setDelayCtr(h.delayCtr() - 1);
                  return;
            }

            debugC(ERROR_DETAILED, kLureDebugAnimations, "Character mode = %d", h.characterMode());
            h.setOccupied(false);
            h.setCharacterMode(CHARMODE_NONE);
            if (h.tempDest().counter != 0) {
                  // Start walking to the previously set destination
                  h.tempDest().counter = 0;
                  h.setDestPosition(h.tempDest().position.x, h.tempDest().position.y);
                  h.currentActions().addFront(START_WALKING, h.roomNumber());
                  h.setWalkFlag(false);
            }
            return;
      }

      CurrentAction action = actions.action();
      PathFinderResult pfResult;

      switch (action) {
      case NO_ACTION:
            // Make sure there is no longer any destination
            h.setDestHotspot(0);
            h.updateMovement2(CHARMODE_IDLE);
            h.doNothing(NULL);
            strcpy(room.statusLine(), "");
            break;

      case DISPATCH_ACTION:
            // Dispatch an action
            h.setDestHotspot(0);

            hotspot = NULL;
            if (actions.top().hasSupportData()) {
                  hsAction = actions.top().supportData().action();

                  if (actions.top().supportData().numParams() > 0) {
                        hotspotId = actions.top().supportData().param((hsAction == USE) ? 1 : 0);
                        hotspot = res.getHotspot(hotspotId);
                  } 
            } else {
                  hsAction = NONE;
            }

            h.doAction(hsAction, hotspot);
            break;

      case EXEC_HOTSPOT_SCRIPT:
            // A hotspot script is in progress for the player, so don't interrupt
            if (h.executeScript()) {
                  // Script is finished, so pop of the execution action
                  actions.pop();
            }
            break;

      case START_WALKING:
            // Start the player walking to the given destination
            h.setOccupied(false);  

            // Reset the path finder / walking sequence
            pathFinder.reset(paths);

            // Set current action to processing walking path
            actions.pop();
            h.currentActions().addFront(PROCESSING_PATH, h.roomNumber());
            // Deliberate fall through to processing walking path

      case PROCESSING_PATH:
            h.setCharacterMode(CHARMODE_NONE);
            res.pausedList().scan(h);

            pfResult = pathFinder.process();
            if (pfResult == PF_UNFINISHED) break;

            // Pathfinding is now complete 
            pathFinder.list(buffer);
            debugC(ERROR_DETAILED, kLureDebugAnimations, 
                  "Pathfind processing done; result=%d, walkFlag=%d\n%s", 
                  pfResult, h.walkFlag(), buffer);

            if ((pfResult != PF_OK) && (h.walkFlag() || (pfResult != PF_DEST_OCCUPIED))) { 
                  
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Blocked state checking");
                  if (h.blockedState() == BS_FINAL) {
                        res.pausedList().reset(h.hotspotId());
                        h.setBlockedState(BS_NONE);
                        h.currentActions().pop();
                        h.setCharacterMode(CHARMODE_PLAYER_WAIT);
                        h.setDelayCtr(7);
                        return;

                  } else if (h.blockedState() != BS_NONE) {
                        h.tempDest().position.x = h.destX();
                        h.tempDest().position.y = h.destY();
                        h.tempDest().counter = 1;
                        h.setBlockedState((BlockedState) ((int) h.blockedState() + 1));
                        h.setRandomDest();
                        return;
                  }
            }

            h.setCharacterMode(CHARMODE_NONE);
            h.setPosition(h.x(), h.y() & 0xFFF8);

            if (pathFinder.isEmpty()) {
                  mouse.setCursorNum(CURSOR_ARROW);
                  h.currentActions().top().setAction(DISPATCH_ACTION);
                  break;
            }

            h.currentActions().top().setAction(WALKING);
            if (mouse.getCursorNum() != CURSOR_CAMERA)
                  mouse.setCursorNum(CURSOR_ARROW);
            
            // Deliberate fall through to walking

      case WALKING:
            // The character is currently moving
            h.setOccupied(false);

            if (h.destHotspotId() != 0) {
                  RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
                  if ((joinRec != NULL) && (joinRec->blocked)) {
                        // Player is walking to a blocked room exit, so stop walking
                        actions.pop();
                        h.setOccupied(true);
                        break;
                  }
            }

            if (res.pausedList().check(PLAYER_ID, numImpinging, impingingList) == 0) {
                  if (h.walkingStep()) {
                        // Walking done
                        if (room.cursorState() == CS_BUMPED)
                              room.setCursorState(CS_NONE);
                        if (h.tempDest().counter != 0) {
                              h.setCharacterMode(CHARMODE_PLAYER_WAIT);
                              h.setDelayCtr(IDLE_COUNTDOWN_SIZE);
                              return;
                        }

                        h.currentActions().top().setAction(DISPATCH_ACTION);
                  }

                  // Check for whether need to change room
                  if (Support::checkRoomChange(h))
                        // Player changinge room - break now to avoid resetting occupied status
                        break;
            }
            h.setOccupied(true);
            break;
      }

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot player anim handler end");
}

void HotspotTickHandlers::followerAnimHandler(Hotspot &h) {
      static int countdownCtr = 0;
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      Hotspot *player = res.getActiveHotspot(PLAYER_ID);

      if ((h.resource()->tickProcId == FOLLOWER_TICK_PROC_2) || (fields.getField(37) == 0)) {
            if (h.currentActions().isEmpty() && (h.roomNumber() != player->roomNumber())) {
                  // Character in different room than player
                  if (h.hotspotId() == GOEWIN_ID) 
                        h.currentActions().addFront(DISPATCH_ACTION, player->roomNumber());
                  else {
                        // Scan through the translation list for an alternate destination room
                        const RoomTranslationRecord *p = &roomTranslations[0];
                        while ((p->srcRoom != 0) && (p->srcRoom != player->roomNumber()))
                              ++p;
                        h.currentActions().addFront(DISPATCH_ACTION, 
                              (p->srcRoom != 0) ? p->destRoom : player->roomNumber());
                  }
            }
      }

      // If some action is in progress, do standard handling
      if (h.characterMode() != CHARMODE_IDLE) {
            standardCharacterAnimHandler(h);
            return;
      }

      // Handle any pause countdown
      if (countdownCtr > 0) {
            --countdownCtr;
            standardCharacterAnimHandler(h);
            return;
      }

      // Handle selecting a random action for the character to do
      RandomActionSet *set = res.randomActions().getRoom(h.roomNumber());
      if (!set) {
            standardCharacterAnimHandler(h);
            return;
      }

      Common::RandomSource rnd;
      RandomActionType actionType;
      uint16 scheduleId;
      g_system->getEventManager()->registerRandomSource(rnd, "lureHotspots");

      int actionIndex = rnd.getRandomNumber(set->numActions() - 1);
      set->getEntry(actionIndex, actionType, scheduleId);

      if (actionType == REPEAT_ONCE_DONE) {
            // Repeat once random action that's already done, so don't repeat it
            standardCharacterAnimHandler(h);
            return;
      }

      // For repeat once actions, make sure the character is in the same room as the player
      if (actionType == REPEAT_ONCE) {
            if (player->roomNumber() != h.roomNumber()) {
                  // Not in the same room, so don't do the action
                  standardCharacterAnimHandler(h);
                  return;
            }
            
            // Flag the action as having been done, so it won't be repeated
            set->setDone(actionIndex);
      }

      if (scheduleId == 0) {
            // No special schedule to perform, so simply set a random action
            h.setRandomDest();
      } else {
            // Prepare the follower to standard the specified schedule
            CharacterScheduleEntry *newEntry = res.charSchedules().getEntry(scheduleId);
            assert(newEntry);
            h.currentActions().addFront(DISPATCH_ACTION, newEntry, h.roomNumber());

            // Set a random delay before beginning the action
            countdownCtr = rnd.getRandomNumber(32);
      }

      standardCharacterAnimHandler(h);
}

void HotspotTickHandlers::jailorAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();
      Game &game = Game::getReference();
      HotspotData *player = res.getHotspot(PLAYER_ID);

      if ((fields.getField(11) != 0) || (h.hotspotId() == CASTLE_SKORL_ID)) {
            if (!h.skipFlag() && !game.preloadFlag() && (h.roomNumber() == player->roomNumber)) {
                  if (Support::charactersIntersecting(h.resource(), player)) {
                        // Skorl has caught the player
                        Game::getReference().setState(GS_RESTORE_RESTART | GS_CAUGHT);
                  }
            }
      }

      standardCharacterAnimHandler(h);
}

void HotspotTickHandlers::sonicRatAnimHandler(Hotspot &h) {
      if (h.actionCtr() == 0) {
            HotspotData *player = Resources::getReference().getHotspot(PLAYER_ID);
            if (Support::charactersIntersecting(h.resource(), player))
                  h.setActionCtr(1);
      } else {
            standardAnimHandler(h);
      }
}

void HotspotTickHandlers::droppingTorchAnimHandler(Hotspot &h) {
      if (h.frameCtr() > 0) 
            h.setFrameCtr(h.frameCtr() - 1);
      else {
            bool result = h.executeScript();
            if (result) {
                  // Changeover to the fire on the straw
                  Resources &res = Resources::getReference();
                  res.deactivateHotspot(h.hotspotId());
                  res.activateHotspot(0x41C);

                  // Add sound
                  Sound.addSound(8);                  

                  // Enable the fire and activate its animation
                  HotspotData *fire = res.getHotspot(0x418);
                  fire->flags |= 0x80;
                  fire->loadOffset = 4;
                  res.activateHotspot(0x418);
            }
      }
}

void HotspotTickHandlers::playerSewerExitAnimHandler(Hotspot &h) {
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
      } else if (h.executeScript()) {
            Resources &res = Resources::getReference();

            // Deactive the dropping animation
            h.setLayer(0);
            res.deactivateHotspot(h.hotspotId());

            // Position the player
            Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
            playerHotspot->setPosition(FULL_SCREEN_WIDTH / 2, (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE) / 2);
            playerHotspot->setDirection(DOWN);
            playerHotspot->setCharacterMode(CHARMODE_NONE);

            // Setup Ratpouch
            Hotspot *ratpouchHotspot = res.getActiveHotspot(RATPOUCH_ID);
            assert(ratpouchHotspot);
            ratpouchHotspot->setCharacterMode(CHARMODE_NONE);
            ratpouchHotspot->setDelayCtr(0);
            ratpouchHotspot->setActions(0x821C00);

            // Ratpouch has previously been moved to room 8. Start him moving to room 7
            ratpouchHotspot->currentActions().clear();
            ratpouchHotspot->currentActions().addFront(DISPATCH_ACTION, 7);
      }
}

void HotspotTickHandlers::fireAnimHandler(Hotspot &h) {
      standardAnimHandler(h);
      h.setOccupied(true);
}

void HotspotTickHandlers::sparkleAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      Hotspot *player = res.getActiveHotspot(PLAYER_ID);
      ValueTableData &fields = res.fieldList();

      h.setRoomNumber(player->roomNumber());
      h.setPosition(player->x() - 14, player->y() - 10);
      h.setActionCtr(h.actionCtr() + 1);
      if (h.actionCtr() == 6) {
            int animIndex;
            if ((fields.getField(11) == 2) || (fields.getField(28) != 0)) {
                  fields.setField(28, 0);
                  animIndex = PLAYER_ANIM_INDEX;
            } else {
                  fields.setField(28, fields.getField(28) + 1);
                  animIndex = SELENA_ANIM_INDEX;
            }

            player->setAnimationIndex(animIndex);
      }

      if (h.executeScript()) {
            HotspotData *data = h.resource();
            res.deactivateHotspot(&h);
            data->roomNumber = 0x1A8;

            if (fields.getField(28) != 0) {
                  Hotspot *ratpouch = res.getActiveHotspot(RATPOUCH_ID);
                  assert(ratpouch);
                  ratpouch->converse(NOONE_ID, 0x854, false);

                  uint16 dataId = res.getCharOffset(4);
                  CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);

                  ratpouch->currentActions().addFront(DISPATCH_ACTION, entry, ratpouch->roomNumber());
                  ratpouch->setActionCtr(0);
            }
      }
}

void HotspotTickHandlers::teaAnimHandler(Hotspot &h) {
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
            return;
      }

      if (h.executeScript()) {
            // Signal that the tea is done
            h.setHotspotScript(0xB82);
            Resources::getReference().fieldList().setField(27, 1);
      }
}

void HotspotTickHandlers::goewinCaptiveAnimHandler(Hotspot &h) {
      if (h.actionCtr() > 0) {
            if (h.executeScript()) {
                  h.setTickProc(STANDARD_CHARACTER_TICK_PROC);
                  h.setActionCtr(0);
            }
      }
}

void HotspotTickHandlers::prisonerAnimHandler(Hotspot &h) {
      ValueTableData &fields = Resources::getReference().fieldList();
      Common::RandomSource rnd;

      g_system->getEventManager()->registerRandomSource(rnd, "lureHotspots");

      h.handleTalkDialog();
      if (h.frameCtr() > 0) {
            h.setFrameCtr(h.frameCtr() - 1);
            return;
      }

      if (h.actionCtr() != 0) {
            if (h.executeScript() == 0) {
                  h.setActionCtr(0);
                  h.setHotspotScript(0x3E0);
            }
            return;
      }

      if ((fields.getField(PRISONER_DEAD) == 0) && (rnd.getRandomNumber(65536) >= 6)) {
            h.setActionCtr(1);
            h.setHotspotScript(0x3F6);
      }
}

void HotspotTickHandlers::catrionaAnimHandler(Hotspot &h) {
      h.handleTalkDialog();
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
      } else {
            h.executeScript();
            int delayVal = (h.actionCtr() == 0) ? 5 : h.actionCtr();
            h.setFrameCtr(delayVal);
      }
}

void HotspotTickHandlers::morkusAnimHandler(Hotspot &h) {
      h.handleTalkDialog();
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
            return;
      }

      if (h.executeScript()) {
            // Script is done - set new script to one of two alternates randomly
            Common::RandomSource rnd;
            g_system->getEventManager()->registerRandomSource(rnd, "lureHotspots");

            h.setHotspotScript(rnd.getRandomNumber(100) >= 50 ? 0x54 : 0); 
            h.setFrameCtr(20 + rnd.getRandomNumber(63));
      }
}

// Special variables used across multiple calls to talkAnimHandler
static TalkEntryData *_talkResponse;
static uint16 talkDestCharacter;

void HotspotTickHandlers::talkAnimHandler(Hotspot &h) {
      // Talk handler
      Resources &res = Resources::getReference();
      StringData &strings = StringData::getReference();
      Screen &screen = Screen::getReference();
      Room &room = Room::getReference();
      Mouse &mouse = Mouse::getReference();
      TalkSelections &talkSelections = res.getTalkSelections();
      TalkData *data = res.getTalkData();
      TalkEntryList &entries = data->entries; 
      Hotspot *charHotspot;
      char buffer[MAX_DESC_SIZE];
      Rect r;
      int lineNum, numLines;
      int selectedLine, responseNumber;
      bool showSelections, keepTalkingFlag;
      TalkEntryList::iterator i;
      TalkEntryData *entry;
      uint16 result, descId;

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk anim handler state = %d", res.getTalkState());
      switch (res.getTalkState()) {
      case TALK_NONE:
            talkDestCharacter = h.resource()->talkDestCharacterId;
            assert(talkDestCharacter != 0);

            // Make sure any other dialog is finished before we start talking
            if (room.isDialogShowing())
                  return;

            // Fall through to TALK_START

      case TALK_START:
            // Handle initial setup of talking options
            // Reset talk entry pointer list
            for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum)
                  talkSelections[lineNum] = NULL;
            
            // Loop through list to find entries to display
            _talkResponse = NULL;
            numLines = 0;
            showSelections = false;

            i = entries.begin();
            for (lineNum = 0; lineNum < res.getTalkStartEntry(); ++lineNum)
                  if (i != entries.end()) ++i;

            for (; i != entries.end(); ++i) {
                  entry = *i;
                  uint8 flags = (uint8) (entry->descId >> 14);
                  if (flags == 3) 
                        // Skip the entry
                        continue;

                  uint16 sequenceOffset = entry->preSequenceId & 0x3fff;
                  bool showLine = sequenceOffset == 0;
                  if (!showLine) {
                        debugC(ERROR_DETAILED, kLureDebugAnimations, 
                              "Checking whether to display line: script=%xh, descId=%d",
                              sequenceOffset, entry->descId);
                        showLine = Script::execute(sequenceOffset) != 0;
                  }

                  if (showLine) {
                        talkSelections[numLines++] = entry;
                        showSelections |= (entry->descId & 0x3fff) != TALK_MAGIC_ID;
                  }

                  if ((entry->preSequenceId & 0x8000) != 0) break;
            }

            if (showSelections && (numLines > 1)) {
                  res.setTalkState(TALK_SELECT);

                  // Make sure the dest character holds still while an option is selected
                  //HotspotData *destHotspot = res.getHotspot(talkDestCharacter);
                  //destHotspot->talkerId = h.hotspotId();
            } else {
                  res.setTalkState(TALK_RESPOND);
                  res.setTalkSelection(1);
            }
            break;

      case TALK_SELECT:
            r.left = 0; r.right = FULL_SCREEN_WIDTH - 1;
            selectedLine = mouse.y() / MENUBAR_Y_SIZE;
            if ((selectedLine > MAX_TALK_SELECTIONS) || ((selectedLine != 0) && 
                  !talkSelections[selectedLine-1]))
                  selectedLine = 0;

            for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum) {
                  if (!talkSelections[lineNum]) break;
                  entry = talkSelections[lineNum];

                  strings.getString(entry->descId & 0x3fff, buffer);

                  // Clear line
                  r.top = (lineNum + 1) * MENUBAR_Y_SIZE;
                  r.bottom = r.top + MENUBAR_Y_SIZE - 1;
                  screen.screen().fillRect(r, 0);

                  // Display line
                  byte colour = (lineNum+1 == selectedLine) ?
                        DIALOG_WHITE_COLOUR : DIALOG_TEXT_COLOUR;
                  screen.screen().writeString(r.left, r.top, buffer, false, colour);
            }

            if ((!mouse.lButton() && !mouse.rButton()) || (selectedLine == 0))
                  break;

            // Set the talk response index to use
            res.setTalkSelection(selectedLine);
            res.setTalkState(TALK_RESPOND);
            break;

      case TALK_RESPOND:
            // Handle initial response to show the question in a talk dialog if needed

            if (h.resource()->talkCountdown != 0) {
                  // Current talk dialog already pending needs to finish
                  h.handleTalkDialog();
                  return;
            }

            // Get the original question for display
            selectedLine = res.getTalkSelection();
            entry = talkSelections[selectedLine-1];
            descId = entry->descId & 0x3fff;
            entry->descId |= 0x4000;
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk line set: line=#%d, desc=%xh",
                  selectedLine, descId);

            // Get the response the destination character will say
            if (descId != TALK_MAGIC_ID) {
                  // Set up to display the question and response in talk dialogs
                  h.converse(talkDestCharacter, descId);
                  res.setTalkState(TALK_RESPOND_2);
            } else {
                  res.setTalkState(TALK_RESPOND_3);
            }
            break;

      case TALK_RESPOND_2:
            // Wait until the question dialog is no longer active
            h.handleTalkDialog();
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk dialog countdown %d", 
                  h.resource()->talkCountdown);

            if (res.getTalkingCharacter() != 0)
                  return;

      case TALK_RESPOND_3:
            // Respond
            selectedLine = res.getTalkSelection();
            entry = talkSelections[selectedLine-1];

            responseNumber = entry->postSequenceId;
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Post sequence Id = %xh", responseNumber);

            if ((responseNumber & 0x8000) != 0) {
                  responseNumber = Script::execute(responseNumber & 0x7fff);
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Post sequence Id = %xh", responseNumber);
            }

            do {
                  _talkResponse = res.getTalkData()->getResponse(responseNumber);
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response pre id = %xh",
                        _talkResponse->preSequenceId);

                  if (!_talkResponse->preSequenceId) break;
                  responseNumber = Script::execute(_talkResponse->preSequenceId);
                  debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response new response = %d",
                        responseNumber);
            } while (responseNumber != TALK_RESPONSE_MAGIC_ID);

            descId = _talkResponse->descId;
            if ((descId & 0x8000) != 0)
                  descId = Script::execute(descId & 0x7fff);

            if (descId != TALK_MAGIC_ID) {
                  charHotspot = res.getActiveHotspot(talkDestCharacter);

                  if (charHotspot != NULL)
                        charHotspot->converse(PLAYER_ID, descId, true);
            } 
            res.setTalkState(TALK_RESPONSE_WAIT);
            break;

      case TALK_RESPONSE_WAIT:
            // Wait until the character's response has finished being displayed
            h.handleTalkDialog();

            charHotspot = res.getActiveHotspot(talkDestCharacter);
            assert(charHotspot);
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk dialog countdown %d", 
                  (charHotspot) ? charHotspot->resource()->talkCountdown : 0);

            if ((charHotspot->resource()->talkCountdown > 0) || (res.getTalkingCharacter() != 0))
                  return;

            result = _talkResponse->postSequenceId;
            debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response post id = %xh", result);

            if (result == 0xffff)
                  keepTalkingFlag = false;
            else {
                  if ((result & 0x8000) == 0) 
                        keepTalkingFlag = true;
                  else {
                        result = Script::execute(result & 0x7fff);
                        keepTalkingFlag = result != 0xffff;
                  }
            }

            debugC(ERROR_DETAILED, kLureDebugAnimations, "Keep Talking flag = %d", keepTalkingFlag);

            if (keepTalkingFlag) {
                  // Reset for loading the next set of talking options
                  res.setTalkStartEntry(result);
                  res.setTalkState(TALK_START);
            } else {
                  // End the conversation
                  res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID);
                  if (charHotspot)
                  {
                        charHotspot->setUseHotspotId(0);
                        charHotspot->resource()->talkerId = 0;
                        charHotspot->setDelayCtr(24);
                  }
                  res.setTalkData(0);
                  res.setCurrentAction(NONE);
                  res.setTalkState(TALK_NONE);
            }
            break;
      }
}

void HotspotTickHandlers::fighterAnimHandler(Hotspot &h) {
      Fights.fighterAnimHandler(h);
}

void HotspotTickHandlers::playerFightAnimHandler(Hotspot &h) {
      Fights.playerAnimHandler(h);
}

void HotspotTickHandlers::grubAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      h.handleTalkDialog();

      Hotspot *character = res.getActiveHotspot(PLAYER_ID);
      uint16 frameNumber = 0;

      if (character->y() < 79) {
            // If player is behind Grub, use Ratpouch if possible
            Hotspot *ratpouch = res.getActiveHotspot(RATPOUCH_ID);
            if ((ratpouch != NULL) && (ratpouch->roomNumber() == h.roomNumber()))
                  character = ratpouch;
      }

      if (character->x() < 72) frameNumber = 0;
      else if (character->x() < 172) frameNumber = 1;
      else frameNumber = 2;

      h.setActionCtr(frameNumber);
      h.setFrameNumber(frameNumber);
}

void HotspotTickHandlers::barmanAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      Room &room = Room::getReference();
      BarEntry &barEntry = res.barmanLists().getDetails(h.roomNumber());
      Common::RandomSource rnd;
      static bool ewanXOffset = false;

      g_system->getEventManager()->registerRandomSource(rnd, "lureHotspots");

      h.handleTalkDialog();
      if (h.delayCtr() > 0) {
            h.setDelayCtr(h.delayCtr() - 1);
            return;
      }
      
      if (h.frameCtr() == 0) {
            // Barman not currently doing something
            if (barEntry.currentCustomer != NULL) {
                  // A customer has been set to be served
                  Hotspot *servee = res.getActiveHotspot(barEntry.currentCustomer->hotspotId);
                  if (servee != NULL) {
                        // Check whether the character is still at the bar
                        if ((servee->y() + servee->heightCopy()) >= ((barEntry.gridLine << 3) + 24)) {
                              // Customer has left - nullify their entry
                              barEntry.currentCustomer->hotspotId = 0;
                              barEntry.currentCustomer->serveFlags = 0;
                              barEntry.currentCustomer = NULL;
                        }
                        else if (servee->hotspotId() != PLAYER_ID) {
                              // Any other NPC character, so serve them
                              barEntry.currentCustomer->serveFlags = 0;
                        } else {
                              // Servee is the player, flag to stop the barman until the player walks away
                              barEntry.currentCustomer->serveFlags &= 0x7f;

                              if ((barEntry.currentCustomer->serveFlags & 7) != 0) {
                                    // Barman needs to do something
                                    h.setFrameCtr(barEntry.currentCustomer->serveFlags);
                                    barEntry.currentCustomer->serveFlags &= 0xf8;

                              } else if (h.resource()->talkerId == 0) {
                                    // Barman is not currently being talked to
                                    // Clear entry from list
                                    barEntry.currentCustomer->hotspotId = 0;
                                    barEntry.currentCustomer->serveFlags = 0;
                                    barEntry.currentCustomer = NULL;
                                    // Set the barman to polish the bar
                                    h.setFrameCtr(2);
                              }
                        }
                        
                        return;
                  }
            }

            // Check for any customer waiting to be served
            for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
                  if ((barEntry.customers[index].serveFlags & 0x80) != 0) {
                        // Found one to serve
                        barEntry.customers[index].serveFlags = 0;
                        barEntry.currentCustomer = &barEntry.customers[index];
                        Hotspot *hotspot = res.getActiveHotspot(barEntry.customers[index].hotspotId);
                        assert(hotspot);
                        h.setSupportValue(hotspot->x());    // Save the position to move to
                        h.setFrameCtr(0x80);                      // Flag for movement
                        return;
                  }
            }

            // At this point, no customers need servering. Empty the table
            barEntry.currentCustomer = NULL;
            for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
                  barEntry.customers[index].hotspotId = 0;
                  barEntry.customers[index].serveFlags = 0;
            }

            // Choose a random action for the barman to do - walk around, polish the bar, or wait
            h.setFrameCtr(rnd.getRandomNumber(2) + 1);
      }

      // At this point the barman is known to be doing something

      if ((h.frameCtr() & 0x80) != 0)     {
            // Bit 7 being set indicates the barman is moving to a destination
            int16 xDiff = h.x() - h.supportValue();
            if (ABS(xDiff) >= 2) {
                  // Keep the barman moving
                  if (xDiff > 0) {
                        // Moving left
                        h.setPosition(h.x() - 2, h.y());
                        h.setActionCtr(h.actionCtr() + 1);
                        if ((h.actionCtr() >= 12) || (h.actionCtr() < 6))
                              h.setActionCtr(6);
                  } else {
                        // Moving right
                        h.setPosition(h.x() + 2, h.y());
                        h.setActionCtr(h.actionCtr() + 1);
                        if (h.actionCtr() >= 6) h.setActionCtr(0);
                  }
            } else {
                  // Stop the barman moving
                  h.setActionCtr(12);
                  h.setFrameCtr(h.frameCtr() & 0x7f);
            }

            h.setFrameNumber(h.actionCtr());
            return;
      }

      // All other actions
      uint16 xp, id;
      const uint16 *frameList;
      uint16 frameNumber;

      BarmanAction action = (BarmanAction) (h.frameCtr() & 0x3F);
      switch (action) {
      case WALK_AROUND:
            // Wander around between the ends of the bar
            if (h.hotspotId() == EWAN_ID)
                  xp = rnd.getRandomNumber(51) + 94;
            else
                  xp = rnd.getRandomNumber(85) + 117;  

            h.setSupportValue(xp);
            h.setFrameCtr(0x83);
            return;

      case POLISH_BAR:
      case SERVE_BEER:
            if (action == SERVE_BEER) {
                  // Serving a beer
                  if ((h.frameCtr() & 0x40) == 0)
                        h.setSupportValue(h.resource()->flags2);

            } else {
                  // Polishing the bar
                  if ((h.frameCtr() & 0x40) == 0) {
                        // New polish beginning
                        id = BG_RANDOM << 8;

                        if (h.hotspotId() == EWAN_ID) {
                              HotspotData *player = res.getHotspot(PLAYER_ID);
                              HotspotData *gwyn = res.getHotspot(GOEWIN_ID);
                              HotspotData *wayne = res.getHotspot(WAYNE_ID);

                              if ((player->roomNumber != 35) && (gwyn->roomNumber != 35) && (wayne->roomNumber != 35)) {
                                    if (rnd.getRandomNumber(1) == 1)
                                          id = BG_EXTRA1 << 8;
                                    else {
                                          // Set up alternate animation
                                          h.setWidth(32);
                                          h.setAnimationIndex(EWAN_ALT_ANIM_INDEX);
                                          ewanXOffset = true;
                                          h.setPosition(h.x() - 8, h.y());
                                          id = BG_EXTRA2 << 8;
                                    }
                              }
                        }

                        h.setSupportValue(id);
                  }
            }

            // At this point, either a polish or a beer serve is in progress
            h.setFrameCtr(h.frameCtr() | 0x40);
            h.setSupportValue(h.supportValue() + 1);  // Move to next frame
            frameList = barEntry.graphics[h.supportValue() >> 8];
            frameNumber = frameList[h.supportValue() & 0xff];

            if (frameNumber != 0) {
                  h.setActionCtr(frameNumber);
                  h.setFrameNumber(frameNumber);
                  return;
            }

            if (h.hotspotId() == EWAN_ID) {
                  // Make sure Ewan is back to his standard animation
                  h.setWidth(16);
                  h.setAnimationIndex(EWAN_ANIM_INDEX);
                  
                  if (ewanXOffset) {
                        h.setPosition(h.x() + 8, h.y());
                        ewanXOffset = false;
                  }
            }
            break;

      case WAIT_DIALOG:
            if (room.isDialogActive()) {
                  h.setFrameNumber(h.actionCtr());
                  return;
            }
            break;

      case WAIT:
            // Immediate break, since the code outside the switch handles stopping the barman
            break;
      }

      // If this point is reached, then the barman should stop whatever he's doing
      if (action != WAIT_DIALOG)
            h.setDelayCtr(10);
      h.setFrameCtr(0);
      h.setActionCtr(12);
      h.setFrameNumber(h.actionCtr());
}

void HotspotTickHandlers::skorlAnimHandler(Hotspot &h) {
      h.handleTalkDialog();
      h.setFrameNumber(h.actionCtr());
}

void HotspotTickHandlers::gargoyleAnimHandler(Hotspot &h) {
      h.handleTalkDialog();
}

void HotspotTickHandlers::goewinShopAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      ValueTableData &fields = res.fieldList();

      h.resource()->actionHotspotId = 0;
      h.setCharacterMode(CHARMODE_WAIT_FOR_INTERACT);

      h.handleTalkDialog();
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
            return;
      }

      h.executeScript();

      if (h.delayCtr() > 0) {
            h.setDelayCtr(h.delayCtr() - 1);

            if (h.delayCtr() == 0) {
                  Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
                  uint16 talkIndex = fields.getField(TALK_INDEX);

                  if ((talkIndex == 12) || (talkIndex == 13) || (talkIndex == 14) ||
                        (playerHotspot->roomNumber() == 34))
                        h.setDelayCtr(1500);
                  else 
                        Script::normalGoewin(0, 0, 0);
            }
      }
}

void HotspotTickHandlers::skullAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();
      uint16 doorId = 0x272E;
      if ((h.hotspotId() == 0x42E) || (h.hotspotId() == 0x431) || (h.hotspotId() == 0x432))
            doorId = 0x272A;
      else if ((h.hotspotId() == 0x42f) || (h.hotspotId() == 0x433))
            doorId = 0x272C;

      RoomExitJoinData *joinRec = res.getExitJoin(doorId);
      if ((h.hotspotId() == 0x42E) || (h.hotspotId() == 0x42F)) {
            h.setFrameNumber(joinRec->blocked ? 0 : 1);
      } else {
            h.setFrameNumber(joinRec->blocked ? 1 : 0);
      }
}

void HotspotTickHandlers::dragonFireAnimHandler(Hotspot &h) {
      if (h.executeScript()) 
            // Script is finished - player is dead
            Game::getReference().setState(GS_RESTORE_RESTART);
}

void HotspotTickHandlers::castleSkorlAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();

      h.handleTalkDialog();
      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
            return;
      }

      if (h.executeScript()) {
            HotspotData *hotspot = res.getHotspot(h.hotspotId());
            assert(hotspot);
            res.deactivateHotspot(hotspot->hotspotId);
            hotspot->roomNumber = 0xffff;
            hotspot->layer = 255;
            hotspot->talkCountdown = 0;
            hotspot->flags |= HOTSPOTFLAG_MENU_EXCLUSION;

            hotspot = res.getHotspot(CASTLE_SKORL_ID);
            hotspot->roomNumber = 45;
            res.activateHotspot(CASTLE_SKORL_ID);
      }
}

void HotspotTickHandlers::rackSerfAnimHandler(Hotspot &h) {
      Resources &res = Resources::getReference();

      // Handle any talking
      h.handleTalkDialog();

      if (h.frameCtr() > 0) {
            h.decrFrameCtr();
            return;
      }

      switch (h.actionCtr()) {
      case 1:
            h.setHotspotScript(RACK_SERF_SCRIPT_ID_1);
            h.setActionCtr(2);
            break;

      case 2:
            if (HotspotScript::execute(&h))
                  h.setActionCtr(0);
            break;

      case 3:
            h.setHotspotScript(RACK_SERF_SCRIPT_ID_2);
            h.setActionCtr(4);
            h.setLayer(2);

      case 4:
            if (HotspotScript::execute(&h)) {
                  h.setLayer(255);
                  res.deactivateHotspot(h.hotspotId());

                  HotspotData *ratpouchData = res.getHotspot(RATPOUCH_ID);
                  ratpouchData->roomNumber = 4;
                  Hotspot *newHotspot = res.activateHotspot(RATPOUCH_ID);
                  newHotspot->converse(PLAYER_ID, 0x9C, true);
            }
            break;

      default:
            break;
      }
}

/*-------------------------------------------------------------------------*/

// support method for the standard character tick proc routine - it gets called
// when the character is in the wrong room designated for an action, and is
// responsible for starting the character walking to the correct exit

void HotspotTickHandlers::npcRoomChange(Hotspot &h) {
      Resources &res = Resources::getReference();

      // Increment number of times an exit has been attempted
      h.setExitCtr(h.exitCtr() + 1);
      if (h.exitCtr() >= 5) {
            // Failed to exit room too many times
            h.setExitCtr(0);
            if (h.currentActions().size() > 1) {
                  // Pending items on stack
                  if (h.startRoomNumber() != 0) {
                        if (h.currentActions().top().supportData().id() != RETURN_SUPPORT_ID) {
                              h.currentActions().top().supportData().setDetails(RETURN, 0);
                        }
                  }
                  h.currentActions().top().setRoomNumber(h.roomNumber());

            } else if ((h.blockedOffset() != 0) && (h.blockedOffset() != 0xffff)) {
                  // Only current action on stack - and there is a block handler 
                  CharacterScheduleEntry *entry = res.charSchedules().getEntry(h.blockedOffset());
                  h.currentActions().top().setSupportData(entry);
                  h.currentActions().top().setRoomNumber(h.roomNumber());
            }

            return;
      }

      // Get room exit coordinates
      RoomExitCoordinateData &exitData = res.coordinateList().getEntry(
            h.roomNumber()).getData(h.currentActions().top().roomNumber());

      if (h.hotspotId() != RATPOUCH_ID) {
            // Count up the number of characters in the room
            HotspotList &list = res.activeHotspots();
            HotspotList::iterator i;
            int numCharacters = 0;

            for (i = list.begin(); i != list.end(); ++i) {
                  if ((h.roomNumber() == (exitData.roomNumber & 0xff)) && (h.layer() != 0) &&
                        (h.hotspotId() >= PLAYER_ID) && (h.hotspotId() < FIRST_NONCHARACTER_ID)) 
                        ++numCharacters;
            }

            if (numCharacters >= 4) {
                  uint16 dataId = res.getCharOffset(0);
                  CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
                  h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
                  
                  return;
            }
      }

      h.setDestPosition(exitData.x, exitData.y);
      h.setDestHotspot(res.exitHotspots().getHotspot(h.roomNumber(), exitData.hotspotIndexId));

      if (h.destHotspotId() != 0xffff) {
            RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());

            if (joinRec->blocked) {
                  // The room exit is blocked - so add an opening action
                  h.currentActions().addFront(OPEN, h.roomNumber(), h.destHotspotId(), 0);
                  h.setBlockedFlag(false);
                  return;
            }
      }

      // No exit hotspot, or it has one that's not blocked. So start the walking
      h.currentActions().top().setAction(START_WALKING);  
      h.setWalkFlag(true);
}

/*-------------------------------------------------------------------------*/
/* Miscellaneous classes                                                   */
/*                                                                         */
/*-------------------------------------------------------------------------*/

// WalkingActionEntry class

// This method performs rounding of the number of steps depending on direciton

int WalkingActionEntry::numSteps() {
      switch (_direction) {
      case UP:
      case DOWN:
            return (_numSteps + 1) >> 1;

      case LEFT:
      case RIGHT:
            return (_numSteps + 3) >> 2;
      default:
            return 0;
      }
}

// PathFinder class

PathFinder::PathFinder(Hotspot *h) { 
      _hotspot = h;
      _inUse = false;
      _list.clear(); 
      _stepCtr = 0; 
}

void PathFinder::clear() {
      _stepCtr = 0;
      _list.clear();
      _inProgress = false;
      _countdownCtr = PATHFIND_COUNTDOWN;
}

void PathFinder::reset(RoomPathsData &src) {
      clear();
      src.decompress(_layer, _hotspot->widthCopy());
      _inUse = true;
}

// Does the next stage of processing to figure out a path to take to a given
// destination. Returns true if the path finding has been completed

PathFinderResult PathFinder::process() {
      bool returnFlag = _inProgress;
      // Check whether the pathfinding can be broken by the countdown counter
      bool breakFlag = (PATHFIND_COUNTDOWN != 0);
      _countdownCtr = PATHFIND_COUNTDOWN;
      int v;
      uint16 *pTemp;
      bool scanFlag = false;
      Direction currDirection = NO_DIRECTION;
      Direction newDirection;
      uint16 numSteps = 0, savedSteps = 0;
      bool altFlag;
      uint16 *pCurrent;
      PathFinderResult result = PF_UNFINISHED;

      if (!_inProgress) {
            // Following code only done during first call to method
            _inProgress = true;
            initVars();

            _xCurrent >>= 3; _yCurrent >>= 3;
            _xDestCurrent >>= 3; _yDestCurrent >>= 3;
            if ((_xCurrent == _xDestCurrent) && (_yCurrent == _yDestCurrent)) {
                  // Very close move
                  if (_xDestPos > 0) 
                        add(RIGHT, _xDestPos);
                  else if (_xDestPos < 0) 
                        add(LEFT, -_xDestPos);

                  _inProgress = false;
                  result = PF_OK;
                  goto final_step;
            }

            // Path finding

            _destX >>= 3;
            _destY >>= 3;
            _pSrc = &_layer[(_yCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xCurrent];
            _pDest = &_layer[(_yDestCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xDestCurrent];

            // Flag starting/ending cells
            *_pSrc = 1;
            _destOccupied = *_pDest != 0;
            result = _destOccupied ? PF_DEST_OCCUPIED : PF_OK;
            *_pDest = 0;

            // Set up the current pointer, adjusting away from edges if necessary 

            if (_xCurrent >= _xDestCurrent) {
                  _xChangeInc = -1;
                  _xChangeStart = ROOM_PATHS_WIDTH;
            } else {
                  _xChangeInc = 1;
                  _xChangeStart = 1;
            }

            if (_yCurrent >= _yDestCurrent) {
                  _yChangeInc = -1;
                  _yChangeStart = ROOM_PATHS_HEIGHT;
            } else {
                  _yChangeInc = 1;
                  _yChangeStart = 1;
            }
      }

      // Major loop to populate data
      _cellPopulated = false;

      while (1) {
            // Loop through to process cells in the given area
            if (!returnFlag) _yCtr = 0;
            while (returnFlag || (_yCtr < ROOM_PATHS_HEIGHT)) {
                  if (!returnFlag) _xCtr = 0;

                  while (returnFlag || (_xCtr < ROOM_PATHS_WIDTH)) {
                        if (!returnFlag) {
                              processCell(&_layer[(_yChangeStart + _yCtr * _yChangeInc) * DECODED_PATHS_WIDTH +
                                    (_xChangeStart + _xCtr * _xChangeInc)]);
                              if (breakFlag && (_countdownCtr <= 0)) return PF_UNFINISHED;
                        } else {
                              returnFlag = false;
                        }
                        ++_xCtr;
                  }
                  ++_yCtr;
            }

            // If the destination cell has been filled in, then break out of loop
            if (*_pDest != 0) break;

            if (_cellPopulated) {
                  // At least one cell populated, so go repeat loop
                  _cellPopulated = false;
            } else {
                  result = PF_PART_PATH;
                  scanFlag = true;
                  break;
            }
      }
      _inProgress = false;

      if (scanFlag || _destOccupied) {
            // Adjust the end point if necessary to stop character walking into occupied area

            // Restore destination's occupied state if necessary
            if (_destOccupied) {
                  *_pDest = 0xffff;
                  _destOccupied = false;
            }

            // Scan through lines
            v = 0xff;
            pTemp = _pDest;
            scanLine(_destX, -1, pTemp, v);
            scanLine(ROOM_PATHS_WIDTH - _destX, 1, pTemp, v);
            scanLine(_destY, -DECODED_PATHS_WIDTH, pTemp, v);
            scanLine(ROOM_PATHS_HEIGHT - _destY, DECODED_PATHS_WIDTH, pTemp, v); 

            if (pTemp == _pDest) {
                  clear();
                  return PF_NO_WALK;
            }

            _pDest = pTemp;
      }

      // ****DEBUG****
      if (_hotspot->hotspotId() == PLAYER_ID) {
            for (int ctr = 0; ctr < DECODED_PATHS_WIDTH * DECODED_PATHS_HEIGHT; ++ctr)
                  Room::getReference().tempLayer[ctr] = _layer[ctr];
      }

      // Determine the walk path by working backwards from the destination, adding in the 
      // walking steps in reverse order until source is reached
      int stageCtr;
      for (stageCtr = 0; stageCtr < 3; ++stageCtr) {
            // Clear out any previously determined directions
            clear();

            altFlag = stageCtr == 1;
            pCurrent = _pDest;

            numSteps = 0;
            currDirection = NO_DIRECTION;
            while (1) {
                  v = *pCurrent - 1;
                  if (v == 0) break;
                  
                  newDirection = NO_DIRECTION;
                  if (!altFlag && (currDirection != LEFT) && (currDirection != RIGHT)) {
                        // Standard order direction checking
                        if (*(pCurrent - DECODED_PATHS_WIDTH) == v) newDirection = DOWN;
                        else if (*(pCurrent + DECODED_PATHS_WIDTH) == v) newDirection = UP;
                        else if (*(pCurrent + 1) == v) newDirection = LEFT;
                        else if (*(pCurrent - 1) == v) newDirection = RIGHT;
                  } else {
                        // Alternate order direction checking
                        if (*(pCurrent + 1) == v) newDirection = LEFT;
                        else if (*(pCurrent - 1) == v) newDirection = RIGHT;
                        else if (*(pCurrent - DECODED_PATHS_WIDTH) == v) newDirection = DOWN;
                        else if (*(pCurrent + DECODED_PATHS_WIDTH) == v) newDirection = UP;
                  }
                  if (newDirection == NO_DIRECTION) 
                        error("Path finding process failed");

                  // Process for the specified direction
                  if (newDirection != currDirection) add(newDirection, 0);

                  switch (newDirection) {
                  case UP:
                        pCurrent += DECODED_PATHS_WIDTH;
                        break;

                  case DOWN:
                        pCurrent -= DECODED_PATHS_WIDTH;
                        break;

                  case LEFT:
                        ++pCurrent;
                        break;

                  case RIGHT:
                        --pCurrent;
                        break;

                  default:
                        break;
                  }

                  ++numSteps;
                  top().rawSteps() += 8;
                  currDirection = newDirection;
            }

            if (stageCtr == 0) 
                  // Save the number of steps needed
                  savedSteps = numSteps;
            if ((stageCtr == 1) && (numSteps <= savedSteps))
                  // Less steps were needed, so break out
                  break;
      }

      // Add a final move if necessary

      if (result == PF_OK) {
            if (_xDestPos < 0) 
                  addBack(LEFT, -_xDestPos);
            else if (_xDestPos > 0) 
                  addBack(RIGHT, _xDestPos);
      }
      
final_step:
      if (_xPos < 0) add(RIGHT, -_xPos);
      else if (_xPos > 0) add(LEFT, _xPos);

      return result;
}

void PathFinder::list(char *buffer) {
      if (buffer) {
            sprintf(buffer, "Pathfinder::list\n");
            buffer += strlen(buffer);
      }
      else {
            printf("Pathfinder::list\n");
      }
      
      ManagedList<WalkingActionEntry *>::iterator i;
      for (i = _list.begin(); i != _list.end(); ++i) {
            WalkingActionEntry *e = *i;
            if (buffer) {
                  sprintf(buffer, "Direction=%d, numSteps=%d\n", e->direction(), e->numSteps());
                  buffer += strlen(buffer);
            }
            else
                  printf("Direction=%d, numSteps=%d\n", e->direction(), e->numSteps());
      }
}

void PathFinder::processCell(uint16 *p) {
      // Only process cells that are still empty
      if (*p == 0) {
            uint16 vMax = 0xffff;
            uint16 vTemp;

            // Check the surrounding cells (up,down,left,right) for values
            // Up
            vTemp = *(p - DECODED_PATHS_WIDTH);
            if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
            // Down
            vTemp = *(p + DECODED_PATHS_WIDTH);
            if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
            // Left
            vTemp = *(p - 1);
            if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;
            // Right
            vTemp = *(p + 1);
            if ((vTemp != 0) && (vTemp < vMax)) vMax = vTemp;

            if (vMax != 0xffff) {
                  // A surrounding cell with a value was found
                  ++vMax;
                  *p = vMax;
                  _cellPopulated = true;
            }

            _countdownCtr -= 3;

      } else {
            --_countdownCtr;
      }
}

void PathFinder::scanLine(int numScans, int changeAmount, uint16 *&pEnd, int &v) {
      uint16 *pTemp = _pDest;

      for (int ctr = 1; ctr <= numScans; ++ctr) {
            pTemp += changeAmount;
            if ((*pTemp != 0) && (*pTemp != 0xffff)) {
                  if ((v < ctr) || ((v == ctr) && (*pTemp >= *pEnd))) return;
                  pEnd = pTemp;
                  v = ctr;
                  break;
            }
      }
}

void PathFinder::initVars() {
      int16 xRight;

      // Set up dest position, adjusting for walking off screen if necessary
      _destX = _hotspot->destX();
      _destY = _hotspot->destY();

      if (_destX < 10) _destX -= 50;
      if (_destX >= FULL_SCREEN_WIDTH-10) _destX += 50;

      _xPos = 0; _yPos = 0;
      _xDestPos = 0; _yDestPos = 0;

      _xCurrent = _hotspot->x();
      if (_xCurrent < 0) {
            _xPos = _xCurrent;
            _xCurrent = 0;
      }
      xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy() - 1;
      if (_xCurrent >= xRight) {
            _xPos = _xCurrent - xRight;
            _xCurrent = xRight;
      }

      _yCurrent = (_hotspot->y() & 0xf8) + _hotspot->heightCopy() - MENUBAR_Y_SIZE - 4;
      if (_yCurrent < 0) {
            _yPos = _yCurrent;
            _yCurrent = 0;
      }
      if (_yCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE)) {
            _yPos = _yCurrent - (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE);
            _yCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE;
      }

      _xDestCurrent = _destX;
      if (_xDestCurrent < 0) {
            _xDestPos = _xDestCurrent;
            _xDestCurrent = 0;
      }
      xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy();
      if (_xDestCurrent >= xRight) {
            _xDestPos = _xDestCurrent - xRight;
            _xDestCurrent = xRight;
      }

      _yDestCurrent = _destY - MENUBAR_Y_SIZE;
      if (_yDestCurrent < 0)
            _yDestCurrent = 0;
      if (_yDestCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE))
            _yDestCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE - 1;

      // Subtract an amount from the countdown counter to compensate for
      // the time spent decompressing the walkable areas set for the room
      _countdownCtr -= 700;
}

void PathFinder::saveToStream(Common::WriteStream *stream) {
      stream->writeByte(_inUse);

      if (_inUse) {
            // Save the path finding plane
            stream->write(_layer, sizeof(RoomPathsDecompressedData));

            // Save any active step sequence
            ManagedList<WalkingActionEntry *>::iterator i;
            for (i = _list.begin(); i != _list.end(); ++i) {
                  WalkingActionEntry *entry = *i;
                  stream->writeByte(entry->direction());
                  stream->writeSint16LE(entry->rawSteps());
            }
            stream->writeByte(0xff);
            stream->writeSint16LE(_stepCtr);
      }
}

void PathFinder::loadFromStream(Common::ReadStream *stream) {
      _inProgress = false;
      _inUse = stream->readByte() != 0;

      if (_inUse) {
            stream->read(_layer, sizeof(RoomPathsDecompressedData));

            _list.clear();
            uint8 direction;
            while ((direction = stream->readByte()) != 0xff) {
                  int steps = stream->readSint16LE();
                  _list.push_back(new WalkingActionEntry((Direction) direction, steps));
            }
            _stepCtr = stream->readSint16LE();
      }
}

// Current action entry class methods

CurrentActionEntry::CurrentActionEntry(CurrentAction newAction, uint16 roomNum) {
      _action = newAction; 
      _supportData = NULL; 
      _dynamicSupportData = false;
      _roomNumber = roomNum;
}

CurrentActionEntry::CurrentActionEntry(CurrentAction newAction, CharacterScheduleEntry *data, uint16 roomNum) { 
      assert(data->parent() != NULL);
      _action = newAction; 
      _supportData = data; 
      _dynamicSupportData = false;
      _roomNumber = roomNum;
}

CurrentActionEntry::CurrentActionEntry(Action newAction, uint16 roomNum, uint16 param1, uint16 param2) {
      _action = DISPATCH_ACTION;
      _dynamicSupportData = true;
      _supportData = new CharacterScheduleEntry();
      uint16 params[2] = {param1, param2};
      _supportData->setDetails2(newAction, 2, params);
      _roomNumber = roomNum;
}

void CurrentActionEntry::setSupportData(uint16 entryId) {
      CharacterScheduleEntry &entry = supportData();

      CharacterScheduleEntry *newEntry = Resources::getReference().
            charSchedules().getEntry(entryId, entry.parent());
      setSupportData(newEntry);
}

void CurrentActionEntry::saveToStream(WriteStream *stream) {
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Saving hotspot action entry dyn=%d id=%d",
            hasSupportData(), hasSupportData() ? supportData().id() : 0);
      stream->writeByte((uint8) _action);
      stream->writeUint16LE(_roomNumber);
      stream->writeByte(hasSupportData());
      if (hasSupportData()) {
            // Handle the support data
            stream->writeByte(_dynamicSupportData);
            if (_dynamicSupportData) {
                  // Write out the dynamic data
                  stream->writeByte(supportData().action());
                  stream->writeSint16LE(supportData().numParams());
                  for (int index = 0; index < supportData().numParams(); ++index)
                        stream->writeUint16LE(supportData().param(index));
            } else {
                  // Write out the Id for the static entry
                  stream->writeUint16LE(supportData().id());
            }
      }
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Finished saving hotspot action entry");
}

CurrentActionEntry *CurrentActionEntry::loadFromStream(ReadStream *stream) {
      Resources &res = Resources::getReference();
      uint8 actionNum = stream->readByte();
      if (actionNum == 0xff) return NULL;
      CurrentActionEntry *result;

      uint16 roomNumber = stream->readUint16LE();
      bool hasSupportData = stream->readByte() != 0;

      if (!hasSupportData) {
            // An entry that doesn't have support data
            result = new CurrentActionEntry(
                  (CurrentAction) actionNum, roomNumber);
      } else {
            // Handle support data for the entry
            bool dynamicData = stream->readByte() != 0;
            if (dynamicData) {
                  // Load action entry that has dynamic data
                  result = new CurrentActionEntry(
                        (CurrentAction) actionNum, roomNumber);
                  result->_supportData = new CharacterScheduleEntry();
                  Action action = (Action) stream->readByte();
                  int numParams = stream->readSint16LE();
                  uint16 *paramList = new uint16[numParams];
                  for (int index = 0; index < numParams; ++index)
                        paramList[index] = stream->readUint16LE();
                        
                  result->_supportData->setDetails2(action, numParams, paramList);
                  delete paramList;
            } else {
                  // Load action entry with an NPC schedule entry
                  uint16 entryId = stream->readUint16LE();
                  CharacterScheduleEntry *entry = res.charSchedules().getEntry(entryId);
                  result = new CurrentActionEntry((CurrentAction) actionNum, roomNumber);
                  result->setSupportData(entry);
            }
      }

      return result;
}

void CurrentActionStack::list(char *buffer) {
      ManagedList<CurrentActionEntry *>::iterator i;

      if (buffer) {
            sprintf(buffer, "CurrentActionStack::list num_actions=%d\n", size());
            buffer += strlen(buffer);
      }
      else
            printf("CurrentActionStack::list num_actions=%d\n", size());

      for (i = _actions.begin(); i != _actions.end(); ++i) {
            CurrentActionEntry *entry = *i;
            if (buffer) {
                  sprintf(buffer, "style=%d room#=%d", entry->action(), entry->roomNumber());
                  buffer += strlen(buffer);
            }
            else
                  printf("style=%d room#=%d", entry->action(), entry->roomNumber());
      
            if (entry->hasSupportData()) {
                  CharacterScheduleEntry &rec = entry->supportData();

                  if (buffer) {
                        sprintf(buffer, ", action=%d params=", rec.action());
                        buffer += strlen(buffer);
                  }
                  else
                        printf(", action=%d params=", rec.action());

                  if (rec.numParams() == 0) 
                        if (buffer) {
                              strcat(buffer, "none");
                              buffer += strlen(buffer);
                        }
                        else
                              printf("none");
                  else {
                        for (int ctr = 0; ctr < rec.numParams(); ++ctr) {
                              if (ctr != 0) {
                                    if (buffer) {
                                          strcpy(buffer, ", ");
                                          buffer += strlen(buffer);
                                    }
                                    else
                                          printf(", ");
                              }

                              if (buffer) {
                                    sprintf(buffer, "%d", rec.param(ctr));
                                    buffer += strlen(buffer);
                              } else 
                                    printf("%d", rec.param(ctr));
                        }
                  }
            }
            if (buffer) {
                  sprintf(buffer, "\n");
                  buffer += strlen(buffer);
            }
            else
                  printf("\n");
      }
}

void CurrentActionStack::saveToStream(WriteStream *stream) {
      ManagedList<CurrentActionEntry *>::iterator i;

      debugC(ERROR_DETAILED, kLureDebugAnimations, "Saving hotspot action stack");
      char buffer[MAX_DESC_SIZE];
      list(buffer);
      debugC(ERROR_DETAILED, kLureDebugAnimations, "%s", buffer);

      for (i = _actions.begin(); i != _actions.end(); ++i) {
            CurrentActionEntry *rec = *i;
            rec->saveToStream(stream);
      }
      stream->writeByte(0xff);      // End of list marker
      debugC(ERROR_DETAILED, kLureDebugAnimations, "Finished saving hotspot action stack");
}

void CurrentActionStack::loadFromStream(ReadStream *stream) {
      CurrentActionEntry *rec;

      _actions.clear();
      while ((rec = CurrentActionEntry::loadFromStream(stream)) != NULL)
            _actions.push_back(rec);
}

/*-------------------------------------------------------------------------*/
/* Support methods                                                         */
/*                                                                         */
/*-------------------------------------------------------------------------*/

// finds a list of character animations whose base area are impinging 
// that of the specified character (ie. are bumping into them)

int Support::findIntersectingCharacters(Hotspot &h, uint16 *charList) {
      int numImpinging = 0;
      Resources &res = Resources::getReference();
      Rect r;
      uint16 hotspotY;

      r.left = h.x();
      r.right = h.x() + h.widthCopy();
      r.top = h.y() + h.heightCopy() - h.yCorrection() - h.charRectY();
      r.bottom = h.y() + h.heightCopy() + h.charRectY();

      HotspotList::iterator i;
      for (i = res.activeHotspots().begin(); i != res.activeHotspots().end(); ++i) {
            Hotspot &hotspot = **i;
            
            // Check for basic reasons to skip checking the animation
            if ((h.hotspotId() == hotspot.hotspotId()) || (hotspot.layer() == 0) ||
                  (h.roomNumber() != hotspot.roomNumber()) || 
                  (hotspot.hotspotId() >= FIRST_NONCHARACTER_ID) ||
                  hotspot.skipFlag()) continue;
            // TODO: See why si+ANIM_HOTSPOT_OFFSET compared aganst di+ANIM_VOICE_CTR

            hotspotY = hotspot.y() + hotspot.heightCopy();
            if ((hotspot.x() >= r.right) || (hotspot.x() + hotspot.widthCopy() <= r.left) ||
                  (hotspotY + hotspot.charRectY() <= r.top) ||
                  (hotspotY - hotspot.charRectY() - hotspot.yCorrection() >= r.bottom))
                  continue;

            // Add hotspot Id to list
            if (numImpinging == MAX_NUM_IMPINGING)
                  error("Exceeded maximum allowable number of impinging characters");
            *charList++ = hotspot.hotspotId();
            ++numImpinging;
      }

      return numImpinging;
}

// Returns true if any other characters are intersecting the specified one

bool Support::checkForIntersectingCharacter(Hotspot &h) {
      uint16 tempList[MAX_NUM_IMPINGING];
      return findIntersectingCharacters(h, tempList) != 0;
}

// Check whether a character needs to change the room they're in

bool Support::checkRoomChange(Hotspot &h) {
      int16 x = h.x() + (h.widthCopy() >> 1);
      int16 y = h.y() + h.heightCopy() - (h.yCorrection() >> 1);

      RoomData *roomData = Resources::getReference().getRoom(h.roomNumber());
      RoomExitData *exitRec = roomData->exits.checkExits(x, y);

      if (exitRec) {
            // End the current walking sequence
            if (exitRec->sequenceOffset != 0xffff) {
                  Script::execute(exitRec->sequenceOffset);
            } else {
                  Support::characterChangeRoom(h, exitRec->roomNumber, 
                        exitRec->x, exitRec->y, exitRec->direction);
            }
      }

      return (exitRec != NULL);
}

void Support::characterChangeRoom(Hotspot &h, uint16 roomNumber, 
                                                  int16 newX, int16 newY, Direction dir) {
      Resources &res = Resources::getReference();
      Room &room = Room::getReference();
      ValueTableData &fields = res.fieldList();

      if (h.hotspotId() == PLAYER_ID) {
            // Room change code for the player
            if (room.cursorState() != CS_NONE) return;

            h.setDirection(dir);
            PlayerNewPosition &p = fields.playerNewPos();
            p.roomNumber = roomNumber;
            p.position.x = newX;
            p.position.y = newY - 48;

            // TODO: Double-check.. is it impinging in leaving room (right now) or entering room
            if (checkForIntersectingCharacter(h)) {
                  h.tempDest().position.x = h.destX();
                  h.tempDest().position.y = h.destY();
                  h.tempDest().counter = 1;
                  Room::getReference().setCursorState(CS_BUMPED);
                  h.setActionCtr(0);
                  h.setBlockedState((BlockedState) ((int) h.blockedState() + 1));
                  h.setDestHotspot(0);
                  h.setRandomDest();
                  p.roomNumber = 0;
            }

      } else {
            // Any other character changing room

            if (checkForIntersectingCharacter(h)) {
                  // Character is blocked, so add a handler for handling it
                  uint16 dataId = res.getCharOffset(0);
                  CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
                  h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
            } else {
                  // Handle character room change
                  h.setRoomNumber(roomNumber);
                  h.setPosition((newX & 0xfff8) | 5, (newY - h.heightCopy()) & 0xfff8);
                  h.setSkipFlag(true);
                  h.setDirection(dir);

                  h.setExitCtr(0);
                  h.currentActions().top().setAction(DISPATCH_ACTION);
            }
      }
}

bool Support::charactersIntersecting(HotspotData *hotspot1, HotspotData *hotspot2) {
      return !((hotspot1->startX + hotspot1->widthCopy + 4 < hotspot2->startX) ||
            (hotspot2->startX + hotspot2->widthCopy + 4 < hotspot1->startX) ||
            (hotspot2->startY + hotspot2->heightCopy - hotspot2->yCorrection - 2 >=
                  hotspot1->startY + hotspot1->heightCopy + 2) ||
            (hotspot2->startY + hotspot2->heightCopy + 2 < 
                  hotspot1->startY + hotspot1->heightCopy - hotspot1->yCorrection - 2));
}

bool Support::isCharacterInList(uint16 *lst, int numEntries, uint16 charId) {
      while (numEntries-- > 0) 
            if (*lst++ == charId) return true;
      return false;
}

void HotspotList::saveToStream(WriteStream *stream) {
      HotspotList::iterator i;
      for (i = begin(); i != end(); ++i) {
            Hotspot *hotspot = *i;
            debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Saving hotspot %xh", hotspot->hotspotId());
            bool dynamicObject = hotspot->hotspotId() != hotspot->originalId();
            stream->writeUint16LE(hotspot->originalId());
            stream->writeByte(dynamicObject);
            stream->writeUint16LE(hotspot->destHotspotId());
            hotspot->saveToStream(stream);

            debugC(ERROR_DETAILED, kLureDebugAnimations, "Saved hotspot %xh", hotspot->hotspotId());
      }
      stream->writeUint16LE(0);
}

void HotspotList::loadFromStream(ReadStream *stream) {
      Resources &res = Resources::getReference();
      Hotspot *hotspot;

      clear();
      uint16 hotspotId = stream->readUint16LE();
      while (hotspotId != 0) {
            debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Loading hotspot %xh", hotspotId);
            bool dynamicObject = stream->readByte() != 0;
            uint16 destHotspotId = stream->readUint16LE();

            if (dynamicObject) {
                  // Add in a dynamic object (such as a floating talk bubble)
                  Hotspot *destHotspot = res.getActiveHotspot(destHotspotId);
                  assert(destHotspot);
                  hotspot = new Hotspot(destHotspot, hotspotId);
            } else {
                  HotspotData *hotspotData = res.getHotspot(hotspotId);
                  assert(hotspotData);
                  hotspot = new Hotspot(hotspotData);
            }

            res.addHotspot(hotspot);
            assert(hotspot);
            hotspot->loadFromStream(stream);

            debugC(ERROR_DETAILED, kLureDebugAnimations, "Loaded hotspot %xh", hotspotId);

            // Get the next hotspot
            hotspotId = stream->readUint16LE();
      }
}

} // end of namespace Lure

Generated by  Doxygen 1.6.0   Back to index