1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "lure/hotspots.h"
24 #include "lure/decode.h"
25 #include "lure/palette.h"
26 #include "lure/disk.h"
27 #include "lure/res.h"
28 #include "lure/scripts.h"
29 #include "lure/room.h"
30 #include "lure/strings.h"
31 #include "lure/res_struct.h"
32 #include "lure/events.h"
33 #include "lure/game.h"
34 #include "lure/fights.h"
35 #include "lure/sound.h"
36 #include "lure/lure.h"
37 #include "common/endian.h"
38 
39 namespace Lure {
40 
Hotspot(HotspotData * res)41 Hotspot::Hotspot(HotspotData *res): _pathFinder(this) {
42 	Resources &resources = Resources::getReference();
43 	bool isEGA = LureEngine::getReference().isEGA();
44 
45 	_data = res;
46 	_anim = NULL;
47 	_frames = NULL;
48 	_numFrames = 0;
49 	_persistant = false;
50 	_direction = NO_DIRECTION;
51 
52 	_hotspotId = res->hotspotId;
53 	_originalId = res->hotspotId;
54 	_roomNumber = res->roomNumber;
55 	_startX = res->startX;
56 	_startY = res->startY;
57 	_destX = res->startX;
58 	_destY = res->startY;
59 	_destHotspotId = 0;
60 	_frameWidth = res->width;
61 	_frameStartsUsed = false;
62 	_height = res->height;
63 	_width = res->width;
64 	_heightCopy = res->heightCopy;
65 	_widthCopy = res->widthCopy;
66 	_yCorrection = res->yCorrection;
67 	_talkX = res->talkX;
68 	_talkY = res->talkY;
69 	_layer = res->layer;
70 	_hotspotScriptOffset = res->hotspotScriptOffset;
71 	_frameCtr = res->tickTimeout;
72 	_tempDest.counter = 0;
73 	_colorOffset = isEGA ? 0 : res->colorOffset;
74 
75 	_override = resources.getHotspotOverride(res->hotspotId);
76 	setAnimation(_data->animRecordId);
77 	_tickHandler = _tickHandlers.getHandler(_data->tickProcId);
78 	_nameBuffer[0] = '\0';
79 
80 	_skipFlag = false;
81 	_charRectY = 0;
82 	_voiceCtr = 0;
83 	_blockedOffset = 0;
84 	_exitCtr = 0;
85 	_walkFlag = false;
86 	_startRoomNumber = 0;
87 	_supportValue = 0;
88 }
89 
90 // Special constructor used to create a voice hotspot
91 
Hotspot(Hotspot * character,uint16 objType)92 Hotspot::Hotspot(Hotspot *character, uint16 objType): _pathFinder(this) {
93 	assert(character);
94 
95 	_originalId = objType;
96 	_data = NULL;
97 	_anim = NULL;
98 	_frames = NULL;
99 	_numFrames = 0;
100 	_persistant = false;
101 	_hotspotId = 0xffff;
102 	_override = NULL;
103 	_colorOffset = 0;
104 	_destHotspotId = character->hotspotId();
105 	_blockedOffset = 0;
106 	_exitCtr = 0;
107 	_voiceCtr = 0;
108 	_walkFlag = false;
109 	_skipFlag = false;
110 	_direction = NO_DIRECTION;
111 
112 	switch (objType) {
113 	case VOICE_ANIM_IDX:
114 		_roomNumber = character->roomNumber();
115 		_destHotspotId = character->hotspotId();
116 		_startX = character->x() + character->talkX() + 12;
117 		_startY = character->y() + character->talkY() - 18;
118 		_destX = _startX;
119 		_destY = _startY;
120 		_layer = 1;
121 		_height = 18;
122 		_width = 32;
123 		_heightCopy = character->height() + 14;
124 		_widthCopy = 24;
125 		_yCorrection = 1;
126 
127 		_frameCtr = 0;
128 		_voiceCtr = 40;
129 
130 		_tickHandler = _tickHandlers.getHandler(VOICE_TICK_PROC_ID);
131 		setAnimationIndex(VOICE_ANIM_INDEX);
132 		break;
133 
134 	case PUZZLED_ANIM_IDX:
135 	case EXCLAMATION_ANIM_IDX:
136 		_roomNumber = character->roomNumber();
137 		_hotspotId = 0xfffe;
138 		_startX = character->x() + character->talkX() + 12;
139 		_startY = character->y() + character->talkY() - 20;
140 		_width = 32;
141 		_height = 18;
142 		_widthCopy = 19;
143 		_heightCopy = 18 + character->heightCopy();
144 		_layer = 1;
145 		_persistant = false;
146 		_yCorrection = 1;
147 		_voiceCtr = CONVERSE_COUNTDOWN_SIZE;
148 
149 		_destHotspotId = character->hotspotId();
150 		_tickHandler = _tickHandlers.getHandler(PUZZLED_TICK_PROC_ID);
151 		setAnimationIndex(VOICE_ANIM_INDEX);
152 		setFrameNumber(objType == PUZZLED_ANIM_IDX ? 1 : 2);
153 
154 		character->setFrameCtr(_voiceCtr);
155 		break;
156 
157 	default:
158 		break;
159 	}
160 
161 	_frameWidth = _width;
162 	_frameStartsUsed = false;
163 	_nameBuffer[0] = '\0';
164 }
165 
Hotspot()166 Hotspot::Hotspot(): _pathFinder(NULL) {
167 	_data = NULL;
168 	_anim = NULL;
169 	_frames = NULL;
170 	_numFrames = 0;
171 	_persistant = false;
172 	_hotspotId = 0xffff;
173 	_override = NULL;
174 	_colorOffset = 0;
175 	_destHotspotId = 0;
176 	_blockedOffset = 0;
177 	_exitCtr = 0;
178 	_voiceCtr = 0;
179 	_walkFlag = false;
180 	_skipFlag = false;
181 	_roomNumber = 0;
182 	_startX = 0;
183 	_startY = 0;
184 	_destX = 0;
185 	_destY = 0;
186 	_layer = 0;
187 	_height = 0;
188 	_width = 0;
189 	_heightCopy = 0;
190 	_widthCopy = 0;
191 	_yCorrection = 0;
192 	_frameCtr = 0;
193 	_tickHandler = NULL;
194 	_frameWidth = _width;
195 	_frameStartsUsed = false;
196 	_tempDest.counter = 0;
197 	_direction = NO_DIRECTION;
198 }
199 
~Hotspot()200 Hotspot::~Hotspot() {
201 	delete _frames;
202 }
203 
setAnimation(uint16 newAnimId)204 void Hotspot::setAnimation(uint16 newAnimId) {
205 	Resources &r = Resources::getReference();
206 	HotspotAnimData *tempAnim;
207 	_animId = newAnimId;
208 	if (_data)
209 		_data->animRecordId = newAnimId;
210 
211 	if (newAnimId == 0)
212 		tempAnim = NULL;
213 	else {
214 		tempAnim = r.getAnimation(newAnimId);
215 		if (tempAnim == NULL)
216 			error("Hotspot %xh tried to set non-existent Animation Id: %xh", _hotspotId, newAnimId);
217 	}
218 
219 	setAnimation(tempAnim);
220 }
221 
setAnimationIndex(int animIndex)222 void Hotspot::setAnimationIndex(int animIndex) {
223 	Resources &r = Resources::getReference();
224 
225 	// Get the animation specified
226 	HotspotAnimList::iterator a = r.animRecords().begin();
227 	for (int i = 0; i < animIndex; i++)
228 		++a;
229 	HotspotAnimData *tempAnim = (*a).get();
230 
231 	_animId = tempAnim->animRecordId;
232 	if (_data)
233 		_data->animRecordId = tempAnim->animRecordId;
234 
235 	setAnimation(tempAnim);
236 }
237 
238 struct SizeOverrideEntry {
239 	int animIndex;
240 	uint16 width, height;
241 };
242 
243 static const SizeOverrideEntry sizeOverrides[] = {
244 	{BLACKSMITH_DEFAULT_ANIM_INDEX, 32, 48},
245 	{BLACKSMITH_HAMMERING_ANIM_INDEX, 48, 47},
246 	{0, 0, 0}
247 };
248 
setAnimation(HotspotAnimData * newRecord)249 void Hotspot::setAnimation(HotspotAnimData *newRecord) {
250 	Disk &disk = Disk::getReference();
251 	Resources &res = Resources::getReference();
252 	uint16 tempWidth, tempHeight;
253 	int16 xStart;
254 	int animIndex = res.getAnimationIndex(newRecord);
255 
256 	if (_frames) {
257 		delete _frames;
258 		_frames = NULL;
259 	}
260 	_anim = NULL;
261 	_numFrames = 0;
262 	_frameNumber = 0;
263 	if (!newRecord)
264 		return;
265 	if (!disk.exists(newRecord->animId))
266 		return;
267 
268 	// Scan for any size overrides - some animations get their size set after decoding, but
269 	// we want it in advance so we can decode the animation straight to a graphic surface
270 	const SizeOverrideEntry *p = &sizeOverrides[0];
271 	while ((p->animIndex != 0) && (p->animIndex != animIndex)) ++p;
272 	if (p->animIndex != 0)
273 		setSize(p->width, p->height);
274 
275 	_anim = newRecord;
276 	MemoryBlock *src = Disk::getReference().getEntry(_anim->animId);
277 
278 	uint16 numEntries = READ_LE_UINT16(src->data());
279 	uint16 *headerEntry = (uint16 *) (src->data() + 2);
280 	assert((numEntries >= 1) && (numEntries < 100));
281 
282 	// Calculate total needed size for output and create memory block to hold it
283 	uint32 totalSize = 0;
284 	for (uint16 ctr = 0; ctr < numEntries; ++ctr, ++headerEntry) {
285 		totalSize += (READ_LE_UINT16(headerEntry) + 31) / 32;
286 	}
287 	totalSize = (totalSize + 0x81) << 4;
288 	MemoryBlock *dest = Memory::allocate(totalSize);
289 
290 	uint32 srcStart = (numEntries + 1) * sizeof(uint16) + 6;
291 	AnimationDecoder::decode_data(src, dest, srcStart);
292 
293 	_numFrames = numEntries;
294 	_frameNumber = 0;
295 
296 	// Special handling need
297 	if (_hotspotId == RACK_SERF_ID) {
298 		_frameStartsUsed = true;
299 		_frames = new Surface(416, 27);
300 	} else {
301 		_frames = new Surface(_width * _numFrames, _height);
302 		_frameStartsUsed = false;
303 	}
304 	_frames->data().setBytes(_colorOffset, 0, _frames->data().size());
305 
306 	byte *pSrc = dest->data() + 0x40;
307 	byte *pDest;
308 	headerEntry = (uint16 *) (src->data() + 2);
309 	MemoryBlock &mDest = _frames->data();
310 	uint16 frameOffset = 0x40;
311 	uint16 *offsetPtr = (uint16 *) src->data();
312 
313 	tempWidth = _width;
314 	tempHeight = _height;
315 
316 	for (uint16 frameNumCtr = 0; frameNumCtr < _numFrames; ++frameNumCtr, ++headerEntry) {
317 
318 		if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0) {
319 			// For animations with an offset table, set the source pointer
320 			pSrc = dest->data() + frameOffset;
321 		}
322 
323 		if (_hotspotId == RACK_SERF_ID) {
324 			// Save the start of each frame for serf, since the size varies
325 			xStart = (frameNumCtr == 0) ? 0 : _frameStarts[frameNumCtr - 1] + tempWidth;
326 			_frameStarts[frameNumCtr] = xStart;
327 
328 			// Switch statement to handle varying size for different frames
329 			switch (frameNumCtr) {
330 			case 3:
331 				tempWidth = 48;
332 				tempHeight = 25;
333 				break;
334 			case 4:
335 				tempHeight = 26;
336 				break;
337 			case 5:
338 				tempWidth = 32;
339 				break;
340 			case 6:
341 				tempHeight = 27;
342 				break;
343 			case 7:
344 				tempWidth = 16;
345 				break;
346 			default:
347 				break;
348 			}
349 		} else {
350 			// Set the X Start based on the frame size
351 			xStart = frameNumCtr * _width;
352 		}
353 
354 		// Copy over the frame, applying the color offset to each nibble
355 		for (uint16 yPos = 0; yPos < tempHeight; ++yPos) {
356 			pDest = mDest.data() + yPos * _frames->width() + xStart;
357 
358 			for (uint16 xPos = 0; xPos < tempWidth / 2; ++xPos) {
359 				*pDest++ = _colorOffset + (*pSrc >> 4);
360 				*pDest++ = _colorOffset + (*pSrc & 0xf);
361 				++pSrc;
362 			}
363 		}
364 
365 		if ((newRecord->flags & PIXELFLAG_HAS_TABLE) != 0)
366 			frameOffset += (READ_LE_UINT16(++offsetPtr) >> 1);
367 	}
368 
369 	delete src;
370 	delete dest;
371 }
372 
copyTo(Surface * dest)373 void Hotspot::copyTo(Surface *dest) {
374 	int16 xPos = _startX;
375 	int16 yPos = _startY;
376 	uint16 hWidth = _frameWidth;
377 	uint16 hHeight = _height;
378 
379 	Common::Rect r(_frameNumber * hWidth, 0, (_frameNumber + 1) * hWidth - 1, hHeight - 1);
380 	if (_frameStartsUsed) {
381 		assert(_frameNumber < MAX_NUM_FRAMES);
382 		r.left = _frameStarts[_frameNumber];
383 		r.right = (_frameNumber == _numFrames - 1) ? _frames->width() - 1 :
384 			_frameStarts[_frameNumber + 1] - 1;
385 		r.bottom = _height - 1;
386 	}
387 
388 	// Handle clipping for X position
389 	if (xPos < 0) {
390 		if (xPos + hWidth <= 0)
391 			// Completely off screen, so don't display
392 			return;
393 
394 		// Reduce the source rectangle to only the on-screen portion
395 		r.left += -xPos;
396 		xPos = 0;
397 	}
398 	else if (xPos >= FULL_SCREEN_WIDTH)
399 		return;
400 	else if (xPos + hWidth > FULL_SCREEN_WIDTH)
401 		r.right = r.left + (FULL_SCREEN_WIDTH - xPos - 1);
402 
403 	// Handle clipping for Y position
404 	if (yPos < 0) {
405 		if (yPos + hHeight <= MENUBAR_Y_SIZE)
406 			// Completely off screen, so don't display
407 			return;
408 
409 		// Reduce the source rectangle to only the on-screen portion
410 		r.top += -yPos + MENUBAR_Y_SIZE;
411 		yPos = MENUBAR_Y_SIZE;
412 	}
413 	else if (yPos >= FULL_SCREEN_HEIGHT)
414 		return;
415 	else if (yPos + hHeight > FULL_SCREEN_HEIGHT)
416 		r.bottom = r.top + (FULL_SCREEN_HEIGHT - yPos - 1);
417 
418 	// Final check to make sure there is anything to display
419 	if ((r.top >= r.bottom) || (r.left >= r.right))
420 		return;
421 
422 	_frames->copyTo(dest, r, (uint16) xPos, (uint16) yPos, _colorOffset);
423 }
424 
incFrameNumber()425 void Hotspot::incFrameNumber() {
426 	++_frameNumber;
427 	if (_frameNumber >= _numFrames)
428 		_frameNumber = 0;
429 }
430 
isActiveAnimation()431 bool Hotspot::isActiveAnimation() {
432 	return ((_numFrames != 0) && (_layer != 0));
433 }
434 
nameId() const435 uint16 Hotspot::nameId() const {
436 	if (_data == NULL)
437 		return 0;
438 	else
439 		return _data->nameId;
440 }
441 
getName()442 const char *Hotspot::getName() {
443 	// If name hasn't been loaded yet, then do so
444 	if (!_nameBuffer[0] && (nameId() != 0))
445 		StringData::getReference().getString(nameId(), _nameBuffer);
446 
447 	return &_nameBuffer[0];
448 }
449 
setPosition(int16 newX,int16 newY)450 void Hotspot::setPosition(int16 newX, int16 newY) {
451 	_startX = newX;
452 	_startY = newY;
453 	if (_data) {
454 		_data->startX = newX;
455 		_data->startY = newY;
456 	}
457 }
458 
setSize(uint16 newWidth,uint16 newHeight)459 void Hotspot::setSize(uint16 newWidth, uint16 newHeight) {
460 	_width = newWidth;
461 	_frameWidth = newWidth;
462 	_height = newHeight;
463 }
464 
executeScript()465 bool Hotspot::executeScript() {
466 	if (_data->hotspotScriptOffset == 0xffff)
467 		return false;
468 	else
469 		return HotspotScript::execute(this);
470 }
471 
tick()472 void Hotspot::tick() {
473 	uint16 id = _hotspotId;
474 	debugC(ERROR_BASIC, kLureDebugAnimations, "Hotspot %xh tick begin", id);
475 	(_tickHandlers.*_tickHandler)(*this);
476 	debugC(ERROR_BASIC, kLureDebugAnimations, "Hotspot %xh tick end", id);
477 }
478 
setTickProc(uint16 newVal)479 void Hotspot::setTickProc(uint16 newVal) {
480 	if (_data)
481 		_data->tickProcId = newVal;
482 
483 	_tickHandler = _tickHandlers.getHandler(newVal);
484 }
485 
walkTo(int16 endPosX,int16 endPosY,uint16 destHotspot)486 void Hotspot::walkTo(int16 endPosX, int16 endPosY, uint16 destHotspot) {
487 	if ((hotspotId() == PLAYER_ID) && (PATHFIND_COUNTDOWN != 0)) {
488 		// Show the clock cursor whilst pathfinding will be calculated
489 		Mouse &mouse = Mouse::getReference();
490 		mouse.setCursorNum(CURSOR_TIME_START, 0, 0);
491 	}
492 
493 	_destX = endPosX;
494 	_destY = endPosY;
495 	_destHotspotId = destHotspot;
496 	currentActions().addFront(START_WALKING, _roomNumber);
497 }
498 
stopWalking()499 void Hotspot::stopWalking() {
500 	_voiceCtr = 0;
501 	setActionCtr(0);
502 	currentActions().clear();
503 	Room::getReference().setCursorState(CS_NONE);
504 }
505 
endAction()506 void Hotspot::endAction() {
507 	Room &room = Room::getReference();
508 
509 	_voiceCtr = 0;
510 	setActionCtr(0);
511 	if (_hotspotId == PLAYER_ID)
512 		room.setCursorState((CursorState) ((int)room.cursorState() & 2));
513 
514 	if (currentActions().top().hasSupportData()) {
515 		CharacterScheduleEntry *rec = currentActions().top().supportData().next();
516 		currentActions().top().setSupportData(rec);
517 	}
518 }
519 
setDirection(Direction dir)520 void Hotspot::setDirection(Direction dir) {
521 	if ((_numFrames == 0) || (_direction == dir))
522 		return;
523 	uint8 newFrameNumber = 0;
524 
525 	switch (dir) {
526 	case UP:
527 		newFrameNumber = _anim->upFrame;
528 		_charRectY = 4;
529 		break;
530 	case DOWN:
531 		newFrameNumber = _anim->downFrame;
532 		_charRectY = 4;
533 		break;
534 	case LEFT:
535 		newFrameNumber = _anim->leftFrame;
536 		_charRectY = 0;
537 		break;
538 	case RIGHT:
539 		newFrameNumber = _anim->rightFrame;
540 		_charRectY = 0;
541 		break;
542 	default:
543 		// No need to change
544 		return;
545 	}
546 
547 	setFrameNumber(newFrameNumber);
548 	_direction = dir;
549 }
550 
551 // Makes the character face the given hotspot
552 
faceHotspot(HotspotData * hotspot)553 void Hotspot::faceHotspot(HotspotData *hotspot) {
554 	Resources &res = Resources::getReference();
555 	Room &room = Room::getReference();
556 	Screen &screen = Screen::getReference();
557 
558 	if (hotspot->hotspotId >= START_NONVISUAL_HOTSPOT_ID) {
559 		// Non visual hotspot
560 		setDirection(hotspot->nonVisualDirection());
561 
562 	} else {
563 		// Visual hotspot
564 		int xp, yp;
565 
566 		HotspotOverrideData *hsEntry = res.getHotspotOverride(hotspot->hotspotId);
567 		if (hsEntry != NULL) {
568 			xp = x() - hsEntry->xs;
569 			yp = y() + heightCopy() - (hsEntry->ys + hotspot->heightCopy);
570 		} else {
571 			xp = x() - hotspot->startX;
572 			yp = y() + heightCopy() - (hotspot->startY + hotspot->heightCopy);
573 		}
574 
575 		if (ABS(yp) >= ABS(xp)) {
576 			if (yp < 0) setDirection(DOWN);
577 			else setDirection(UP);
578 		} else {
579 			if (xp < 0) setDirection(RIGHT);
580 			else setDirection(LEFT);
581 		}
582 	}
583 
584 	if (hotspotId() == PLAYER_ID) {
585 		room.update();
586 		screen.update();
587 	}
588 }
589 
faceHotspot(uint16 id)590 void Hotspot::faceHotspot(uint16 id) {
591 	HotspotData *hotspot = Resources::getReference().getHotspot(id);
592 	assert(hotspot != NULL);
593 	faceHotspot(hotspot);
594 }
595 
596 // Sets a character walking to a random destination position
597 
setRandomDest()598 void Hotspot::setRandomDest() {
599 	Resources &res = Resources::getReference();
600 	RoomData *roomData = res.getRoom(roomNumber());
601 	Common::Rect &rect = roomData->walkBounds;
602 	Common::RandomSource &rnd = LureEngine::getReference().rnd();
603 	int16 xp, yp;
604 
605 	if (currentActions().isEmpty())
606 		currentActions().addFront(START_WALKING, roomNumber());
607 	else
608 		currentActions().top().setAction(START_WALKING);
609 	_walkFlag = true;
610 
611 	// Try up to 20 times to find an unoccupied destination
612 	for (int tryCtr = 0; tryCtr < 20; ++tryCtr) {
613 		xp = rect.left + rnd.getRandomNumber(rect.right - rect.left);
614 		yp = rect.top + rnd.getRandomNumber(rect.bottom - rect.top);
615 		setDestPosition(xp, yp);
616 		setDestHotspot(0);
617 
618 		// Check if three sequential blocks at chosen destination are unoccupied
619 		if (!roomData->paths.isOccupied(xp, yp, 3))
620 			break;
621 	}
622 }
623 
624 // Sets or clears the hotspot as occupying an area in its room's pathfinding data
625 
setOccupied(bool occupiedFlag)626 void Hotspot::setOccupied(bool occupiedFlag) {
627 	if ((coveredFlag() != VB_INITIAL) &&
628 		(occupiedFlag == (coveredFlag() == VB_TRUE)))
629 		return;
630 	setCoveredFlag(occupiedFlag ? VB_TRUE : VB_FALSE);
631 
632 	int xp = x() >> 3;
633 	int yp = (y() - 8 + heightCopy() - 4) >> 3;
634 	int widthVal = MAX(widthCopy() >> 3, 1);
635 
636 	// Handle cropping for screen left
637 	if (xp < 0) {
638 		xp = -xp;
639 		widthVal -= xp;
640 		if (widthVal <= 0)
641 			return;
642 		xp = 0;
643 	}
644 
645 	// Handle cropping for screen right
646 	int x2 = xp + widthVal - ROOM_PATHS_WIDTH - 1;
647 	if (x2 >= 0) {
648 		widthVal -= (x2 + 1);
649 		if (widthVal <= 0)
650 			return;
651 	}
652 
653 	RoomPathsData &paths = Resources::getReference().getRoom(_roomNumber)->paths;
654 	if (occupiedFlag) {
655 		paths.setOccupied(xp, yp, widthVal);
656 	} else {
657 		paths.clearOccupied(xp, yp, widthVal);
658 	}
659 }
660 
661 // walks the character a single step in a sequence defined by the walking list
662 
walkingStep()663 bool Hotspot::walkingStep() {
664 	if (_pathFinder.isEmpty())
665 		return true;
666 
667 	// Check to see if the end of the next straight walking slice
668 	if (_pathFinder.stepCtr() >= _pathFinder.top().numSteps()) {
669 		// Move to next slice in walking sequence
670 		_pathFinder.stepCtr() = 0;
671 		_pathFinder.pop();
672 		if (_pathFinder.isEmpty())
673 			return true;
674 	}
675 
676 	if (_pathFinder.stepCtr() == 0)
677 		// At start of new slice, set the direction
678 		setDirection(_pathFinder.top().direction());
679 
680 	MovementDataList *frameSet;
681 	switch (_pathFinder.top().direction()) {
682 	case UP:
683 		frameSet = &_anim->upFrames;
684 		break;
685 	case DOWN:
686 		frameSet = &_anim->downFrames;
687 		break;
688 	case LEFT:
689 		frameSet = &_anim->leftFrames;
690 		break;
691 	case RIGHT:
692 		frameSet = &_anim->rightFrames;
693 		break;
694 	default:
695 		return true;
696 	}
697 
698 	int16 _xChange, _yChange;
699 	uint16 nextFrame;
700 	if (frameSet->getFrame(frameNumber(), _xChange, _yChange, nextFrame)) {
701 		setFrameNumber(nextFrame);
702 		setPosition(x() + _xChange, y() + _yChange);
703 
704 		++_pathFinder.stepCtr();
705 	} else {
706 		warning("Hotspot %xh dir frame not found: currentFrame=%d, dir=%s",
707 			_hotspotId, frameNumber(), directionList[(int)_pathFinder.top().direction()]);
708 	}
709 
710 	return false;
711 }
712 
updateMovement()713 void Hotspot::updateMovement() {
714 	assert(_data != NULL);
715 	if (currentActions().action() == EXEC_HOTSPOT_SCRIPT) {
716 		if (_data->coveredFlag) {
717 			// Reset position and direction
718 			resetPosition();
719 		} else {
720 			// Make sure the cell occupied by character is covered
721 			_data->coveredFlag = VB_TRUE;
722 			setOccupied(true);
723 		}
724 	}
725 
726 	resetDirection();
727 }
728 
updateMovement2(CharacterMode value)729 void Hotspot::updateMovement2(CharacterMode value) {
730 	setCharacterMode(value);
731 	updateMovement();
732 }
733 
resetPosition()734 void Hotspot::resetPosition() {
735 	setPosition((x() & 0xf8) | 5, y());
736 	setDirection(direction());
737 }
738 
converse(uint16 destCharacterId,uint16 messageId,bool srcStandStill,bool destStandStill)739 void Hotspot::converse(uint16 destCharacterId, uint16 messageId, bool srcStandStill,
740 					   bool destStandStill) {
741 	assert(_data);
742 	_data->talkDestCharacterId = destCharacterId;
743 	_data->talkMessageId = messageId;
744 	_data->talkCountdown = CONVERSE_COUNTDOWN_SIZE;
745 
746 	if ((destCharacterId != 0) && (destCharacterId != NOONE_ID)) {
747 		// Talking to a destination - add in any talk countdown from the destination,
748 		// in case the destination is already in process of talking
749 		HotspotData *hotspot = Resources::getReference().getHotspot(destCharacterId);
750 		_data->talkCountdown += hotspot->talkCountdown;
751 
752 		if (destStandStill) {
753 			hotspot->talkerId = _hotspotId;
754 			hotspot->talkGate = 0;
755 		}
756 	}
757 
758 	if (srcStandStill) {
759 		setDelayCtr(_data->talkCountdown);
760 		_data->characterMode = CHARMODE_CONVERSING;
761 	}
762 }
763 
showMessage(uint16 messageId,uint16 destCharacterId)764 void Hotspot::showMessage(uint16 messageId, uint16 destCharacterId) {
765 	debugC(ERROR_DETAILED, kLureDebugStrings, "Hotspot::showMessage messageId=%xh srcChar=%xh, destChar=%xh",
766 		messageId, _hotspotId, destCharacterId);
767 	Resources &res = Resources::getReference();
768 	char nameBuffer[MAX_HOTSPOT_NAME_SIZE];
769 	MemoryBlock *data = res.messagesData();
770 	Hotspot *hotspot;
771 	uint8 *msgData = (uint8 *) data->data();
772 	uint16 idVal;
773 	messageId &= 0x7fff;
774 
775 	// Skip through header to find table for given character
776 	uint headerEnd = READ_LE_UINT16(msgData + 2);
777 	uint idx = 0;
778 	while ((idx < headerEnd) && (READ_LE_UINT16(msgData + idx) != hotspotId()))
779 		idx += 2 * sizeof(uint16);
780 
781 	if (idx == headerEnd) {
782 		// Given character doesn't have a message set, so fall back on a simple puzzled animation
783 		hotspot = new Hotspot(this, PUZZLED_ANIM_IDX);
784 		res.addHotspot(hotspot);
785 		return;
786 	}
787 
788 	// Scan through secondary list
789 	uint16 *v = (uint16 *) (msgData + READ_LE_UINT16(msgData + idx + sizeof(uint16)));
790 	while ((idVal = READ_LE_UINT16(v)) != 0xffff) {
791 		++v;
792 		if (READ_LE_UINT16(v) == messageId)
793 			break;
794 		++v;
795 	}
796 
797 	// default response if a specific response not found
798 
799 	if (idVal == 0xffff)
800 		idVal = 0x8c4;
801 	debugC(ERROR_DETAILED, kLureDebugStrings, "Hotspot::showMessage idVal=%xh", idVal);
802 
803 	if (idVal == 0x76) {
804 		// Special code id for showing the puzzled talk bubble
805 		hotspot = new Hotspot(this, PUZZLED_ANIM_IDX);
806 		res.addHotspot(hotspot);
807 
808 	} else if (idVal == 0x120) {
809 		// Special code id for showing the exclamation talk bubble
810 		hotspot = new Hotspot(this, EXCLAMATION_ANIM_IDX);
811 		res.addHotspot(hotspot);
812 
813 	} else if (idVal >= 0x8000) {
814 		// Handle string display
815 		idVal &= 0x7fff;
816 		HotspotData *hotspotData = res.getHotspot(res.fieldList().getField(ACTIVE_HOTSPOT_ID));
817 		const char *itemName = NULL;
818 		if (hotspotData != NULL) {
819 			StringData::getReference().getString(hotspotData->nameId, nameBuffer);
820 			itemName = nameBuffer;
821 		}
822 
823 		Dialog::show(idVal, itemName, this->getName());
824 
825 	} else if (idVal != 0) {
826 		// Handle message as a talking dialog
827 		converse(destCharacterId, idVal, true, false);
828 	}
829 }
830 
handleTalkDialog()831 void Hotspot::handleTalkDialog() {
832 	assert(_data);
833 	Resources &res = Resources::getReference();
834 	ValueTableData &fields = res.fieldList();
835 	Room &room = Room::getReference();
836 
837 	// Return if no talk dialog is necessary
838 	if (_data->talkCountdown == 0)
839 		return;
840 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk countdown = %d", _data->talkCountdown);
841 
842 	if (_data->talkCountdown == CONVERSE_COUNTDOWN_SIZE) {
843 		// Check if there's already an active dialog - if so, wait until it's finished
844 		if (room.isDialogShowing() && (res.getTalkingCharacter() != _hotspotId)) {
845 			++_data->talkCountdown;
846 			if (delayCtr() > 0)
847 				setDelayCtr(delayCtr() + 2);
848 
849 			if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
850 				Hotspot *destCharacter = res.getActiveHotspot(_data->talkDestCharacterId);
851 				if (destCharacter->resource()->talkCountdown > CONVERSE_COUNTDOWN_SIZE) {
852 					destCharacter->resource()->talkCountdown += 2;
853 					if (destCharacter->delayCtr() > 0)
854 						destCharacter->setDelayCtr(destCharacter->delayCtr() + 2);
855 				}
856 			}
857 			return;
858 		}
859 
860 		// Time to set up the dialog for the character
861 		--_data->talkCountdown;
862 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk dialog opening");
863 		startTalkDialog();
864 
865 		if ((_data->talkDestCharacterId != NOONE_ID) && (_data->talkDestCharacterId != 0) &&
866 			(_hotspotId < FIRST_NONCHARACTER_ID)) {
867 			// Speaking to a hotspot
868 			fields.setField(ACTIVE_HOTSPOT_ID, _data->talkDestCharacterId);
869 
870 			// Face the character to the hotspot
871 			HotspotData *destHotspot = res.getHotspot(_data->talkDestCharacterId);
872 			assert(destHotspot != NULL);
873 			faceHotspot(destHotspot);
874 
875 			// If the hotspot is also a character, then face it to the speaker
876 			if (_data->talkDestCharacterId < FIRST_NONCHARACTER_ID) {
877 				Hotspot *charHotspot = res.getActiveHotspot(_data->talkDestCharacterId);
878 				if (charHotspot != NULL)
879 					charHotspot->faceHotspot(resource());
880 			}
881 		}
882 /*
883 	} else if (game.fastTextFlag()) {
884 		// Fast text speed
885 		--_data->talkCountdown;
886 	} else if (fields.textCtr2() != 0) {
887 		fields.textCtr2() = 1;
888 		--_data->talkCountdown;
889 	} else {
890 		--_data->talkCountdown;
891 		--fields.textCtr2();
892 	}*/
893 
894 	} else if ((room.talkDialog() != NULL) && (room.talkDialog()->isBuilding())) {
895 		return;
896 
897 	} else if (_data->talkCountdown > 0) {
898 		--_data->talkCountdown;
899 
900 		if (_data->talkCountdown == 0) {
901 			// Talking is finish - stop talking and free voice animation
902 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk dialog close");
903 			room.setTalkDialog(0, 0, 0, 0);
904 /*
905 			if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
906 				HotspotData *destChar = res.getHotspot(_data->talkDestCharacterId);
907 				destChar->talkerId = 0;
908 			}
909 
910 			_data->talkerId = 0;
911 			_data->talkGate = 0;
912 */
913 		}
914 	}
915 
916 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk handler method end");
917 }
918 
startTalkDialog()919 void Hotspot::startTalkDialog() {
920 	assert(_data);
921 	Room &room = Room::getReference();
922 
923 	if ((_data->talkDestCharacterId != 0) && (_data->talkDestCharacterId != NOONE_ID)) {
924 //		HotspotData *hotspot = Resources::getReference().getHotspot(_data->talkDestCharacterId);
925 //		hotspot->talkerId = _hotspotId;
926 	}
927 
928 	if (room.roomNumber() != roomNumber())
929 		return;
930 
931 	room.setTalkDialog(hotspotId(), _data->talkDestCharacterId, _data->useHotspotId,
932 		_data->talkMessageId);
933 }
934 
935 /*-------------------------------------------------------------------------*/
936 /* Hotspot action handling                                                 */
937 /*                                                                         */
938 /*-------------------------------------------------------------------------*/
939 
940 static const uint16 validRoomExitHotspots[] = {0x2711, 0x2712, 0x2714, 0x2715, 0x2716, 0x2717,
941 	0x2718, 0x2719, 0x271A, 0x271E, 0x271F, 0x2720, 0x2721, 0x2722, 0x2725, 0x2726,
942 	0x2729, 0x272A, 0x272B, 0x272C, 0x272D, 0x272E, 0x272F, 0};
943 
isRoomExit(uint16 id)944 bool Hotspot::isRoomExit(uint16 id) {
945 	for (const uint16 *p = &validRoomExitHotspots[0]; *p != 0; ++p)
946 		if (*p == id)
947 			return true;
948 	return false;
949 }
950 
actionPrecheck(HotspotData * hotspot)951 HotspotPrecheckResult Hotspot::actionPrecheck(HotspotData *hotspot) {
952 	Resources &res = Resources::getReference();
953 	ValueTableData &fields = res.fieldList();
954 
955 	if ((hotspot->hotspotId == SID_ID) || (hotspot->hotspotId == EWAN_ID) ||
956 		(hotspot->hotspotId == NELLIE_ID)) {
957 		// Check for a bar place
958 		if (getBarPlace() == BP_KEEP_TRYING)
959 			return PC_WAIT;
960 	} else if (hotspot->roomNumber != roomNumber()) {
961 		// Object is not in the same room
962 		if (actionCtr() == 0)
963 			showMessage(0, hotspot->hotspotId);
964 		setActionCtr(0);
965 		return PC_NOT_IN_ROOM;
966 	} else if (actionCtr() != 0) {
967 		// loc_883
968 		setActionCtr(actionCtr() + 1);
969 		if (actionCtr() >= 6) {
970 			warning("actionCtr exceeded");
971 			setActionCtr(0);
972 			showMessage(13, NOONE_ID);
973 			return PC_EXCESS;
974 		}
975 
976 		if ((hotspot->hotspotId >= FIRST_NONCHARACTER_ID) ||
977 			(hotspot->characterMode == CHARMODE_INTERACTING) ||
978 			(hotspot->characterMode == CHARMODE_WAIT_FOR_PLAYER) ||
979 			(hotspot->characterMode == CHARMODE_WAIT_FOR_INTERACT)) {
980 			// loc_880
981 			if (characterWalkingCheck(hotspot->hotspotId))
982 				return PC_WAIT;
983 		} else {
984 			// loc_886
985 			setActionCtr(0);
986 			showMessage(14, NOONE_ID);
987 			return PC_FAILED;
988 		}
989 	} else {
990 		setActionCtr(1);
991 
992 		if ((hotspot->hotspotId >= FIRST_NONCHARACTER_ID) ||
993 			((hotspot->actionHotspotId != _hotspotId) &&
994 			 (hotspot->characterMode == CHARMODE_WAIT_FOR_PLAYER))) {
995 			// loc_880
996 			if (characterWalkingCheck(hotspot->hotspotId))
997 				return PC_WAIT;
998 
999 		} else if (hotspot->actionHotspotId != _hotspotId) {
1000 			if (fields.getField(AREA_FLAG) != 2) {
1001 				showMessage(5, hotspot->hotspotId);
1002 				setDelayCtr(4);
1003 			}
1004 
1005 			hotspot->talkGate = GENERAL_MAGIC_ID;
1006 			hotspot->talkerId = _hotspotId;
1007 			return PC_WAIT;
1008 		}
1009 	}
1010 
1011 	// loc_888
1012 	setActionCtr(0);
1013 	if (hotspot->hotspotId < FIRST_NONCHARACTER_ID) {
1014 		hotspot->characterMode = CHARMODE_INTERACTING;
1015 		hotspot->delayCtr = 30;
1016 		hotspot->actionHotspotId = _hotspotId;
1017 	}
1018 
1019 	// If the player had called out to someone to wait, close down that talk dialog
1020 	if ((_hotspotId == PLAYER_ID) && (res.getTalkingCharacter() == PLAYER_ID))
1021 		Room::getReference().setTalkDialog(0, 0, 0, 0);
1022 
1023 	return PC_EXECUTE;
1024 }
1025 
getBarPlace()1026 BarPlaceResult Hotspot::getBarPlace() {
1027 	Resources &res = Resources::getReference();
1028 	BarEntry &barEntry = res.barmanLists().getDetails(roomNumber());
1029 
1030 	if (actionCtr() != 0) {
1031 		// Already at bar
1032 		// Find the character's slot in the bar entry list
1033 		for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
1034 			if (barEntry.customers[index].hotspotId == hotspotId())
1035 				return ((barEntry.customers[index].serveFlags & 0x80) == 0) ? BP_GOT_THERE : BP_KEEP_TRYING;
1036 		}
1037 
1038 		setActionCtr(0);
1039 		return BP_KEEP_TRYING;
1040 	}
1041 
1042 	// Try and find a bar place
1043 	if (!findClearBarPlace())
1044 		return BP_KEEP_TRYING;
1045 
1046 	// First scan for any existing entry for the character
1047 	int index = -1;
1048 	while (++index < NUM_SERVE_CUSTOMERS) {
1049 		if (barEntry.customers[index].hotspotId == hotspotId())
1050 			break;
1051 	}
1052 	if (index == NUM_SERVE_CUSTOMERS) {
1053 		// Not already present - so scan for an empty slot
1054 		index = -1;
1055 		while (++index < NUM_SERVE_CUSTOMERS) {
1056 			if (barEntry.customers[index].hotspotId == 0)
1057 				break;
1058 		}
1059 
1060 		if (index == NUM_SERVE_CUSTOMERS)
1061 			// No slots available, so flag to keep trying
1062 			return BP_KEEP_TRYING;
1063 	}
1064 
1065 	// Set up the slot entry for the character
1066 	barEntry.customers[index].hotspotId = hotspotId();
1067 	barEntry.customers[index].serveFlags = 0x82;
1068 	setActionCtr(1);
1069 	updateMovement();
1070 	setDirection(UP);
1071 
1072 	return BP_KEEP_TRYING;
1073 }
1074 
findClearBarPlace()1075 bool Hotspot::findClearBarPlace() {
1076 	// Check if character has reached the bar
1077 	Resources &res = Resources::getReference();
1078 	BarEntry &barEntry = res.barmanLists().getDetails(roomNumber());
1079 	if ((y() + heightCopy()) < ((barEntry.gridLine << 3) + 24))
1080 		return true;
1081 
1082 	RoomPathsData &paths = res.getRoom(roomNumber())->paths;
1083 
1084 	// Scan backwards from the right side for 4 free blocks along the bar line block
1085 	int numFree = 0;
1086 	for (int xp = ROOM_PATHS_WIDTH - 1; xp >= 0; --xp) {
1087 		if (paths.isOccupied(xp, barEntry.gridLine))
1088 			numFree = 0;
1089 		else if (++numFree == 4) {
1090 			// Start character walking to the found position
1091 			walkTo(xp * 8, (barEntry.gridLine << 3) + 8);
1092 			return false;
1093 		}
1094 	}
1095 
1096 	return false;
1097 }
1098 
characterWalkingCheck(uint16 id)1099 bool Hotspot::characterWalkingCheck(uint16 id) {
1100 	Resources &res = Resources::getReference();
1101 	int16 xp, yp;
1102 	bool altFlag;
1103 	HotspotData *hotspot;
1104 
1105 	// Note that several invalid hotspot Ids are used to identify special walk to
1106 	// coordinates used throughout the game
1107 
1108 	_walkFlag = true;
1109 	altFlag = false;
1110 
1111 	switch (id) {
1112 	case 997:
1113 		xp = 169; yp = 146;
1114 		altFlag = true;
1115 		break;
1116 
1117 	case 998:
1118 		xp = 124; yp = 169;
1119 		break;
1120 
1121 	case 999:
1122 		xp = 78; yp = 162;
1123 		break;
1124 
1125 	default:
1126 		hotspot = res.getHotspot(id);
1127 		if (hotspot == NULL) {
1128 			// Should never come here, as all other constants are handled
1129 			warning("characterWalkingCheck done on unknown hotspot Id %xh", id);
1130 			xp = 78; yp = 162;
1131 		} else if ((hotspot->walkX == 0) && (hotspot->walkY == 0)) {
1132 			// The hotspot doesn't have any walk co-ordinates
1133 			xp = hotspot->startX;
1134 			yp = hotspot->startY + hotspot->heightCopy - 4;
1135 			_walkFlag = false;
1136 		} else {
1137 			xp = hotspot->walkX;
1138 			yp = hotspot->walkY & 0x7fff;
1139 			altFlag = (hotspot->walkY & 0x8000) != 0;
1140 		}
1141 		break;
1142 	}
1143 
1144 	if (altFlag) {
1145 		// Alternate walking check
1146 		if (((x() >> 3) != (xp >> 3)) ||
1147 			((((y() + heightCopy()) >> 3) - 1) != (yp >> 3))) {
1148 			// Walk to the specified destination
1149 			walkTo(xp, yp);
1150 			return true;
1151 		} else {
1152 			return false;
1153 		}
1154 	}
1155 
1156 	// Default walking handling
1157 	if ((ABS(x() - xp) >= 8) ||
1158 		(ABS(y() + heightCopy() - yp - 1) >= 19)) {
1159 		walkTo(xp, yp);
1160 		return true;
1161 	}
1162 
1163 	return false;
1164 }
1165 
doorCloseCheck(uint16 doorId)1166 bool Hotspot::doorCloseCheck(uint16 doorId) {
1167 	Resources &res = Resources::getReference();
1168 	Hotspot *doorHotspot = res.getActiveHotspot(doorId);
1169 	if (!doorHotspot) {
1170 		warning("Hotspot %xh is not currently active", doorId);
1171 		return true;
1172 	}
1173 
1174 	Common::Rect bounds(doorHotspot->x(), doorHotspot->y() + doorHotspot->heightCopy()
1175 		- doorHotspot->yCorrection() - doorHotspot->charRectY(),
1176 		doorHotspot->x() + doorHotspot->widthCopy(),
1177 		doorHotspot->y() + doorHotspot->heightCopy() + doorHotspot->charRectY());
1178 
1179 	// Loop through active hotspots
1180 	HotspotList::iterator i;
1181 	HotspotList &lst = res.activeHotspots();
1182 	for (i = lst.begin(); i != lst.end(); ++i) {
1183 		Hotspot const &hsCurrent = **i;
1184 
1185 		// Skip entry if it's the door or the character
1186 		if ((hsCurrent.hotspotId() == hotspotId()) ||
1187 			(hsCurrent.hotspotId() == doorHotspot->hotspotId()))
1188 			continue;
1189 
1190 		// Skip entry if it doesn't meet certain criteria
1191 		if ((hsCurrent.layer() == 0) ||
1192 			(hsCurrent.roomNumber() != doorHotspot->roomNumber()) ||
1193 			(hsCurrent.hotspotId() < PLAYER_ID) ||
1194 			((hsCurrent.hotspotId() >= 0x408) && (hsCurrent.hotspotId() < 0x2710)))
1195 			continue;
1196 
1197 		// Also skip entry if special Id
1198 		if ((hsCurrent.hotspotId() == 0xfffe) || (hsCurrent.hotspotId() == 0xffff))
1199 			continue;
1200 
1201 		// Check to see if the character is intersecting the door area
1202 		int tempY = hsCurrent.y() + hsCurrent.heightCopy();
1203 		if ((hsCurrent.x() >= bounds.right) ||
1204 			(hsCurrent.x() + hsCurrent.widthCopy() <= bounds.left) ||
1205 			(tempY + hsCurrent.charRectY() < bounds.top) ||
1206 			(tempY - hsCurrent.yCorrection() - hsCurrent.charRectY() > bounds.bottom))
1207 			continue;
1208 
1209 		// At this point we know a character is blocking door, so return false
1210 		return false;
1211 	}
1212 
1213 	// No blocking characters, so return true that the door can be closed
1214 	return true;
1215 }
1216 
resetDirection()1217 void Hotspot::resetDirection() {
1218 	uint16 newFrameNumber;
1219 	switch (_direction) {
1220 	case UP:
1221 		newFrameNumber = _anim->upFrame;
1222 		break;
1223 	case DOWN:
1224 		newFrameNumber = _anim->downFrame;
1225 		break;
1226 	case LEFT:
1227 		newFrameNumber = _anim->leftFrame;
1228 		break;
1229 	case RIGHT:
1230 		newFrameNumber = _anim->rightFrame;
1231 		break;
1232 	default:
1233 		// No need to change
1234 		return;
1235 	}
1236 
1237 	setFrameNumber(newFrameNumber);
1238 }
1239 
1240 /*-------------------------------------------------------------------------*/
1241 
1242 typedef void (Hotspot::*ActionProcPtr)(HotspotData *hotspot);
1243 
doAction()1244 void Hotspot::doAction() {
1245 	CurrentActionEntry &entry = currentActions().top();
1246 	HotspotData *hotspot = NULL;
1247 
1248 	if (!entry.hasSupportData() || (entry.supportData().action() == NONE)) {
1249 		doAction(NONE, NULL);
1250 	} else {
1251 		if (entry.supportData().numParams() > 0)
1252 			hotspot = Resources::getReference().getHotspot(entry.supportData().param(
1253 				(entry.supportData().action() == USE) ? 1 : 0));
1254 		doAction(entry.supportData().action(), hotspot);
1255 	}
1256 }
1257 
doAction(Action action,HotspotData * hotspot)1258 void Hotspot::doAction(Action action, HotspotData *hotspot) {
1259 	StringList &stringList = Resources::getReference().stringList();
1260 	int charId = _hotspotId;
1261 	debugC(ERROR_INTERMEDIATE, kLureDebugHotspots,  "Action charId=%xh Action=%d/%s",
1262 		charId, (int)action, (action > EXAMINE) ? NULL : stringList.getString((int)action));
1263 
1264 	// Set the ACTIVE_HOTSPOT_ID and USE_HOTSPOT_ID fields
1265 	if (hotspot != NULL) {
1266 		ValueTableData &fields = Resources::getReference().fieldList();
1267 		fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
1268 
1269 		if (action == USE)
1270 			fields.setField(USE_HOTSPOT_ID, currentActions().top().supportData().param(0));
1271 		else if ((action == GIVE) || (action == ASK))
1272 			fields.setField(USE_HOTSPOT_ID, currentActions().top().supportData().param(1));
1273 		 else
1274 			fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
1275 	}
1276 
1277 	// Call the appropriate action method
1278 	switch (action) {
1279 	case GET:
1280 		doGet(hotspot);
1281 		break;
1282 	case PUSH:
1283 	case PULL:
1284 	case OPERATE:
1285 		doOperate(hotspot);
1286 		break;
1287 	case OPEN:
1288 		doOpen(hotspot);
1289 		break;
1290 	case CLOSE:
1291 		doClose(hotspot);
1292 		break;
1293 	case LOCK:
1294 	case UNLOCK:
1295 		doLockUnlock(hotspot);
1296 		break;
1297 	case USE:
1298 		doUse(hotspot);
1299 		break;
1300 	case GIVE:
1301 		doGive(hotspot);
1302 		break;
1303 	case TALK_TO:
1304 		doTalkTo(hotspot);
1305 		break;
1306 	case TELL:
1307 		doTell(hotspot);
1308 		break;
1309 	case LOOK:
1310 		doLook(hotspot);
1311 		break;
1312 	case LOOK_AT:
1313 		doLookAt(hotspot);
1314 		break;
1315 	case LOOK_THROUGH:
1316 		doLookThrough(hotspot);
1317 		break;
1318 	case ASK:
1319 		doAsk(hotspot);
1320 		break;
1321 	case DRINK:
1322 		doDrink(hotspot);
1323 		break;
1324 	case STATUS:
1325 		doStatus(hotspot);
1326 		break;
1327 	case GO_TO:
1328 		doGoto(hotspot);
1329 		break;
1330 	case RETURN:
1331 		doReturn(hotspot);
1332 		break;
1333 	case BRIBE:
1334 		doBribe(hotspot);
1335 		break;
1336 	case EXAMINE:
1337 		doExamine(hotspot);
1338 		break;
1339 	case NPC_SET_ROOM_AND_OFFSET:
1340 		npcSetRoomAndBlockedOffset(hotspot);
1341 		break;
1342 	case NPC_TALK_TO_PLAYER:
1343 		npcHeySir(hotspot);
1344 		break;
1345 	case NPC_EXEC_SCRIPT:
1346 		npcExecScript(hotspot);
1347 		break;
1348 	case NPC_RESET_PAUSED_LIST:
1349 		npcResetPausedList(hotspot);
1350 		break;
1351 	case NPC_SET_RAND_DEST:
1352 		npcSetRandomDest(hotspot);
1353 		break;
1354 	case NPC_WALKING_CHECK:
1355 		npcWalkingCheck(hotspot);
1356 		break;
1357 	case NPC_SET_SUPPORT_OFFSET:
1358 		npcSetSupportOffset(hotspot);
1359 		break;
1360 	case NPC_SUPPORT_OFFSET_COND:
1361 		npcSupportOffsetConditional(hotspot);
1362 		break;
1363 	case NPC_DISPATCH_ACTION:
1364 		npcDispatchAction(hotspot);
1365 		break;
1366 	case NPC_TALK_NPC_TO_NPC:
1367 		npcTalkNpcToNpc(hotspot);
1368 		break;
1369 	case NPC_PAUSE:
1370 		npcPause(hotspot);
1371 		break;
1372 	case NPC_START_TALKING:
1373 		npcStartTalking(hotspot);
1374 		break;
1375 	case NPC_JUMP_ADDRESS:
1376 		npcJumpAddress(hotspot);
1377 		break;
1378 	default:
1379 		doNothing(hotspot);
1380 		break;
1381 	}
1382 
1383 	debugC(ERROR_DETAILED, kLureDebugHotspots,  "Action charId=%xh Action=%d/%s Complete",
1384 		charId, (int)action, (action > EXAMINE) ? NULL : stringList.getString((int)action));
1385 }
1386 
doNothing(HotspotData * hotspot)1387 void Hotspot::doNothing(HotspotData *hotspot) {
1388 	if (!currentActions().isEmpty()) {
1389 		currentActions().pop();
1390 		if (!currentActions().isEmpty()) {
1391 			setBlockedFlag(false);
1392 			currentActions().top().setAction(DISPATCH_ACTION);
1393 			return;
1394 		}
1395 	}
1396 
1397 	if (hotspotId() == PLAYER_ID)
1398 		Room::getReference().setCursorState(CS_NONE);
1399 }
1400 
doGet(HotspotData * hotspot)1401 void Hotspot::doGet(HotspotData *hotspot) {
1402 	Resources &res = Resources::getReference();
1403 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1404 
1405 	if (result == PC_WAIT)
1406 		return;
1407 	else if (result != PC_EXECUTE) {
1408 		endAction();
1409 		return;
1410 	}
1411 
1412 	faceHotspot(hotspot);
1413 	endAction();
1414 
1415 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GET);
1416 	if (sequenceOffset >= 0x8000) {
1417 		showMessage(sequenceOffset);
1418 		return;
1419 	}
1420 
1421 	if (sequenceOffset != 0) {
1422 		uint16 execResult = Script::execute(sequenceOffset);
1423 
1424 		if (execResult == 1)
1425 			return;
1426 		else if (execResult != 0) {
1427 			showMessage(execResult);
1428 			return;
1429 		}
1430 	}
1431 
1432 	// Move hotspot into characters's inventory
1433 	hotspot->roomNumber = hotspotId();
1434 
1435 	if (hotspot->hotspotId < START_NONVISUAL_HOTSPOT_ID) {
1436 		// Deactive hotspot animation
1437 		Resources::getReference().deactivateHotspot(hotspot->hotspotId);
1438 		// Remove any 'on the ground' description for the hotspot
1439 		hotspot->descId2 = 0;
1440 	}
1441 }
1442 
doOperate(HotspotData * hotspot)1443 void Hotspot::doOperate(HotspotData *hotspot) {
1444 	Resources &res = Resources::getReference();
1445 	Action action = currentActions().top().supportData().action();
1446 
1447 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1448 	if (result == PC_WAIT)
1449 		return;
1450 	else if (result != PC_EXECUTE) {
1451 		endAction();
1452 		return;
1453 	}
1454 
1455 	setActionCtr(0);
1456 	faceHotspot(hotspot);
1457 	endAction();
1458 
1459 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
1460 	if (sequenceOffset >= 0x8000) {
1461 		showMessage(sequenceOffset);
1462 	} else {
1463 		sequenceOffset = Script::execute(sequenceOffset);
1464 		if (sequenceOffset > 1)
1465 			showMessage(sequenceOffset);
1466 	}
1467 }
1468 
doOpen(HotspotData * hotspot)1469 void Hotspot::doOpen(HotspotData *hotspot) {
1470 	Resources &res = Resources::getReference();
1471 	RoomExitJoinData *joinRec;
1472 
1473 	if (isRoomExit(hotspot->hotspotId)) {
1474 		joinRec = res.getExitJoin(hotspot->hotspotId);
1475 		if (!joinRec->blocked) {
1476 			// Room exit is already open
1477 			showMessage(4);
1478 			endAction();
1479 			return;
1480 		}
1481 	}
1482 
1483 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1484 	if (result == PC_WAIT)
1485 		return;
1486 	else if (result != PC_EXECUTE) {
1487 		endAction();
1488 		return;
1489 	}
1490 
1491 	faceHotspot(hotspot);
1492 	setActionCtr(0);
1493 	endAction();
1494 
1495 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, OPEN);
1496 	if (sequenceOffset >= 0x8000) {
1497 		// Message to display
1498 		showMessage(sequenceOffset);
1499 		return;
1500 	}
1501 
1502 	if (sequenceOffset != 0) {
1503 		sequenceOffset = Script::execute(sequenceOffset);
1504 
1505 		if (sequenceOffset == 1)
1506 			return;
1507 		if (sequenceOffset != 0) {
1508 			if (_exitCtr != 0)
1509 				_exitCtr = 4;
1510 			showMessage(sequenceOffset);
1511 			return;
1512 		}
1513 	}
1514 
1515 	joinRec = res.getExitJoin(hotspot->hotspotId);
1516 	if (joinRec->blocked) {
1517 		joinRec->blocked = 0;
1518 
1519 		if (hotspotId() != PLAYER_ID) {
1520 			setCharacterMode(CHARMODE_PAUSED);
1521 			setDelayCtr(4);
1522 		}
1523 	}
1524 }
1525 
doClose(HotspotData * hotspot)1526 void Hotspot::doClose(HotspotData *hotspot) {
1527 	Resources &res = Resources::getReference();
1528 	RoomExitJoinData *joinRec;
1529 
1530 	if (isRoomExit(hotspot->hotspotId)) {
1531 		joinRec = res.getExitJoin(hotspot->hotspotId);
1532 		if (joinRec->blocked) {
1533 			// Room exit is already closed/blocked
1534 			showMessage(3);
1535 			endAction();
1536 			return;
1537 		}
1538 	}
1539 
1540 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1541 	if (result == PC_WAIT)
1542 		return;
1543 	else if (result != PC_EXECUTE) {
1544 		endAction();
1545 		return;
1546 	}
1547 
1548 	faceHotspot(hotspot);
1549 	setActionCtr(0);
1550 	endAction();
1551 
1552 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, CLOSE);
1553 	if (sequenceOffset >= 0x8000) {
1554 		// Message to display
1555 		showMessage(sequenceOffset);
1556 		return;
1557 	} else if (sequenceOffset != 0) {
1558 		// Otherwise handle script
1559 		sequenceOffset = Script::execute(sequenceOffset);
1560 
1561 		if (sequenceOffset != 0) {
1562 			showMessage(sequenceOffset);
1563 			return;
1564 		}
1565 	}
1566 
1567 	joinRec = res.getExitJoin(hotspot->hotspotId);
1568 	if (!joinRec->blocked) {
1569 		// Close the door
1570 		if (!doorCloseCheck(joinRec->hotspots[0].hotspotId) ||
1571 			!doorCloseCheck(joinRec->hotspots[1].hotspotId)) {
1572 			// A character is preventing the door from closing
1573 			showMessage(2);
1574 		} else {
1575 			// Flag the door as closed
1576 			joinRec->blocked = 1;
1577 		}
1578 	}
1579 }
1580 
doUse(HotspotData * hotspot)1581 void Hotspot::doUse(HotspotData *hotspot) {
1582 	Resources &res = Resources::getReference();
1583 	uint16 usedId = currentActions().top().supportData().param(0);
1584 	HotspotData *usedHotspot = res.getHotspot(usedId);
1585 	_data->useHotspotId = usedId;
1586 
1587 	if (usedHotspot->roomNumber != hotspotId()) {
1588 		// Item to be used is not in character's inventory - say "What???"
1589 		endAction();
1590 		showMessage(0xF);
1591 		return;
1592 	}
1593 
1594 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1595 	if (result == PC_WAIT)
1596 		return;
1597 	else if (result != PC_EXECUTE) {
1598 		endAction();
1599 		return;
1600 	}
1601 
1602 	faceHotspot(hotspot);
1603 	endAction();
1604 
1605 	if (hotspotId() == RATPOUCH_ID) {
1606 		_tempDest.position.x = 40;
1607 		setFrameCtr(80);
1608 	}
1609 
1610 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, USE);
1611 
1612 	if (sequenceOffset >= 0x8000) {
1613 		showMessage(sequenceOffset);
1614 	} else if (sequenceOffset == 0) {
1615 		showMessage(17);
1616 	} else {
1617 		sequenceOffset = Script::execute(sequenceOffset);
1618 		if (sequenceOffset != 0)
1619 			showMessage(sequenceOffset);
1620 	}
1621 }
1622 
doGive(HotspotData * hotspot)1623 void Hotspot::doGive(HotspotData *hotspot) {
1624 	Resources &res = Resources::getReference();
1625 	uint16 usedId = currentActions().top().supportData().param(1);
1626 	HotspotData *usedHotspot = res.getHotspot(usedId);
1627 	_data->useHotspotId = usedId;
1628 
1629 	if (usedHotspot->roomNumber != hotspotId()) {
1630 		// Item to be used is not in character's inventory - say "What???"
1631 		endAction();
1632 		showMessage(0xF);
1633 		return;
1634 	}
1635 
1636 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1637 	if (result == PC_WAIT)
1638 		return;
1639 	else if (result != PC_EXECUTE) {
1640 		endAction();
1641 		return;
1642 	}
1643 
1644 	faceHotspot(hotspot);
1645 	endAction();
1646 
1647 	if ((hotspot->hotspotId != PRISONER_ID) || (usedId != BOTTLE_HOTSPOT_ID))
1648 		showMessage(7, hotspot->hotspotId);
1649 
1650 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, GIVE);
1651 
1652 	if (sequenceOffset >= 0x8000) {
1653 		showMessage(sequenceOffset);
1654 	} else if (sequenceOffset != 0) {
1655 		sequenceOffset = Script::execute(sequenceOffset);
1656 		if (sequenceOffset == NOONE_ID) {
1657 			// Start a conversation based on the index of field #6
1658 			uint16 index = res.fieldList().getField(GIVE_TALK_INDEX);
1659 			uint16 id = res.getGiveTalkId(index);
1660 			startTalk(hotspot, id);
1661 
1662 		} else if (sequenceOffset == 0) {
1663 			// Move item into character's inventory
1664 			HotspotData *usedItem = res.getHotspot(usedId);
1665 			usedItem->roomNumber = hotspot->hotspotId;
1666 		} else if (sequenceOffset > 1) {
1667 			Hotspot *destCharacter = res.getActiveHotspot(hotspot->hotspotId);
1668 			if (destCharacter != NULL)
1669 				destCharacter->showMessage(sequenceOffset, hotspotId());
1670 		}
1671 	}
1672 }
1673 
doTalkTo(HotspotData * hotspot)1674 void Hotspot::doTalkTo(HotspotData *hotspot) {
1675 	Resources &res = Resources::getReference();
1676 	ValueTableData &fields = res.fieldList();
1677 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
1678 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
1679 
1680 	if ((hotspot->hotspotId != SKORL_ID) && ((hotspot->roomNumber != 28) ||
1681 		(hotspot->hotspotId != BLACKSMITH_ID))) {
1682 
1683 		HotspotPrecheckResult result = actionPrecheck(hotspot);
1684 		if (result == PC_WAIT)
1685 			return;
1686 		else if (result != PC_EXECUTE) {
1687 			endAction();
1688 			return;
1689 		}
1690 	}
1691 
1692 	faceHotspot(hotspot);
1693 	endAction();
1694 
1695 	// WORKAROUND: Fix crash when talking when an ask conversation is active
1696 	if (_data->talkDestCharacterId != 0) {
1697 		// Don't allow the talk to start
1698 		return;
1699 	}
1700 
1701 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TALK_TO);
1702 	if (sequenceOffset >= 0x8000) {
1703 		showMessage(sequenceOffset);
1704 		return;
1705 	}
1706 
1707 	if (sequenceOffset != 0) {
1708 		uint16 result = Script::execute(sequenceOffset);
1709 
1710 		if (result != 0) {
1711 			endAction();
1712 			return;
1713 		}
1714 	}
1715 
1716 	// Start talking with character
1717 	startTalk(hotspot, getTalkId(hotspot));
1718 }
1719 
doTell(HotspotData * hotspot)1720 void Hotspot::doTell(HotspotData *hotspot) {
1721 	Resources &res = Resources::getReference();
1722 	ValueTableData &fields = res.fieldList();
1723 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
1724 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
1725 	Hotspot *character = res.getActiveHotspot(hotspot->hotspotId);
1726 	assert(character);
1727 
1728 	HotspotPrecheckResult hsResult = actionPrecheck(hotspot);
1729 	if (hsResult == PC_WAIT)
1730 		return;
1731 	else if (hsResult != PC_EXECUTE) {
1732 		endAction();
1733 		return;
1734 	}
1735 
1736 	converse(hotspot->hotspotId, 0x7C, true, false);
1737 
1738 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, TELL);
1739 	if (sequenceOffset >= 0x8000) {
1740 		showMessage(sequenceOffset);
1741 	} else if (sequenceOffset != 0) {
1742 		uint16 result = Script::execute(sequenceOffset);
1743 
1744 		if (result == 0) {
1745 			// Build up sequence of commands for character to follow
1746 			CharacterScheduleEntry &cmdData = currentActions().top().supportData();
1747 			character->setStartRoomNumber(character->roomNumber());
1748 			character->currentActions().clear();
1749 			character->setBlockedFlag(false);
1750 
1751 			for (int index = 1; index < cmdData.numParams(); index += 3) {
1752 				character->currentActions().addBack((Action) cmdData.param(index), 0,
1753 					cmdData.param(index + 1), cmdData.param(index + 2));
1754 			}
1755 		}
1756 	}
1757 
1758 	endAction();
1759 }
1760 
doLook(HotspotData * hotspot)1761 void Hotspot::doLook(HotspotData *hotspot) {
1762 	endAction();
1763 	Dialog::show(Room::getReference().descId());
1764 }
1765 
1766 static const uint16 hotspotLookAtList[] = {0x411, 0x412, 0x41F, 0x420, 0x421, 0x422, 0x426,
1767 	0x427, 0x428, 0x429, 0x436, 0x437, 0};
1768 
doLookAt(HotspotData * hotspot)1769 void Hotspot::doLookAt(HotspotData *hotspot) {
1770 	doLookAction(hotspot, LOOK_AT);
1771 }
1772 
doLookThrough(HotspotData * hotspot)1773 void Hotspot::doLookThrough(HotspotData *hotspot) {
1774 	doLookAction(hotspot, LOOK_THROUGH);
1775 }
1776 
doLookAction(HotspotData * hotspot,Action action)1777 void Hotspot::doLookAction(HotspotData *hotspot, Action action) {
1778 	Resources &res = Resources::getReference();
1779 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
1780 
1781 	if (hotspot->hotspotId >= FIRST_NONCHARACTER_ID) {
1782 		// Check if the hotspot appears in the list of hotspots that don't
1783 		// need to be walked to before being looked at
1784 		const uint16 *tempId = &hotspotLookAtList[0];
1785 		while ((*tempId != 0) && (*tempId != hotspot->hotspotId))
1786 			++tempId;
1787 		if (!*tempId) {
1788 			// Hotspot wasn't in the list
1789 			HotspotPrecheckResult result = actionPrecheck(hotspot);
1790 			if (result == PC_WAIT)
1791 				return;
1792 			else if (result != PC_EXECUTE) {
1793 				endAction();
1794 				return;
1795 			}
1796 		}
1797 	}
1798 
1799 	faceHotspot(hotspot);
1800 	setActionCtr(0);
1801 	endAction();
1802 
1803 	if (sequenceOffset >= 0x8000) {
1804 		showMessage(sequenceOffset);
1805 	} else {
1806 		if (sequenceOffset != 0)
1807 			sequenceOffset = Script::execute(sequenceOffset);
1808 
1809 		if (sequenceOffset == 0) {
1810 			uint16 descId = (hotspot->descId2 != 0) ? hotspot->descId2 : hotspot->descId;
1811 			Dialog::show(descId);
1812 		}
1813 	}
1814 }
1815 
doAsk(HotspotData * hotspot)1816 void Hotspot::doAsk(HotspotData *hotspot) {
1817 	Resources &res = Resources::getReference();
1818 	uint16 usedId = currentActions().top().supportData().param(1);
1819 	Hotspot *destCharacter = res.getActiveHotspot(hotspot->hotspotId);
1820 	HotspotData *usedHotspot = res.getHotspot(usedId);
1821 	_data->useHotspotId = usedId;
1822 
1823 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1824 	if (result == PC_WAIT)
1825 		return;
1826 	else if (result != PC_EXECUTE) {
1827 		endAction();
1828 		return;
1829 	}
1830 
1831 	faceHotspot(hotspot);
1832 	endAction();
1833 	showMessage(9, hotspot->hotspotId); // CHARACTER, DO YOU HAVE ITEM?
1834 
1835 	// Get the action and handle the reply
1836 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, ASK);
1837 
1838 	if (sequenceOffset >= 0x8000) {
1839 		if (destCharacter != NULL)
1840 			destCharacter->showMessage(sequenceOffset, hotspotId());
1841 	} else if (sequenceOffset != 0) {
1842 		sequenceOffset = Script::execute(sequenceOffset);
1843 
1844 		if (sequenceOffset == 0) {
1845 			// Give item to character
1846 			usedHotspot->roomNumber = hotspotId();
1847 			if (destCharacter != NULL)
1848 				destCharacter->showMessage(32, hotspotId());
1849 		} else if ((sequenceOffset != 1) && (destCharacter != NULL)) {
1850 			destCharacter->showMessage(sequenceOffset, hotspotId());
1851 		}
1852 	}
1853 }
1854 
doDrink(HotspotData * hotspot)1855 void Hotspot::doDrink(HotspotData *hotspot) {
1856 	Resources &res = Resources::getReference();
1857 	ValueTableData &fields = res.fieldList();
1858 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
1859 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
1860 
1861 	endAction();
1862 
1863 	// Make sure item is in character's inventory
1864 	if (hotspot->roomNumber != hotspotId()) {
1865 		showMessage(0xF);
1866 		return;
1867 	}
1868 
1869 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, DRINK);
1870 
1871 	if (sequenceOffset >= 0x8000) {
1872 		showMessage(sequenceOffset);
1873 	} else if (sequenceOffset == 0) {
1874 		showMessage(22);
1875 	} else {
1876 		uint16 result = Script::execute(sequenceOffset);
1877 		if (result == 0) {
1878 			// Item has been drunk, so remove item from game
1879 			hotspot->roomNumber = 0;
1880 		} else if (result != 1) {
1881 			showMessage(result);
1882 		}
1883 	}
1884 }
1885 
1886 // doStatus
1887 // Handle the status window
1888 
doStatus(HotspotData * hotspot)1889 void Hotspot::doStatus(HotspotData *hotspot) {
1890 	char buffer[MAX_DESC_SIZE];
1891 	uint16 numItems = 0;
1892 	Resources &res = Resources::getReference();
1893 	StringList &stringList = res.stringList();
1894 	StringData &strings = StringData::getReference();
1895 	Room &room = Room::getReference();
1896 
1897 	room.update();
1898 	endAction();
1899 
1900 	strings.getString(room.roomNumber(), buffer);
1901 	Common::strlcat(buffer, "\n\n", MAX_DESC_SIZE);
1902 	Common::strlcat(buffer, stringList.getString(S_YOU_ARE_CARRYING), MAX_DESC_SIZE);
1903 
1904 	// Scan through the list and add in any items assigned to the player
1905 	HotspotDataList &list = res.hotspotData();
1906 	HotspotDataList::iterator i;
1907 	for (i = list.begin(); i != list.end(); ++i) {
1908 		HotspotData const &rec = **i;
1909 
1910 		if (rec.roomNumber == PLAYER_ID) {
1911 			if (numItems++ == 0)
1912 				Common::strlcat(buffer, ": ", MAX_DESC_SIZE);
1913 			else
1914 				Common::strlcat(buffer, ", ", MAX_DESC_SIZE);
1915 			strings.getString(rec.nameId, buffer + strlen(buffer));
1916 		}
1917 	}
1918 
1919 	// If there were no items, add in the word 'nothing'
1920 	if (numItems == 0)
1921 		Common::strlcat(buffer, stringList.getString(S_INV_NOTHING), MAX_DESC_SIZE);
1922 
1923 	// If the player has money, add it in
1924 	uint16 numGroats = res.fieldList().numGroats();
1925 	if (numGroats > 0) {
1926 		Common::strlcat(buffer, "\n\n", MAX_DESC_SIZE);
1927 		Common::strlcat(buffer, stringList.getString(S_YOU_HAVE), MAX_DESC_SIZE);
1928 		snprintf(buffer + strlen(buffer), MAX_DESC_SIZE - strlen(buffer), "%d", numGroats);
1929 		Common::strlcat(buffer, " ", MAX_DESC_SIZE);
1930 		Common::strlcat(buffer, stringList.getString((numGroats == 1) ? S_GROAT : S_GROATS), MAX_DESC_SIZE); // Make sure we're not overrunning
1931 	}
1932 
1933 	// Display the dialog
1934 	Screen &screen = Screen::getReference();
1935 	Mouse &mouse = Mouse::getReference();
1936 	mouse.cursorOff();
1937 
1938 	Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, buffer);
1939 	s->copyToScreen(INFO_DIALOG_X, (FULL_SCREEN_HEIGHT-s->height())/2);
1940 
1941 	Events::getReference().waitForPress();
1942 	screen.update();
1943 	mouse.cursorOn();
1944 }
1945 
1946 // doGoto
1947 // Sets the room for the character to go to
1948 
doGoto(HotspotData * hotspot)1949 void Hotspot::doGoto(HotspotData *hotspot) {
1950 	_exitCtr = 0;
1951 	_blockedOffset = 0;
1952 	currentActions().top().setRoomNumber(currentActions().top().supportData().param(0));
1953 	endAction();
1954 }
1955 
doReturn(HotspotData * hotspot)1956 void Hotspot::doReturn(HotspotData *hotspot) {
1957 	currentActions().top().setRoomNumber(startRoomNumber());
1958 	endAction();
1959 }
1960 
1961 static const uint16 bribe_hotspot_list[] = {0x421, 0x879, 0x3E9, 0x8C7, 0x429, 0x8D1,
1962 	0x422, 0x8D4, 0x420, 0x8D6, 0x42B, 0x956, 0x3F2, 0xBE6, 0};
1963 
doBribe(HotspotData * hotspot)1964 void Hotspot::doBribe(HotspotData *hotspot) {
1965 	Resources &res = Resources::getReference();
1966 	ValueTableData &fields = res.fieldList();
1967 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
1968 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
1969 
1970 	HotspotPrecheckResult result = actionPrecheck(hotspot);
1971 	if (result == PC_WAIT)
1972 		return;
1973 	else if (result != PC_EXECUTE) {
1974 		endAction();
1975 		return;
1976 	}
1977 
1978 	const uint16 *tempId = &bribe_hotspot_list[0];
1979 	uint16 sequenceOffset = 0x14B;     // Default sequence offset
1980 	while (*tempId != 0) {
1981 		if (*tempId++ == hotspotId()) {
1982 			sequenceOffset = *tempId;
1983 			if ((sequenceOffset & 0x8000) != 0)
1984 				sequenceOffset = Script::execute(sequenceOffset & 0x7fff);
1985 			break;
1986 		}
1987 		++tempId;                      // Move over entry's sequence offset
1988 	}
1989 
1990 	faceHotspot(hotspot);
1991 	setActionCtr(0);
1992 	endAction();
1993 
1994 	sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, BRIBE);
1995 	if (sequenceOffset != 0) {
1996 		sequenceOffset = Script::execute(sequenceOffset);
1997 		if (sequenceOffset != 0)
1998 			return;
1999 	}
2000 
2001 	uint16 talkIndex = res.fieldList().getField(TALK_INDEX);
2002 	showMessage((talkIndex == 6) ? 0x30 : 0x29);
2003 }
2004 
doExamine(HotspotData * hotspot)2005 void Hotspot::doExamine(HotspotData *hotspot) {
2006 	Resources &res = Resources::getReference();
2007 	ValueTableData &fields = res.fieldList();
2008 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
2009 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
2010 
2011 	endAction();
2012 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, EXAMINE);
2013 
2014 	if (sequenceOffset >= 0x8000) {
2015 		showMessage(sequenceOffset);
2016 	} else {
2017 		if (sequenceOffset != 0)
2018 			sequenceOffset = Script::execute(sequenceOffset);
2019 
2020 		if (sequenceOffset == 0) {
2021 			Dialog::show(hotspot->descId);
2022 		}
2023 	}
2024 }
2025 
doLockUnlock(HotspotData * hotspot)2026 void Hotspot::doLockUnlock(HotspotData *hotspot) {
2027 	Action action = currentActions().top().supportData().action();
2028 	Resources &res = Resources::getReference();
2029 	ValueTableData &fields = res.fieldList();
2030 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
2031 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
2032 
2033 	HotspotPrecheckResult result = actionPrecheck(hotspot);
2034 	if (result == PC_WAIT)
2035 		return;
2036 	else if (result != PC_EXECUTE) {
2037 		endAction();
2038 		return;
2039 	}
2040 
2041 	faceHotspot(hotspot);
2042 	endAction();
2043 
2044 	uint16 sequenceOffset = res.getHotspotAction(hotspot->actionsOffset, action);
2045 
2046 	if (sequenceOffset >= 0x8000) {
2047 		showMessage(sequenceOffset);
2048 	} else {
2049 		if (sequenceOffset != 0)
2050 			Script::execute(sequenceOffset);
2051 	}
2052 }
2053 
npcSetRoomAndBlockedOffset(HotspotData * hotspot)2054 void Hotspot::npcSetRoomAndBlockedOffset(HotspotData *hotspot) {
2055 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2056 	_exitCtr = 0;
2057 
2058 	_blockedOffset = entry.param(1);
2059 	currentActions().top().setRoomNumber(entry.param(0));
2060 	endAction();
2061 }
2062 
npcHeySir(HotspotData * hotspot)2063 void Hotspot::npcHeySir(HotspotData *hotspot) {
2064 	Resources &res = Resources::getReference();
2065 
2066 	// If player is performing an action, wait until it's done
2067 	Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
2068 	if (!playerHotspot->currentActions().isEmpty()) {
2069 		setDelayCtr(12);
2070 		setCharacterMode(CHARMODE_PAUSED);
2071 		setActionCtr(0);
2072 		return;
2073 	}
2074 
2075 	// TODO: Check storage of hotspot Id in talk_first=player/talk_second=0
2076 
2077 	// Get the npc to say "Hey Sir" to player
2078 	showMessage(0x22, PLAYER_ID);
2079 
2080 	// Get the character to remain in place for a while
2081 	setDelayCtr(130);
2082 	setCharacterMode(CHARMODE_WAIT_FOR_PLAYER);
2083 
2084 	// Set the talk override to the specified Id
2085 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2086 	_data->talkOverride = entry.param(0);
2087 
2088 	doNothing(hotspot);
2089 }
2090 
npcExecScript(HotspotData * hotspot)2091 void Hotspot::npcExecScript(HotspotData *hotspot) {
2092 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2093 	uint16 offset = entry.param(0);
2094 	endAction();
2095 	Script::execute(offset);
2096 }
2097 
npcResetPausedList(HotspotData * hotspot)2098 void Hotspot::npcResetPausedList(HotspotData *hotspot) {
2099 	Resources &res = Resources::getReference();
2100 	setCharacterMode(CHARMODE_HESITATE);
2101 	setDelayCtr(IDLE_COUNTDOWN_SIZE + 1);
2102 
2103 	res.pausedList().reset(hotspotId());
2104 	endAction();
2105 }
2106 
npcSetRandomDest(HotspotData * hotspot)2107 void Hotspot::npcSetRandomDest(HotspotData *hotspot) {
2108 	endAction();
2109 	setRandomDest();
2110 }
2111 
npcWalkingCheck(HotspotData * hotspot)2112 void Hotspot::npcWalkingCheck(HotspotData *hotspot) {
2113 	Resources &res = Resources::getReference();
2114 	ValueTableData &fields = res.fieldList();
2115 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2116 	uint16 hId = entry.param(0);
2117 
2118 	endAction();
2119 	fields.setField(USE_HOTSPOT_ID, hId);
2120 	fields.setField(ACTIVE_HOTSPOT_ID, hId);
2121 
2122 	if ((hId < PLAYER_ID) || (hotspot->roomNumber == _roomNumber)) {
2123 		characterWalkingCheck(hId);
2124 	}
2125 }
2126 
npcSetSupportOffset(HotspotData * hotspot)2127 void Hotspot::npcSetSupportOffset(HotspotData *hotspot) {
2128 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2129 	uint16 entryId = entry.param(0);
2130 
2131 	CharacterScheduleEntry *newEntry = Resources::getReference().
2132 		charSchedules().getEntry(entryId, entry.parent());
2133 	currentActions().top().setSupportData(newEntry);
2134 }
2135 
npcSupportOffsetConditional(HotspotData * hotspot)2136 void Hotspot::npcSupportOffsetConditional(HotspotData *hotspot) {
2137 	Resources &res = Resources::getReference();
2138 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2139 	CharacterScheduleEntry *newEntry;
2140 	uint16 scriptOffset = entry.param(0);
2141 	uint16 entryId = entry.param(1);
2142 
2143 	if (Script::execute(scriptOffset) == 0) {
2144 		// Not succeeded, get next entry
2145 		newEntry = entry.next();
2146 	} else {
2147 		// Get entry specified by parameter 1
2148 		newEntry = res.charSchedules().getEntry(entryId, entry.parent());
2149 	}
2150 
2151 	currentActions().top().setSupportData(newEntry);
2152 	HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL : res.getHotspot(
2153 		(newEntry->action() == USE) ? newEntry->param(1) : newEntry->param(0));
2154 	doAction(newEntry->action(), hotspotData);
2155 }
2156 
npcDispatchAction(HotspotData * hotspot)2157 void Hotspot::npcDispatchAction(HotspotData *hotspot) {
2158 	Resources &res = Resources::getReference();
2159 	ValueTableData &fields = res.fieldList();
2160 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2161 
2162 	fields.setField(USE_HOTSPOT_ID, entry.param(0));
2163 	fields.setField(ACTIVE_HOTSPOT_ID, entry.param(0));
2164 
2165 	HotspotPrecheckResult result = actionPrecheck(hotspot);
2166 	if (result == PC_EXECUTE) {
2167 		endAction();
2168 	} else if (result != PC_WAIT) {
2169 		CharacterScheduleEntry *newEntry = Resources::getReference().
2170 			charSchedules().getEntry(entry.param(0), entry.parent());
2171 		currentActions().top().setSupportData(newEntry);
2172 
2173 		HotspotData *hotspotData = (newEntry->numParams() == 0) ? NULL :
2174 			res.getHotspot(newEntry->param((newEntry->action() == USE) ? 1 : 0));
2175 		doAction(newEntry->action(), hotspotData);
2176 	}
2177 }
2178 
npcTalkNpcToNpc(HotspotData * hotspot)2179 void Hotspot::npcTalkNpcToNpc(HotspotData *hotspot) {
2180 	Resources &res = Resources::getReference();
2181 	ValueTableData &fields = res.fieldList();
2182 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2183 	fields.setField(ACTIVE_HOTSPOT_ID, hotspot->hotspotId);
2184 	fields.setField(USE_HOTSPOT_ID, hotspot->hotspotId);
2185 
2186 	HotspotPrecheckResult result = actionPrecheck(hotspot);
2187 	if (result == PC_WAIT)
2188 		return;
2189 	else if (result != PC_EXECUTE) {
2190 		endAction();
2191 		return;
2192 	}
2193 
2194 	// If dest is already talking, keep exiting until they're free
2195 	if (hotspot->talkCountdown != 0)
2196 		return;
2197 
2198 	// Handle the source's talk message
2199 	if (entry.param(1) != 0) {
2200 		converse(hotspot->hotspotId, entry.param(1), true, false);
2201 		_data->talkCountdown += entry.param(2);
2202 		setDelayCtr(delayCtr() + entry.param(2));
2203 	}
2204 
2205 	// Handle the destination's response message
2206 	if (entry.param(3) != 0) {
2207 		Hotspot *destHotspot = res.getActiveHotspot(hotspot->hotspotId);
2208 		assert(destHotspot);
2209 		destHotspot->converse(this->hotspotId(), entry.param(3), true, false);
2210 	}
2211 
2212 	endAction();
2213 }
2214 
npcPause(HotspotData * hotspot)2215 void Hotspot::npcPause(HotspotData *hotspot) {
2216 	uint16 delayAmount = currentActions().top().supportData().param(0);
2217 	endAction();
2218 
2219 	setCharacterMode(CHARMODE_PAUSED);
2220 	setDelayCtr(delayAmount);
2221 }
2222 
npcStartTalking(HotspotData * hotspot)2223 void Hotspot::npcStartTalking(HotspotData *hotspot) {
2224 	CharacterScheduleEntry &entry = currentActions().top().supportData();
2225 	uint16 stringId = entry.param(0);
2226 	uint16 destHotspot = entry.param(1);
2227 
2228 	converse(destHotspot, stringId, false);
2229 	endAction();
2230 }
2231 
npcJumpAddress(HotspotData * hotspot)2232 void Hotspot::npcJumpAddress(HotspotData *hotspot) {
2233 	Resources &res = Resources::getReference();
2234 	ValueTableData &fields = res.fieldList();
2235 	int procIndex = currentActions().top().supportData().param(0);
2236 	Hotspot *player;
2237 	CharacterScheduleEntry *entry;
2238 	endAction();
2239 
2240 	switch (procIndex) {
2241 	case 0:
2242 		if (fields.getField(OLD_ROOM_NUMBER) == 19) {
2243 			fields.setField(TALK_INDEX, 24);
2244 			res.getHotspot(0x3F1)->nameId = 0x154;
2245 			Dialog::show(0xAB9);
2246 		}
2247 		break;
2248 
2249 	case 1:
2250 		player = res.getActiveHotspot(PLAYER_ID);
2251 		if (player->y() < 52) {
2252 			entry = res.charSchedules().getEntry(JUMP_ADDR_2_SUPPORT_ID, NULL);
2253 			assert(entry);
2254 
2255 			currentActions().clear();
2256 			currentActions().addFront(DISPATCH_ACTION, entry, ROOMNUM_CELLAR);
2257 		}
2258 		break;
2259 
2260 	default:
2261 		error("Hotspot::npcJumpAddress - invalid method index %d", procIndex);
2262 		break;
2263 	}
2264 }
2265 
2266 /*------------------------------------------------------------------------*/
2267 
getTalkId(HotspotData * charHotspot)2268 uint16 Hotspot::getTalkId(HotspotData *charHotspot) {
2269 	Resources &res = Resources::getReference();
2270 	uint16 talkIndex;
2271 	TalkHeaderData *headerEntry;
2272 	bool isEnglish = LureEngine::getReference().getLanguage() == Common::EN_ANY;
2273 
2274 	// If the hotspot has a talk data override, return it
2275 	if (charHotspot->talkOverride != 0) {
2276 		// Has an override, so return it and reset back to zero
2277 		uint16 result = charHotspot->talkOverride;
2278 		charHotspot->talkOverride = 0;
2279 		return result;
2280 	}
2281 
2282 	// Get offset of talk set to use
2283 	headerEntry = res.getTalkHeader(charHotspot->hotspotId);
2284 
2285 	// Check whether character is a stranger
2286 	if ((isEnglish && (charHotspot->nameId == 378)) ||
2287 		(!isEnglish && ((charHotspot->nameId == 381) || (charHotspot->nameId == 382))))
2288 		// Is a stranger, so force talk Index to be 0 (initial talk)
2289 		talkIndex = 0;
2290 	else
2291 		// Set the talk index based on the current game-wide talk index
2292 		talkIndex = res.fieldList().getField(TALK_INDEX) + 1;
2293 
2294 	return headerEntry->getEntry(talkIndex);
2295 }
2296 
startTalk(HotspotData * charHotspot,uint16 id)2297 void Hotspot::startTalk(HotspotData *charHotspot, uint16 id) {
2298 	Resources &res = Resources::getReference();
2299 
2300 	// Set for providing talk listing
2301 	setTickProc(TALK_TICK_PROC_ID);
2302 
2303 	// Signal the character that they're being talked to
2304 	charHotspot->talkerId = _hotspotId;
2305 	charHotspot->talkGate = 0;
2306 	charHotspot->talkDestCharacterId = _hotspotId;
2307 	_data->talkDestCharacterId = charHotspot->hotspotId;
2308 	_data->talkGate = 0;
2309 
2310 	// Set the active talk data
2311 	res.setTalkStartEntry(0);
2312 	res.setTalkData(id);
2313 	if (!res.getTalkData())
2314 		error("Talk failed - invalid offset: Character=%xh, offset=%xh",
2315 			charHotspot->hotspotId, id);
2316 }
2317 
saveToStream(Common::WriteStream * stream) const2318 void Hotspot::saveToStream(Common::WriteStream *stream) const {
2319 	if (_data)
2320 		_data->npcSchedule.saveToStream(stream);
2321 	else
2322 		// Hotspot doesn't have an underlying data object, so write out an empty actions list
2323 		stream->writeByte(0xff);
2324 
2325 	_pathFinder.saveToStream(stream);
2326 
2327 	stream->writeUint16LE(_roomNumber);
2328 	stream->writeSint16LE(_startX);
2329 	stream->writeSint16LE(_startY);
2330 	stream->writeSint16LE(_destX);
2331 	stream->writeSint16LE(_destY);
2332 	stream->writeUint16LE(_destHotspotId);
2333 	stream->writeByte(_tempDest.counter);
2334 	stream->writeSint16LE(_tempDest.position.x);
2335 	stream->writeSint16LE(_tempDest.position.y);
2336 	stream->writeUint16LE(_frameWidth);
2337 	stream->writeUint16LE(_height);
2338 	stream->writeUint16LE(_width);
2339 	stream->writeUint16LE(_heightCopy);
2340 	stream->writeUint16LE(_widthCopy);
2341 	stream->writeUint16LE(_yCorrection);
2342 	stream->writeUint16LE(_talkX);
2343 	stream->writeUint16LE(_talkY);
2344 	stream->writeByte(_layer);
2345 	stream->writeUint16LE(_hotspotScriptOffset);
2346 	stream->writeByte(_colorOffset);
2347 	stream->writeByte((byte)_direction);
2348 	stream->writeUint16LE(_animId);
2349 	stream->writeUint16LE(_frameNumber);
2350 
2351 	stream->writeUint16LE(_frameCtr);
2352 	stream->writeByte(_skipFlag);
2353 	stream->writeUint16LE(_charRectY);
2354 	stream->writeUint16LE(_voiceCtr);
2355 	stream->writeUint16LE(_blockedOffset);
2356 	stream->writeUint16LE(_exitCtr);
2357 	stream->writeByte(_walkFlag);
2358 	stream->writeByte(_persistant);
2359 	stream->writeUint16LE(_startRoomNumber);
2360 	stream->writeUint16LE(_supportValue);
2361 }
2362 
loadFromStream(Common::ReadStream * stream)2363 void Hotspot::loadFromStream(Common::ReadStream *stream) {
2364 	if (_data)
2365 		_data->npcSchedule.loadFromStream(stream);
2366 	else {
2367 		// Dummy read of terminator for empty actions list
2368 		byte dummy = stream->readByte();
2369 		assert(dummy == 0xff);
2370 	}
2371 
2372 	_pathFinder.loadFromStream(stream);
2373 
2374 	_roomNumber = stream->readUint16LE();
2375 	_startX = stream->readSint16LE();
2376 	_startY = stream->readSint16LE();
2377 	_destX = stream->readSint16LE();
2378 	_destY = stream->readSint16LE();
2379 	_destHotspotId = stream->readUint16LE();
2380 	_tempDest.counter = stream->readByte();
2381 	_tempDest.position.x = stream->readSint16LE();
2382 	_tempDest.position.y = stream->readSint16LE();
2383 	_frameWidth = stream->readUint16LE();
2384 	_height = stream->readUint16LE();
2385 	_width = stream->readUint16LE();
2386 	_heightCopy = stream->readUint16LE();
2387 	_widthCopy = stream->readUint16LE();
2388 	_yCorrection = stream->readUint16LE();
2389 	_talkX = stream->readUint16LE();
2390 	_talkY = stream->readUint16LE();
2391 	_layer = stream->readByte();
2392 	_hotspotScriptOffset = stream->readUint16LE();
2393 	_colorOffset = stream->readByte();
2394 	_direction = (Direction)stream->readByte();
2395 	setAnimation(stream->readUint16LE());
2396 	setFrameNumber(stream->readUint16LE());
2397 
2398 	_frameCtr = stream->readUint16LE();
2399 	_skipFlag = stream->readByte() != 0;
2400 	_charRectY = stream->readUint16LE();
2401 	_voiceCtr = stream->readUint16LE();
2402 	_blockedOffset = stream->readUint16LE();
2403 	_exitCtr = stream->readUint16LE();
2404 	_walkFlag = stream->readByte() != 0;
2405 	_persistant = stream->readByte() != 0;
2406 	_startRoomNumber = stream->readUint16LE();
2407 	_supportValue = stream->readUint16LE();
2408 }
2409 
2410 /*------------------------------------------------------------------------*/
2411 
HotspotTickHandlers()2412 HotspotTickHandlers::HotspotTickHandlers() {
2413 	countdownCtr = 0;
2414 	ewanXOffset = false;
2415 }
2416 
getHandler(uint16 procIndex)2417 HandlerMethodPtr HotspotTickHandlers::getHandler(uint16 procIndex) {
2418 	switch (procIndex) {
2419 	case 1:
2420 		return &HotspotTickHandlers::defaultHandler;
2421 	case STANDARD_CHARACTER_TICK_PROC:
2422 		return &HotspotTickHandlers::standardCharacterAnimHandler;
2423 	case PLAYER_TICK_PROC_ID:
2424 		return &HotspotTickHandlers::playerAnimHandler;
2425 	case VOICE_TICK_PROC_ID:
2426 		return &HotspotTickHandlers::voiceBubbleAnimHandler;
2427 	case PUZZLED_TICK_PROC_ID:
2428 		return &HotspotTickHandlers::puzzledAnimHandler;
2429 	case 6:
2430 		return &HotspotTickHandlers::roomExitAnimHandler;
2431 	case 7:
2432 	case FOLLOWER_TICK_PROC_2:
2433 		return &HotspotTickHandlers::followerAnimHandler;
2434 	case JAILOR_TICK_PROC_ID:
2435 	case 10:
2436 		return &HotspotTickHandlers::jailorAnimHandler;
2437 	case STANDARD_ANIM_2_TICK_PROC:
2438 		return &HotspotTickHandlers::standardAnimHandler2;
2439 	case STANDARD_ANIM_TICK_PROC:
2440 		return &HotspotTickHandlers::standardAnimHandler;
2441 	case 13:
2442 		return &HotspotTickHandlers::sonicRatAnimHandler;
2443 	case 14:
2444 		return &HotspotTickHandlers::droppingTorchAnimHandler;
2445 	case 15:
2446 		return &HotspotTickHandlers::playerSewerExitAnimHandler;
2447 	case 16:
2448 		return &HotspotTickHandlers::fireAnimHandler;
2449 	case 17:
2450 		return &HotspotTickHandlers::sparkleAnimHandler;
2451 	case 18:
2452 		return &HotspotTickHandlers::teaAnimHandler;
2453 	case 19:
2454 		return &HotspotTickHandlers::goewinCaptiveAnimHandler;
2455 	case 20:
2456 		return &HotspotTickHandlers::prisonerAnimHandler;
2457 	case 21:
2458 		return &HotspotTickHandlers::catrionaAnimHandler;
2459 	case 22:
2460 		return &HotspotTickHandlers::morkusAnimHandler;
2461 	case 23:
2462 		return &HotspotTickHandlers::grubAnimHandler;
2463 	case 24:
2464 		return &HotspotTickHandlers::barmanAnimHandler;
2465 	case 25:
2466 		return &HotspotTickHandlers::skorlAnimHandler;
2467 	case 26:
2468 		return &HotspotTickHandlers::gargoyleAnimHandler;
2469 	case GOEWIN_SHOP_TICK_PROC:
2470 		return &HotspotTickHandlers::goewinShopAnimHandler;
2471 	case 28:
2472 	case 29:
2473 	case 30:
2474 	case 31:
2475 	case 32:
2476 	case 33:
2477 		return &HotspotTickHandlers::skullAnimHandler;
2478 	case 34:
2479 		return &HotspotTickHandlers::dragonFireAnimHandler;
2480 	case 35:
2481 		return &HotspotTickHandlers::castleSkorlAnimHandler;
2482 	case 36:
2483 		return &HotspotTickHandlers::rackSerfAnimHandler;
2484 	case TALK_TICK_PROC_ID:
2485 		return &HotspotTickHandlers::talkAnimHandler;
2486 	case 38:
2487 		return &HotspotTickHandlers::fighterAnimHandler;
2488 	case PLAYER_FIGHT_TICK_PROC_ID:
2489 		return &HotspotTickHandlers::playerFightAnimHandler;
2490 	default:
2491 		error("Unknown tick proc Id %xh for hotspot", procIndex);
2492 	}
2493 }
2494 
defaultHandler(Hotspot & h)2495 void HotspotTickHandlers::defaultHandler(Hotspot &h) {
2496 	// No handling done
2497 }
2498 
standardAnimHandler(Hotspot & h)2499 void HotspotTickHandlers::standardAnimHandler(Hotspot &h) {
2500 	Resources &res = Resources::getReference();
2501 
2502 	if (h.frameCtr() > 0)
2503 		h.decrFrameCtr();
2504 	else {
2505 		if (h.executeScript()) {
2506 			// Script is finished - deactivate hotspot and move it to an out of range room
2507 			HotspotData *data = h.resource();
2508 			res.deactivateHotspot(&h);
2509 			data->roomNumber |= 0x8000;
2510 		}
2511 	}
2512 }
2513 
standardAnimHandler2(Hotspot & h)2514 void HotspotTickHandlers::standardAnimHandler2(Hotspot &h) {
2515 	h.handleTalkDialog();
2516 	standardAnimHandler(h);
2517 }
2518 
standardCharacterAnimHandler(Hotspot & h)2519 void HotspotTickHandlers::standardCharacterAnimHandler(Hotspot &h) {
2520 	Resources &res = Resources::getReference();
2521 	ValueTableData &fields = res.fieldList();
2522 	RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
2523 	PathFinder &pathFinder = h.pathFinder();
2524 	CurrentActionStack &actions = h.currentActions();
2525 	Hotspot *player = res.getActiveHotspot(PLAYER_ID);
2526 	uint16 impingingList[MAX_NUM_IMPINGING];
2527 	int numImpinging;
2528 	bool bumpedPlayer;
2529 
2530 	if (h.currentActions().action() != WALKING) {
2531 		Common::String buffer = h.currentActions().getDebugInfo();
2532 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character p=(%d,%d,%d) bs=%d\n%s",
2533 			h.x(), h.y(), h.roomNumber(), h.blockedState(), buffer.c_str());
2534 	}
2535 
2536 	// Handle any active talk dialog
2537 	h.handleTalkDialog();
2538 
2539 	// If someone is talking to the character, this stops them from moving for the duration)
2540 	if (h.resource()->talkerId != 0) {
2541 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Talker Id = %xh, talk_gate = %d",
2542 			h.resource()->talkerId, h.talkGate());
2543 		if (h.talkGate() == GENERAL_MAGIC_ID) {
2544 			fields.setField(ACTIVE_HOTSPOT_ID, h.talkGate());
2545 			fields.setField(USE_HOTSPOT_ID, h.resource()->talkerId);
2546 			Script::execute(h.talkScript());
2547 			h.resource()->talkerId = 0;
2548 		} else {
2549 			h.updateMovement();
2550 			return;
2551 		}
2552 	}
2553 
2554 	// If a frame countdown is in progress, then decrement and exit
2555 	if (h.frameCtr() > 0) {
2556 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Frame ctr = %d", h.frameCtr());
2557 		h.decrFrameCtr();
2558 		return;
2559 	}
2560 
2561 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 2");
2562 	numImpinging = Support::findIntersectingCharacters(h, impingingList);
2563 	bumpedPlayer = (numImpinging == 0) ? false :
2564 		Support::isCharacterInList(impingingList, numImpinging, PLAYER_ID);
2565 
2566 	// Check for character having just changed room
2567 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 3");
2568 	if (h.skipFlag()) {
2569 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Skip flag was set");
2570 
2571 		if (numImpinging > 0) {
2572 			// Scan to check if the character has bumped into player
2573 			if (bumpedPlayer && (player->characterMode() == CHARMODE_IDLE)) {
2574 				// Signal the player to move out of the way automatically
2575 				player->setBlockedState(BS_INITIAL);
2576 				player->setDestHotspot(0);
2577 				player->setRandomDest();
2578 
2579 				Room::getReference().setCursorState(CS_BUMPED);
2580 				debugC(ERROR_DETAILED, kLureDebugAnimations, "Player bumped code starting");
2581 
2582 			} else {
2583 				// Signal the character to pause briefly to allow bumped
2584 				// character time to start moving out of the way
2585 				h.setDelayCtr(10);
2586 				h.setCharacterMode(CHARMODE_PAUSED);
2587 				debugC(ERROR_DETAILED, kLureDebugAnimations, "Pausing after bumping into character");
2588 			}
2589 			return;
2590 		}
2591 
2592 		h.setSkipFlag(false);
2593 	}
2594 
2595 	if (h.resource()->scriptHotspotId != 0) {
2596 		// Character bumped against another
2597 		fields.setField(USE_HOTSPOT_ID, h.resource()->scriptHotspotId);
2598 		Script::execute(h.resource()->tickScriptOffset);
2599 		h.resource()->scriptHotspotId = 0;
2600 	}
2601 
2602 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 4");
2603 	if (h.pauseCtr() != 0) {
2604 		debugC(ERROR_DETAILED, kLureDebugAnimations, "pause ctr = %d", h.pauseCtr());
2605 		h.updateMovement();
2606 		h.pathFinder().clear();
2607 		if (h.pauseCtr() > 1) {
2608 			res.pausedList().scan(h);
2609 			return;
2610 		} else {
2611 			h.setPauseCtr(0);
2612 			if (h.characterMode() == CHARMODE_NONE) {
2613 				h.pathFinder().clear();
2614 				return;
2615 			}
2616 		}
2617 	}
2618 
2619 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 5");
2620 	if (h.characterMode() != CHARMODE_NONE) {
2621 		debugC(ERROR_DETAILED, kLureDebugAnimations, "char mode = %d, delay ctr = %d",
2622 			h.characterMode(), h.delayCtr());
2623 
2624 		if (h.characterMode() == CHARMODE_PLAYER_WAIT) {
2625 			h.updateMovement();
2626 			if (bumpedPlayer)
2627 				return;
2628 		} else {
2629 			// All other character modes
2630 			if (h.delayCtr() > 0) {
2631 				// There is some countdown left to do
2632 				h.updateMovement();
2633 
2634 				bool decrementFlag = (h.resource()->actionHotspotId == 0);
2635 				if (!decrementFlag) {
2636 					HotspotData *hotspot = res.getHotspot(h.resource()->actionHotspotId);
2637 					assert(hotspot);
2638 					decrementFlag = (hotspot->roomNumber != h.roomNumber()) ? false :
2639 						Support::charactersIntersecting(hotspot, h.resource());
2640 				}
2641 
2642 				if (decrementFlag) {
2643 					h.setDelayCtr(h.delayCtr() - 1);
2644 					return;
2645 				}
2646 			}
2647 		}
2648 
2649 		h.resource()->actionHotspotId = 0;
2650 		CharacterMode currentMode = h.characterMode();
2651 		h.setCharacterMode(CHARMODE_NONE);
2652 		h.pathFinder().clear();
2653 
2654 		if ((currentMode == CHARMODE_WAIT_FOR_PLAYER) || (currentMode == CHARMODE_WAIT_FOR_INTERACT)) {
2655 			h.resource()->talkOverride = 0;
2656 			h.showMessage(1);
2657 		}
2658 		return;
2659 	}
2660 
2661 	/* interactHotspotId never seems to be set
2662 	if ((h.resource()->interactHotspotId != 0) && !player->currentActions().isEmpty())  {
2663 		h.setActionCtr(99);
2664 		if (!actions.isEmpty())
2665 			actions.top().setAction(DISPATCH_ACTION);
2666 	}
2667 	*/
2668 
2669 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 6");
2670 	CurrentAction action = actions.action();
2671 	PathFinderResult pfResult;
2672 
2673 	switch (action) {
2674 	case NO_ACTION:
2675 		h.updateMovement2(CHARMODE_IDLE);
2676 		break;
2677 
2678 	case DISPATCH_ACTION:
2679 		// Dispatch an action
2680 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character dispatch action");
2681 
2682 		if (actions.top().roomNumber() == 0)
2683 			actions.top().setRoomNumber(h.roomNumber());
2684 		if (actions.top().roomNumber() == h.roomNumber()) {
2685 			// NPC in correct room for action
2686 			h.setSkipFlag(false);
2687 			h.doAction();
2688 		} else {
2689 			// NPC in wrong room for action
2690 			npcRoomChange(h);
2691 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character change room request");
2692 		}
2693 		break;
2694 
2695 	case EXEC_HOTSPOT_SCRIPT:
2696 		// A hotspot script is in progress for the player, so don't interrupt
2697 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character exec hotspot script");
2698 		if (h.executeScript()) {
2699 			// Script is finished
2700 			actions.top().setAction(DISPATCH_ACTION);
2701 		}
2702 		break;
2703 
2704 	case START_WALKING:
2705 		// Start the character walking to the given destination
2706 
2707 		debugC(ERROR_DETAILED, kLureDebugAnimations,
2708 			"Hotspot standard character exec start walking => (%d,%d)",
2709 			h.destX(), h.destY());
2710 
2711 		h.setCoveredFlag(VB_INITIAL);
2712 		h.setOccupied(false);
2713 		pathFinder.reset(paths);
2714 		h.currentActions().top().setAction(PROCESSING_PATH);
2715 
2716 		// fall through
2717 
2718 	case PROCESSING_PATH:
2719 		// Handle processing pathfinding
2720 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character processing path");
2721 		res.pausedList().scan(h);
2722 
2723 		pfResult = pathFinder.process();
2724 		if (pfResult == PF_UNFINISHED)
2725 			break;
2726 
2727 		debugC(ERROR_DETAILED, kLureDebugAnimations,
2728 			"pathFinder done: result = %d", pfResult);
2729 
2730 		// Post-processing checks
2731 		if ((pfResult == PF_OK) ||
2732 			((h.destHotspotId() == 0) && (pfResult == PF_DEST_OCCUPIED))) {
2733 			// Standard processing
2734 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Standard result handling");
2735 
2736 			h.setBlockedState(BS_NONE);
2737 			if (h.pathFinder().isEmpty()) {
2738 				// No path was defined
2739 				h.currentActions().top().setAction(DISPATCH_ACTION);
2740 				return;
2741 			}
2742 			h.currentActions().top().setAction(WALKING);
2743 
2744 			// WORKAROUND: A character that had enteredg an exit area might have been blocked from entering the new room.
2745 			// The Y position adjust below could thus place a character further into the exit area. So don't do the
2746 			// position adjustment if the user is already in an exit area
2747 			int16 x = h.x() + (h.widthCopy() >> 1);
2748 			int16 y = h.y() + h.heightCopy() - (h.yCorrection() >> 1);
2749 
2750 			RoomData *roomData = Resources::getReference().getRoom(h.roomNumber());
2751 			RoomExitData *exitRec = roomData->exits.checkExits(x, y);
2752 
2753 			if (!exitRec)
2754 				h.setPosition(h.x(), h.y() & 0xfff8);
2755 
2756 		} else if (h.blockedState() == BS_FINAL) {
2757 			// If this point is reached, the character twice hasn't found a walking path
2758 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Character is hopelessly blocked");
2759 
2760 			res.pausedList().reset(h.hotspotId());
2761 			h.updateMovement();
2762 
2763 			assert(!h.currentActions().isEmpty());
2764 			h.currentActions().pop();
2765 
2766 			h.setBlockedFlag(false);
2767 			h.setBlockedState(BS_NONE);
2768 			h.setCharacterMode(CHARMODE_PAUSED);
2769 			h.setDelayCtr(2);
2770 
2771 			if (h.currentActions().isEmpty() ||
2772 				(h.currentActions().top().roomNumber() != h.roomNumber()))
2773 				h.setDestHotspot(0xffff);
2774 
2775 			if (bumpedPlayer)
2776 				h.setCharacterMode(CHARMODE_PLAYER_WAIT);
2777 
2778 		} else {
2779 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Character is blocked from moving");
2780 			CharacterScheduleEntry *newEntry = res.charSchedules().getEntry(RETURN_SUPPORT_ID);
2781 			assert(newEntry);
2782 
2783 			// Increment the blocked state
2784 			h.setBlockedState((BlockedState) ((int)h.blockedState() + 1));
2785 			if (!h.blockedFlag()) {
2786 				// Not already handling blocked, so add a new dummy action so that the new
2787 				// action set below will not replace the existing one
2788 				h.currentActions().addFront(DISPATCH_ACTION, 0);
2789 				h.setBlockedFlag(true);
2790 			}
2791 
2792 			// Set the current action
2793 			CurrentActionEntry &entry = h.currentActions().top();
2794 			entry.setAction(DISPATCH_ACTION);
2795 			entry.setSupportData(newEntry);
2796 			entry.setRoomNumber(h.roomNumber());
2797 		}
2798 
2799 		// If the top action is now walking, deliberately fall through to the case entry;
2800 		// otherwise break out to exit method
2801 		if (h.currentActions().isEmpty() || h.currentActions().top().action() != WALKING)
2802 			break;
2803 
2804 		// fall through
2805 
2806 	case WALKING:
2807 		// The character is currently moving
2808 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character walking");
2809 		h.setOccupied(false);
2810 
2811 		// If the character is walking to an exit hotspot, make sure it's still open
2812 		if ((h.destHotspotId() != 0) && (h.destHotspotId() != 0xffff)) {
2813 			// Player is walking to a room exit hotspot
2814 			RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
2815 			if (joinRec->blocked) {
2816 				// Exit now blocked, so stop walking
2817 				actions.top().setAction(DISPATCH_ACTION);
2818 				h.setOccupied(true);
2819 				break;
2820 			}
2821 		}
2822 
2823 		if (res.pausedList().check(h.hotspotId(), numImpinging, impingingList) == 0) {
2824 			if (h.walkingStep())
2825 				// Walking done
2826 				h.currentActions().top().setAction(DISPATCH_ACTION);
2827 
2828 //			if (h.destHotspotId() != 0) {
2829 				// Walking to an exit, check for any required room change
2830 			if (Support::checkRoomChange(h))
2831 				break;
2832 //			}
2833 		}
2834 
2835 		h.setOccupied(true);
2836 		break;
2837 
2838 	default:
2839 		break;
2840 	}
2841 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot standard character point 7");
2842 }
2843 
voiceBubbleAnimHandler(Hotspot & h)2844 void HotspotTickHandlers::voiceBubbleAnimHandler(Hotspot &h) {
2845 	Resources &res = Resources::getReference();
2846 	debugC(ERROR_DETAILED, kLureDebugAnimations,
2847 		"Voice Bubble anim handler: char = %xh, ctr = %d, char speaking ctr = %d",
2848 		h.hotspotId(), h.voiceCtr(),
2849 		res.getHotspot(res.getTalkingCharacter())->talkCountdown);
2850 
2851 	if (h.voiceCtr() != 0)
2852 		h.setVoiceCtr(h.voiceCtr() - 1);
2853 
2854 	if (h.voiceCtr() != 0) {
2855 		// Countdown not yet ended
2856 		HotspotData *charHotspot = res.getHotspot(res.getTalkingCharacter());
2857 		if (charHotspot->roomNumber == h.roomNumber()) {
2858 			// Character is still in the same room as when it began speaking
2859 			if (charHotspot->talkCountdown != 0) {
2860 				// Character still talking
2861 				if (!res.checkHotspotExtent(charHotspot)) {
2862 					// Set voice bubble off screen to hide it
2863 					h.setPosition(h.x(), -100);
2864 				} else {
2865 					// Keep voice bubble in track with character
2866 					h.setPosition(charHotspot->startX + charHotspot->talkX + 12,
2867 						charHotspot->startY + charHotspot->talkY - 18);
2868 				}
2869 				return;
2870 			}
2871 		}
2872 	}
2873 
2874 	// End of voice time, so unload
2875 	res.deactivateHotspot(&h);
2876 	return;
2877 }
2878 
puzzledAnimHandler(Hotspot & h)2879 void HotspotTickHandlers::puzzledAnimHandler(Hotspot &h) {
2880 	Resources &res = Resources::getReference();
2881 	HotspotData *charHotspot = res.getHotspot(h.destHotspotId());
2882 	assert(charHotspot);
2883 
2884 	h.setVoiceCtr(h.voiceCtr() - 1);
2885 	if ((charHotspot->roomNumber != h.roomNumber()) || (h.voiceCtr() == 0) ||
2886 		!res.checkHotspotExtent(charHotspot)) {
2887 		// Remove the animation
2888 		res.deactivateHotspot(&h);
2889 		return;
2890 	}
2891 
2892 	h.setPosition(charHotspot->startX + charHotspot->talkX + 12,
2893 		charHotspot->startY + charHotspot->talkY - 20);
2894 }
2895 
roomExitAnimHandler(Hotspot & h)2896 void HotspotTickHandlers::roomExitAnimHandler(Hotspot &h) {
2897 	Resources &res = Resources::getReference();
2898 	ValueTableData &fields = res.fieldList();
2899 	Room &room = Room::getReference();
2900 
2901 	RoomExitJoinData *rec = res.getExitJoin(h.hotspotId());
2902 	if (!rec)
2903 		return;
2904 	RoomExitJoinStruct &rs = (rec->hotspots[0].hotspotId == h.hotspotId()) ?
2905 		rec->hotspots[0] : rec->hotspots[1];
2906 
2907 	if ((rec->blocked != 0) && (rs.currentFrame != rs.destFrame)) {
2908 		// Closing the door
2909 		h.setOccupied(true);
2910 
2911 		++rs.currentFrame;
2912 		if ((rs.currentFrame == rs.destFrame) && (h.roomNumber() == room.roomNumber()) && (rs.closeSound != 0))
2913 			Sound.addSound(rs.closeSound);
2914 
2915 	} else if ((rec->blocked == 0) && (rs.currentFrame != 0)) {
2916 		// Opening the door
2917 		h.setOccupied(false);
2918 
2919 		if ((rs.currentFrame == rs.destFrame) && (h.roomNumber() == room.roomNumber()) && (rs.openSound != 0)) {
2920 			Sound.addSound(rs.openSound);
2921 
2922 			// If in the outside village, trash reverb
2923 			if (fields.getField(AREA_FLAG) == 1)
2924 				Sound.musicInterface_TrashReverb();
2925 		}
2926 		--rs.currentFrame;
2927 	}
2928 
2929 	h.setFrameNumber(rs.currentFrame);
2930 }
2931 
playerAnimHandler(Hotspot & h)2932 void HotspotTickHandlers::playerAnimHandler(Hotspot &h) {
2933 	Resources &res = Resources::getReference();
2934 	Room &room = Room::getReference();
2935 	Mouse &mouse = Mouse::getReference();
2936 	RoomPathsData &paths = Resources::getReference().getRoom(h.roomNumber())->paths;
2937 	PathFinder &pathFinder = h.pathFinder();
2938 	CurrentActionStack &actions = h.currentActions();
2939 	uint16 impingingList[MAX_NUM_IMPINGING];
2940 	int numImpinging;
2941 	Action hsAction;
2942 	uint16 hotspotId;
2943 	HotspotData *hotspot;
2944 	Common::String buffer;
2945 
2946 	buffer = h.currentActions().getDebugInfo();
2947 	debugC(ERROR_DETAILED, kLureDebugAnimations,
2948 		"Hotspot player anim handler p=(%d,%d,%d) bs=%d\n%s",
2949 		h.x(), h.y(), h.roomNumber(), h.blockedState(), buffer.c_str());
2950 
2951 	h.handleTalkDialog();
2952 
2953 	// If a frame countdown is in progress, then decrement and exit
2954 	if (h.frameCtr() > 0) {
2955 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Frame countdown = %d", h.frameCtr());
2956 		h.decrFrameCtr();
2957 		return;
2958 	}
2959 
2960 	numImpinging = Support::findIntersectingCharacters(h, impingingList);
2961 
2962 	if (h.skipFlag()) {
2963 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Skip flag set: numImpinging = %d", numImpinging);
2964 		if (numImpinging > 0)
2965 			return;
2966 		h.setSkipFlag(false);
2967 	}
2968 
2969 	/* interactHotspotId never seems to be set
2970 	if (h.resource()->interactHotspotId != 0) {
2971 		h.resource()->interactHotspotId = 0;
2972 		Hotspot *hotspot = res.getActiveHotspot(h.resource()->interactHotspotId);
2973 		assert(hotspot);
2974 		if ((hotspot->characterMode() != CHARMODE_WAIT_FOR_INTERACT) &&
2975 			!actions.isEmpty())
2976 			actions.top().setAction(ACTION_NONE);
2977 	}
2978 	*/
2979 
2980 	if (h.pauseCtr() > 0) {
2981 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Pause countdown = %d", h.pauseCtr());
2982 		h.updateMovement();
2983 		h.pathFinder().clear();
2984 
2985 		if (h.pauseCtr() == 1) {
2986 			h.setPauseCtr(0);
2987 			if (h.characterMode() == 0) {
2988 				h.setOccupied(false);
2989 				return;
2990 			}
2991 		} else {
2992 			res.pausedList().scan(h);
2993 			return;
2994 		}
2995 	}
2996 
2997 	if ((h.characterMode() != CHARMODE_NONE) && (h.characterMode() != CHARMODE_IDLE)) {
2998 		if (h.delayCtr() != 0) {
2999 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Delay countdown = %d", h.delayCtr());
3000 			h.updateMovement();
3001 			h.pathFinder().clear();
3002 			h.setDelayCtr(h.delayCtr() - 1);
3003 			return;
3004 		}
3005 
3006 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Character mode = %d", h.characterMode());
3007 		h.setOccupied(false);
3008 		h.setCharacterMode(CHARMODE_NONE);
3009 		if (h.tempDest().counter != 0) {
3010 			// Start walking to the previously set destination
3011 			h.tempDest().counter = 0;
3012 			h.setDestPosition(h.tempDest().position.x, h.tempDest().position.y);
3013 			h.currentActions().addFront(START_WALKING, h.roomNumber());
3014 			h.setWalkFlag(false);
3015 		}
3016 		return;
3017 	}
3018 
3019 	CurrentAction action = actions.action();
3020 	PathFinderResult pfResult;
3021 
3022 	switch (action) {
3023 	case NO_ACTION:
3024 		// Make sure there is no longer any destination
3025 		h.setDestHotspot(0);
3026 		h.updateMovement2(CHARMODE_IDLE);
3027 		h.doNothing(NULL);
3028 		strcpy(room.statusLine(), "");
3029 		break;
3030 
3031 	case DISPATCH_ACTION:
3032 		// Dispatch an action
3033 		h.setDestHotspot(0);
3034 
3035 		hotspot = NULL;
3036 		if (actions.top().hasSupportData()) {
3037 			hsAction = actions.top().supportData().action();
3038 
3039 			if (actions.top().supportData().numParams() > 0) {
3040 				hotspotId = actions.top().supportData().param((hsAction == USE) ? 1 : 0);
3041 				hotspot = res.getHotspot(hotspotId);
3042 			}
3043 		} else {
3044 			hsAction = NONE;
3045 		}
3046 
3047 		h.doAction(hsAction, hotspot);
3048 		break;
3049 
3050 	case EXEC_HOTSPOT_SCRIPT:
3051 		// A hotspot script is in progress for the player, so don't interrupt
3052 		if (h.executeScript()) {
3053 			// Script is finished, so pop of the execution action
3054 			actions.pop();
3055 		}
3056 		break;
3057 
3058 	case START_WALKING:
3059 		// Start the player walking to the given destination
3060 		h.setCoveredFlag(VB_INITIAL);
3061 		h.setOccupied(false);
3062 
3063 		// Reset the path finder / walking sequence
3064 		pathFinder.reset(paths);
3065 
3066 		// Set current action to processing walking path
3067 		actions.pop();
3068 		h.currentActions().addFront(PROCESSING_PATH, h.roomNumber());
3069 
3070 		// fall through
3071 
3072 	case PROCESSING_PATH:
3073 		h.setCharacterMode(CHARMODE_NONE);
3074 		res.pausedList().scan(h);
3075 
3076 		pfResult = pathFinder.process();
3077 		if (pfResult == PF_UNFINISHED)
3078 			break;
3079 
3080 		// Pathfinding is now complete
3081 		buffer = pathFinder.getDebugInfo();
3082 		debugC(ERROR_DETAILED, kLureDebugAnimations,
3083 			"Pathfind processing done; result=%d, walkFlag=%d\n%s",
3084 			pfResult, h.walkFlag(), buffer.c_str());
3085 
3086 		if ((pfResult != PF_OK) && (h.walkFlag() || (pfResult != PF_DEST_OCCUPIED))) {
3087 
3088 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Blocked state checking");
3089 			if (h.blockedState() == BS_FINAL) {
3090 				res.pausedList().reset(h.hotspotId());
3091 				h.setBlockedState(BS_NONE);
3092 				h.currentActions().pop();
3093 				h.setCharacterMode(CHARMODE_PLAYER_WAIT);
3094 				h.setDelayCtr(7);
3095 				return;
3096 
3097 			} else if (h.blockedState() != BS_NONE) {
3098 				h.tempDest().position.x = h.destX();
3099 				h.tempDest().position.y = h.destY();
3100 				h.tempDest().counter = 1;
3101 				h.setBlockedState((BlockedState) ((int)h.blockedState() + 1));
3102 				h.setRandomDest();
3103 				return;
3104 			}
3105 		}
3106 
3107 		h.setCharacterMode(CHARMODE_NONE);
3108 		h.setPosition(h.x(), h.y() & 0xFFF8);
3109 
3110 		if (pathFinder.isEmpty()) {
3111 			mouse.setCursorNum(CURSOR_ARROW);
3112 			h.currentActions().top().setAction(DISPATCH_ACTION);
3113 			break;
3114 		}
3115 
3116 		h.currentActions().top().setAction(WALKING);
3117 		if (mouse.getCursorNum() != CURSOR_CAMERA)
3118 			mouse.setCursorNum(CURSOR_ARROW);
3119 
3120 		// fall through
3121 
3122 	case WALKING:
3123 		// The character is currently moving
3124 		h.setOccupied(false);
3125 
3126 		if (h.destHotspotId() != 0) {
3127 			RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
3128 			if ((joinRec != NULL) && (joinRec->blocked)) {
3129 				// Player is walking to a blocked room exit, so stop walking
3130 				actions.pop();
3131 				h.setOccupied(true);
3132 				break;
3133 			}
3134 		}
3135 
3136 		if (res.pausedList().check(PLAYER_ID, numImpinging, impingingList) == 0) {
3137 			if (h.walkingStep()) {
3138 				// Walking done
3139 				if (room.cursorState() == CS_BUMPED)
3140 					room.setCursorState(CS_NONE);
3141 				if (h.tempDest().counter != 0) {
3142 					h.setCharacterMode(CHARMODE_PLAYER_WAIT);
3143 					h.setDelayCtr(IDLE_COUNTDOWN_SIZE);
3144 					return;
3145 				}
3146 
3147 				h.currentActions().top().setAction(DISPATCH_ACTION);
3148 			}
3149 
3150 			// Check for whether need to change room
3151 			if (Support::checkRoomChange(h))
3152 				// Player changinge room - break now to avoid resetting occupied status
3153 				break;
3154 		}
3155 		h.setOccupied(true);
3156 		break;
3157 
3158 	default:
3159 		break;
3160 	}
3161 
3162 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Hotspot player anim handler end");
3163 }
3164 
followerAnimHandler(Hotspot & h)3165 void HotspotTickHandlers::followerAnimHandler(Hotspot &h) {
3166 	Resources &res = Resources::getReference();
3167 	ValueTableData &fields = res.fieldList();
3168 	Hotspot *player = res.getActiveHotspot(PLAYER_ID);
3169 
3170 	if ((h.resource()->tickProcId == FOLLOWER_TICK_PROC_2) || (fields.getField(37) == 0)) {
3171 		if (h.currentActions().isEmpty() && (h.roomNumber() != player->roomNumber())) {
3172 			// Character in different room than player
3173 			if (h.hotspotId() == GOEWIN_ID)
3174 				h.currentActions().addFront(DISPATCH_ACTION, player->roomNumber());
3175 			else {
3176 				// Scan through the translation list for an alternate destination room
3177 				const RoomTranslationRecord *p = &roomTranslations[0];
3178 				while ((p->srcRoom != 0) && (p->srcRoom != player->roomNumber()))
3179 					++p;
3180 
3181 				if (p->destRoom == h.roomNumber())
3182 					// Character is already in destination room, so set a random dest
3183 					h.setRandomDest();
3184 				else
3185 					// Move character to either the player's room, or found alternate destination
3186 					h.currentActions().addFront(DISPATCH_ACTION,
3187 						(p->srcRoom != 0) ? p->destRoom : player->roomNumber());
3188 			}
3189 		}
3190 	}
3191 
3192 	// If some action is in progress, do standard handling
3193 	if (h.characterMode() != CHARMODE_IDLE) {
3194 		standardCharacterAnimHandler(h);
3195 		return;
3196 	}
3197 
3198 	// Handle any pause countdown
3199 	if (countdownCtr > 0) {
3200 		--countdownCtr;
3201 		standardCharacterAnimHandler(h);
3202 		return;
3203 	}
3204 
3205 	// Handle selecting a random action for the character to do
3206 	RandomActionSet *set = res.randomActions().getRoom(h.roomNumber());
3207 	if (!set) {
3208 		standardCharacterAnimHandler(h);
3209 		return;
3210 	}
3211 
3212 	Common::RandomSource &rnd = LureEngine::getReference().rnd();
3213 	RandomActionType actionType;
3214 	uint16 scheduleId;
3215 
3216 	int actionIndex = rnd.getRandomNumber(set->numActions() - 1);
3217 	set->getEntry(actionIndex, actionType, scheduleId);
3218 
3219 	if (actionType == REPEAT_ONCE_DONE)	{
3220 		// Repeat once random action that's already done, so don't repeat it
3221 		standardCharacterAnimHandler(h);
3222 		return;
3223 	}
3224 
3225 	// For repeat once actions, make sure the character is in the same room as the player
3226 	if (actionType == REPEAT_ONCE) {
3227 		if (player->roomNumber() != h.roomNumber()) {
3228 			// Not in the same room, so don't do the action
3229 			standardCharacterAnimHandler(h);
3230 			return;
3231 		}
3232 
3233 		// Flag the action as having been done, so it won't be repeated
3234 		set->setDone(actionIndex);
3235 	}
3236 
3237 	if (scheduleId == 0) {
3238 		// No special schedule to perform, so simply set a random destination
3239 		h.setRandomDest();
3240 	} else {
3241 		// Prepare the follower to standard the specified schedule
3242 		CharacterScheduleEntry *newEntry = res.charSchedules().getEntry(scheduleId);
3243 		assert(newEntry);
3244 		h.currentActions().addFront(DISPATCH_ACTION, newEntry, h.roomNumber());
3245 
3246 		// Set a random delay before beginning the action
3247 		countdownCtr = rnd.getRandomNumber(32);
3248 	}
3249 
3250 	standardCharacterAnimHandler(h);
3251 }
3252 
jailorAnimHandler(Hotspot & h)3253 void HotspotTickHandlers::jailorAnimHandler(Hotspot &h) {
3254 	Resources &res = Resources::getReference();
3255 	ValueTableData &fields = res.fieldList();
3256 	Game &game = Game::getReference();
3257 	HotspotData *player = res.getHotspot(PLAYER_ID);
3258 
3259 	if ((fields.getField(11) != 0) || (h.hotspotId() == CASTLE_SKORL_ID)) {
3260 		if (!h.skipFlag() && !game.preloadFlag() && (h.roomNumber() == player->roomNumber)) {
3261 			if (Support::charactersIntersecting(h.resource(), player)) {
3262 				// Skorl has caught the player
3263 				Game::getReference().setState(GS_RESTORE_RESTART | GS_CAUGHT);
3264 			}
3265 		}
3266 	}
3267 
3268 	standardCharacterAnimHandler(h);
3269 }
3270 
sonicRatAnimHandler(Hotspot & h)3271 void HotspotTickHandlers::sonicRatAnimHandler(Hotspot &h) {
3272 	if (h.actionCtr() == 0) {
3273 		HotspotData *player = Resources::getReference().getHotspot(PLAYER_ID);
3274 		if (Support::charactersIntersecting(h.resource(), player))
3275 			h.setActionCtr(1);
3276 	} else {
3277 		standardAnimHandler(h);
3278 	}
3279 }
3280 
droppingTorchAnimHandler(Hotspot & h)3281 void HotspotTickHandlers::droppingTorchAnimHandler(Hotspot &h) {
3282 	if (h.frameCtr() > 0)
3283 		h.setFrameCtr(h.frameCtr() - 1);
3284 	else {
3285 		bool result = h.executeScript();
3286 		if (result) {
3287 			// Changeover to the fire on the straw
3288 			Resources &res = Resources::getReference();
3289 			res.deactivateHotspot(h.hotspotId());
3290 			res.activateHotspot(0x41C);
3291 
3292 			// Add sound
3293 			Sound.addSound(8);
3294 
3295 			// Enable the fire and activate its animation
3296 			HotspotData *fire = res.getHotspot(0x418);
3297 			fire->flags |= 0x80;
3298 			fire->loadOffset = 4;
3299 			res.activateHotspot(0x418);
3300 			fire->loadOffset = 2;
3301 		}
3302 	}
3303 }
3304 
playerSewerExitAnimHandler(Hotspot & h)3305 void HotspotTickHandlers::playerSewerExitAnimHandler(Hotspot &h) {
3306 	if (h.frameCtr() > 0) {
3307 		h.decrFrameCtr();
3308 	} else if (h.executeScript()) {
3309 		Resources &res = Resources::getReference();
3310 
3311 		// Deactive the dropping animation
3312 		h.setLayer(0);
3313 		res.deactivateHotspot(h.hotspotId());
3314 
3315 		// Position the player
3316 		Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
3317 		playerHotspot->setPosition(FULL_SCREEN_WIDTH / 2, (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE) / 2);
3318 		playerHotspot->setDirection(DOWN);
3319 		playerHotspot->setCharacterMode(CHARMODE_NONE);
3320 
3321 		// Setup Ratpouch
3322 		Hotspot *ratpouchHotspot = res.getActiveHotspot(RATPOUCH_ID);
3323 		assert(ratpouchHotspot);
3324 		ratpouchHotspot->setCharacterMode(CHARMODE_NONE);
3325 		ratpouchHotspot->setDelayCtr(0);
3326 		ratpouchHotspot->setActions(0x821C00);
3327 
3328 		// Ratpouch has previously been moved to room 8. Start him moving to room 7
3329 		ratpouchHotspot->currentActions().clear();
3330 		ratpouchHotspot->currentActions().addFront(DISPATCH_ACTION, 7);
3331 	}
3332 }
3333 
fireAnimHandler(Hotspot & h)3334 void HotspotTickHandlers::fireAnimHandler(Hotspot &h) {
3335 	standardAnimHandler(h);
3336 	h.setOccupied(true);
3337 }
3338 
sparkleAnimHandler(Hotspot & h)3339 void HotspotTickHandlers::sparkleAnimHandler(Hotspot &h) {
3340 	Resources &res = Resources::getReference();
3341 	Hotspot *player = res.getActiveHotspot(PLAYER_ID);
3342 	ValueTableData &fields = res.fieldList();
3343 
3344 	h.setRoomNumber(player->roomNumber());
3345 	h.setPosition(player->x() - 14, player->y() - 10);
3346 	h.setActionCtr(h.actionCtr() + 1);
3347 	if (h.actionCtr() == 6) {
3348 		int animIndex;
3349 		if ((fields.getField(11) == 2) || (fields.getField(28) != 0)) {
3350 			fields.setField(28, 0);
3351 			animIndex = PLAYER_ANIM_INDEX;
3352 		} else {
3353 			fields.setField(28, fields.getField(28) + 1);
3354 			animIndex = SELENA_ANIM_INDEX;
3355 		}
3356 
3357 		player->setAnimationIndex(animIndex);
3358 	}
3359 
3360 	if (h.executeScript()) {
3361 		HotspotData *data = h.resource();
3362 		res.deactivateHotspot(&h);
3363 		data->roomNumber = 0x1A8;
3364 
3365 		if (fields.getField(28) != 0) {
3366 			Hotspot *ratpouch = res.getActiveHotspot(RATPOUCH_ID);
3367 			assert(ratpouch);
3368 			ratpouch->converse(NOONE_ID, 0x854, false);
3369 
3370 			uint16 dataId = res.getCharOffset(4);
3371 			CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
3372 
3373 			ratpouch->currentActions().addFront(DISPATCH_ACTION, entry, ratpouch->roomNumber());
3374 			ratpouch->setActionCtr(0);
3375 		}
3376 	}
3377 }
3378 
teaAnimHandler(Hotspot & h)3379 void HotspotTickHandlers::teaAnimHandler(Hotspot &h) {
3380 	if (h.frameCtr() > 0) {
3381 		h.decrFrameCtr();
3382 		return;
3383 	}
3384 
3385 	if (h.executeScript()) {
3386 		// Signal that the tea is done
3387 		h.setHotspotScript(0xB82);
3388 		Resources::getReference().fieldList().setField(27, 1);
3389 	}
3390 }
3391 
goewinCaptiveAnimHandler(Hotspot & h)3392 void HotspotTickHandlers::goewinCaptiveAnimHandler(Hotspot &h) {
3393 	if (h.actionCtr() > 0) {
3394 		if (h.executeScript()) {
3395 			h.setTickProc(STANDARD_CHARACTER_TICK_PROC);
3396 			h.setActionCtr(0);
3397 		}
3398 	}
3399 }
3400 
prisonerAnimHandler(Hotspot & h)3401 void HotspotTickHandlers::prisonerAnimHandler(Hotspot &h) {
3402 	ValueTableData &fields = Resources::getReference().fieldList();
3403 	Common::RandomSource &rnd = LureEngine::getReference().rnd();
3404 
3405 	h.handleTalkDialog();
3406 	if (h.frameCtr() > 0) {
3407 		h.setFrameCtr(h.frameCtr() - 1);
3408 		return;
3409 	}
3410 
3411 	if (h.actionCtr() != 0) {
3412 		if (h.executeScript() == 0) {
3413 			h.setActionCtr(0);
3414 			h.setHotspotScript(0x3E0);
3415 		}
3416 		return;
3417 	}
3418 
3419 	if ((fields.getField(PRISONER_DEAD) == 0) && (rnd.getRandomNumber(65536) >= 6)) {
3420 		h.setActionCtr(1);
3421 		h.setHotspotScript(0x3F6);
3422 	}
3423 }
3424 
catrionaAnimHandler(Hotspot & h)3425 void HotspotTickHandlers::catrionaAnimHandler(Hotspot &h) {
3426 	h.handleTalkDialog();
3427 	if (h.frameCtr() > 0) {
3428 		h.decrFrameCtr();
3429 	} else {
3430 		h.executeScript();
3431 		int delayVal = (h.actionCtr() == 0) ? 5 : h.actionCtr();
3432 		h.setFrameCtr(delayVal);
3433 	}
3434 }
3435 
morkusAnimHandler(Hotspot & h)3436 void HotspotTickHandlers::morkusAnimHandler(Hotspot &h) {
3437 	h.handleTalkDialog();
3438 	if (h.frameCtr() > 0) {
3439 		h.decrFrameCtr();
3440 		return;
3441 	}
3442 
3443 	if (h.executeScript()) {
3444 		// Script is done - set new script to one of two alternates randomly
3445 		Common::RandomSource &rnd = LureEngine::getReference().rnd();
3446 
3447 		h.setHotspotScript(rnd.getRandomNumber(100) >= 50 ? 0x54 : 0);
3448 		h.setFrameCtr(20 + rnd.getRandomNumber(63));
3449 	}
3450 }
3451 
talkAnimHandler(Hotspot & h)3452 void HotspotTickHandlers::talkAnimHandler(Hotspot &h) {
3453 	// Talk handler
3454 	Resources &res = Resources::getReference();
3455 	StringData &strings = StringData::getReference();
3456 	Screen &screen = Screen::getReference();
3457 	Room &room = Room::getReference();
3458 	Mouse &mouse = Mouse::getReference();
3459 	TalkSelections &talkSelections = res.getTalkSelections();
3460 	TalkData *data = res.getTalkData();
3461 	TalkEntryList &entries = data->entries;
3462 	Hotspot *charHotspot;
3463 	char buffer[MAX_DESC_SIZE];
3464 	Common::Rect r;
3465 	int lineNum, numLines;
3466 	int selectedLine, responseNumber;
3467 	bool showSelections, keepTalkingFlag;
3468 	TalkEntryList::iterator i;
3469 	TalkEntryData *entry;
3470 	uint16 result, descId;
3471 
3472 	debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk anim handler state = %d", res.getTalkState());
3473 	switch (res.getTalkState()) {
3474 	case TALK_NONE:
3475 		talkDestCharacter = h.resource()->talkDestCharacterId;
3476 		assert(talkDestCharacter != 0);
3477 
3478 		// Make sure any other dialog is finished before we start talking
3479 		if (room.isDialogShowing())
3480 			return;
3481 
3482 		// fall through
3483 
3484 	case TALK_START:
3485 		// Handle initial setup of talking options
3486 		// Reset talk entry pointer list
3487 		for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum)
3488 			talkSelections[lineNum] = NULL;
3489 
3490 		// Loop through list to find entries to display
3491 		_talkResponse = NULL;
3492 		numLines = 0;
3493 		showSelections = false;
3494 
3495 		i = entries.begin();
3496 		for (lineNum = 0; lineNum < res.getTalkStartEntry(); ++lineNum)
3497 			if (i != entries.end()) ++i;
3498 
3499 		for (; i != entries.end(); ++i) {
3500 			entry = (*i).get();
3501 			uint8 flags = (uint8) (entry->descId >> 14);
3502 			if (flags == 3)
3503 				// Skip the entry
3504 				continue;
3505 
3506 			uint16 sequenceOffset = entry->preSequenceId & 0x3fff;
3507 			bool showLine = sequenceOffset == 0;
3508 			if (!showLine) {
3509 				debugC(ERROR_DETAILED, kLureDebugAnimations,
3510 					"Checking whether to display line: script=%xh, descId=%d",
3511 					sequenceOffset, entry->descId);
3512 				showLine = Script::execute(sequenceOffset) != 0;
3513 			}
3514 
3515 			if (showLine) {
3516 				talkSelections[numLines++] = entry;
3517 				showSelections |= (entry->descId & 0x3fff) != TALK_MAGIC_ID;
3518 			}
3519 
3520 			if ((entry->preSequenceId & 0x8000) != 0)
3521 				break;
3522 		}
3523 
3524 		if (showSelections && (numLines > 1)) {
3525 			res.setTalkState(TALK_SELECT);
3526 
3527 			// Make sure the dest character holds still while an option is selected
3528 			//HotspotData *destHotspot = res.getHotspot(talkDestCharacter);
3529 			//destHotspot->talkerId = h.hotspotId();
3530 		} else {
3531 			res.setTalkState(TALK_RESPOND);
3532 			res.setTalkSelection(1);
3533 		}
3534 		break;
3535 
3536 	case TALK_SELECT:
3537 		r.left = 0; r.right = FULL_SCREEN_WIDTH - 1;
3538 		selectedLine = mouse.y() / MENUBAR_Y_SIZE;
3539 		if ((selectedLine > MAX_TALK_SELECTIONS) || ((selectedLine != 0) &&
3540 			!talkSelections[selectedLine-1]))
3541 			selectedLine = 0;
3542 
3543 		for (lineNum = 0; lineNum < MAX_TALK_SELECTIONS; ++lineNum) {
3544 			if (!talkSelections[lineNum]) break;
3545 			entry = talkSelections[lineNum];
3546 
3547 			strings.getString(entry->descId & 0x3fff, buffer);
3548 
3549 			// Clear line
3550 			r.top = (lineNum + 1) * MENUBAR_Y_SIZE;
3551 			r.bottom = r.top + MENUBAR_Y_SIZE - 1;
3552 			screen.screen().fillRect(r, 0);
3553 
3554 			// Display line
3555 			byte color = LureEngine::getReference().isEGA() ?
3556 				((lineNum + 1 == selectedLine) ? EGA_DIALOG_WHITE_COLOR : EGA_DIALOG_TEXT_COLOR) :
3557 				((lineNum + 1 == selectedLine) ? VGA_DIALOG_WHITE_COLOR : VGA_DIALOG_TEXT_COLOR);
3558 			screen.screen().writeString(r.left, r.top, buffer, false, color);
3559 		}
3560 
3561 		if (mouse.mButton() || mouse.rButton()) {
3562 			// Abort the conversation
3563 			talkEndConversation();
3564 
3565 			// Have destination character show question speech bubble
3566 			charHotspot = res.getActiveHotspot(talkDestCharacter);
3567 			if (charHotspot != NULL)
3568 				charHotspot->showMessage(13, NOONE_ID);
3569 
3570 		} else if (mouse.lButton() && (selectedLine != 0)) {
3571 			// Set the talk response index to use
3572 			res.setTalkSelection(selectedLine);
3573 			res.setTalkState(TALK_RESPOND);
3574 		}
3575 		break;
3576 
3577 	case TALK_RESPOND:
3578 		// Handle initial response to show the question in a talk dialog if needed
3579 
3580 		if (h.resource()->talkCountdown != 0) {
3581 			// Current talk dialog already pending needs to finish
3582 			h.handleTalkDialog();
3583 			return;
3584 		}
3585 
3586 		// Get the original question for display
3587 		selectedLine = res.getTalkSelection();
3588 		entry = talkSelections[selectedLine-1];
3589 		descId = entry->descId & 0x3fff;
3590 		entry->descId |= 0x4000;
3591 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Talk line set: line=#%d, desc=%xh",
3592 			selectedLine, descId);
3593 
3594 		// Get the response the destination character will say
3595 		if (descId != TALK_MAGIC_ID) {
3596 			// Set up to display the question and response in talk dialogs
3597 			h.converse(talkDestCharacter, descId);
3598 			res.setTalkState(TALK_RESPOND_2);
3599 		} else {
3600 			res.setTalkState(TALK_RESPOND_3);
3601 		}
3602 		break;
3603 
3604 	case TALK_RESPOND_2:
3605 		// Wait until the question dialog is no longer active
3606 		h.handleTalkDialog();
3607 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk dialog countdown %d",
3608 			h.resource()->talkCountdown);
3609 
3610 		if (res.getTalkingCharacter() != 0)
3611 			return;
3612 
3613 		// fall through
3614 
3615 	case TALK_RESPOND_3:
3616 		// Respond
3617 		selectedLine = res.getTalkSelection();
3618 		entry = talkSelections[selectedLine-1];
3619 
3620 		responseNumber = entry->postSequenceId;
3621 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Post sequence Id = %xh", responseNumber);
3622 
3623 		if ((responseNumber & 0x8000) != 0) {
3624 			responseNumber = Script::execute(responseNumber & 0x7fff);
3625 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Post sequence Id = %xh", responseNumber);
3626 		}
3627 
3628 		do {
3629 			_talkResponse = res.getTalkData()->getResponse(responseNumber);
3630 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response pre id = %xh",
3631 				_talkResponse->preSequenceId);
3632 
3633 			if (!_talkResponse->preSequenceId)
3634 				break;
3635 			responseNumber = Script::execute(_talkResponse->preSequenceId);
3636 			debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response new response = %d",
3637 				responseNumber);
3638 
3639 			// FIXME: Fix for resetting the character being talked to
3640 			// after talking to Goewin whilst transformed
3641 			if (_talkResponse->preSequenceId == 10902) {
3642 				HotspotData *character = res.getHotspot(PLAYER_ID);
3643 				character->talkDestCharacterId = 0;
3644 			}
3645 		} while (responseNumber != TALK_RESPONSE_MAGIC_ID);
3646 
3647 		descId = _talkResponse->descId;
3648 		if ((descId & 0x8000) != 0)
3649 			descId = Script::execute(descId & 0x7fff);
3650 
3651 		if (descId != TALK_MAGIC_ID) {
3652 			charHotspot = res.getActiveHotspot(talkDestCharacter);
3653 
3654 			if (charHotspot != NULL)
3655 				charHotspot->converse(PLAYER_ID, descId, true);
3656 		}
3657 		res.setTalkState(TALK_RESPONSE_WAIT);
3658 		break;
3659 
3660 	case TALK_RESPONSE_WAIT:
3661 		// Wait until the character's response has finished being displayed
3662 		h.handleTalkDialog();
3663 
3664 		charHotspot = res.getActiveHotspot(talkDestCharacter);
3665 		assert(charHotspot);
3666 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Player talk dialog countdown %d",
3667 			(charHotspot) ? charHotspot->resource()->talkCountdown : 0);
3668 
3669 		if ((charHotspot->resource()->talkCountdown > 0) || (res.getTalkingCharacter() != 0))
3670 			return;
3671 
3672 		result = _talkResponse->postSequenceId;
3673 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Character response post id = %xh", result);
3674 
3675 		if (result == 0xffff)
3676 			keepTalkingFlag = false;
3677 		else {
3678 			if ((result & 0x8000) == 0)
3679 				keepTalkingFlag = true;
3680 			else {
3681 				result = Script::execute(result & 0x7fff);
3682 				keepTalkingFlag = result != 0xffff;
3683 			}
3684 		}
3685 
3686 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Keep Talking flag = %d", keepTalkingFlag);
3687 
3688 		if (keepTalkingFlag) {
3689 			// Reset for loading the next set of talking options
3690 			res.setTalkStartEntry(result);
3691 			res.setTalkState(TALK_START);
3692 		} else {
3693 			// End the conversation
3694 			talkEndConversation();
3695 		}
3696 		break;
3697 
3698 	default:
3699 		break;
3700 	}
3701 }
3702 
talkEndConversation()3703 void HotspotTickHandlers::talkEndConversation() {
3704 	Resources &res = Resources::getReference();
3705 	Hotspot *charHotspot = res.getActiveHotspot(talkDestCharacter);
3706 	assert(charHotspot);
3707 
3708 	res.getActiveHotspot(PLAYER_ID)->setTickProc(PLAYER_TICK_PROC_ID);
3709 	charHotspot->setUseHotspotId(0);
3710 	charHotspot->resource()->talkerId = 0;
3711 	charHotspot->setDelayCtr(24);
3712 
3713 	res.setTalkData(0);
3714 	res.setCurrentAction(NONE);
3715 	res.setTalkState(TALK_NONE);
3716 }
3717 
fighterAnimHandler(Hotspot & h)3718 void HotspotTickHandlers::fighterAnimHandler(Hotspot &h) {
3719 	Fights.fighterAnimHandler(h);
3720 }
3721 
playerFightAnimHandler(Hotspot & h)3722 void HotspotTickHandlers::playerFightAnimHandler(Hotspot &h) {
3723 	Fights.playerAnimHandler(h);
3724 }
3725 
grubAnimHandler(Hotspot & h)3726 void HotspotTickHandlers::grubAnimHandler(Hotspot &h) {
3727 	Resources &res = Resources::getReference();
3728 	h.handleTalkDialog();
3729 
3730 	Hotspot *character = res.getActiveHotspot(PLAYER_ID);
3731 	uint16 frameNumber = 0;
3732 
3733 	if (character->y() < 79) {
3734 		// If player is behind Grub, use Ratpouch if possible
3735 		Hotspot *ratpouch = res.getActiveHotspot(RATPOUCH_ID);
3736 		if ((ratpouch != NULL) && (ratpouch->roomNumber() == h.roomNumber()))
3737 			character = ratpouch;
3738 	}
3739 
3740 	if (character->x() < 72)
3741 		frameNumber = 0;
3742 	else if (character->x() < 172)
3743 		frameNumber = 1;
3744 	else
3745 		frameNumber = 2;
3746 
3747 	h.setActionCtr(frameNumber);
3748 	h.setFrameNumber(frameNumber);
3749 }
3750 
barmanAnimHandler(Hotspot & h)3751 void HotspotTickHandlers::barmanAnimHandler(Hotspot &h) {
3752 	Resources &res = Resources::getReference();
3753 	Room &room = Room::getReference();
3754 	BarEntry &barEntry = res.barmanLists().getDetails(h.roomNumber());
3755 	Common::RandomSource &rnd = LureEngine::getReference().rnd();
3756 
3757 	h.handleTalkDialog();
3758 	if (h.delayCtr() > 0) {
3759 		h.setDelayCtr(h.delayCtr() - 1);
3760 		return;
3761 	}
3762 
3763 	if (h.frameCtr() == 0) {
3764 		// Barman not currently doing something
3765 		if (barEntry.currentCustomer != NULL) {
3766 			// A customer has been set to be served
3767 			Hotspot *servee = res.getActiveHotspot(barEntry.currentCustomer->hotspotId);
3768 			if (servee != NULL) {
3769 				// Check whether the character is still at the bar
3770 				if ((servee->y() + servee->heightCopy()) >= ((barEntry.gridLine << 3) + 24)) {
3771 					// Customer has left - nullify their entry
3772 					barEntry.currentCustomer->hotspotId = 0;
3773 					barEntry.currentCustomer->serveFlags = 0;
3774 					barEntry.currentCustomer = NULL;
3775 				}
3776 				else if (servee->hotspotId() != PLAYER_ID) {
3777 					// Any other NPC character, so serve them
3778 					barEntry.currentCustomer->serveFlags = 0;
3779 				} else {
3780 					// Servee is the player, flag to stop the barman until the player walks away
3781 					barEntry.currentCustomer->serveFlags &= 0x7f;
3782 
3783 					if ((barEntry.currentCustomer->serveFlags & 7) != 0) {
3784 						// Barman needs to do something
3785 						h.setFrameCtr(barEntry.currentCustomer->serveFlags);
3786 						barEntry.currentCustomer->serveFlags &= 0xf8;
3787 
3788 					} else if (h.resource()->talkerId == 0) {
3789 						// Barman is not currently being talked to
3790 						// Clear entry from list
3791 						barEntry.currentCustomer->hotspotId = 0;
3792 						barEntry.currentCustomer->serveFlags = 0;
3793 						barEntry.currentCustomer = NULL;
3794 						// Set the barman to polish the bar
3795 						h.setFrameCtr(2);
3796 					}
3797 				}
3798 
3799 				return;
3800 			}
3801 		}
3802 
3803 		// Check for any customer waiting to be served
3804 		for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
3805 			if ((barEntry.customers[index].serveFlags & 0x80) != 0) {
3806 				// Found one to serve
3807 				barEntry.customers[index].serveFlags = 0;
3808 				barEntry.currentCustomer = &barEntry.customers[index];
3809 				Hotspot *hotspot = res.getActiveHotspot(barEntry.customers[index].hotspotId);
3810 				assert(hotspot);
3811 				h.setSupportValue(hotspot->x());    // Save the position to move to
3812 				h.setFrameCtr(0x80);				// Flag for movement
3813 				return;
3814 			}
3815 		}
3816 
3817 		// At this point, no customers need servering. Empty the table
3818 		barEntry.currentCustomer = NULL;
3819 		for (int index = 0; index < NUM_SERVE_CUSTOMERS; ++index) {
3820 			barEntry.customers[index].hotspotId = 0;
3821 			barEntry.customers[index].serveFlags = 0;
3822 		}
3823 
3824 		// Choose a random action for the barman to do - walk around, polish the bar, or wait
3825 		h.setFrameCtr(rnd.getRandomNumber(2) + 1);
3826 	}
3827 
3828 	// At this point the barman is known to be doing something
3829 
3830 	if ((h.frameCtr() & 0x80) != 0)	{
3831 		// Bit 7 being set indicates the barman is moving to a destination
3832 		int16 xDiff = h.x() - h.supportValue();
3833 		if (ABS(xDiff) >= 2) {
3834 			// Keep the barman moving
3835 			if (xDiff > 0) {
3836 				// Moving left
3837 				h.setPosition(h.x() - 2, h.y());
3838 				h.setActionCtr(h.actionCtr() + 1);
3839 				if ((h.actionCtr() >= 12) || (h.actionCtr() < 6))
3840 					h.setActionCtr(6);
3841 			} else {
3842 				// Moving right
3843 				h.setPosition(h.x() + 2, h.y());
3844 				h.setActionCtr(h.actionCtr() + 1);
3845 				if (h.actionCtr() >= 6)
3846 					h.setActionCtr(0);
3847 			}
3848 		} else {
3849 			// Stop the barman moving
3850 			h.setActionCtr(12);
3851 			h.setFrameCtr(h.frameCtr() & 0x7f);
3852 		}
3853 
3854 		h.setFrameNumber(h.actionCtr());
3855 		return;
3856 	}
3857 
3858 	// All other actions
3859 	uint16 xp, id;
3860 	const uint16 *frameList;
3861 	uint16 frameNumber;
3862 
3863 	BarmanAction action = (BarmanAction) (h.frameCtr() & 0x3F);
3864 	switch (action) {
3865 	case WALK_AROUND:
3866 		// Wander around between the ends of the bar
3867 		if (h.hotspotId() == EWAN_ID)
3868 			xp = rnd.getRandomNumber(51) + 94;
3869 		else
3870 			xp = rnd.getRandomNumber(85) + 117;
3871 
3872 		h.setSupportValue(xp);
3873 		h.setFrameCtr(0x83);
3874 		return;
3875 
3876 	case POLISH_BAR:
3877 	case SERVE_BEER:
3878 		if (action == SERVE_BEER) {
3879 			// Serving a beer
3880 			if ((h.frameCtr() & 0x40) == 0)
3881 				h.setSupportValue(h.resource()->flags2);
3882 
3883 		} else {
3884 			// Polishing the bar
3885 			if ((h.frameCtr() & 0x40) == 0) {
3886 				// New polish beginning
3887 				id = BG_RANDOM << 8;
3888 
3889 				if (h.hotspotId() == EWAN_ID) {
3890 					HotspotData *player = res.getHotspot(PLAYER_ID);
3891 					HotspotData *gwyn = res.getHotspot(GWEN_ID);
3892 					HotspotData *wayne = res.getHotspot(WAYNE_ID);
3893 
3894 					if ((player->roomNumber != 35) && (gwyn->roomNumber != 35) && (wayne->roomNumber != 35)) {
3895 						h.setAnimationIndex(EWAN_ANIM_INDEX);
3896 						if (rnd.getRandomNumber(1) == 1)
3897 							id = BG_EXTRA1 << 8;
3898 						else {
3899 							// Set up alternate animation
3900 							h.setWidth(32);
3901 							h.setAnimationIndex(EWAN_ALT_ANIM_INDEX);
3902 							ewanXOffset = true;
3903 							h.setPosition(h.x() - 8, h.y());
3904 							id = BG_EXTRA2 << 8;
3905 						}
3906 					}
3907 				}
3908 
3909 				h.setSupportValue(id);
3910 			}
3911 		}
3912 
3913 		// At this point, either a polish or a beer serve is in progress
3914 		h.setFrameCtr(h.frameCtr() | 0x40);
3915 		h.setSupportValue(h.supportValue() + 1);  // Move to next frame
3916 		frameList = barEntry.graphics[h.supportValue() >> 8];
3917 		frameNumber = frameList[h.supportValue() & 0xff];
3918 
3919 		if (frameNumber != 0) {
3920 			h.setActionCtr(frameNumber);
3921 			h.setFrameNumber(frameNumber);
3922 			return;
3923 		}
3924 
3925 		if (h.hotspotId() == EWAN_ID) {
3926 			// Make sure Ewan is back to his standard animation
3927 			h.setWidth(16);
3928 			h.setAnimationIndex(EWAN_ANIM_INDEX);
3929 
3930 			if (ewanXOffset) {
3931 				h.setPosition(h.x() + 8, h.y());
3932 				ewanXOffset = false;
3933 			}
3934 		}
3935 		break;
3936 
3937 	case WAIT_DIALOG:
3938 		if (room.isDialogActive()) {
3939 			h.setFrameNumber(h.actionCtr());
3940 			return;
3941 		}
3942 		break;
3943 
3944 	case WAIT:
3945 	default:
3946 		// Immediate break, since the code outside the switch handles stopping the barman
3947 		break;
3948 	}
3949 
3950 	// If this point is reached, then the barman should stop whatever he's doing
3951 	if (action != WAIT_DIALOG)
3952 		h.setDelayCtr(10);
3953 	h.setFrameCtr(0);
3954 	h.setActionCtr(12);
3955 	h.setFrameNumber(h.actionCtr());
3956 }
3957 
skorlAnimHandler(Hotspot & h)3958 void HotspotTickHandlers::skorlAnimHandler(Hotspot &h) {
3959 	h.handleTalkDialog();
3960 	h.setFrameNumber(h.actionCtr());
3961 }
3962 
gargoyleAnimHandler(Hotspot & h)3963 void HotspotTickHandlers::gargoyleAnimHandler(Hotspot &h) {
3964 	h.handleTalkDialog();
3965 }
3966 
goewinShopAnimHandler(Hotspot & h)3967 void HotspotTickHandlers::goewinShopAnimHandler(Hotspot &h) {
3968 	Resources &res = Resources::getReference();
3969 	ValueTableData &fields = res.fieldList();
3970 
3971 	h.resource()->actionHotspotId = 0;
3972 	h.setCharacterMode(CHARMODE_WAIT_FOR_INTERACT);
3973 
3974 	h.handleTalkDialog();
3975 	if (h.frameCtr() > 0) {
3976 		h.decrFrameCtr();
3977 		return;
3978 	}
3979 
3980 	h.executeScript();
3981 
3982 	if (h.delayCtr() > 0) {
3983 		h.setDelayCtr(h.delayCtr() - 1);
3984 
3985 		if (h.delayCtr() == 0) {
3986 			Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
3987 			uint16 talkIndex = fields.getField(TALK_INDEX);
3988 
3989 			if ((talkIndex == 12) || (talkIndex == 13) || (talkIndex == 14) ||
3990 				(playerHotspot->roomNumber() == 34))
3991 				h.setDelayCtr(1500);
3992 			else
3993 				Script::normalGoewin(0, 0, 0);
3994 		}
3995 	}
3996 }
3997 
skullAnimHandler(Hotspot & h)3998 void HotspotTickHandlers::skullAnimHandler(Hotspot &h) {
3999 	Resources &res = Resources::getReference();
4000 	uint16 doorId = 0x272E;
4001 	if ((h.hotspotId() == 0x42E) || (h.hotspotId() == 0x431) || (h.hotspotId() == 0x432))
4002 		doorId = 0x272A;
4003 	else if ((h.hotspotId() == 0x42f) || (h.hotspotId() == 0x433))
4004 		doorId = 0x272C;
4005 
4006 	RoomExitJoinData *joinRec = res.getExitJoin(doorId);
4007 	if ((h.hotspotId() == 0x42E) || (h.hotspotId() == 0x42F)) {
4008 		h.setFrameNumber(joinRec->blocked ? 0 : 1);
4009 	} else {
4010 		h.setFrameNumber(joinRec->blocked ? 1 : 0);
4011 	}
4012 }
4013 
dragonFireAnimHandler(Hotspot & h)4014 void HotspotTickHandlers::dragonFireAnimHandler(Hotspot &h) {
4015 	if (h.executeScript())
4016 		// Script is finished - player is dead
4017 		Game::getReference().setState(GS_RESTORE_RESTART);
4018 }
4019 
castleSkorlAnimHandler(Hotspot & h)4020 void HotspotTickHandlers::castleSkorlAnimHandler(Hotspot &h) {
4021 	Resources &res = Resources::getReference();
4022 
4023 	h.handleTalkDialog();
4024 	if (h.frameCtr() > 0) {
4025 		h.decrFrameCtr();
4026 		return;
4027 	}
4028 
4029 	if (h.executeScript()) {
4030 		HotspotData *hotspot = res.getHotspot(h.hotspotId());
4031 		assert(hotspot);
4032 		res.deactivateHotspot(hotspot->hotspotId);
4033 		hotspot->roomNumber = 0xffff;
4034 		hotspot->layer = 255;
4035 		hotspot->talkCountdown = 0;
4036 		hotspot->flags |= HOTSPOTFLAG_MENU_EXCLUSION;
4037 
4038 		hotspot = res.getHotspot(CASTLE_SKORL_ID);
4039 		hotspot->roomNumber = 45;
4040 		res.activateHotspot(CASTLE_SKORL_ID);
4041 	}
4042 }
4043 
rackSerfAnimHandler(Hotspot & h)4044 void HotspotTickHandlers::rackSerfAnimHandler(Hotspot &h) {
4045 	Resources &res = Resources::getReference();
4046 
4047 	// Handle any talking
4048 	h.handleTalkDialog();
4049 
4050 	if (h.frameCtr() > 0) {
4051 		h.decrFrameCtr();
4052 		return;
4053 	}
4054 
4055 	switch (h.actionCtr()) {
4056 	case 1:
4057 		h.setHotspotScript(RACK_SERF_SCRIPT_ID_1);
4058 		h.setActionCtr(2);
4059 		break;
4060 
4061 	case 2:
4062 		if (HotspotScript::execute(&h))
4063 			h.setActionCtr(0);
4064 		break;
4065 
4066 	case 3:
4067 		h.setHotspotScript(RACK_SERF_SCRIPT_ID_2);
4068 		h.setActionCtr(4);
4069 		h.setLayer(2);
4070 
4071 		// fall through
4072 
4073 	case 4:
4074 		if (HotspotScript::execute(&h)) {
4075 			h.setLayer(255);
4076 			res.deactivateHotspot(h.hotspotId());
4077 
4078 			HotspotData *ratpouchData = res.getHotspot(RATPOUCH_ID);
4079 			ratpouchData->roomNumber = 4;
4080 			Hotspot *newHotspot = res.activateHotspot(RATPOUCH_ID);
4081 			newHotspot->converse(PLAYER_ID, 0x9C, true);
4082 		}
4083 		break;
4084 
4085 	default:
4086 		break;
4087 	}
4088 }
4089 
4090 /*-------------------------------------------------------------------------*/
4091 
4092 // support method for the standard character tick proc routine - it gets called
4093 // when the character is in the wrong room designated for an action, and is
4094 // responsible for starting the character walking to the correct exit
4095 
npcRoomChange(Hotspot & h)4096 void HotspotTickHandlers::npcRoomChange(Hotspot &h) {
4097 	Resources &res = Resources::getReference();
4098 
4099 	// Increment number of times an exit has been attempted
4100 	h.setExitCtr(h.exitCtr() + 1);
4101 	if (h.exitCtr() >= 5) {
4102 		// Failed to exit room too many times
4103 		h.setExitCtr(0);
4104 
4105 		if (!h.currentActions().isEmpty()) {
4106 			if (h.startRoomNumber() != 0) {
4107 				// If character isn't already returning to starting room, redirect them to the
4108 				// player's current room
4109 				if (!h.currentActions().bottom().hasSupportData() ||
4110 					(h.currentActions().bottom().supportData().action() != RETURN)) {
4111 					// Start follower returning
4112 					Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
4113 					h.currentActions().clear();
4114 					h.currentActions().addFront(RETURN, playerHotspot->roomNumber(), 0, 0);
4115 				}
4116 			}
4117 
4118 		} else if ((h.blockedOffset() != 0) && (h.blockedOffset() != 0xffff)) {
4119 			// Only current action on stack - and there is a block handler
4120 			CharacterScheduleEntry *entry = res.charSchedules().getEntry(h.blockedOffset());
4121 			h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
4122 		}
4123 
4124 		return;
4125 	}
4126 
4127 	// Get room exit coordinates
4128 	uint16 srcRoom = h.roomNumber(),
4129 		destRoom = h.currentActions().top().roomNumber();
4130 	RoomExitCoordinates &coords = res.coordinateList().getEntry(srcRoom);
4131 	RoomExitCoordinateData &exitData = coords.getData(destRoom);
4132 
4133 	if (h.hotspotId() != RATPOUCH_ID) {
4134 		// Count up the number of characters in the room
4135 		HotspotList &list = res.activeHotspots();
4136 		HotspotList::iterator i;
4137 		int numCharacters = 0;
4138 
4139 		for (i = list.begin(); i != list.end(); ++i) {
4140 			if ((h.roomNumber() == (exitData.roomNumber & 0xff)) && (h.layer() != 0) &&
4141 				(h.hotspotId() >= PLAYER_ID) && (h.hotspotId() < FIRST_NONCHARACTER_ID))
4142 				++numCharacters;
4143 		}
4144 
4145 		if (numCharacters >= 4) {
4146 			uint16 dataId = res.getCharOffset(0);
4147 			CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
4148 			h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
4149 
4150 			return;
4151 		}
4152 	}
4153 
4154 	h.setDestPosition(exitData.x, exitData.y);
4155 	h.setDestHotspot(res.exitHotspots().getHotspot(h.roomNumber(), exitData.hotspotIndexId));
4156 
4157 	if (h.destHotspotId() != 0xffff) {
4158 		RoomExitJoinData *joinRec = res.getExitJoin(h.destHotspotId());
4159 
4160 		if (joinRec->blocked) {
4161 			// The room exit is blocked - so add an opening action
4162 			h.currentActions().addFront(OPEN, h.roomNumber(), h.destHotspotId(), 0);
4163 			h.setBlockedFlag(false);
4164 			return;
4165 		}
4166 	}
4167 
4168 	// No exit hotspot, or it has one that's not blocked. So start the walking
4169 	h.currentActions().top().setAction(START_WALKING);
4170 	h.setWalkFlag(true);
4171 }
4172 
4173 /*-------------------------------------------------------------------------*/
4174 /* Miscellaneous classes                                                   */
4175 /*                                                                         */
4176 /*-------------------------------------------------------------------------*/
4177 
4178 // WalkingActionEntry class
4179 
4180 // This method performs rounding of the number of steps depending on direciton
4181 
numSteps() const4182 int WalkingActionEntry::numSteps() const {
4183 	switch (_direction) {
4184 	case UP:
4185 	case DOWN:
4186 		return (_numSteps + 1) >> 1;
4187 
4188 	case LEFT:
4189 	case RIGHT:
4190 		return (_numSteps + 3) >> 2;
4191 	default:
4192 		return 0;
4193 	}
4194 }
4195 
4196 // PathFinder class
4197 
PathFinder(Hotspot * h)4198 PathFinder::PathFinder(Hotspot *h) {
4199 	_hotspot = h;
4200 	_inUse = false;
4201 	_list.clear();
4202 	_stepCtr = 0;
4203 }
4204 
clear()4205 void PathFinder::clear() {
4206 	_stepCtr = 0;
4207 	_list.clear();
4208 	_inProgress = false;
4209 	_countdownCtr = PATHFIND_COUNTDOWN;
4210 }
4211 
reset(RoomPathsData & src)4212 void PathFinder::reset(RoomPathsData &src) {
4213 	clear();
4214 	src.decompress(_layer, _hotspot->widthCopy());
4215 	_inUse = true;
4216 }
4217 
4218 // Does the next stage of processing to figure out a path to take to a given
4219 // destination. Returns true if the path finding has been completed
4220 
process()4221 PathFinderResult PathFinder::process() {
4222 	bool returnFlag = _inProgress;
4223 	// Check whether the pathfinding can be broken by the countdown counter
4224 	bool breakFlag = (PATHFIND_COUNTDOWN != 0);
4225 	_countdownCtr = PATHFIND_COUNTDOWN;
4226 	int v;
4227 	uint16 *pTemp;
4228 	bool scanFlag = false;
4229 	Direction currDirection = NO_DIRECTION;
4230 	Direction newDirection;
4231 	uint16 numSteps = 0, savedSteps = 0;
4232 	bool altFlag;
4233 	uint16 *pCurrent;
4234 	PathFinderResult result = PF_UNFINISHED;
4235 	bool skipToFinalStep = false;
4236 
4237 	if (!_inProgress) {
4238 		// Following code only done during first call to method
4239 		_inProgress = true;
4240 		initVars();
4241 
4242 		Common::Point diff(_destX - _xCurrent, _destY - _yCurrent);
4243 		_xCurrent >>= 3; _yCurrent >>= 3;
4244 		_xDestCurrent >>= 3; _yDestCurrent >>= 3;
4245 		if ((_xCurrent == _xDestCurrent) && (_yCurrent == _yDestCurrent)) {
4246 			// Very close move
4247 			if (_xDestPos > 0)
4248 				add(RIGHT, _xDestPos);
4249 			else if (_xDestPos < 0)
4250 				add(LEFT, -_xDestPos);
4251 			else if (diff.y > 0)
4252 				add(DOWN, diff.y);
4253 			else
4254 				add(UP, -diff.y);
4255 
4256 			_inProgress = false;
4257 			result = PF_OK;
4258 			skipToFinalStep = true;
4259 		} else {
4260 			// Path finding
4261 
4262 			_destX >>= 3;
4263 			_destY >>= 3;
4264 			_pSrc = &_layer[(_yCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xCurrent];
4265 			_pDest = &_layer[(_yDestCurrent + 1) * DECODED_PATHS_WIDTH + 1 + _xDestCurrent];
4266 
4267 			// Flag starting/ending cells
4268 			*_pSrc = 1;
4269 			_destOccupied = *_pDest != 0;
4270 			result = _destOccupied ? PF_DEST_OCCUPIED : PF_OK;
4271 			*_pDest = 0;
4272 
4273 			// Set up the current pointer, adjusting away from edges if necessary
4274 
4275 			if (_xCurrent >= _xDestCurrent) {
4276 				_xChangeInc = -1;
4277 				_xChangeStart = ROOM_PATHS_WIDTH;
4278 			} else {
4279 				_xChangeInc = 1;
4280 				_xChangeStart = 1;
4281 			}
4282 
4283 			if (_yCurrent >= _yDestCurrent) {
4284 				_yChangeInc = -1;
4285 				_yChangeStart = ROOM_PATHS_HEIGHT;
4286 			} else {
4287 				_yChangeInc = 1;
4288 				_yChangeStart = 1;
4289 			}
4290 		}
4291 	}
4292 
4293 	if (!skipToFinalStep) {
4294 		// Major loop to populate data
4295 		_cellPopulated = false;
4296 
4297 		while (1) {
4298 			// Loop through to process cells in the given area
4299 			if (!returnFlag)
4300 				_yCtr = 0;
4301 			while (returnFlag || (_yCtr < ROOM_PATHS_HEIGHT)) {
4302 				if (!returnFlag) _xCtr = 0;
4303 
4304 				while (returnFlag || (_xCtr < ROOM_PATHS_WIDTH)) {
4305 					if (!returnFlag) {
4306 						processCell(&_layer[(_yChangeStart + _yCtr * _yChangeInc) * DECODED_PATHS_WIDTH +
4307 							(_xChangeStart + _xCtr * _xChangeInc)]);
4308 						if (breakFlag && (_countdownCtr <= 0))
4309 							return PF_UNFINISHED;
4310 					} else {
4311 						returnFlag = false;
4312 					}
4313 					++_xCtr;
4314 				}
4315 				++_yCtr;
4316 			}
4317 
4318 			// If the destination cell has been filled in, then break out of loop
4319 			if (*_pDest != 0)
4320 				break;
4321 
4322 			if (_cellPopulated) {
4323 				// At least one cell populated, so go repeat loop
4324 				_cellPopulated = false;
4325 			} else {
4326 				result = PF_PART_PATH;
4327 				scanFlag = true;
4328 				break;
4329 			}
4330 		}
4331 		_inProgress = false;
4332 
4333 		if (scanFlag || _destOccupied) {
4334 			// Adjust the end point if necessary to stop character walking into occupied area
4335 
4336 			// Restore destination's occupied state if necessary
4337 			if (_destOccupied) {
4338 				*_pDest = 0xffff;
4339 				_destOccupied = false;
4340 			}
4341 
4342 			// Scan through lines
4343 			v = 0xff;
4344 			pTemp = _pDest;
4345 			scanLine(_destX, -1, pTemp, v);
4346 			scanLine(ROOM_PATHS_WIDTH - _destX, 1, pTemp, v);
4347 			scanLine(_destY, -DECODED_PATHS_WIDTH, pTemp, v);
4348 			scanLine(ROOM_PATHS_HEIGHT - _destY, DECODED_PATHS_WIDTH, pTemp, v);
4349 
4350 			if (pTemp == _pDest) {
4351 				clear();
4352 				return PF_NO_WALK;
4353 			}
4354 
4355 			_pDest = pTemp;
4356 		}
4357 
4358 		// ****DEBUG****
4359 		if (_hotspot->hotspotId() == PLAYER_ID) {
4360 			for (int ctr = 0; ctr < DECODED_PATHS_WIDTH * DECODED_PATHS_HEIGHT; ++ctr)
4361 				Room::getReference().tempLayer[ctr] = _layer[ctr];
4362 		}
4363 
4364 		// Determine the walk path by working backwards from the destination, adding in the
4365 		// walking steps in reverse order until source is reached
4366 		int stageCtr;
4367 		for (stageCtr = 0; stageCtr < 3; ++stageCtr) {
4368 			// Clear out any previously determined directions
4369 			clear();
4370 
4371 			altFlag = stageCtr == 1;
4372 			pCurrent = _pDest;
4373 
4374 			numSteps = 0;
4375 			currDirection = NO_DIRECTION;
4376 			while (1) {
4377 				v = *pCurrent - 1;
4378 				if (v == 0)
4379 					break;
4380 
4381 				newDirection = NO_DIRECTION;
4382 				if (!altFlag && (currDirection != LEFT) && (currDirection != RIGHT)) {
4383 					// Standard order direction checking
4384 					if (*(pCurrent - DECODED_PATHS_WIDTH) == v)
4385 						newDirection = DOWN;
4386 					else if (*(pCurrent + DECODED_PATHS_WIDTH) == v)
4387 						newDirection = UP;
4388 					else if (*(pCurrent + 1) == v)
4389 						newDirection = LEFT;
4390 					else if (*(pCurrent - 1) == v)
4391 						newDirection = RIGHT;
4392 				} else {
4393 					// Alternate order direction checking
4394 					if (*(pCurrent + 1) == v)
4395 						newDirection = LEFT;
4396 					else if (*(pCurrent - 1) == v)
4397 						newDirection = RIGHT;
4398 					else if (*(pCurrent - DECODED_PATHS_WIDTH) == v)
4399 						newDirection = DOWN;
4400 					else if (*(pCurrent + DECODED_PATHS_WIDTH) == v)
4401 						newDirection = UP;
4402 				}
4403 				if (newDirection == NO_DIRECTION)
4404 					error("Path finding process failed");
4405 
4406 				// Process for the specified direction
4407 				if (newDirection != currDirection)
4408 					add(newDirection, 0);
4409 
4410 				switch (newDirection) {
4411 				case UP:
4412 					pCurrent += DECODED_PATHS_WIDTH;
4413 					break;
4414 
4415 				case DOWN:
4416 					pCurrent -= DECODED_PATHS_WIDTH;
4417 					break;
4418 
4419 				case LEFT:
4420 					++pCurrent;
4421 					break;
4422 
4423 				case RIGHT:
4424 					--pCurrent;
4425 					break;
4426 
4427 				default:
4428 					break;
4429 				}
4430 
4431 				++numSteps;
4432 				top().rawSteps() += 8;
4433 				currDirection = newDirection;
4434 			}
4435 
4436 			if (stageCtr == 0)
4437 				// Save the number of steps needed
4438 				savedSteps = numSteps;
4439 			if ((stageCtr == 1) && (numSteps <= savedSteps))
4440 				// Less steps were needed, so break out
4441 				break;
4442 		}
4443 
4444 		// Add final movement if necessary
4445 
4446 		if (result == PF_OK) {
4447 			if (_xDestPos < 0)
4448 				addBack(LEFT, -_xDestPos);
4449 			else if (_xDestPos > 0)
4450 				addBack(RIGHT, _xDestPos);
4451 		}
4452 	}
4453 
4454 	// Final Step
4455 	if (_xPos < 0)
4456 		add(RIGHT, -_xPos);
4457 	else if (_xPos > 0)
4458 		add(LEFT, _xPos);
4459 
4460 	return result;
4461 }
4462 
getDebugInfo() const4463 Common::String PathFinder::getDebugInfo() const {
4464 	Common::String buffer;
4465 	buffer += "Pathfinder::list(\n";
4466 
4467 	WalkingActionList::const_iterator i;
4468 	for (i = _list.begin(); i != _list.end(); ++i) {
4469 		WalkingActionEntry const &e = **i;
4470 		buffer += Common::String::format("Direction=%d, numSteps=%d\n", e.direction(), e.numSteps());
4471 	}
4472 
4473 	return buffer;
4474 }
4475 
processCell(uint16 * p)4476 void PathFinder::processCell(uint16 *p) {
4477 	// Only process cells that are still empty
4478 	if (*p == 0) {
4479 		uint16 vMax = 0xffff;
4480 		uint16 vTemp;
4481 
4482 		// Check the surrounding cells (up,down,left,right) for values
4483 		// Up
4484 		vTemp = *(p - DECODED_PATHS_WIDTH);
4485 		if ((vTemp != 0) && (vTemp < vMax))
4486 			vMax = vTemp;
4487 		// Down
4488 		vTemp = *(p + DECODED_PATHS_WIDTH);
4489 		if ((vTemp != 0) && (vTemp < vMax))
4490 			vMax = vTemp;
4491 		// Left
4492 		vTemp = *(p - 1);
4493 		if ((vTemp != 0) && (vTemp < vMax))
4494 			vMax = vTemp;
4495 		// Right
4496 		vTemp = *(p + 1);
4497 		if ((vTemp != 0) && (vTemp < vMax))
4498 			vMax = vTemp;
4499 
4500 		if (vMax != 0xffff) {
4501 			// A surrounding cell with a value was found
4502 			++vMax;
4503 			*p = vMax;
4504 			_cellPopulated = true;
4505 		}
4506 
4507 		_countdownCtr -= 3;
4508 
4509 	} else {
4510 		--_countdownCtr;
4511 	}
4512 }
4513 
scanLine(int numScans,int changeAmount,uint16 * & pEnd,int & v)4514 void PathFinder::scanLine(int numScans, int changeAmount, uint16 *&pEnd, int &v) {
4515 	uint16 *pTemp = _pDest;
4516 
4517 	for (int ctr = 1; ctr <= numScans; ++ctr) {
4518 		pTemp += changeAmount;
4519 		if ((*pTemp != 0) && (*pTemp != 0xffff)) {
4520 			if ((v < ctr) || ((v == ctr) && (*pTemp >= *pEnd)))
4521 				return;
4522 			pEnd = pTemp;
4523 			v = ctr;
4524 			break;
4525 		}
4526 	}
4527 }
4528 
initVars()4529 void PathFinder::initVars() {
4530 	int16 xRight;
4531 
4532 	// Set up dest position, adjusting for walking off screen if necessary
4533 	_destX = _hotspot->destX();
4534 	_destY = _hotspot->destY();
4535 
4536 	if (_destX < 10)
4537 		_destX -= 50;
4538 	if (_destX >= FULL_SCREEN_WIDTH-10)
4539 		_destX += 50;
4540 
4541 	_xPos = 0;
4542 	_yPos = 0;
4543 	_xDestPos = 0;
4544 	_yDestPos = 0;
4545 
4546 	_xCurrent = _hotspot->x();
4547 	if (_xCurrent < 0) {
4548 		_xPos = _xCurrent;
4549 		_xCurrent = 0;
4550 	}
4551 	xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy() - 1;
4552 	if (_xCurrent >= xRight) {
4553 		_xPos = _xCurrent - xRight;
4554 		_xCurrent = xRight;
4555 	}
4556 
4557 	_yCurrent = (_hotspot->y() & 0xf8) + _hotspot->heightCopy() - MENUBAR_Y_SIZE - 4;
4558 	if (_yCurrent < 0) {
4559 		_yPos = _yCurrent;
4560 		_yCurrent = 0;
4561 	}
4562 	if (_yCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE)) {
4563 		_yPos = _yCurrent - (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE);
4564 		_yCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE;
4565 	}
4566 
4567 	_xDestCurrent = _destX;
4568 	if (_xDestCurrent < 0) {
4569 		_xDestPos = _xDestCurrent;
4570 		_xDestCurrent = 0;
4571 	}
4572 	xRight = FULL_SCREEN_WIDTH - _hotspot->widthCopy();
4573 	if (_xDestCurrent >= xRight) {
4574 		_xDestPos = _xDestCurrent - xRight;
4575 		_xDestCurrent = xRight;
4576 	}
4577 
4578 	_yDestCurrent = _destY - MENUBAR_Y_SIZE;
4579 	if (_yDestCurrent < 0)
4580 		_yDestCurrent = 0;
4581 	if (_yDestCurrent >= (FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE))
4582 		_yDestCurrent = FULL_SCREEN_HEIGHT - MENUBAR_Y_SIZE - 1;
4583 
4584 	// Subtract an amount from the countdown counter to compensate for
4585 	// the time spent decompressing the walkable areas set for the room
4586 	_countdownCtr -= 700;
4587 }
4588 
saveToStream(Common::WriteStream * stream) const4589 void PathFinder::saveToStream(Common::WriteStream *stream) const {
4590 	stream->writeByte(_inUse);
4591 
4592 	if (_inUse) {
4593 		// Save the path finding plane
4594 		stream->write(_layer, sizeof(RoomPathsDecompressedData));
4595 
4596 		// Save any active step sequence
4597 		for (WalkingActionList::const_iterator i = _list.begin(); i != _list.end(); ++i) {
4598 			WalkingActionEntry &entry = **i;
4599 			stream->writeByte(entry.direction());
4600 			stream->writeSint16LE(entry.rawSteps());
4601 		}
4602 		stream->writeByte(0xff);
4603 		stream->writeSint16LE(_stepCtr);
4604 	}
4605 }
4606 
loadFromStream(Common::ReadStream * stream)4607 void PathFinder::loadFromStream(Common::ReadStream *stream) {
4608 	_inProgress = false;
4609 	_inUse = stream->readByte() != 0;
4610 
4611 	if (_inUse) {
4612 		stream->read(_layer, sizeof(RoomPathsDecompressedData));
4613 
4614 		_list.clear();
4615 		uint8 direction;
4616 		while ((direction = stream->readByte()) != 0xff) {
4617 			int steps = stream->readSint16LE();
4618 			_list.push_back(WalkingActionList::value_type(new WalkingActionEntry((Direction) direction, steps)));
4619 		}
4620 		_stepCtr = stream->readSint16LE();
4621 	}
4622 }
4623 
4624 /*-------------------------------------------------------------------------*/
4625 /* Support methods                                                         */
4626 /*                                                                         */
4627 /*-------------------------------------------------------------------------*/
4628 
4629 // finds a list of character animations whose base area are impinging
4630 // that of the specified character (ie. are bumping into them)
4631 
findIntersectingCharacters(Hotspot & h,uint16 * charList,int16 xp,int16 yp,int roomNumber)4632 int Support::findIntersectingCharacters(Hotspot &h, uint16 *charList, int16 xp, int16 yp, int roomNumber) {
4633 	int numImpinging = 0;
4634 	Resources &res = Resources::getReference();
4635 	Common::Rect r;
4636 	uint16 hotspotY;
4637 
4638 	// If a specific x/y/room isn't provided, use the specified hotspot's current location
4639 	if (roomNumber == -1) {
4640 		xp = h.x();
4641 		yp = h.y();
4642 		roomNumber = h.roomNumber();
4643 	}
4644 
4645 	r.left = xp;
4646 	r.right = xp + h.widthCopy();
4647 	r.top = yp + h.heightCopy() - h.yCorrection() - h.charRectY();
4648 	r.bottom = yp + h.heightCopy() + h.charRectY();
4649 
4650 	HotspotList::iterator i;
4651 	for (i = res.activeHotspots().begin(); i != res.activeHotspots().end(); ++i) {
4652 		Hotspot &hotspot = **i;
4653 
4654 		// Check for basic reasons to skip checking the animation
4655 		if ((h.hotspotId() == hotspot.hotspotId()) || (hotspot.layer() == 0) ||
4656 			(roomNumber != hotspot.roomNumber()) ||
4657 			(hotspot.hotspotId() >= FIRST_NONCHARACTER_ID) ||
4658 			hotspot.skipFlag()) continue;
4659 		// TODO: See why si+ANIM_HOTSPOT_OFFSET compared aganst di+ANIM_VOICE_CTR
4660 
4661 		hotspotY = hotspot.y() + hotspot.heightCopy();
4662 		if ((hotspot.x() >= r.right) || (hotspot.x() + hotspot.widthCopy() <= r.left) ||
4663 			(hotspotY + hotspot.charRectY() <= r.top) ||
4664 			(hotspotY - hotspot.charRectY() - hotspot.yCorrection() >= r.bottom))
4665 			continue;
4666 
4667 		// Add hotspot Id to list
4668 		if (numImpinging == MAX_NUM_IMPINGING)
4669 			error("Exceeded maximum allowable number of impinging characters");
4670 		*charList++ = hotspot.hotspotId();
4671 		++numImpinging;
4672 	}
4673 
4674 	return numImpinging;
4675 }
4676 
4677 // Returns true if any other characters are intersecting the specified one
4678 
checkForIntersectingCharacter(Hotspot & h,int16 xp,int16 yp,int roomNumber)4679 bool Support::checkForIntersectingCharacter(Hotspot &h, int16 xp, int16 yp, int roomNumber) {
4680 	uint16 tempList[MAX_NUM_IMPINGING];
4681 	return findIntersectingCharacters(h, tempList, xp, yp, roomNumber) != 0;
4682 }
4683 
4684 // Check whether a character needs to change the room they're in
4685 
checkRoomChange(Hotspot & h)4686 bool Support::checkRoomChange(Hotspot &h) {
4687 	int16 x = h.x() + (h.widthCopy() >> 1);
4688 	int16 y = h.y() + h.heightCopy() - (h.yCorrection() >> 1);
4689 
4690 	RoomData *roomData = Resources::getReference().getRoom(h.roomNumber());
4691 	RoomExitData *exitRec = roomData->exits.checkExits(x, y);
4692 
4693 	if (exitRec) {
4694 		// End the current walking sequence
4695 		if (exitRec->sequenceOffset != 0xffff) {
4696 			Script::execute(exitRec->sequenceOffset);
4697 		} else {
4698 			Support::characterChangeRoom(h, exitRec->roomNumber,
4699 				exitRec->x, exitRec->y, exitRec->direction);
4700 		}
4701 	}
4702 
4703 	return (exitRec != NULL);
4704 }
4705 
characterChangeRoom(Hotspot & h,uint16 roomNumber,int16 newX,int16 newY,Direction dir)4706 void Support::characterChangeRoom(Hotspot &h, uint16 roomNumber,
4707 								  int16 newX, int16 newY, Direction dir) {
4708 	Resources &res = Resources::getReference();
4709 	Room &room = Room::getReference();
4710 	ValueTableData &fields = res.fieldList();
4711 
4712 	if (h.hotspotId() == PLAYER_ID) {
4713 		// Room change code for the player
4714 		if (room.cursorState() != CS_NONE)
4715 			return;
4716 		PlayerNewPosition &p = fields.playerNewPos();
4717 
4718 		if (checkForIntersectingCharacter(h, newX, newY - 48, roomNumber)) {
4719 			// Another character is blocking the exit in the other room, so set the player to
4720 			// temporarily move to a random destination in the current room
4721 			h.tempDest().position.x = h.destX();
4722 			h.tempDest().position.y = h.destY();
4723 			h.tempDest().counter = 1;
4724 			Room::getReference().setCursorState(CS_BUMPED);
4725 			h.setActionCtr(0);
4726 			h.setBlockedState((BlockedState) ((int)h.blockedState() + 1));
4727 			h.setDestHotspot(0);
4728 			h.setRandomDest();
4729 			p.roomNumber = 0;
4730 		} else {
4731 			// Flag the new location to move the player to (which will be handled by the outer game loop)
4732 			h.setDirection(dir);
4733 			p.roomNumber = roomNumber;
4734 			p.position.x = newX;
4735 			p.position.y = newY - 48;
4736 		}
4737 
4738 	} else {
4739 		// Any other character changing room
4740 		newX = (newX & 0xfff8) | 5;
4741 		newY = (newY - h.heightCopy()) & 0xfff8;
4742 
4743 		if (checkForIntersectingCharacter(h, newX, newY, roomNumber)) {
4744 			// Character is blocked, so add a handler for handling it
4745 			uint16 dataId = res.getCharOffset(0);
4746 			CharacterScheduleEntry *entry = res.charSchedules().getEntry(dataId);
4747 			h.currentActions().addFront(DISPATCH_ACTION, entry, h.roomNumber());
4748 		} else {
4749 			// Handle character room change
4750 			h.setRoomNumber(roomNumber);
4751 			h.setPosition(newX, newY);
4752 			h.setSkipFlag(true);
4753 			h.setDirection(dir);
4754 
4755 			h.setExitCtr(0);
4756 			h.currentActions().top().setAction(DISPATCH_ACTION);
4757 		}
4758 	}
4759 }
4760 
charactersIntersecting(HotspotData * hotspot1,HotspotData * hotspot2)4761 bool Support::charactersIntersecting(HotspotData *hotspot1, HotspotData *hotspot2) {
4762 	return !((hotspot1->startX + hotspot1->widthCopy + 4 < hotspot2->startX) ||
4763 		(hotspot2->startX + hotspot2->widthCopy + 4 < hotspot1->startX) ||
4764 		(hotspot2->startY + hotspot2->heightCopy - hotspot2->yCorrection - 2 >=
4765 			hotspot1->startY + hotspot1->heightCopy + 2) ||
4766 		(hotspot2->startY + hotspot2->heightCopy + 2 <
4767 			hotspot1->startY + hotspot1->heightCopy - hotspot1->yCorrection - 2));
4768 }
4769 
isCharacterInList(uint16 * lst,int numEntries,uint16 charId)4770 bool Support::isCharacterInList(uint16 *lst, int numEntries, uint16 charId) {
4771 	while (numEntries-- > 0)
4772 		if (*lst++ == charId)
4773 			return true;
4774 	return false;
4775 }
4776 
saveToStream(Common::WriteStream * stream) const4777 void HotspotList::saveToStream(Common::WriteStream *stream) const {
4778 	for (HotspotList::const_iterator i = begin(); i != end(); ++i) {
4779 		Hotspot const &hotspot = **i;
4780 		debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Saving hotspot %xh", hotspot.hotspotId());
4781 		bool dynamicObject = hotspot.hotspotId() != hotspot.originalId();
4782 		stream->writeUint16LE(hotspot.originalId());
4783 		stream->writeByte(dynamicObject);
4784 		stream->writeUint16LE(hotspot.destHotspotId());
4785 		hotspot.saveToStream(stream);
4786 
4787 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Saved hotspot %xh", hotspot.hotspotId());
4788 	}
4789 	stream->writeUint16LE(0);
4790 }
4791 
loadFromStream(Common::ReadStream * stream)4792 void HotspotList::loadFromStream(Common::ReadStream *stream) {
4793 	Resources &res = Resources::getReference();
4794 	Hotspot *hotspot;
4795 
4796 	clear();
4797 	uint16 hotspotId = stream->readUint16LE();
4798 	while (hotspotId != 0) {
4799 		debugC(ERROR_INTERMEDIATE, kLureDebugAnimations, "Loading hotspot %xh", hotspotId);
4800 		bool dynamicObject = stream->readByte() != 0;
4801 		uint16 destHotspotId = stream->readUint16LE();
4802 
4803 		if (dynamicObject) {
4804 			// Add in a dynamic object (such as a floating talk bubble)
4805 			Hotspot *destHotspot = res.getActiveHotspot(destHotspotId);
4806 			assert(destHotspot);
4807 			hotspot = new Hotspot(destHotspot, hotspotId);
4808 		} else {
4809 			HotspotData *hotspotData = res.getHotspot(hotspotId);
4810 			assert(hotspotData);
4811 			hotspot = new Hotspot(hotspotData);
4812 		}
4813 
4814 		res.addHotspot(hotspot);
4815 		assert(hotspot);
4816 		hotspot->loadFromStream(stream);
4817 
4818 		debugC(ERROR_DETAILED, kLureDebugAnimations, "Loaded hotspot %xh", hotspotId);
4819 
4820 		// Get the next hotspot
4821 		hotspotId = stream->readUint16LE();
4822 	}
4823 }
4824 
4825 } // End of namespace Lure
4826