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