/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "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(); bool isEGA = LureEngine::getReference().isEGA(); _data = res; _anim = NULL; _frames = NULL; _numFrames = 0; _persistant = false; _direction = NO_DIRECTION; _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; _tempDest.counter = 0; _colorOffset = isEGA ? 0 : res->colorOffset; _override = resources.getHotspotOverride(res->hotspotId); setAnimation(_data->animRecordId); _tickHandler = _tickHandlers.getHandler(_data->tickProcId); _nameBuffer[0] = '\0'; _skipFlag = false; _charRectY = 0; _voiceCtr = 0; _blockedOffset = 0; _exitCtr = 0; _walkFlag = false; _startRoomNumber = 0; _supportValue = 0; } // 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; _colorOffset = 0; _destHotspotId = character->hotspotId(); _blockedOffset = 0; _exitCtr = 0; _voiceCtr = 0; _walkFlag = false; _skipFlag = false; _direction = NO_DIRECTION; 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 = _tickHandlers.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 = _tickHandlers.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; _colorOffset = 0; _destHotspotId = 0; _blockedOffset = 0; _exitCtr = 0; _voiceCtr = 0; _walkFlag = false; _skipFlag = false; _roomNumber = 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; _direction = NO_DIRECTION; } Hotspot::~Hotspot() { 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-existent Animation Id: %xh", _hotspotId, newAnimId); } setAnimation(tempAnim); } void Hotspot::setAnimationIndex(int animIndex) { Resources &r = Resources::getReference(); // Get the animation specified HotspotAnimList::iterator a = r.animRecords().begin(); for (int i = 0; i < animIndex; i++) ++a; HotspotAnimData *tempAnim = (*a).get(); _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(_colorOffset, 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 color 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++ = _colorOffset + (*pSrc >> 4); *pDest++ = _colorOffset + (*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; Common::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, _colorOffset); } void Hotspot::incFrameNumber() { ++_frameNumber; if (_frameNumber >= _numFrames) _frameNumber = 0; } bool Hotspot::isActiveAnimation() { return ((_numFrames != 0) && (_layer != 0)); } uint16 Hotspot::nameId() const { 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); (_tickHandlers.*_tickHandler)(*this); debugC(ERROR_BASIC, kLureDebugAnimations, "Hotspot %xh tick end", id); } void Hotspot::setTickProc(uint16 newVal) { if (_data) _data->tickProcId = newVal; _tickHandler = _tickHandlers.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 = LureEngine::getReference().rnd(); int16 xp, yp; 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; uint8 *msgData = (uint8 *) data->data(); uint16 idVal; messageId &= 0x7fff; // Skip through header to find table for given character uint headerEnd = READ_LE_UINT16(msgData + 2); uint idx = 0; while ((idx < headerEnd) && (READ_LE_UINT16(msgData + idx) != hotspotId())) idx += 2 * sizeof(uint16); if (idx == headerEnd) { // Given character doesn't have a message set, so fall back on a simple puzzled animation hotspot = new Hotspot(this, PUZZLED_ANIM_IDX); res.addHotspot(hotspot); return; } // Scan through secondary list uint16 *v = (uint16 *) (msgData + READ_LE_UINT16(msgData + idx + sizeof(uint16))); 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; } Common::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 const &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(); int charId = _hotspotId; debugC(ERROR_INTERMEDIATE, kLureDebugHotspots, "Action charId=%xh Action=%d/%s", charId, (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", charId, (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(); // WORKAROUND: Fix crash when talking when an ask conversation is active if (_data->talkDestCharacterId != 0) { // Don't allow the talk to start return; } 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); Common::strlcat(buffer, "\n\n", MAX_DESC_SIZE); Common::strlcat(buffer, stringList.getString(S_YOU_ARE_CARRYING), MAX_DESC_SIZE); // 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 const &rec = **i; if (rec.roomNumber == PLAYER_ID) { if (numItems++ == 0) Common::strlcat(buffer, ": ", MAX_DESC_SIZE); else Common::strlcat(buffer, ", ", MAX_DESC_SIZE); strings.getString(rec.nameId, buffer + strlen(buffer)); } } // If there were no items, add in the word 'nothing' if (numItems == 0) Common::strlcat(buffer, stringList.getString(S_INV_NOTHING), MAX_DESC_SIZE); // If the player has money, add it in uint16 numGroats = res.fieldList().numGroats(); if (numGroats > 0) { Common::strlcat(buffer, "\n\n", MAX_DESC_SIZE); Common::strlcat(buffer, stringList.getString(S_YOU_HAVE), MAX_DESC_SIZE); snprintf(buffer + strlen(buffer), MAX_DESC_SIZE - strlen(buffer), "%d", numGroats); Common::strlcat(buffer, " ", MAX_DESC_SIZE); Common::strlcat(buffer, stringList.getString((numGroats == 1) ? S_GROAT : S_GROATS), MAX_DESC_SIZE); // Make sure we're not overrunning } // 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() == Common::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) const { if (_data) _data->npcSchedule.saveToStream(stream); else // Hotspot doesn't have an underlying data object, so write out an empty actions list stream->writeByte(0xff); _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(_colorOffset); 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) { if (_data) _data->npcSchedule.loadFromStream(stream); else { // Dummy read of terminator for empty actions list byte dummy = stream->readByte(); assert(dummy == 0xff); } _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(); _colorOffset = 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(); } /*------------------------------------------------------------------------*/ HotspotTickHandlers::HotspotTickHandlers() { countdownCtr = 0; ewanXOffset = false; } HandlerMethodPtr HotspotTickHandlers::getHandler(uint16 procIndex) { switch (procIndex) { case 1: return &HotspotTickHandlers::defaultHandler; case STANDARD_CHARACTER_TICK_PROC: return &HotspotTickHandlers::standardCharacterAnimHandler; case PLAYER_TICK_PROC_ID: return &HotspotTickHandlers::playerAnimHandler; case VOICE_TICK_PROC_ID: return &HotspotTickHandlers::voiceBubbleAnimHandler; case PUZZLED_TICK_PROC_ID: return &HotspotTickHandlers::puzzledAnimHandler; case 6: return &HotspotTickHandlers::roomExitAnimHandler; case 7: case FOLLOWER_TICK_PROC_2: return &HotspotTickHandlers::followerAnimHandler; case JAILOR_TICK_PROC_ID: case 10: return &HotspotTickHandlers::jailorAnimHandler; case STANDARD_ANIM_2_TICK_PROC: return &HotspotTickHandlers::standardAnimHandler2; case STANDARD_ANIM_TICK_PROC: return &HotspotTickHandlers::standardAnimHandler; case 13: return &HotspotTickHandlers::sonicRatAnimHandler; case 14: return &HotspotTickHandlers::droppingTorchAnimHandler; case 15: return &HotspotTickHandlers::playerSewerExitAnimHandler; case 16: return &HotspotTickHandlers::fireAnimHandler; case 17: return &HotspotTickHandlers::sparkleAnimHandler; case 18: return &HotspotTickHandlers::teaAnimHandler; case 19: return &HotspotTickHandlers::goewinCaptiveAnimHandler; case 20: return &HotspotTickHandlers::prisonerAnimHandler; case 21: return &HotspotTickHandlers::catrionaAnimHandler; case 22: return &HotspotTickHandlers::morkusAnimHandler; case 23: return &HotspotTickHandlers::grubAnimHandler; case 24: return &HotspotTickHandlers::barmanAnimHandler; case 25: return &HotspotTickHandlers::skorlAnimHandler; case 26: return &HotspotTickHandlers::gargoyleAnimHandler; case GOEWIN_SHOP_TICK_PROC: return &HotspotTickHandlers::goewinShopAnimHandler; case 28: case 29: case 30: case 31: case 32: case 33: return &HotspotTickHandlers::skullAnimHandler; case 34: return &HotspotTickHandlers::dragonFireAnimHandler; case 35: return &HotspotTickHandlers::castleSkorlAnimHandler; case 36: return &HotspotTickHandlers::rackSerfAnimHandler; case TALK_TICK_PROC_ID: return &HotspotTickHandlers::talkAnimHandler; case 38: return &HotspotTickHandlers::fighterAnimHandler; case PLAYER_FIGHT_TICK_PROC_ID: return &HotspotTickHandlers::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) { Common::String buffer = h.currentActions().getDebugInfo(); debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character p=(%d,%d,%d) bs=%d\n%s", h.x(), h.y(), h.roomNumber(), h.blockedState(), buffer.c_str()); } // 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 character walking to the given destination debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character exec start walking => (%d,%d)", h.destX(), h.destY()); h.setCoveredFlag(VB_INITIAL); h.setOccupied(false); pathFinder.reset(paths); h.currentActions().top().setAction(PROCESSING_PATH); // fall through 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); // WORKAROUND: A character that had enteredg an exit area might have been blocked from entering the new room. // The Y position adjust below could thus place a character further into the exit area. So don't do the // position adjustment if the user is already in an exit area 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) 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; // fall through 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; Common::String buffer; buffer = h.currentActions().getDebugInfo(); 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.c_str()); 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.setCoveredFlag(VB_INITIAL); 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()); // fall through case PROCESSING_PATH: h.setCharacterMode(CHARMODE_NONE); res.pausedList().scan(h); pfResult = pathFinder.process(); if (pfResult == PF_UNFINISHED) break; // Pathfinding is now complete buffer = pathFinder.getDebugInfo(); debugC(ERROR_DETAILED, kLureDebugAnimations, "Pathfind processing done; result=%d, walkFlag=%d\n%s", pfResult, h.walkFlag(), buffer.c_str()); 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); // fall through 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) { 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; if (p->destRoom == h.roomNumber()) // Character is already in destination room, so set a random dest h.setRandomDest(); else // Move character to either the player's room, or found alternate destination 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 = LureEngine::getReference().rnd(); RandomActionType actionType; uint16 scheduleId; 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 destination 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 = LureEngine::getReference().rnd(); 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 = LureEngine::getReference().rnd(); h.setHotspotScript(rnd.getRandomNumber(100) >= 50 ? 0x54 : 0); h.setFrameCtr(20 + rnd.getRandomNumber(63)); } } 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]; Common::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 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).get(); 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 color = LureEngine::getReference().isEGA() ? ((lineNum + 1 == selectedLine) ? EGA_DIALOG_WHITE_COLOR : EGA_DIALOG_TEXT_COLOR) : ((lineNum + 1 == selectedLine) ? VGA_DIALOG_WHITE_COLOR : VGA_DIALOG_TEXT_COLOR); screen.screen().writeString(r.left, r.top, buffer, false, color); } if (mouse.mButton() || mouse.rButton()) { // Abort the conversation talkEndConversation(); // Have destination character show question speech bubble charHotspot = res.getActiveHotspot(talkDestCharacter); if (charHotspot != NULL) charHotspot->showMessage(13, NOONE_ID); } else if (mouse.lButton() && (selectedLine != 0)) { // 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; // fall through 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 talkEndConversation(); } break; } } void HotspotTickHandlers::talkEndConversation() { Resources &res = Resources::getReference(); Hotspot *charHotspot = res.getActiveHotspot(talkDestCharacter); assert(charHotspot); res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID); charHotspot->setUseHotspotId(0); charHotspot->resource()->talkerId = 0; charHotspot->setDelayCtr(24); res.setTalkData(0); res.setCurrentAction(NONE); res.setTalkState(TALK_NONE); } 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 = LureEngine::getReference().rnd(); 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(GWEN_ID); HotspotData *wayne = res.getHotspot(WAYNE_ID); if ((player->roomNumber != 35) && (gwyn->roomNumber != 35) && (wayne->roomNumber != 35)) { h.setAnimationIndex(EWAN_ANIM_INDEX); 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); // fall through 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().isEmpty()) { if (h.startRoomNumber() != 0) { // If character isn't already returning to starting room, redirect them to the // player's current room if (!h.currentActions().bottom().hasSupportData() || (h.currentActions().bottom().supportData().action() != RETURN)) { // Start follower returning Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID); h.currentActions().clear(); h.currentActions().addFront(RETURN, playerHotspot->roomNumber(), 0, 0); } } } 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().addFront(DISPATCH_ACTION, entry, 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() const { 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; bool skipToFinalStep = false; if (!_inProgress) { // Following code only done during first call to method _inProgress = true; initVars(); Common::Point diff(_destX - _xCurrent, _destY - _yCurrent); _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); else if (diff.y > 0) add(DOWN, diff.y); else add(UP, -diff.y); _inProgress = false; result = PF_OK; skipToFinalStep = true; } else { // 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; } } } if (!skipToFinalStep) { // 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 final movement 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; } Common::String PathFinder::getDebugInfo() const { Common::String buffer; buffer += "Pathfinder::list(\n"; WalkingActionList::const_iterator i; for (i = _list.begin(); i != _list.end(); ++i) { WalkingActionEntry const &e = **i; buffer += Common::String::format("Direction=%d, numSteps=%d\n", e.direction(), e.numSteps()); } return buffer; } 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) const { stream->writeByte(_inUse); if (_inUse) { // Save the path finding plane stream->write(_layer, sizeof(RoomPathsDecompressedData)); // Save any active step sequence for (WalkingActionList::const_iterator 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(WalkingActionList::value_type(new WalkingActionEntry((Direction) direction, steps))); } _stepCtr = stream->readSint16LE(); } } /*-------------------------------------------------------------------------*/ /* 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, int16 xp, int16 yp, int roomNumber) { int numImpinging = 0; Resources &res = Resources::getReference(); Common::Rect r; uint16 hotspotY; // If a specific x/y/room isn't provided, use the specified hotspot's current location if (roomNumber == -1) { xp = h.x(); yp = h.y(); roomNumber = h.roomNumber(); } r.left = xp; r.right = xp + h.widthCopy(); r.top = yp + h.heightCopy() - h.yCorrection() - h.charRectY(); r.bottom = yp + 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) || (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, int16 xp, int16 yp, int roomNumber) { uint16 tempList[MAX_NUM_IMPINGING]; return findIntersectingCharacters(h, tempList, xp, yp, roomNumber) != 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; PlayerNewPosition &p = fields.playerNewPos(); if (checkForIntersectingCharacter(h, newX, newY - 48, roomNumber)) { // Another character is blocking the exit in the other room, so set the player to // temporarily move to a random destination in the current room 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 { // Flag the new location to move the player to (which will be handled by the outer game loop) h.setDirection(dir); p.roomNumber = roomNumber; p.position.x = newX; p.position.y = newY - 48; } } else { // Any other character changing room newX = (newX & 0xfff8) | 5; newY = (newY - h.heightCopy()) & 0xfff8; if (checkForIntersectingCharacter(h, newX, newY, roomNumber)) { // 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, newY); 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(Common::WriteStream *stream) const { for (HotspotList::const_iterator i = begin(); i != end(); ++i) { Hotspot const &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(Common::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