scummvm/engines/lure/hotspots.cpp
Torbjörn Andersson d6af07989d This could be completely wrong, but I don't think Hotspot::stopWalking() is
supposed to call setCurrentAction() in Resources, because then ScummVM will
crash in getCurrentActionStr() whenever we try to interact with any object.

Since Hotspot::walkTo() calls setCurrentAction() in Hotspot, it seems like a
reasonable guess that this is the setCurrentAction() that stopWalking() should
call as well.

svn-id: r22409
2006-05-11 08:25:09 +00:00

2136 lines
55 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2005-2006 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#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"
namespace Lure {
Hotspot::Hotspot(HotspotData *res): _pathFinder(this) {
_data = res;
_anim = NULL;
_frames = NULL;
_numFrames = 0;
_persistant = false;
_hotspotId = res->hotspotId;
_roomNumber = res->roomNumber;
_startX = res->startX;
_startY = res->startY;
_destX = res->startX;
_destY = res->startY;
_destHotspotId = 0;
_height = res->height;
_width = res->width;
_heightCopy = res->heightCopy;
_widthCopy = res->widthCopy;
_yCorrection = res->yCorrection;
_talkX = res->talkX;
_talkY = res->talkY;
_layer = res->layer;
_sequenceOffset = res->sequenceOffset;
_tickCtr = res->tickTimeout;
_actions = res->actions;
_colourOffset = res->colourOffset;
_override = Resources::getReference().getHotspotOverride(res->hotspotId);
if (_data->animRecordId != 0)
setAnimation(_data->animRecordId);
_tickHandler = HotspotTickHandlers::getHandler(_data->tickProcOffset);
_frameCtr = 0;
_skipFlag = false;
_charRectY = 0;
_actionCtr = 0;
}
// Special constructor used to create a voice hotspot
Hotspot::Hotspot(Hotspot *character, uint16 objType): _pathFinder(this) {
_data = NULL;
_anim = NULL;
_frames = NULL;
_numFrames = 0;
_persistant = false;
_hotspotId = 0xffff;
_override = NULL;
_colourOffset = 0;
_destHotspotId = character->hotspotId();
switch (objType) {
case VOICE_ANIM_ID:
_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;
_layer = 2;
_tickHandler = HotspotTickHandlers::getHandler(VOICE_TICK_PROC_ID);
_tickCtr = 0;
setAnimation(VOICE_ANIM_ID);
break;
default:
break;
}
}
Hotspot::~Hotspot() {
if (_frames) delete _frames;
}
void Hotspot::setAnimation(uint16 newAnimId) {
Resources &r = Resources::getReference();
HotspotAnimData *tempAnim;
if (newAnimId == 0) tempAnim = NULL;
else tempAnim = r.getAnimation(newAnimId);
setAnimation(tempAnim);
}
void Hotspot::setAnimation(HotspotAnimData *newRecord) {
Disk &r = Disk::getReference();
if (_frames) {
delete _frames;
_frames = NULL;
}
_anim = NULL;
_numFrames = 0;
_frameNumber = 0;
if (!newRecord) return;
if (!r.exists(newRecord->animId)) return;
_anim = newRecord;
MemoryBlock *src = Disk::getReference().getEntry(_anim->animId);
uint16 *numEntries = (uint16 *) src->data();
uint16 *headerEntry = (uint16 *) (src->data() + 2);
if ((*numEntries > 99) || (*numEntries == 0)) {
// Wobbly, likely something wrong with the resoure
_width = 1;
_numFrames = 1;
_frameNumber = 0;
_frames = new Surface(1, 1);
_frames->data().setBytes(_colourOffset, 0, 1);
return;
}
// 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 += (*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;
_frames = new Surface(_width * _numFrames, _height);
_frames->data().setBytes(_colourOffset, 0, _frames->data().size());
byte *pSrc = dest->data() + 0x40;
byte *pDest;
headerEntry = (uint16 *) (src->data() + 2);
MemoryBlock &mDest = _frames->data();
for (uint16 frameNumCtr = 0; frameNumCtr < _numFrames; ++frameNumCtr, ++headerEntry) {
if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0) {
// For animations with an offset table, set the source point for each frame
uint16 frameOffset = *((uint16 *) (src->data() + ((frameNumCtr + 1) * sizeof(uint16)))) + 0x40;
if ((uint32) frameOffset + _height * (_width / 2) > dest->size())
error("Invalid frame offset in animation %x", newRecord->animRecordId);
pSrc = dest->data() + frameOffset;
}
// Copy over the frame, applying the colour offset to each nibble
for (uint16 yPos = 0; yPos < _height; ++yPos) {
pDest = mDest.data() + (yPos * _numFrames + frameNumCtr) * _width;
for (uint16 ctr = 0; ctr < _width / 2; ++ctr) {
*pDest++ = _colourOffset + (*pSrc >> 4);
*pDest++ = _colourOffset + (*pSrc & 0xf);
++pSrc;
}
}
}
delete src;
delete dest;
}
void Hotspot::copyTo(Surface *dest) {
int16 xPos = _startX;
int16 yPos = _startY;
uint16 hWidth = _width;
uint16 hHeight = _height;
Rect r(_frameNumber * hWidth, 0, (_frameNumber + 1) * hWidth - 1,
hHeight - 1);
if (yPos < 0) {
if (yPos + hHeight <= 0)
// Completely off screen, so don't display
return;
// Reduce the source rectangle to only the on-screen portion
r.top = -yPos;
yPos = 0;
}
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;
}
if (xPos >= FULL_SCREEN_WIDTH)
return;
else if (xPos + hWidth > FULL_SCREEN_WIDTH)
r.right = (_frameNumber * hWidth) + (FULL_SCREEN_WIDTH - xPos) - 1;
if (yPos >= FULL_SCREEN_HEIGHT)
return;
else if (yPos + hHeight > FULL_SCREEN_HEIGHT)
r.bottom = FULL_SCREEN_HEIGHT - yPos - 1;
_frames->copyTo(dest, r, (uint16) xPos, (uint16) yPos, _colourOffset);
}
void Hotspot::incFrameNumber() {
++_frameNumber;
if (_frameNumber >= _numFrames)
_frameNumber = 0;
}
bool Hotspot::isActiveAnimation() {
return ((_numFrames != 0) && (_layer != 0));
}
uint16 Hotspot::nameId() {
if (_data == NULL)
return 0;
else
return _data->nameId;
}
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;
_height = newHeight;
}
bool Hotspot::executeScript() {
if (_data->sequenceOffset == 0)
return false;
else
return HotspotScript::execute(this);
}
void Hotspot::tick() {
_tickHandler(*this);
}
void Hotspot::setTickProc(uint16 newVal) {
if (_data)
_data->tickProcOffset = newVal;
_tickHandler = HotspotTickHandlers::getHandler(newVal);
}
void Hotspot::walkTo(int16 endPosX, int16 endPosY, uint16 destHotspot) {
if ((hotspotId() == PLAYER_ID) && (PATHFIND_COUNTDOWN != 0)) {
// Show the clock cursor whilst pathfinding will be calculated
Mouse &mouse = Mouse::getReference();
mouse.setCursorNum(CURSOR_TIME_START, 0, 0);
}
_destX = endPosX;
_destY = endPosY;
_destHotspotId = destHotspot;
setCurrentAction(START_WALKING);
}
void Hotspot::stopWalking() {
// TODO: voiceCtr = 0
_actionCtr = 0;
_currentActions.clear();
Room::getReference().setCursorState(CS_NONE);
setCurrentAction(NO_ACTION);
}
void Hotspot::setDirection(Direction dir) {
_direction = dir;
switch (dir) {
case UP:
setFrameNumber(_anim->upFrame);
_charRectY = 4;
break;
case DOWN:
setFrameNumber(_anim->downFrame);
_charRectY = 4;
break;
case LEFT:
setFrameNumber(_anim->leftFrame);
_charRectY = 0;
break;
case RIGHT:
setFrameNumber(_anim->rightFrame);
_charRectY = 0;
break;
default:
break;
}
}
// Makes the character face the given hotspot
void Hotspot::faceHotspot(HotspotData *hotspot) {
if (hotspot->hotspotId >= START_NONVISUAL_HOTSPOT_ID) {
// Non visual hotspot
setDirection(hotspot->nonVisualDirection());
} else {
// Visual hotspot
int xp = x() - hotspot->startX;
int 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::getReference().update();
Screen::getReference().update();
}
}
// Sets or clears the hotspot as occupying an area in its room's pathfinding data
void Hotspot::setOccupied(bool occupiedFlag) {
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;
frameSet->getFrame(frameNumber(), _xChange, _yChange, nextFrame);
setFrameNumber(nextFrame);
setPosition(x() + _xChange, y() + _yChange);
++_pathFinder.stepCtr();
return false;
}
/*-------------------------------------------------------------------------*/
/* Hotspot action handling */
/* */
/*-------------------------------------------------------------------------*/
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 (uint16 *p = &validRoomExitHotspots[0]; *p != 0; ++p)
if (*p == id) return true;
return false;
}
HotspotPrecheckResult Hotspot::actionPrecheck(HotspotData *hotspot) {
if ((hotspot->hotspotId == 0x420) || (hotspot->hotspotId == 0x436) ||
(hotspot->hotspotId == 0x429)) {
// TODO: figure out specific handling code
actionPrecheck3(hotspot);
return PC_EXECUTE;
} else {
return actionPrecheck2(hotspot);
}
}
HotspotPrecheckResult Hotspot::actionPrecheck2(HotspotData *hotspot) {
ValueTableData fields = Resources::getReference().fieldList();
if (hotspot->roomNumber != roomNumber()) {
// Hotspot isn't in same room as character
if (_actionCtr == 0)
Dialog::showMessage(0, hotspotId());
_actionCtr = 0;
return PC_NOT_IN_ROOM;
} else if (_actionCtr != 0) {
// loc_883
++_actionCtr;
if (_actionCtr >= 6) {
warning("actionCtr exceeded");
_actionCtr = 0;
Dialog::showMessage(0xD, hotspotId());
return PC_EXCESS;
}
if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
// TODO: Does other checks on HS[44] -> loc_886
_actionCtr = 0;
Dialog::showMessage(0xE, hotspotId());
return PC_UNKNOWN;
}
} else {
_actionCtr = 1;
if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
// TODO: Extra Checks
Dialog::showMessage(5, hotspotId());
return PC_INITIAL;
}
}
if (characterWalkingCheck(hotspot)) {
return PC_INITIAL;
} else {
actionPrecheck3(hotspot);
return PC_EXECUTE;
}
}
void Hotspot::actionPrecheck3(HotspotData *hotspot) {
_actionCtr = 0;
if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
// TODO: HS[44]=8, HS[42]=1E, HS[50]=ID
}
}
bool Hotspot::characterWalkingCheck(HotspotData *hotspot) {
int16 xp, yp;
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;
} else {
xp = hotspot->walkX;
yp = hotspot->walkY & 0x7fff;
if ((hotspot->walkY & 0x8000) != 0) {
// Special handling for walking
// if (((xp >> 3) != (x() >> 3)) ||
// ((((y() + heightCopy()) >> 3) - 1) != (yp >> 3))) {
if ((ABS(xp - x()) > 8) ||
(ABS(yp - (y() + heightCopy())) > 8)) {
walkTo(xp, yp);
return true;
} else {
return false;
}
}
}
// Default walking handling
// TODO: ANIM[27h] = 1 if hotspot has walk co-ordinates
if ((ABS(x() - xp) >= 8) ||
(ABS(y() + heightCopy() - yp - 1) >= 19)) {
walkTo(xp, yp);
return true;
}
return false;
}
bool Hotspot::doorCloseCheck(uint16 doorId) {
Resources &res = Resources::getReference();
Hotspot *doorHotspot = res.getActiveHotspot(doorId);
if (!doorHotspot) {
warning("Hotspot %xh is not currently active", doorId);
return true;
}
Rect bounds(doorHotspot->x(), doorHotspot->y() + doorHotspot->heightCopy()
- doorHotspot->yCorrection() - doorHotspot->charRectY(),
doorHotspot->x() + doorHotspot->widthCopy(),
doorHotspot->y() + doorHotspot->heightCopy() + doorHotspot->charRectY());
// Loop through active hotspots
HotspotList::iterator i;
HotspotList &lst = res.activeHotspots();
for (i = lst.begin(); i != lst.end(); ++i) {
Hotspot *hsCurrent = *i;
// Skip entry if it's the door or the character
if ((hsCurrent->hotspotId() == hotspotId()) ||
(hsCurrent->hotspotId() == doorHotspot->hotspotId()))
continue;
// Skip entry if it doesn't meet certain criteria
if ((hsCurrent->layer() == 0) ||
(hsCurrent->roomNumber() != doorHotspot->roomNumber()) ||
(hsCurrent->hotspotId() < PLAYER_ID) ||
((hsCurrent->hotspotId() >= 0x408) && (hsCurrent->hotspotId() < 0x2710)))
continue;
// Also skip entry if special Id
if ((hsCurrent->hotspotId() == 0xfffe) || (hsCurrent->hotspotId() == 0xffff))
continue;
// Check the dimensions of the animation
if ((hsCurrent->x() < bounds.right) &&
((hsCurrent->x() + hsCurrent->widthCopy()) > bounds.left) &&
((hsCurrent->y() + hsCurrent->heightCopy() + hsCurrent->charRectY()) >= bounds.top) &&
((hsCurrent->y() + hsCurrent->heightCopy() - hsCurrent->charRectY()
- hsCurrent->yCorrection()) > bounds.bottom)) {
// Return false - the door can't be closed
return false;
}
}
// No blocking characters, so return true that the door can be closed
return true;
}
/*-------------------------------------------------------------------------*/
void Hotspot::doAction(Action action, HotspotData *hotspot) {
switch (action) {
case GET:
doGet(hotspot);
break;
case PUSH:
case PULL:
case OPERATE:
doOperate(hotspot, action);
break;
case OPEN:
doOpen(hotspot);
break;
case CLOSE:
doClose(hotspot);
break;
case LOCK:
doLockUnlock(hotspot);
break;
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();
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();
break;
case BRIBE:
doBribe(hotspot);
break;
case EXAMINE:
doExamine(hotspot);
break;
default:
doSimple(hotspot, action);
break;
}
}
void Hotspot::doGet(HotspotData *hotspot) {
Resources &res = Resources::getReference();
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result !=PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GET);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
return;
}
if (sequenceOffset != 0) {
uint16 execResult = Script::execute(sequenceOffset);
if (execResult == 1) return;
else if (execResult != 0) {
Dialog::showMessage(execResult, hotspotId());
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, Action action) {
Resources &res = Resources::getReference();
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
_actionCtr = 0;
faceHotspot(hotspot);
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else {
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset > 1)
Dialog::showMessage(sequenceOffset, hotspotId());
}
}
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
Dialog::showMessage(4, hotspotId());
stopWalking();
return;
}
}
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
_actionCtr = 0;
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, OPEN);
if (sequenceOffset >= 0x8000) {
// Message to display
Dialog::showMessage(sequenceOffset, hotspotId());
return;
}
if (sequenceOffset != 0) {
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset == 1) return;
if (sequenceOffset != 0) {
// TODO: HS[60h] check
Dialog::showMessage(sequenceOffset, hotspotId());
return;
}
}
joinRec = res.getExitJoin(hotspot->hotspotId);
if (joinRec->blocked) {
joinRec->blocked = 0;
if (hotspotId() != PLAYER_ID) {
// TODO: HS[44h]=3, HS[42h]W = 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
Dialog::showMessage(3, hotspotId());
stopWalking();
return;
}
}
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
_actionCtr = 0;
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, CLOSE);
if (sequenceOffset >= 0x8000) {
// Message to display
Dialog::showMessage(sequenceOffset, hotspotId());
} else if (sequenceOffset != 0) {
// Otherwise handle script
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset != 0) {
Dialog::showMessage(sequenceOffset, hotspotId());
return;
}
}
joinRec = res.getExitJoin(hotspot->hotspotId);
if (!joinRec->blocked) {
// Close the door
if (!doorCloseCheck(joinRec->hotspot1Id) ||
!doorCloseCheck(joinRec->hotspot2Id)) {
// A character is preventing the door from closing
Dialog::showMessage(2, hotspotId());
} else {
// Flag the door as closed
joinRec->blocked = 1;
}
}
}
void Hotspot::doUse(HotspotData *hotspot) {
Resources &res = Resources::getReference();
uint16 usedId = _currentActions.top().usedId();
HotspotData *usedHotspot = res.getHotspot(usedId);
ValueTableData &fields = res.fieldList();
fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
fields.setField(USE_HOTSPOT_ID, usedHotspot->hotspotId);
if (usedHotspot->roomNumber != hotspotId()) {
// Item to be used is not in character's inventory - say "What???"
stopWalking();
Dialog::showMessage(0xF, hotspotId());
return;
}
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
stopWalking();
// TODO: If character=3E9h, HS[-1]=28h, HS[1Fh]=50h
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, USE);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else if (sequenceOffset == 0) {
Dialog::showMessage(17, hotspotId());
} else {
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset != 0)
Dialog::showMessage(sequenceOffset, hotspotId());
}
}
void Hotspot::doGive(HotspotData *hotspot) {
Resources &res = Resources::getReference();
uint16 usedId = _currentActions.top().usedId();
HotspotData *usedHotspot = res.getHotspot(usedId);
ValueTableData &fields = res.fieldList();
fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
fields.setField(USE_HOTSPOT_ID, usedId);
if (usedHotspot->roomNumber != hotspotId()) {
// Item to be used is not in character's inventory - say "What???"
stopWalking();
Dialog::showMessage(0xF, hotspotId());
return;
}
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
stopWalking();
if ((hotspotId() != 0x412) || (usedId != 0x2710))
Dialog::showMessage(7, hotspotId());
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GIVE);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else if (sequenceOffset != 0) {
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset == NOONE_ID) {
// TODO
} else if (sequenceOffset == 0) {
// Move item into character's inventory
HotspotData *usedItem = res.getHotspot(usedId);
usedItem->roomNumber = hotspotId();
} else if (sequenceOffset > 1) {
Dialog::showMessage(result, hotspotId());
}
}
}
void Hotspot::doTalkTo(HotspotData *hotspot) {
Resources &res = Resources::getReference();
uint16 usedId = _currentActions.top().usedId();
ValueTableData &fields = res.fieldList();
fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
fields.setField(USE_HOTSPOT_ID, usedId);
if ((hotspot->hotspotId != SKORL_ID) && ((hotspot->roomNumber != 28) ||
(hotspot->hotspotId != 0x3EB))) {
HotspotPrecheckResult result = actionPrecheck(hotspot);
if (result == PC_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
}
faceHotspot(hotspot);
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TALK_TO);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
return;
}
if (sequenceOffset != 0) {
uint16 result = Script::execute(sequenceOffset);
if (result != 0) {
stopWalking();
return;
}
}
// Start talking with character
startTalk(hotspot);
}
void Hotspot::doTell(HotspotData *hotspot) {
// TODO
}
void Hotspot::doLook() {
stopWalking();
Dialog::show(Room::getReference().descId());
}
uint16 hotspotLookAtList[] = {0x411, 0x412, 0x41F, 0x420, 0x421, 0x422, 0x426,
0x427, 0x428, 0x429, 0x436, 0x437, 0};
void Hotspot::doLookAt(HotspotData *hotspot) {
Resources &res = Resources::getReference();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, LOOK_AT);
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
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_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
}
}
faceHotspot(hotspot);
_actionCtr = 0;
stopWalking();
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} 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::doLookThrough(HotspotData *hotspot) {
Resources &res = Resources::getReference();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, LOOK_THROUGH);
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
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_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
}
}
faceHotspot(hotspot);
_actionCtr = 0;
stopWalking();
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} 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) {
// TODO
}
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);
// Make sure item is in character's inventory
if (hotspot->hotspotId != hotspotId()) {
Dialog::showMessage(0xF, hotspotId());
return;
}
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, DRINK);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else if (sequenceOffset == 0) {
Dialog::showMessage(22, hotspotId());
} 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) {
Dialog::showMessage(result, hotspotId());
}
}
}
// doStatus
// Handle the status window
void Hotspot::doStatus() {
char buffer[MAX_DESC_SIZE];
uint16 numItems = 0;
StringData &strings = StringData::getReference();
Resources &resources = Resources::getReference();
Room &room = Room::getReference();
warning("status0 %d %d", room.cursorState(), resources.getCurrentAction());
room.update();
stopWalking();
strings.getString(room.roomNumber(), buffer, NULL, NULL);
strcat(buffer, "\n\nYou are carrying ");
// Scan through the list and add in any items assigned to the player
HotspotDataList &list = resources.hotspotData();
HotspotDataList::iterator i;
for (i = list.begin(); i != list.end(); ++i) {
HotspotData *rec = *i;
if (rec->roomNumber == PLAYER_ID) {
if (numItems++ == 0) strcat(buffer, ": ");
else strcat(buffer, ", ");
strings.getString(rec->nameId, buffer + strlen(buffer), NULL, NULL);
}
}
warning("status1 %d", room.cursorState());
// If there were no items, add in the word 'nothing'
if (numItems == 0) strcat(buffer, "nothing.");
// If the player has money, add it in
uint16 numGroats = resources.fieldList().numGroats();
if (numGroats > 0) {
sprintf(buffer + strlen(buffer), "\n\nYou have %d groat", numGroats);
if (numGroats > 1) strcat(buffer, "s");
}
// Display the dialog
Screen &screen = Screen::getReference();
Mouse &mouse = Mouse::getReference();
mouse.cursorOff();
warning("status2 %d", room.cursorState());
Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, buffer);
s->copyToScreen(INFO_DIALOG_X, (FULL_SCREEN_HEIGHT-s->height())/2);
warning("status3");
Events::getReference().waitForPress();
warning("status4");
screen.update();
mouse.cursorOn();
}
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_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
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
}
// TODO: call to talk_setup
faceHotspot(hotspot);
_actionCtr = 0;
stopWalking();
sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, BRIBE);
if (sequenceOffset != 0) {
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset != 0) return;
}
// TODO: handle character message display
}
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);
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, EXAMINE);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else {
if (sequenceOffset != 0)
sequenceOffset = Script::execute(sequenceOffset);
if (sequenceOffset == 0) {
Dialog::show(hotspot->descId);
}
}
}
void Hotspot::doLockUnlock(HotspotData *hotspot) {
Action action = _currentActions.top().hotspotAction();
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_INITIAL) return;
else if (result != PC_EXECUTE) {
stopWalking();
return;
}
faceHotspot(hotspot);
stopWalking();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else {
if (sequenceOffset != 0)
Script::execute(sequenceOffset);
}
}
void Hotspot::doSimple(HotspotData *hotspot, Action action) {
Resources &res = Resources::getReference();
uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
if (sequenceOffset >= 0x8000) {
Dialog::showMessage(sequenceOffset, hotspotId());
} else if (sequenceOffset != 0) {
Script::execute(sequenceOffset);
}
}
void Hotspot::startTalk(HotspotData *charHotspot) {
Resources &res = Resources::getReference();
uint16 talkIndex;
setTickProc(TALK_TICK_PROC_ID); // Set for providing talk listing
// Get offset of talk set to use
TalkHeaderData *headerEntry = res.getTalkHeader(charHotspot->hotspotId);
uint16 talkOffset;
// Calculate talk index to use
if (charHotspot->nameId == STRANGER_ID)
talkIndex = 0;
else
talkIndex = res.fieldList().getField(TALK_INDEX) + 1;
talkOffset = headerEntry->getEntry(talkIndex);
// Set the active talk data
res.setTalkStartEntry(0);
res.setTalkData(talkOffset);
if (!res.getTalkData())
error("Talk failed - invalid offset: Character=%xh, index=%d, offset=%xh",
charHotspot->hotspotId, talkIndex, talkOffset);
}
/*------------------------------------------------------------------------*/
HandlerMethodPtr HotspotTickHandlers::getHandler(uint16 procOffset) {
switch (procOffset) {
case 0x4F82:
return standardCharacterAnimHandler;
case 0x7F3A:
return standardAnimHandler;
case 0x7207:
return roomExitAnimHandler;
case PLAYER_TICK_PROC_ID:
return playerAnimHandler;
case 0x7F69:
return droppingTorchAnimHandler;
case 0x8009:
return fireAnimHandler;
case TALK_TICK_PROC_ID:
return talkAnimHandler;
case 0x8241:
return headAnimationHandler;
default:
return defaultHandler;
}
}
void HotspotTickHandlers::defaultHandler(Hotspot &h) {
// No handling done
}
void HotspotTickHandlers::standardAnimHandler(Hotspot &h) {
if (h.tickCtr() > 0)
h.setTickCtr(h.tickCtr() - 1);
else
h.executeScript();
}
void HotspotTickHandlers::standardCharacterAnimHandler(Hotspot &h) {
}
void HotspotTickHandlers::roomExitAnimHandler(Hotspot &h) {
RoomExitJoinData *rec = Resources::getReference().getExitJoin(h.hotspotId());
if (!rec) return;
byte *currentFrame, *destFrame;
if (rec->hotspot1Id == h.hotspotId()) {
currentFrame = &rec->h1CurrentFrame;
destFrame = &rec->h1DestFrame;
} else {
currentFrame = &rec->h2CurrentFrame;
destFrame = &rec->h2DestFrame;
}
if ((rec->blocked != 0) && (*currentFrame != *destFrame)) {
// Closing the door
h.setOccupied(true);
++*currentFrame;
if (*currentFrame == *destFrame) {
// TODO: play closed door sound
}
} else if ((rec->blocked == 0) && (*currentFrame != 0)) {
// Opening the door
h.setOccupied(false);
--*currentFrame;
if (*currentFrame == *destFrame) {
//TODO: Check against script val 88 and play sound
}
}
h.setFrameNumber(*currentFrame);
}
void HotspotTickHandlers::playerAnimHandler(Hotspot &h) {
Resources &res = Resources::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;
// TODO: handle talk dialogs countdown if necessary
numImpinging = Support::findIntersectingCharacters(h, impingingList);
if (h.skipFlag()) {
if (numImpinging > 0)
return;
h.setSkipFlag(false);
}
// If a frame countdown is in progress, then decrement and exit
if (h.frameCtr() > 0) {
h.decrFrameCtr();
return;
}
CurrentAction action = actions.action();
switch (action) {
case NO_ACTION:
// Make sure there is no longer any destination
h.setDestHotspot(0);
break;
case DISPATCH_ACTION:
// Dispatch an action
h.setDestHotspot(0);
hsAction = actions.top().hotspotAction();
hotspotId = actions.top().hotspotId();
hotspot = (hotspotId == 0) ? NULL : res.getHotspot(hotspotId);
h.doAction(hsAction, hotspot);
break;
case EXEC_HOTSPOT_SCRIPT:
// A hotspot script is in progress for the player, so don't interrupt
if (h.executeScript()) {
// Script is finished, so pop of the execution action
actions.pop();
}
break;
case START_WALKING:
// Start the player walking to the given destination
h.setOccupied(false);
// Reset the path finder / walking sequence
pathFinder.reset(paths);
// Set current action to processing walking path
actions.pop();
h.setCurrentAction(PROCESSING_PATH);
// Deliberate fall through to processing walking path
case PROCESSING_PATH:
if (!pathFinder.process()) break;
// Pathfinding is now complete
actions.pop();
if (pathFinder.isEmpty()) {
mouse.setCursorNum(CURSOR_ARROW);
break;
}
if (mouse.getCursorNum() != CURSOR_CAMERA)
mouse.setCursorNum(CURSOR_ARROW);
h.setCurrentAction(WALKING);
h.setPosition(h.x(), h.y() & 0xFFF8);
// Deliberate fall through to walking
case WALKING:
// The character is currently moving
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.pop();
break;
}
}
if (h.walkingStep()) {
// Walking done
h.currentActions().pop();
}
// Check for whether need to change room
Support::checkRoomChange(h);
break;
}
}
void HotspotTickHandlers::droppingTorchAnimHandler(Hotspot &h) {
if (h.tickCtr() > 0)
h.setTickCtr(h.tickCtr() - 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);
// Enable the fire and activate its animation
HotspotData *fire = res.getHotspot(0x418);
fire->flags |= 0x80;
fire->loadOffset = 0x7172;
res.activateHotspot(0x418);
}
}
}
void HotspotTickHandlers::fireAnimHandler(Hotspot &h) {
standardAnimHandler(h);
h.setOccupied(true);
}
// Special variables used across multiple calls to talkAnimHandler
static TalkEntryData *_talkResponse;
void HotspotTickHandlers::talkAnimHandler(Hotspot &h) {
// Talk handler
Resources &res = Resources::getReference();
Room &room = Room::getReference();
ValueTableData &fields = res.fieldList();
StringData &strings = StringData::getReference();
Screen &screen = Screen::getReference();
Mouse &mouse = Mouse::getReference();
TalkSelections &talkSelections = res.getTalkSelections();
TalkData *data = res.getTalkData();
TalkEntryList &entries = data->entries;
char buffer[MAX_DESC_SIZE];
Rect r;
int lineNum, numLines;
int selectedLine, responseNumber;
bool showSelections, keepTalkingFlag;
TalkEntryList::iterator i;
TalkEntryData *entry;
uint16 result, descId, charId;
switch (res.getTalkState()) {
case TALK_NONE:
// Handle initial setup of talking options
// Reset talk entry pointer list
for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum)
talkSelections[lineNum] = NULL;
// Loop through list to find entries to display
_talkResponse = NULL;
numLines = 0;
showSelections = false;
i = entries.begin();
for (lineNum = 0; lineNum < res.getTalkStartEntry(); ++lineNum)
if (i != entries.end()) ++i;
for (; i != entries.end(); ++i) {
entry = *i;
uint8 flags = (uint8) (entry->descId >> 14);
if (flags == 3)
// Skip the entry
continue;
uint16 sequenceOffset = entry->preSequenceId & 0x3fff;
bool showLine = sequenceOffset == 0;
if (!showLine)
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);
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, NULL, NULL);
// Clear line
r.top = (lineNum + 1) * MENUBAR_Y_SIZE;
r.bottom = r.top + MENUBAR_Y_SIZE - 1;
screen.screen().fillRect(r, 0);
// Display line
byte colour = (lineNum+1 == selectedLine) ?
DIALOG_WHITE_COLOUR : DIALOG_TEXT_COLOUR;
screen.screen().writeString(r.left, r.top, buffer, false, colour);
}
if ((!mouse.lButton() && !mouse.rButton()) || (selectedLine == 0))
break;
// Set the talk response index to use
res.setTalkSelection(selectedLine);
res.setTalkState(TALK_RESPOND);
break;
case TALK_RESPOND:
// Handle initial response to show the question in a talk dialog if needed
selectedLine = res.getTalkSelection();
entry = talkSelections[selectedLine-1];
descId = entry->descId & 0x3fff;
entry->descId |= 0x4000;
if (descId != TALK_MAGIC_ID) {
// Set up to display the question in a talk dialog
room.setTalkDialog(PLAYER_ID, descId);
res.setTalkState(TALK_RESPONSE_WAIT);
} else {
res.setTalkState(TALK_RESPOND_2);
}
break;
case TALK_RESPOND_2:
if (!_talkResponse) {
// Handles bringing up the response talk dialog
charId = fields.getField(ACTIVE_HOTSPOT_ID);
selectedLine = res.getTalkSelection();
entry = talkSelections[selectedLine-1];
responseNumber = entry->postSequenceId;
if ((responseNumber & 0x8000) != 0)
responseNumber = Script::execute(responseNumber & 0x7fff);
do {
_talkResponse = res.getTalkData()->getResponse(responseNumber);
if (!_talkResponse->preSequenceId) break;
responseNumber = Script::execute(_talkResponse->preSequenceId);
} while (responseNumber != TALK_RESPONSE_MAGIC_ID);
descId = _talkResponse->descId;
if ((descId & 0x8000) != 0)
descId = Script::execute(descId & 0x7fff);
if (descId != TALK_MAGIC_ID) {
room.setTalkDialog(charId, descId);
res.setTalkState(TALK_RESPONSE_WAIT);
return;
}
}
// Handle checking whether to keep talking
result = _talkResponse->postSequenceId;
if (result == 0xffff)
keepTalkingFlag = false;
else {
if ((result & 0x8000) == 0)
keepTalkingFlag = true;
else {
result = Script::execute(result & 0x7fff);
keepTalkingFlag = result != 0xffff;
}
}
if (keepTalkingFlag) {
// Reset for loading the next set of talking options
res.setTalkStartEntry(result);
res.setTalkState(TALK_NONE);
} else {
// End the conversation
res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID);
res.setTalkData(0);
res.setCurrentAction(NONE);
res.setTalkState(TALK_NONE);
}
break;
case TALK_RESPONSE_WAIT:
// Keeps waiting while a talk dialog is active
break;
}
}
void HotspotTickHandlers::headAnimationHandler(Hotspot &h) {
Resources &res = Resources::getReference();
Hotspot *character = res.getActiveHotspot(PLAYER_ID);
uint16 frameNumber = 0;
if (character->y() < 79) {
//character = res.getActiveHotspot(RATPOUCH_ID);
frameNumber = 1;
} else {
if (character->x() < 72) frameNumber = 0;
else if (character->x() < 172) frameNumber = 1;
else frameNumber = 2;
}
h.setFrameNumber(frameNumber);
}
/*-------------------------------------------------------------------------*/
/* Miscellaneous classes */
/* */
/*-------------------------------------------------------------------------*/
// WalkingActionEntry class
// This method performs rounding of the number of steps depending on direciton
int WalkingActionEntry::numSteps() {
switch (_direction) {
case UP:
case DOWN:
return (_numSteps + 1) >> 1;
case LEFT:
case RIGHT:
return (_numSteps + 3) >> 2;
default:
return 0;
}
}
// PathFinder class
PathFinder::PathFinder(Hotspot *h) {
_hotspot = h;
_list.clear();
_stepCtr = 0;
}
void PathFinder::reset(RoomPathsData &src) {
_stepCtr = 0;
_list.clear();
src.decompress(_layer, _hotspot->widthCopy());
_inProgress = false;
_countdownCtr = PATHFIND_COUNTDOWN;
}
// 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
bool 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;
if (!_inProgress) {
// Following code only done during first call to method
_inProgress = true;
initVars();
_xCurrent >>= 3; _yCurrent >>= 3;
_xDestCurrent >>= 3; _yDestCurrent >>= 3;
if ((_xCurrent == _xDestCurrent) && (_yCurrent == _yDestCurrent)) {
// Very close move
if (_xDestPos > 0)
add(RIGHT, _xDestPos);
else if (_xDestPos < 0)
add(LEFT, -_xDestPos);
goto final_step;
}
// Path finding
_destX >>= 3;
_destY >>= 3;
_pSrc = &_layer[(_yCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xCurrent];
_pDest = &_layer[(_yDestCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xDestCurrent];
// Flag starting/ending cells
*_pSrc = 1;
_destOccupied = *_pDest != 0;
_result = _destOccupied ? PF_DEST_OCCUPIED : PF_OK;
*_pDest = 0;
// Set up the current pointer, adjusting away from edges if necessary
if (_xCurrent >= _xDestCurrent) {
_xChangeInc = -1;
_xChangeStart = ROOM_PATHS_WIDTH;
} else {
_xChangeInc = 1;
_xChangeStart = 1;
}
if (_yCurrent >= _yDestCurrent) {
_yChangeInc = -1;
_yChangeStart = ROOM_PATHS_HEIGHT;
} else {
_yChangeInc = 1;
_yChangeStart = 1;
}
}
// Major loop to populate data
_cellPopulated = false;
while (1) {
// Loop through to process cells in the given area
if (!returnFlag) _yCtr = 0;
while (returnFlag || (_yCtr < ROOM_PATHS_HEIGHT)) {
if (!returnFlag) _xCtr = 0;
while (returnFlag || (_xCtr < ROOM_PATHS_WIDTH)) {
if (!returnFlag) {
processCell(&_layer[(_yChangeStart + _yCtr * _yChangeInc) * DECODED_PATHS_WIDTH +
(_xChangeStart + _xCtr * _xChangeInc)]);
if (breakFlag && (_countdownCtr <= 0)) return false;
} 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_NO_PATH;
scanFlag = true;
break;
}
}
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) {
_result = PF_NO_WALK;
clear();
return true;
}
_pDest = pTemp;
}
// ****DEBUG****
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
for (int stageCtr = 0; stageCtr < 3; ++stageCtr) {
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;
// Clear out any previously determined directions
clear();
}
// Add a final move if necessary
if (_result == PF_OK) {
if (_xDestPos < 0)
addBack(LEFT, -_xDestPos);
else if (_xDestPos > 0)
addBack(RIGHT, _xDestPos);
}
final_step:
if (_xPos < 0) add(LEFT, -_xPos);
else if (_xPos > 0) add(RIGHT, _xPos);
return true;
}
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 - 8;
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;
}
/*-------------------------------------------------------------------------*/
/* Support methods */
/* */
/*-------------------------------------------------------------------------*/
// finds a list of character animations whose base area are impinging
// that of the specified character (ie. are bumping into them)
int Support::findIntersectingCharacters(Hotspot &h, uint16 *charList) {
int numImpinging = 0;
Resources &res = Resources::getReference();
Rect r;
r.left = h.x();
r.right = h.x() + h.widthCopy();
r.top = h.y() + h.heightCopy() - h.yCorrection() - h.charRectY();
r.bottom = h.y() + h.heightCopy() + h.charRectY();
HotspotList::iterator i;
for (i = res.activeHotspots().begin(); i != res.activeHotspots().end(); ++i) {
Hotspot &hotspot = **i;
// Check for basic reasons to skip checking the animation
if ((h.hotspotId() == hotspot.hotspotId()) || (hotspot.layer() == 0) ||
(h.roomNumber() != hotspot.roomNumber()) || (h.hotspotId() >= FIRST_NONCHARACTER_ID) ||
h.skipFlag()) continue;
// TODO: See why si+ANIM_HOTSPOT_OFFSET compared aganst di+ANIM_VOICE_CTR
if ((hotspot.x() > r.right) || (hotspot.x() + hotspot.widthCopy() >= r.left) ||
(hotspot.y() + hotspot.heightCopy() + hotspot.charRectY() < r.top) ||
(hotspot.y() + hotspot.heightCopy() - hotspot.charRectY()
- hotspot.yCorrection() >= r.bottom))
continue;
// Add hotspot Id to list
if (numImpinging == MAX_NUM_IMPINGING)
error("Exceeded maximum allowable number of impinging characters");
*charList++ = hotspot.hotspotId();
++numImpinging;
}
return numImpinging;
}
// Returns true if any other characters are intersecting the specified one
bool Support::checkForIntersectingCharacter(Hotspot &h) {
uint16 tempList[MAX_NUM_IMPINGING];
return findIntersectingCharacters(h, tempList) != 0;
}
// Check whether a character needs to change the room they're in
void 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) {
if (exitRec->sequenceOffset != 0xffff) {
Script::execute(exitRec->sequenceOffset);
} else {
Support::characterChangeRoom(h, exitRec->roomNumber,
exitRec->x, exitRec->y, exitRec->direction);
}
}
}
void Support::characterChangeRoom(Hotspot &h, uint16 roomNumber,
int16 newX, int16 newY, Direction dir) {
ValueTableData &fields = Resources::getReference().fieldList();
if (h.hotspotId() == PLAYER_ID) {
// Room change code for the player
h.setDirection(dir);
PlayerNewPosition &p = fields.playerNewPos();
p.roomNumber = roomNumber;
p.position.x = newX;
p.position.y = newY - 48;
// TODO: Call sub_136, and if !ZF reset new room number back to 0
} else {
// Any other character changing room
if (checkForIntersectingCharacter(h)) {
// Character is blocked, so abort room change
h.currentActions().clear();
} else {
// Handle character room change
h.setRoomNumber(roomNumber);
h.setPosition((newX & 0xfff8) || 5, (newY - h.heightCopy()) & 0xfff8);
h.setSkipFlag(true);
h.setDirection(dir);
h.currentActions().pop();
}
}
}
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));
}
} // end of namespace Lure