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 "common/str.h"
24
25 #include "gob/hotspots.h"
26 #include "gob/global.h"
27 #include "gob/draw.h"
28 #include "gob/game.h"
29 #include "gob/script.h"
30 #include "gob/inter.h"
31
32 namespace Gob {
33
Hotspot()34 Hotspots::Hotspot::Hotspot() {
35 clear();
36 }
37
Hotspot(uint16 i,uint16 l,uint16 t,uint16 r,uint16 b,uint16 f,uint16 k,uint16 enter,uint16 leave,uint16 pos)38 Hotspots::Hotspot::Hotspot(uint16 i,
39 uint16 l, uint16 t, uint16 r, uint16 b, uint16 f, uint16 k,
40 uint16 enter, uint16 leave, uint16 pos) {
41
42 id = i;
43 left = l;
44 top = t;
45 right = r;
46 bottom = b;
47 flags = f;
48 key = k;
49 funcEnter = enter;
50 funcLeave = leave;
51 funcPos = pos;
52 script = 0;
53 }
54
clear()55 void Hotspots::Hotspot::clear() {
56 id = 0;
57 left = 0xFFFF;
58 top = 0;
59 right = 0;
60 bottom = 0;
61 flags = 0;
62 key = 0;
63 funcEnter = 0;
64 funcLeave = 0;
65 funcPos = 0;
66 script = 0;
67 }
68
getType() const69 Hotspots::Type Hotspots::Hotspot::getType() const {
70 return (Type) (flags & 0xF);
71 }
72
getButton() const73 MouseButtons Hotspots::Hotspot::getButton() const {
74 uint8 buttonBits = ((flags & 0x70) >> 4);
75
76 if (buttonBits == 0)
77 return kMouseButtonsLeft;
78 if (buttonBits == 1)
79 return kMouseButtonsRight;
80 if (buttonBits == 2)
81 return kMouseButtonsAny;
82
83 return kMouseButtonsNone;
84 }
85
getWindow() const86 uint16 Hotspots::Hotspot::getWindow() const {
87 return (flags & 0x0F00);
88 }
89
getCursor() const90 uint8 Hotspots::Hotspot::getCursor() const {
91 return (flags & 0xF000) >> 12;
92 }
93
getState(uint16 id)94 uint8 Hotspots::Hotspot::getState(uint16 id) {
95 return (id & 0xF000) >> 12;
96 }
97
getState() const98 uint8 Hotspots::Hotspot::getState() const {
99 return getState(id);
100 }
101
isEnd() const102 bool Hotspots::Hotspot::isEnd() const {
103 return (left == 0xFFFF);
104 }
105
isInput() const106 bool Hotspots::Hotspot::isInput() const {
107 if (getType() < kTypeInput1NoLeave)
108 return false;
109
110 if (getType() > kTypeInputFloatLeave)
111 return false;
112
113 return true;
114 }
115
isActiveInput() const116 bool Hotspots::Hotspot::isActiveInput() const {
117 if (isEnd())
118 return false;
119
120 if (!isFilledEnabled())
121 return false;
122
123 if (!isInput())
124 return false;
125
126 return true;
127 }
128
isInputLeave() const129 bool Hotspots::Hotspot::isInputLeave() const {
130 if (!isInput())
131 return false;
132
133 if (!(getType() & 1))
134 return true;
135
136 return false;
137 }
138
isFilled() const139 bool Hotspots::Hotspot::isFilled() const {
140 return getState() & kStateFilled;
141 }
142
isFilledEnabled() const143 bool Hotspots::Hotspot::isFilledEnabled() const {
144 return (getState() & kStateFilledDisabled) == kStateFilled;
145 }
146
isFilledNew() const147 bool Hotspots::Hotspot::isFilledNew() const {
148 return getState() == kStateFilled;
149 }
150
isDisabled() const151 bool Hotspots::Hotspot::isDisabled() const {
152 return getState() & kStateDisabled;
153 }
154
isIn(uint16 x,uint16 y) const155 bool Hotspots::Hotspot::isIn(uint16 x, uint16 y) const {
156 // FIXME: the cast to int16 is a hack, to fix handling of Gob2 problems related to
157 // hotspots with negative offset (to temporary disable them).
158 if ((int16) x < (int16) left)
159 return false;
160 if ((int16) x > (int16) right)
161 return false;
162 if ((int16) y < (int16) top)
163 return false;
164 if ((int16) y > (int16) bottom)
165 return false;
166
167 return true;
168 }
169
buttonMatch(MouseButtons button) const170 bool Hotspots::Hotspot::buttonMatch(MouseButtons button) const {
171 MouseButtons myButton = getButton();
172
173 if (myButton == kMouseButtonsAny)
174 // Any button allowed
175 return true;
176
177 if (myButton == kMouseButtonsNone)
178 // No button allowed
179 return false;
180
181 if (myButton == button)
182 // Exact match
183 return true;
184
185 return false;
186 }
187
disable()188 void Hotspots::Hotspot::disable() {
189 id |= (kStateDisabled << 12);
190 }
191
enable()192 void Hotspots::Hotspot::enable() {
193 id &= ~(kStateDisabled << 12);
194 }
195
196
Hotspots(GobEngine * vm)197 Hotspots::Hotspots(GobEngine *vm) : _vm(vm) {
198 _hotspots = new Hotspot[kHotspotCount];
199
200 _shouldPush = false;
201
202 _currentKey = 0;
203 _currentIndex = 0;
204 _currentId = 0;
205 _currentX = 0;
206 _currentY = 0;
207 }
208
~Hotspots()209 Hotspots::~Hotspots() {
210 delete[] _hotspots;
211
212 // Pop the whole stack and free each element's memory
213 while (!_stack.empty()) {
214
215 StackEntry backup = _stack.pop();
216
217 delete[] backup.hotspots;
218 }
219 }
220
clear()221 void Hotspots::clear() {
222 _currentKey = 0;
223
224 for (int i = 0; i < kHotspotCount; i++)
225 _hotspots[i].clear();
226 }
227
add(uint16 id,uint16 left,uint16 top,uint16 right,uint16 bottom,uint16 flags,uint16 key,uint16 funcEnter,uint16 funcLeave,uint16 funcPos)228 uint16 Hotspots::add(uint16 id,
229 uint16 left, uint16 top, uint16 right, uint16 bottom,
230 uint16 flags, uint16 key,
231 uint16 funcEnter, uint16 funcLeave, uint16 funcPos) {
232
233 Hotspot hotspot(id, left, top, right, bottom,
234 flags, key, funcEnter, funcLeave, funcPos);
235
236 return add(hotspot);
237 }
238
add(const Hotspot & hotspot)239 uint16 Hotspots::add(const Hotspot &hotspot) {
240 for (int i = 0; i < kHotspotCount; i++) {
241 Hotspot &spot = _hotspots[i];
242
243 // free space => add same id => update
244 if (! (spot.isEnd() || (spot.id == hotspot.id)))
245 continue;
246
247 // When updating, keep disabled state intact
248 uint16 id = hotspot.id;
249 if ((spot.id & ~(kStateDisabled << 12)) ==
250 (hotspot.id & ~(kStateDisabled << 12)))
251 id = spot.id;
252
253 // Set
254 spot = hotspot;
255 spot.id = id;
256
257 // Remember the current script
258 spot.script = _vm->_game->_script;
259
260 debugC(1, kDebugHotspots, "Adding hotspot %03d: Coord:%3d+%3d+%3d+%3d - id:%04X, key:%04X, flag:%04X - fcts:%5d, %5d, %5d",
261 i, spot.left, spot.top, spot.right, spot.bottom,
262 spot.id, spot.key, spot.flags, spot.funcEnter, spot.funcLeave, spot.funcPos);
263
264 return i;
265 }
266
267 error("Hotspots::add(): Hotspot array full");
268 return 0xFFFF; // for compilers that don't support NORETURN
269 }
270
remove(uint16 id)271 void Hotspots::remove(uint16 id) {
272 for (int i = 0; i < kHotspotCount; i++) {
273 if (_hotspots[i].id == id) {
274 debugC(1, kDebugHotspots, "Removing hotspot %d: %X", i, id);
275 _hotspots[i].clear();
276 }
277 }
278 }
279
removeState(uint8 state)280 void Hotspots::removeState(uint8 state) {
281 for (int i = 0; i < kHotspotCount; i++) {
282 Hotspot &spot = _hotspots[i];
283
284 if (spot.getState() == state) {
285 debugC(1, kDebugHotspots, "Removing hotspot %d: %X (by state %X)", i, spot.id, state);
286 spot.clear();
287 }
288 }
289 }
290
recalculate(bool force)291 void Hotspots::recalculate(bool force) {
292 debugC(5, kDebugHotspots, "Recalculating hotspots");
293
294 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
295 Hotspot &spot = _hotspots[i];
296
297 if (!force && ((spot.flags & 0x80) != 0))
298 // Not forcing a special hotspot
299 continue;
300
301 if (spot.funcPos == 0)
302 // Simple coordinates don't need update
303 continue;
304
305 // Setting the needed script
306 Script *curScript = _vm->_game->_script;
307
308 _vm->_game->_script = spot.script;
309 if (!_vm->_game->_script)
310 _vm->_game->_script = curScript;
311
312 // Calling the function that contains the positions
313 _vm->_game->_script->call(spot.funcPos);
314
315 // Calculate positions
316 int16 left = _vm->_game->_script->readValExpr();
317 int16 top = _vm->_game->_script->readValExpr();
318 int16 width = _vm->_game->_script->readValExpr();
319 int16 height = _vm->_game->_script->readValExpr();
320
321 // Re-read the flags too, if applicable
322 uint16 flags = 0;
323 if (spot.getState() == (kStateFilled | kStateType2))
324 flags = _vm->_game->_script->readValExpr();
325
326 // Apply backDelta, if needed
327 if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != -1)) {
328 left += _vm->_draw->_backDeltaX;
329 top += _vm->_draw->_backDeltaY;
330 }
331
332 // Clamping
333 if (left < 0) {
334 width += left;
335 left = 0;
336 }
337 if (top < 0) {
338 height += top;
339 top = 0;
340 }
341
342 // Set the updated position
343 spot.left = left;
344 spot.top = top;
345 spot.right = left + width - 1;
346 spot.bottom = top + height - 1;
347
348 if (spot.getState() == (kStateFilled | kStateType2))
349 spot.flags = flags;
350
351 // Return
352 _vm->_game->_script->pop();
353
354 _vm->_game->_script = curScript;
355 }
356 }
357
push(uint8 all,bool force)358 void Hotspots::push(uint8 all, bool force) {
359 debugC(1, kDebugHotspots, "Pushing hotspots (%d, %d)", all, force);
360
361 // Should we push at all?
362 if (!_shouldPush && !force)
363 return;
364
365 // Count the hotspots
366 uint32 size = 0;
367 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
368 Hotspot &spot = _hotspots[i];
369
370 // Save all of them
371 if ( (all == 1) ||
372 // Don't save the global ones
373 ((all == 0) && (spot.id >= 20)) ||
374 // Only save disabled ones
375 ((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
376 (spot.getState() == (kStateDisabled)) ||
377 (spot.getState() == (kStateFilledDisabled | kStateType2))))) {
378 size++;
379 }
380
381 }
382
383 StackEntry backup;
384
385 backup.shouldPush = _shouldPush;
386 backup.size = size;
387 backup.key = _currentKey;
388 backup.id = _currentId;
389 backup.index = _currentIndex;
390 backup.x = _currentX;
391 backup.y = _currentY;
392
393 backup.hotspots = new Hotspot[size];
394
395 // Copy the hotspots
396 Hotspot *destPtr = backup.hotspots;
397 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
398 Hotspot &spot = _hotspots[i];
399
400 // Save all of them
401 if ( (all == 1) ||
402 // Don't save the global ones
403 ((all == 0) && (spot.id >= 20)) ||
404 // Only save disabled ones
405 ((all == 2) && ((spot.getState() == (kStateFilledDisabled | kStateType1)) ||
406 (spot.getState() == (kStateDisabled)) ||
407 (spot.getState() == (kStateFilledDisabled | kStateType2))))) {
408
409 memcpy(destPtr, &spot, sizeof(Hotspot));
410 destPtr++;
411
412 spot.clear();
413 }
414
415 }
416
417 // Reset current state
418 _shouldPush = false;
419 _currentKey = 0;
420 _currentId = 0;
421 _currentIndex = 0;
422 _currentX = 0;
423 _currentY = 0;
424
425 _stack.push(backup);
426 }
427
pop()428 void Hotspots::pop() {
429 debugC(1, kDebugHotspots, "Popping hotspots");
430
431 assert(!_stack.empty());
432
433 StackEntry backup = _stack.pop();
434
435 // Find the end of the filled hotspot space
436 int i;
437 Hotspot *destPtr = _hotspots;
438 for (i = 0; i < kHotspotCount; i++, destPtr++) {
439 if (destPtr->isEnd())
440 break;
441 }
442
443 if (((uint32) (kHotspotCount - i)) < backup.size)
444 error("Hotspots::pop(): Not enough free space in the current Hotspot "
445 "array to pop %d elements (got %d)", backup.size, kHotspotCount - i);
446
447 // Copy
448 memcpy(destPtr, backup.hotspots, backup.size * sizeof(Hotspot));
449
450 _shouldPush = backup.shouldPush;
451 _currentKey = backup.key;
452 _currentId = backup.id;
453 _currentIndex = backup.index;
454 _currentX = backup.x;
455 _currentY = backup.y;
456
457 delete[] backup.hotspots;
458 }
459
isValid(uint16 key,uint16 id,uint16 index) const460 bool Hotspots::isValid(uint16 key, uint16 id, uint16 index) const {
461 if (index >= kHotspotCount)
462 return false;
463
464 if (key == 0)
465 return false;
466
467 if (!(Hotspot::getState(id) & kStateFilled))
468 return false;
469
470 return true;
471 }
472
call(uint16 offset)473 void Hotspots::call(uint16 offset) {
474 debugC(4, kDebugHotspots, "Calling hotspot function %d", offset);
475
476 _vm->_game->_script->call(offset);
477
478 _shouldPush = true;
479
480 Common::Stack<StackEntry>::size_type stackSize = _stack.size();
481
482 _vm->_inter->funcBlock(0);
483
484 while (stackSize != _stack.size())
485 pop();
486
487 _shouldPush = false;
488
489 _vm->_game->_script->pop();
490
491 recalculate(false);
492 }
493
enter(uint16 index)494 void Hotspots::enter(uint16 index) {
495 debugC(2, kDebugHotspots, "Entering hotspot %d", index);
496
497 if (index >= kHotspotCount) {
498 warning("Hotspots::enter(): Index %d out of range", index);
499 return;
500 }
501
502 Hotspot &spot = _hotspots[index];
503
504 // If requested, write the ID into a variable
505 if ((spot.getState() == (kStateFilled | kStateType1)) ||
506 (spot.getState() == (kStateFilled | kStateType2)))
507 WRITE_VAR(17, -(spot.id & 0x0FFF));
508
509 _currentX = _vm->_global->_inter_mouseX;
510 _currentY = _vm->_global->_inter_mouseY;
511
512 if (spot.funcEnter != 0)
513 call(spot.funcEnter);
514 }
515
leave(uint16 index)516 void Hotspots::leave(uint16 index) {
517 debugC(2, kDebugHotspots, "Leaving hotspot %d", index);
518
519 if (index >= kHotspotCount) {
520 warning("Hotspots::leave(): Index %d out of range", index);
521 return;
522 }
523
524 Hotspot &spot = _hotspots[index];
525
526 // If requested, write the ID into a variable
527 if ((spot.getState() == (kStateFilled | kStateType1)) ||
528 (spot.getState() == (kStateFilled | kStateType2)))
529 WRITE_VAR(17, spot.id & 0x0FFF);
530
531 if (spot.funcLeave != 0)
532 call(spot.funcLeave);
533 }
534
windowCursor(int16 & dx,int16 & dy) const535 int16 Hotspots::windowCursor(int16 &dx, int16 &dy) const {
536 if (!(_vm->_draw->_renderFlags & RENDERFLAG_HASWINDOWS))
537 return 0;
538
539 for (int i = 0; i < 10; i++) {
540 if (_vm->_draw->_fascinWin[i].id == -1)
541 // No such windows
542 continue;
543
544 const int left = _vm->_draw->_fascinWin[i].left;
545 const int top = _vm->_draw->_fascinWin[i].top;
546 const int right = _vm->_draw->_fascinWin[i].left + _vm->_draw->_fascinWin[i].width - 1;
547 const int bottom = _vm->_draw->_fascinWin[i].top + _vm->_draw->_fascinWin[i].height - 1;
548
549 if ((_vm->_global->_inter_mouseX < left) || (_vm->_global->_inter_mouseX > right) ||
550 (_vm->_global->_inter_mouseY < top ) || (_vm->_global->_inter_mouseY > bottom))
551 // We're not inside that window
552 continue;
553
554 if (_vm->_draw->_fascinWin[i].id != (_vm->_draw->_winCount - 1))
555 // Only consider the top-most window
556 continue;
557
558 dx = _vm->_draw->_fascinWin[i].left;
559 dy = _vm->_draw->_fascinWin[i].top;
560
561 if ((_vm->_global->_inter_mouseX < (left + 12)) && (_vm->_global->_inter_mouseY < (top + 12)) &&
562 (VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 2))
563 // Cursor on 'Close Window'
564 return 5;
565
566 if ((_vm->_global->_inter_mouseX > (right - 12)) & (_vm->_global->_inter_mouseY < (top + 12)) &&
567 (VAR((_vm->_draw->_winVarArrayStatus / 4) + i) & 4))
568 // Cursor on 'Move Window'
569 return 6;
570
571 return -1;
572 }
573
574 return 0;
575 }
576
checkMouse(Type type,uint16 & id,uint16 & index) const577 uint16 Hotspots::checkMouse(Type type, uint16 &id, uint16 &index) const {
578 id = 0;
579 index = 0;
580
581 int16 dx = 0;
582 int16 dy = 0;
583 int16 winId = _vm->_draw->getWinFromCoord(dx, dy);
584
585 if (winId < 0) {
586 winId = 0;
587 dx = 0;
588 dy = 0;
589 } else
590 winId *= 256;
591
592 if (type == kTypeMove) {
593 // Check where the mouse was moved to
594
595 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
596 const Hotspot &spot = _hotspots[i];
597
598 if (spot.isDisabled())
599 // Only consider enabled hotspots
600 continue;
601
602 if (spot.getType() > kTypeMove)
603 // Only consider click and move hotspots
604 continue;
605
606 if (spot.getWindow() != winId)
607 // Only check the current window
608 continue;
609
610 if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
611 // If we're not in it, ignore it
612 continue;
613
614 id = spot.id;
615 index = i;
616
617 return spot.key;
618 }
619
620 return 0;
621
622 } else if (type == kTypeClick) {
623 // Check if something was clicked
624
625 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
626 const Hotspot &spot = _hotspots[i];
627
628 if (spot.isDisabled())
629 // Only consider enabled hotspots
630 continue;
631
632 if (spot.getWindow() != winId)
633 // Only check the active window
634 continue;
635
636 if (spot.getType() < kTypeMove)
637 // Only consider hotspots that can be clicked
638 continue;
639
640 if (!spot.isIn(_vm->_global->_inter_mouseX - dx, _vm->_global->_inter_mouseY - dy))
641 // If we're not in it, ignore it
642 continue;
643
644 if (!spot.buttonMatch(_vm->_game->_mouseButtons))
645 // Don't follow hotspots with button requirements we don't meet
646 continue;
647
648 id = spot.id;
649 index = i;
650
651 if ((spot.getType() == kTypeMove) || (spot.getType() == kTypeClick))
652 // It's a move or click => return the key
653 return spot.key;
654
655 // Otherwise, the key has a different meaning, so ignore it
656 return 0;
657 }
658
659 if (_vm->_game->_mouseButtons != kMouseButtonsLeft)
660 // Let the right mouse button act as an escape key
661 return kKeyEscape;
662
663 return 0;
664 }
665
666 return 0;
667 }
668
checkHotspotChanged()669 bool Hotspots::checkHotspotChanged() {
670 uint16 key, id, index;
671
672 // Get the current hotspot
673 key = checkMouse(kTypeMove, id, index);
674
675 uint16 mouseX = _vm->_global->_inter_mouseX;
676 uint16 mouseY = _vm->_global->_inter_mouseY;
677
678 if (key == _currentKey) {
679 // Still the same hotspot, just update the mouse position
680
681 _currentX = mouseX;
682 _currentY = mouseY;
683 return false;
684 }
685
686 // In Geisha, no move hotspot changes should occur when
687 // we didn't actually move the mouse
688 if (_vm->getGameType() == kGameTypeGeisha)
689 if ((mouseX == _currentX) && (mouseY == _currentY))
690 return false;
691
692 // Leave the old area
693 if (isValid(_currentKey, _currentId,_currentIndex))
694 leave(_currentIndex);
695
696 _currentKey = key;
697 _currentId = id;
698 _currentIndex = index;
699 _currentX = mouseX;
700 _currentY = mouseY;
701
702 // Enter the new one
703 if (isValid(key, id, index))
704 enter(index);
705
706 return true;
707 }
708
check(uint8 handleMouse,int16 delay,uint16 & id,uint16 & index)709 uint16 Hotspots::check(uint8 handleMouse, int16 delay, uint16 &id, uint16 &index) {
710 if (delay >= -1) {
711 _currentKey = 0;
712 _currentId = 0;
713 _currentIndex = 0;
714 }
715
716 id = 0;
717 index = 0;
718
719 if (handleMouse) {
720 if ((_vm->_draw->_cursorIndex == -1) && (_currentKey == 0)) {
721 // Last know state: No hotspot hit. Look if that changed
722
723 _currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);
724
725 if (isValid(_currentKey, _currentId, _currentIndex))
726 enter(_currentIndex);
727 }
728 _vm->_draw->animateCursor(-1);
729 }
730
731 uint32 startTime = _vm->_util->getTimeKey();
732
733 // Update display
734 _vm->_draw->blitInvalidated();
735 _vm->_video->waitRetrace();
736
737 uint16 key = 0;
738 while (key == 0) {
739
740 if (_vm->_inter->_terminate || _vm->shouldQuit()) {
741 if (handleMouse)
742 _vm->_draw->blitCursor();
743 return 0;
744 }
745
746 // Anything changed?
747 checkHotspotChanged();
748
749 // Update display
750 if (!_vm->_draw->_noInvalidated) {
751 if (handleMouse)
752 _vm->_draw->animateCursor(-1);
753 else
754 _vm->_draw->blitInvalidated();
755 _vm->_video->waitRetrace();
756 }
757
758 if (handleMouse)
759 _vm->_game->evaluateScroll();
760
761 // Update keyboard and mouse state
762 key = _vm->_game->checkKeys(&_vm->_global->_inter_mouseX,
763 &_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons, handleMouse);
764
765 if (!handleMouse && (_vm->_game->_mouseButtons != kMouseButtonsNone)) {
766 // We don't want any mouse input but got one => Wait till it went away
767
768 _vm->_util->waitMouseRelease(0);
769 key = 3;
770 }
771
772 if (key != 0) {
773 // Got a key press
774
775 if (handleMouse & 1)
776 _vm->_draw->blitCursor();
777
778 id = 0;
779 index = 0;
780
781 // Leave the current hotspot
782 if (isValid(_currentKey, _currentId, _currentIndex))
783 leave(_currentIndex);
784
785 _currentKey = 0;
786 break;
787 }
788
789 if (handleMouse) {
790
791 if (_vm->_game->_mouseButtons != kMouseButtonsNone) {
792 // Mouse button pressed
793 int i = _vm->_draw->handleCurWin();
794
795 if (!i) {
796 _vm->_draw->animateCursor(2);
797 if (delay > 0) {
798 // If a delay was requested, wait the specified time
799 _vm->_util->delay(delay);
800 } else if (handleMouse & 1)
801 _vm->_util->waitMouseRelease(1);
802
803 _vm->_draw->animateCursor(-1);
804
805 // Which region was clicked?
806 key = checkMouse(kTypeClick, id, index);
807
808 if ((key != 0) || (id != 0)) {
809 // Got a valid region
810
811 if ( (handleMouse & 1) &&
812 ((delay <= 0) || (_vm->_game->_mouseButtons == kMouseButtonsNone)))
813 _vm->_draw->blitCursor();
814
815
816 if ((key != _currentKey) && (_vm->getGameType() != kGameTypeFascination) &&
817 (_vm->getGameType() != kGameTypeGeisha))
818 // If the hotspot changed, leave the old one
819 // Code not present in Fascination executables
820 leave(_currentIndex);
821
822 _currentKey = 0;
823 break;
824 }
825
826 if (handleMouse & 4)
827 // Nothing further than one simple check was requested => return
828 return 0;
829
830 // Leave the current area
831 if (_currentKey != 0)
832 leave(_currentIndex);
833
834 // No click, but do we have a move event? If so, enter that hotspot
835 _currentKey = checkMouse(kTypeMove, _currentId, _currentIndex);
836 if (isValid(_currentKey, _currentId, _currentIndex))
837 enter(_currentIndex);
838 } else {
839 WRITE_VAR(16, (int32)i);
840 id = 0;
841 index = 0;
842 return 0;
843 }
844 } else
845 // No mouse button pressed, check whether the position changed at least
846 checkHotspotChanged();
847 }
848
849 if ((delay == -2) && (key == 0) &&
850 (_vm->_game->_mouseButtons == kMouseButtonsNone)) {
851 // Nothing found and no further handling requested. Return.
852
853 id = 0;
854 index = 0;
855 break;
856 }
857
858 if (handleMouse)
859 _vm->_draw->animateCursor(-1);
860
861 if ((delay < 0) && (key == 0) &&
862 (_vm->_game->_mouseButtons == kMouseButtonsNone)) {
863
864 // Look if we've maybe reached the timeout
865
866 uint32 curTime = _vm->_util->getTimeKey();
867 if ((curTime + delay) > startTime) {
868 // If so, return
869
870 id = 0;
871 index = 0;
872 break;
873 }
874
875 }
876
877 // Sleep for a short amount of time
878 _vm->_util->delay(10);
879
880 }
881
882 return key;
883 }
884
check(uint8 handleMouse,int16 delay)885 uint16 Hotspots::check(uint8 handleMouse, int16 delay) {
886 uint16 id, index;
887
888 // Check and ignore the id and index
889 return Hotspots::check(handleMouse, delay, id, index);
890 }
891
updateInput(uint16 xPos,uint16 yPos,uint16 width,uint16 height,uint16 backColor,uint16 frontColor,char * str,uint16 fontIndex,Type type,int16 & duration,uint16 & id,uint16 & index)892 uint16 Hotspots::updateInput(uint16 xPos, uint16 yPos, uint16 width, uint16 height,
893 uint16 backColor, uint16 frontColor, char *str, uint16 fontIndex,
894 Type type, int16 &duration, uint16 &id, uint16 &index) {
895
896 if ((fontIndex >= Draw::kFontCount) || !_vm->_draw->_fonts[fontIndex]) {
897 warning("Hotspots::updateInput(): Invalid font specified: %d", fontIndex);
898 return 0;
899 }
900
901 // Check if we need to consider mouse events
902 bool handleMouse = false;
903 if ( (_vm->_game->_handleMouse != 0) &&
904 ((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
905 handleMouse = true;
906
907 const Font &font = *_vm->_draw->_fonts[fontIndex];
908
909 // Current position in the string, preset to the end
910 uint32 pos = strlen(str);
911 /* Size of input field in characters.
912 * If the font is not monospaced, we can't know that */
913 uint32 editSize = font.isMonospaced() ? (width / font.getCharWidth()) : 0;
914
915 uint16 key = 0;
916 char tempStr[256];
917
918 while (1) {
919 // If we the edit field has enough space, add a space for the new character
920 Common::strlcpy(tempStr, str, 255);
921 strcat(tempStr, " ");
922 if ((editSize != 0) && strlen(tempStr) > editSize)
923 Common::strlcpy(tempStr, str, 256);
924
925 // Clear input area
926 fillRect(xPos, yPos,
927 font.isMonospaced() ? (editSize * font.getCharWidth()) : width, height,
928 backColor);
929
930 // Print the current string, vertically centered
931 printText(xPos, yPos + (height - font.getCharHeight()) / 2,
932 tempStr, fontIndex, frontColor);
933
934 // If we've reached the end of the input field, set the cursor to the last character
935 if ((editSize != 0) && (pos == editSize))
936 pos--;
937
938 // The character under the cursor
939 char curSym = tempStr[pos];
940
941 if (_vm->_inter->_variables)
942 WRITE_VAR(56, pos);
943
944 bool first = true;
945 while (1) {
946 tempStr[0] = curSym;
947 tempStr[1] = 0;
948
949 // Draw cursor
950 uint16 cursorX, cursorY, cursorWidth, cursorHeight;
951 getTextCursorPos(font, str, pos, xPos, yPos, width, height,
952 cursorX, cursorY, cursorWidth, cursorHeight);
953 fillRect(cursorX, cursorY, cursorWidth, cursorHeight, frontColor);
954
955 if (first) {
956 // The first time, purge old information too
957 key = check(handleMouse, -1, id, index);
958
959 if (key == 0)
960 // We didn't catch any input, let's try again with a real timeout
961 key = check(handleMouse, -300, id, index);
962
963 first = false;
964 } else
965 // Try to catch a character
966 key = check(handleMouse, -300, id, index);
967
968 tempStr[0] = curSym;
969 tempStr[1] = 0;
970
971 // Clear cursor
972 getTextCursorPos(font, str, pos, xPos, yPos, width, height,
973 cursorX, cursorY, cursorWidth, cursorHeight);
974 fillRect(cursorX, cursorY, cursorWidth, cursorHeight, backColor);
975
976 // Print the current string, vertically centered
977 printText(cursorX, yPos + (height - font.getCharHeight()) / 2,
978 tempStr, fontIndex, frontColor);
979
980 if ((key != 0) || (id != 0))
981 // We did get a key, stop looking
982 break;
983
984 // Try again
985 key = check(handleMouse, -300, id, index);
986
987 if ((key != 0) || (id != 0) ||
988 _vm->_inter->_terminate || _vm->shouldQuit())
989 // We did get a key, stop looking
990 break;
991
992 if (duration > 0) {
993 // Look if we reached the time limit
994 duration -= 600;
995 if (duration <= 1) {
996 // If so, abort
997 key = 0;
998 id = 0;
999 break;
1000 }
1001 }
1002 }
1003
1004 if ((key == 0) || (id != 0) ||
1005 _vm->_inter->_terminate || _vm->shouldQuit())
1006 // Got no key, or a region ID instead, return
1007 return 0;
1008
1009 switch (key) {
1010 case kKeyRight:
1011 // If possible, move the cursor right
1012 if (((editSize != 0) && ((pos > strlen(str)) || (pos > (editSize - 1)))) ||
1013 ((editSize == 0) && (pos > strlen(str)))) {
1014 pos++;
1015 continue;
1016 }
1017 // Continue downwards instead
1018 return kKeyDown;
1019
1020 case kKeyLeft:
1021 // If possible, move the cursor left
1022 if (pos > 0) {
1023 pos--;
1024 continue;
1025 }
1026 // Continue upwards instead
1027 return kKeyUp;
1028
1029 case kKeyBackspace:
1030 if (pos > 0) {
1031 // Delete the character to the left
1032 _vm->_util->cutFromStr(str, pos - 1, 1);
1033 pos--;
1034 } else {
1035 if (pos < strlen(str))
1036 // Delete the character to the right
1037 _vm->_util->cutFromStr(str, pos, 1);
1038 }
1039 continue;
1040
1041 case kKeyDelete:
1042 if (pos >= strlen(str))
1043 continue;
1044
1045 // Delete the character to the right
1046 _vm->_util->cutFromStr(str, pos, 1);
1047 continue;
1048
1049 case kKeyReturn:
1050 case kKeyF1:
1051 case kKeyF2:
1052 case kKeyF3:
1053 case kKeyF4:
1054 case kKeyF5:
1055 case kKeyF6:
1056 case kKeyF7:
1057 case kKeyF8:
1058 case kKeyF9:
1059 case kKeyF10:
1060 case kKeyUp:
1061 case kKeyDown:
1062 return key;
1063
1064 case kKeyEscape:
1065 // If we got an escape event, wait until the mouse buttons have been released
1066 if (_vm->_global->_useMouse != 0)
1067 continue;
1068
1069 _vm->_game->_forceHandleMouse = !_vm->_game->_forceHandleMouse;
1070
1071 handleMouse = false;
1072 if ( (_vm->_game->_handleMouse != 0) &&
1073 ((_vm->_global->_useMouse != 0) || (_vm->_game->_forceHandleMouse != 0)))
1074 handleMouse = true;
1075
1076 while (_vm->_global->_pressedKeys[1] != 0)
1077 ;
1078 continue;
1079
1080 default:
1081 // Got a "normal" key
1082
1083 uint16 savedKey = key;
1084
1085 key &= 0xFF;
1086
1087 if (((type == kTypeInputFloatNoLeave) || (type == kTypeInputFloatLeave)) &&
1088 (key >= ' ') && (key <= 0xFF)) {
1089
1090 // Only allow character found in numerical floating values
1091
1092 const char *str1 = "0123456789-.,+ ";
1093 const char *str2 = "0123456789-,,+ ";
1094
1095 if ((((savedKey >> 8) > 1) && ((savedKey >> 8) < 12)) &&
1096 ((_vm->_global->_pressedKeys[42] != 0) ||
1097 (_vm->_global->_pressedKeys[56] != 0)))
1098 key = ((savedKey >> 8) - 1) % 10 + '0';
1099
1100 int i;
1101 for (i = 0; str1[i] != 0; i++) {
1102 if (key == str1[i]) {
1103 key = str2[i];
1104 break;
1105 }
1106 }
1107
1108 if (i == (int16) strlen(str1))
1109 key = 0;
1110 }
1111
1112 if ((key >= ' ') && (key <= 0xFF)) {
1113 if (editSize == 0) {
1114 // Length of the string + current character + next one
1115 int length = _vm->_draw->stringLength(str, fontIndex) +
1116 font.getCharWidth(' ') + font.getCharWidth(key);
1117
1118 if (length > width)
1119 // We're above the limit, ignore the key
1120 continue;
1121
1122 if (((int32) strlen(str)) >= (_vm->_global->_inter_animDataSize * 4 - 1))
1123 // Above the limit of character allowed in a string, ignore the key
1124 continue;
1125
1126 } else {
1127 if (strlen(str) > editSize)
1128 // We're over the upper character limit for this field
1129 continue;
1130 else if (editSize == strlen(str))
1131 // We've reached the upper limit, overwrite the last character
1132 _vm->_util->cutFromStr(str, strlen(str) - 1, 1);
1133 }
1134
1135 // Advance cursor
1136 pos++;
1137 tempStr[0] = key;
1138 tempStr[1] = 0;
1139
1140 // Add character
1141 _vm->_util->insertStr(tempStr, str, pos - 1);
1142 }
1143 }
1144 }
1145 }
1146
handleInputs(int16 time,uint16 inputCount,uint16 & curInput,InputDesc * inputs,uint16 & id,uint16 & index)1147 uint16 Hotspots::handleInputs(int16 time, uint16 inputCount, uint16 &curInput,
1148 InputDesc *inputs, uint16 &id, uint16 &index) {
1149
1150 // Redraw all texts in all inputs we currently manage
1151 updateAllTexts(inputs);
1152
1153 for (int i = 0; i < 40; i++)
1154 WRITE_VAR(17 + i, 0);
1155
1156 while (1) {
1157 // Find the hotspot index to our current input
1158 uint16 hotspotIndex = inputToHotspot(curInput);
1159
1160 assert(hotspotIndex != 0xFFFF);
1161
1162 Hotspot inputSpot = _hotspots[hotspotIndex];
1163
1164 // Handle input events from that input field
1165 uint16 key = updateInput(inputSpot.left, inputSpot.top,
1166 inputSpot.right - inputSpot.left + 1,
1167 inputSpot.bottom - inputSpot.top + 1,
1168 inputs[curInput].backColor, inputs[curInput].frontColor,
1169 GET_VARO_STR(inputSpot.key), inputs[curInput].fontIndex,
1170 inputSpot.getType(), time, id, index);
1171
1172 if (_vm->_inter->_terminate)
1173 return 0;
1174
1175 switch (key) {
1176 case kKeyNone:
1177 if (id == 0)
1178 // No key and no hotspot => return
1179 return 0;
1180
1181 if (_vm->_game->_mouseButtons != kMouseButtonsNone)
1182 // Clicked something, get the hotspot index
1183 index = findClickedInput(index);
1184
1185 if (!_hotspots[index].isInput())
1186 // It's no input, return
1187 return 0;
1188
1189 // Get the associated input index
1190 curInput = hotspotToInput(index);
1191 break;
1192
1193 case kKeyF1:
1194 case kKeyF2:
1195 case kKeyF3:
1196 case kKeyF4:
1197 case kKeyF5:
1198 case kKeyF6:
1199 case kKeyF7:
1200 case kKeyF8:
1201 case kKeyF9:
1202 case kKeyF10:
1203 return key;
1204
1205 case kKeyReturn:
1206 // Just one input => return
1207 if (inputCount == 1)
1208 return kKeyReturn;
1209
1210 // End of input chain reached => wrap
1211 if (curInput == (inputCount - 1)) {
1212 curInput = 0;
1213 break;
1214 }
1215
1216 // Next input
1217 curInput++;
1218 break;
1219
1220 case kKeyDown:
1221 // Next input
1222 if ((inputCount - 1) > curInput)
1223 curInput++;
1224 break;
1225
1226 case kKeyUp:
1227 // Previous input
1228 if (curInput > 0)
1229 curInput--;
1230 break;
1231
1232 default:
1233 break;
1234 }
1235 }
1236 }
1237
evaluateNew(uint16 i,uint16 * ids,InputDesc * inputs,uint16 & inputId,bool & hasInput,uint16 & inputCount)1238 void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
1239 uint16 &inputId, bool &hasInput, uint16 &inputCount) {
1240
1241 ids[i] = 0;
1242
1243 // Type and window
1244 byte type = _vm->_game->_script->readByte();
1245 byte windowNum = 0;
1246
1247 if ((type & 0x40) != 0) {
1248 // Got a window ID
1249
1250 type -= 0x40;
1251 windowNum = _vm->_game->_script->readByte();
1252 }
1253
1254 // Coordinates
1255 uint16 left, top, width, height, right, bottom;
1256 uint32 funcPos = 0;
1257 if ((type & 0x80) != 0) {
1258 // Complex coordinate expressions
1259 funcPos = _vm->_game->_script->pos();
1260 left = _vm->_game->_script->readValExpr();
1261 top = _vm->_game->_script->readValExpr();
1262 width = _vm->_game->_script->readValExpr();
1263 height = _vm->_game->_script->readValExpr();
1264 } else {
1265 // Immediate values
1266 funcPos = 0;
1267 left = _vm->_game->_script->readUint16();
1268 top = _vm->_game->_script->readUint16();
1269 width = _vm->_game->_script->readUint16();
1270 height = _vm->_game->_script->readUint16();
1271 }
1272 type &= 0x7F;
1273
1274 // Draw a border around the hotspot
1275 if (_vm->_draw->_renderFlags & RENDERFLAG_BORDERHOTSPOTS) {
1276 Surface &surface = *_vm->_draw->_spritesArray[_vm->_draw->_destSurface];
1277
1278 _vm->_video->dirtyRectsAll();
1279
1280 if (windowNum == 0) {
1281 // The hotspot is not inside a window, just draw border it
1282 surface.drawRect(left, top, left + width - 1, top + height - 1, 0);
1283
1284 } else {
1285 // The hotspot is inside a window, only draw it if it's the topmost window
1286
1287 if ((_vm->_draw->_fascinWin[windowNum].id != -1) &&
1288 (_vm->_draw->_fascinWin[windowNum].id == (_vm->_draw->_winCount - 1))) {
1289
1290 const uint16 wLeft = left + _vm->_draw->_fascinWin[windowNum].left;
1291 const uint16 wTop = top + _vm->_draw->_fascinWin[windowNum].top;
1292
1293 surface.drawRect(wLeft, wTop, wLeft + width - 1, wTop + height - 1, 0);
1294 }
1295 }
1296 }
1297
1298 // Apply global drawing offset
1299 if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != 0xFFFF)) {
1300 left += _vm->_draw->_backDeltaX;
1301 top += _vm->_draw->_backDeltaY;
1302 }
1303
1304 right = left + width - 1;
1305 bottom = top + height - 1;
1306
1307 // Enabling the hotspots again
1308 if ((type == kTypeEnable2) || (type == kTypeEnable1)) {
1309 uint8 wantedState = 0;
1310 if (type == kTypeEnable2)
1311 wantedState = kStateFilledDisabled | kStateType2;
1312 else
1313 wantedState = kStateFilledDisabled | kStateType1;
1314
1315 _vm->_game->_script->skip(6);
1316
1317 for (int j = 0; j < kHotspotCount; j++) {
1318 Hotspot &spot = _hotspots[j];
1319
1320 if (spot.getState() == wantedState) {
1321 spot.enable();
1322 spot.funcEnter = _vm->_game->_script->pos();
1323 spot.funcLeave = _vm->_game->_script->pos();
1324 }
1325 }
1326
1327 _vm->_game->_script->skipBlock();
1328
1329 return;
1330 }
1331
1332 int16 key = 0;
1333 int16 flags = 0;
1334 Font *font = 0;
1335 uint32 funcEnter = 0, funcLeave = 0;
1336
1337 if ((windowNum != 0) && (type != 0) && (type != 2))
1338 debugC(0, kDebugHotspots, "evaluateNew - type %d, win %d",type, windowNum);
1339
1340 // Evaluate parameters for the new hotspot
1341 switch (type) {
1342 case kTypeNone:
1343 _vm->_game->_script->skip(6);
1344
1345 funcEnter = _vm->_game->_script->pos();
1346 _vm->_game->_script->skipBlock();
1347
1348 funcLeave = _vm->_game->_script->pos();
1349 _vm->_game->_script->skipBlock();
1350
1351 key = i + ((kStateFilled | kStateType2) << 12);
1352 flags = type + (windowNum << 8);
1353 break;
1354
1355 case kTypeMove:
1356 key = _vm->_game->_script->readInt16();
1357 ids[i] = _vm->_game->_script->readInt16();
1358 flags = _vm->_game->_script->readInt16();
1359
1360 funcEnter = _vm->_game->_script->pos();
1361 _vm->_game->_script->skipBlock();
1362
1363 funcLeave = _vm->_game->_script->pos();
1364 _vm->_game->_script->skipBlock();
1365
1366 if (key == 0)
1367 key = i + ((kStateFilled | kStateType2) << 12);
1368
1369 flags = type + (windowNum << 8) + (flags << 4);
1370 break;
1371
1372 case kTypeInput1NoLeave:
1373 case kTypeInput1Leave:
1374 case kTypeInput2NoLeave:
1375 case kTypeInput2Leave:
1376 case kTypeInput3NoLeave:
1377 case kTypeInput3Leave:
1378 case kTypeInputFloatNoLeave:
1379 case kTypeInputFloatLeave:
1380 hasInput = true;
1381
1382 _vm->_util->clearKeyBuf();
1383
1384 // Input text parameters
1385 key = _vm->_game->_script->readVarIndex();
1386 inputs[inputCount].fontIndex = _vm->_game->_script->readInt16();
1387 inputs[inputCount].backColor = _vm->_game->_script->readByte();
1388 inputs[inputCount].frontColor = _vm->_game->_script->readByte();
1389 inputs[inputCount].length = 0;
1390 inputs[inputCount].str = 0;
1391
1392 if ((type >= kTypeInput2NoLeave) && (type <= kTypeInput3Leave)) {
1393 inputs[inputCount].length = _vm->_game->_script->readUint16();
1394
1395 inputs[inputCount].str =
1396 (const char *)(_vm->_game->_script->getData() + _vm->_game->_script->pos());
1397
1398 _vm->_game->_script->skip(inputs[inputCount].length);
1399 }
1400
1401 if (left == 0xFFFF) {
1402 if (!(type & 1))
1403 // No coordinates but a leave block => skip it
1404 _vm->_game->_script->skipBlock();
1405 break;
1406 }
1407
1408 font = _vm->_draw->_fonts[inputs[inputCount].fontIndex];
1409 if (font->isMonospaced())
1410 right = left + width * font->getCharWidth() - 1;
1411
1412 funcEnter = 0;
1413 funcPos = 0;
1414 funcLeave = 0;
1415 if (!(type & 1)) {
1416 // Got a leave
1417 funcLeave = _vm->_game->_script->pos();
1418 _vm->_game->_script->skipBlock();
1419 }
1420
1421 flags = type;
1422
1423 inputCount++;
1424 break;
1425
1426 case 20:
1427 inputId = i;
1428 // fall through
1429 case kTypeClick:
1430 key = _vm->_game->_script->readInt16();
1431 ids[i] = _vm->_game->_script->readInt16();
1432 flags = _vm->_game->_script->readInt16();
1433
1434 if (flags > 3)
1435 warning("evaluateNew: Warning, use of type 2 or 20. flags = %d, should be %d", flags, flags&3);
1436
1437 funcEnter = 0;
1438
1439 funcLeave = _vm->_game->_script->pos();
1440 _vm->_game->_script->skipBlock();
1441
1442 flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
1443 break;
1444
1445 case kTypeClickEnter:
1446 key = _vm->_game->_script->readInt16();
1447 ids[i] = _vm->_game->_script->readInt16();
1448 flags = _vm->_game->_script->readInt16() & 3;
1449
1450 funcEnter = _vm->_game->_script->pos();
1451 _vm->_game->_script->skipBlock();
1452
1453 funcLeave = 0;
1454
1455 flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
1456 break;
1457
1458 default:
1459 break;
1460 }
1461
1462 // Add the new hotspot
1463 add(i | (kStateFilled << 12), left, top, right, bottom,
1464 flags, key, funcEnter, funcLeave, funcPos);
1465 }
1466
evaluateFind(uint16 key,int16 timeVal,const uint16 * ids,uint16 leaveWindowIndex,uint16 hotspotIndex1,uint16 hotspotIndex2,uint16 endIndex,int16 & duration,uint16 & id,uint16 & index,bool & finished)1467 bool Hotspots::evaluateFind(uint16 key, int16 timeVal, const uint16 *ids,
1468 uint16 leaveWindowIndex, uint16 hotspotIndex1, uint16 hotspotIndex2,
1469 uint16 endIndex, int16 &duration, uint16 &id, uint16 &index, bool &finished) {
1470
1471 bool fascinCheck = false;
1472
1473 if (id != 0)
1474 // We already found a hotspot, nothing to do
1475 return true;
1476
1477 if (key != 0) {
1478 // We've got a key
1479
1480 // Find the hotspot with that key associated
1481 findKey(key, id, index);
1482 if (id != 0)
1483 // Found it
1484 return true;
1485
1486 // Try it case insensitively
1487 findKeyCaseInsensitive(key, id, index);
1488 if (id != 0)
1489 // Found it
1490 return true;
1491
1492 return false;
1493 }
1494 if ((_vm->getGameType() == kGameTypeFascination) && (getCurrentHotspot()))
1495 fascinCheck = true;
1496
1497 if ((duration != 0) && (!fascinCheck)) {
1498 // We've got a time duration
1499
1500 if (hotspotIndex1 != 0) {
1501 finished =
1502 leaveNthPlain(hotspotIndex1, endIndex, timeVal, ids, id, index, duration);
1503 } else if (hotspotIndex2 != 0) {
1504 findNthPlain(hotspotIndex2, endIndex, id, index);
1505 } else {
1506 // Enter the first hotspot
1507 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1508 Hotspot &spot = _hotspots[i];
1509 if (spot.isFilledNew()) {
1510 id = spot.id;
1511 index = i;
1512 break;
1513 }
1514 }
1515
1516 // Leave the current hotspot
1517 if ((_currentKey != 0) && (_hotspots[_currentIndex].funcLeave != 0))
1518 call(_hotspots[_currentIndex].funcLeave);
1519
1520 _currentKey = 0;
1521 }
1522
1523 if (id != 0)
1524 return true;
1525
1526 return false;
1527 } else {
1528 if (leaveWindowIndex != 0)
1529 findNthPlain(leaveWindowIndex, endIndex, id, index);
1530
1531 if (id != 0)
1532 return true;
1533 }
1534
1535 return false;
1536 }
1537
evaluate()1538 void Hotspots::evaluate() {
1539 InputDesc inputs[20];
1540 uint16 ids[kHotspotCount];
1541
1542 // Push all local hotspots
1543 push(0);
1544
1545 // Find the current end of the hotspot block
1546 uint16 endIndex = 0;
1547 while (!_hotspots[endIndex].isEnd())
1548 endIndex++;
1549
1550 _shouldPush = false;
1551
1552 _vm->_game->_script->skip(1);
1553
1554 // Number of new hotspots
1555 byte count = _vm->_game->_script->readByte();
1556
1557 // Parameters of this block
1558 _vm->_game->_handleMouse = _vm->_game->_script->peekByte(0);
1559 int16 duration = _vm->_game->_script->peekByte(1);
1560
1561 byte leaveWindowIndex = 0;
1562 if (_vm->getGameType() == kGameTypeFascination)
1563 leaveWindowIndex = _vm->_game->_script->peekByte(2);
1564
1565 byte hotspotIndex1 = _vm->_game->_script->peekByte(3);
1566 byte hotspotIndex2 = _vm->_game->_script->peekByte(4);
1567 bool needRecalculation = _vm->_game->_script->peekByte(5) != 0;
1568
1569 // Seconds -> Milliseconds
1570 duration *= 1000;
1571
1572 if ((hotspotIndex1 != 0) || (hotspotIndex2 != 0)) {
1573 duration /= 100;
1574 if (_vm->_game->_script->peekByte(1) == 100)
1575 duration = 2;
1576 }
1577
1578 int16 timeVal = duration;
1579
1580 _vm->_game->_script->skip(6);
1581
1582 setCurrentHotspot(0, 0);
1583
1584 bool finishedDuration = false;
1585
1586 uint16 id = 0;
1587 uint16 inputId = 0xFFFF;
1588 uint16 index = 0;
1589
1590 bool hasInput = false;
1591 uint16 inputCount = 0;
1592
1593 // Adding new hotspots
1594 for (uint16 i = 0; i < count; i++)
1595 evaluateNew(i, ids, inputs, inputId, hasInput, inputCount);
1596
1597 // Recalculate all hotspots if requested
1598 if (needRecalculation)
1599 recalculate(true);
1600
1601 _vm->_game->_forceHandleMouse = 0;
1602 _vm->_util->clearKeyBuf();
1603
1604 while ((id == 0) && !_vm->_inter->_terminate && !_vm->shouldQuit()) {
1605 uint16 key = 0;
1606 if (hasInput) {
1607 // Input
1608
1609 uint16 curInput = 0;
1610
1611 key = handleInputs(duration, inputCount, curInput, inputs, id, index);
1612
1613 // Notify the script of the current input index
1614 WRITE_VAR(17 + 38, curInput);
1615 if (key == kKeyReturn) {
1616 // Return pressed, invoke input leave
1617 findFirstInputLeave(id, inputId, index);
1618 break;
1619 }
1620 } else
1621 // Normal move or click check
1622 key = check(_vm->_game->_handleMouse, -duration, id, index);
1623
1624 key = convertSpecialKey(key);
1625
1626 // Try to find a fitting hotspot
1627 evaluateFind(key, timeVal, ids, leaveWindowIndex, hotspotIndex1, hotspotIndex2, endIndex,
1628 duration, id, index, finishedDuration);
1629
1630 if (finishedDuration)
1631 break;
1632
1633 if ((id == 0) || (_hotspots[index].funcLeave != 0))
1634 // We don't have a new ID, but haven't yet handled the leave function
1635 continue;
1636
1637 _vm->_inter->storeMouse();
1638
1639 setCurrentHotspot(ids, id);
1640
1641 // Enter it
1642 if (_hotspots[index].funcEnter != 0)
1643 call(_hotspots[index].funcEnter);
1644
1645 setCurrentHotspot(0, 0);
1646 id = 0;
1647 }
1648
1649 if ((id & 0xFFF) == inputId)
1650 matchInputStrings(inputs);
1651
1652 if (_vm->_game->_handleMouse == 1)
1653 _vm->_draw->blitCursor();
1654
1655 if (!_vm->_inter->_terminate && (!finishedDuration)) {
1656 _vm->_game->_script->seek(_hotspots[index].funcLeave);
1657
1658 _vm->_inter->storeMouse();
1659 if (getCurrentHotspot() == 0) {
1660 // No hotspot currently handled, now we'll handle the newly found one
1661
1662 setCurrentHotspot(ids, id);
1663 }
1664 } else
1665 _vm->_game->_script->setFinished(true);
1666
1667 for (int i = 0; i < count; i++)
1668 // Remove all local hotspots
1669 remove(i + (kStateFilled << 12));
1670
1671 for (int i = 0; i < kHotspotCount; i++) {
1672 Hotspot &spot = _hotspots[i];
1673
1674 // Disable the ones still there
1675 if ((spot.getState() == (kStateFilled | kStateType1)) ||
1676 (spot.getState() == (kStateFilled | kStateType2)))
1677 spot.disable();
1678 }
1679
1680 }
1681
findCursor(uint16 x,uint16 y) const1682 int16 Hotspots::findCursor(uint16 x, uint16 y) const {
1683 int16 cursor = 0;
1684
1685 int16 deltax = 0;
1686 int16 deltay = 0;
1687
1688 // Fascination uses hard-coded windows
1689 if (_vm->getGameType() == kGameTypeFascination) {
1690 cursor = windowCursor(deltax, deltay);
1691
1692 // We're in a window and in an area that forces a specific cursor
1693 if (cursor > 0)
1694 return cursor;
1695
1696 // We're somewhere else inside a window
1697 if (cursor < 0) {
1698 int16 curType = -cursor * 256;
1699 cursor = 0;
1700
1701 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1702 const Hotspot &spot = _hotspots[i];
1703 // this check is /really/ Fascination specific.
1704 // It's illogical, so if it's to be reused in Adi games... Be careful!
1705 if ((spot.flags & 0xFF00) == curType)
1706 if (spot.isIn(x - deltax, y - deltay)) {
1707 if (spot.getType() < kTypeInput1NoLeave)
1708 cursor = 1;
1709 else
1710 cursor = 3;
1711 break;
1712 }
1713 }
1714
1715 if (_vm->_draw->_cursorAnimLow[cursor] == -1)
1716 // If the cursor is invalid... there's a generic "click" cursor
1717 cursor = 1;
1718
1719 return cursor;
1720 }
1721
1722 }
1723
1724 // Normal, non-window cursor handling
1725 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1726 const Hotspot &spot = _hotspots[i];
1727
1728 if ((spot.getWindow() != 0) || spot.isDisabled())
1729 // Ignore disabled and non-main-windowed hotspots
1730 continue;
1731
1732 if (!spot.isIn(x, y))
1733 // We're not in that hotspot, ignore it
1734 continue;
1735
1736 if (spot.getCursor() == 0) {
1737 // Hotspot doesn't itself specify a cursor...
1738 if (spot.getType() >= kTypeInput1NoLeave) {
1739 // ...but the type has a generic one
1740 cursor = 3;
1741 break;
1742 } else if ((spot.getButton() != kMouseButtonsRight) && (cursor == 0))
1743 // ...but there's a generic "click" cursor
1744 cursor = 1;
1745 } else if (cursor == 0)
1746 // Hotspot had an attached cursor index
1747 cursor = spot.getCursor();
1748 }
1749
1750 return cursor;
1751 }
1752
oPlaytoons_F_1B()1753 void Hotspots::oPlaytoons_F_1B() {
1754 int16 shortId;
1755 int16 longId;
1756 int16 var2;
1757 int16 fontIndex;
1758 int16 var4;
1759
1760 uint16 left;
1761 uint16 top;
1762 uint16 right;
1763 uint16 bottom;
1764
1765 shortId = _vm->_game->_script->readValExpr();
1766 var2 = _vm->_game->_script->readValExpr();
1767
1768 _vm->_game->_script->evalExpr(0);
1769
1770 fontIndex = _vm->_game->_script->readValExpr();
1771 var4 = _vm->_game->_script->readValExpr();
1772
1773 // this variable is always set to 0 in Playtoons
1774 // var_4 += unk_var;
1775
1776 for (int i = 0; i < kHotspotCount; i++) {
1777 if (_hotspots[i].isEnd())
1778 return;
1779
1780 if ((_hotspots[i].id == 0xD000 + shortId) || (_hotspots[i].id == 0xB000 + shortId) ||
1781 (_hotspots[i].id == 0x4000 + shortId)) {
1782 longId = _hotspots[i].id;
1783 warning("oPlaytoons_F_1B: shortId %d, var2 %d fontIndex %d var4 %d - longId %d", shortId, var2, fontIndex, var4, longId);
1784
1785 left = _hotspots[i].left;
1786 top = _hotspots[i].top;
1787 right = _hotspots[i].right;
1788 bottom = _hotspots[i].bottom;
1789
1790 left += 2;
1791 top += 2;
1792 right -= 2;
1793 bottom -= 2;
1794 if ((_vm->_draw->_needAdjust != 2) && (_vm->_draw->_needAdjust != 10)) {
1795 left += 2;
1796 top += 2;
1797 right -= 2;
1798 bottom -= 2;
1799 }
1800 _vm->_draw->oPlaytoons_sub_F_1B(0x8000+ var2, left, top, right, bottom, _vm->_game->_script->getResultStr(), fontIndex, var4, shortId);
1801 return;
1802 }
1803 }
1804 warning("shortId not found %d", shortId);
1805 return;
1806 }
1807
inputToHotspot(uint16 input) const1808 uint16 Hotspots::inputToHotspot(uint16 input) const {
1809 uint16 inputIndex = 0;
1810 for (int i = 0; i < kHotspotCount; i++) {
1811 const Hotspot &spot = _hotspots[i];
1812
1813 if (!spot.isActiveInput())
1814 // Not an active input
1815 continue;
1816
1817 if (inputIndex == input)
1818 // We've found our input
1819 return i;
1820
1821 // Next one
1822 inputIndex++;
1823 }
1824
1825 // None found
1826 return 0xFFFF;
1827 }
1828
hotspotToInput(uint16 hotspot) const1829 uint16 Hotspots::hotspotToInput(uint16 hotspot) const {
1830 uint16 input = 0;
1831
1832 for (int i = 0; i < kHotspotCount; i++) {
1833 const Hotspot &spot = _hotspots[i];
1834
1835 if (!spot.isActiveInput())
1836 // Not an active input
1837 continue;
1838
1839 if (i == hotspot)
1840 // We've found our hotspot
1841 break;
1842
1843 // Next one
1844 input++;
1845 }
1846
1847 return input;
1848 }
1849
findClickedInput(uint16 index) const1850 uint16 Hotspots::findClickedInput(uint16 index) const {
1851 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1852 const Hotspot &spot = _hotspots[i];
1853
1854 if (spot.getWindow() != 0)
1855 // Ignore other windows
1856 continue;
1857
1858 if (spot.isDisabled())
1859 // Ignore disabled hotspots
1860 continue;
1861
1862 if (!spot.isIn(_vm->_global->_inter_mouseX, _vm->_global->_inter_mouseY))
1863 // This one wasn't it
1864 continue;
1865
1866 if (spot.getCursor() != 0)
1867 // This one specifies a cursor, so we don't want it
1868 continue;
1869
1870 if (!spot.isInput())
1871 // It's no input
1872 continue;
1873
1874 index = i;
1875 break;
1876 }
1877
1878 return index;
1879 }
1880
findFirstInputLeave(uint16 & id,uint16 & inputId,uint16 & index) const1881 bool Hotspots::findFirstInputLeave(uint16 &id, uint16 &inputId, uint16 &index) const {
1882 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1883 const Hotspot &spot = _hotspots[i];
1884
1885 if (!spot.isFilledEnabled())
1886 // Not filled or disabled
1887 continue;
1888
1889 if (!spot.isInputLeave())
1890 // Not an input with a leave function
1891 continue;
1892
1893 id = spot.id;
1894 inputId = spot.id & 0x7FFF;
1895 index = i;
1896 return true;
1897 }
1898
1899 return false;
1900 }
1901
findKey(uint16 key,uint16 & id,uint16 & index) const1902 bool Hotspots::findKey(uint16 key, uint16 &id, uint16 &index) const {
1903 id = 0;
1904 index = 0;
1905
1906 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1907 const Hotspot &spot = _hotspots[i];
1908
1909 if (!spot.isFilledEnabled())
1910 // Not filled or disabled
1911 continue;
1912
1913 // Key match Catch all
1914 if ((spot.key == key) || (spot.key == 0x7FFF)) {
1915 id = spot.id;
1916 index = i;
1917 return true;
1918 }
1919 }
1920
1921 return false;
1922 }
1923
findKeyCaseInsensitive(uint16 key,uint16 & id,uint16 & index) const1924 bool Hotspots::findKeyCaseInsensitive(uint16 key, uint16 &id, uint16 &index) const {
1925 id = 0;
1926 index = 0;
1927
1928 for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1929 const Hotspot &spot = _hotspots[i];
1930
1931 if (!spot.isFilledEnabled())
1932 // Not filled or disabled, ignore
1933 continue;
1934
1935 if ((spot.key & 0xFF00) != 0)
1936 continue;
1937
1938 if (spot.key == 0)
1939 // No associated key, ignore
1940 continue;
1941
1942 // Compare
1943 if (toupper(key & 0xFF) == toupper(spot.key)) {
1944 id = spot.id;
1945 index = i;
1946 return true;
1947 }
1948 }
1949
1950 return false;
1951 }
1952
findNthPlain(uint16 n,uint16 startIndex,uint16 & id,uint16 & index) const1953 bool Hotspots::findNthPlain(uint16 n, uint16 startIndex, uint16 &id, uint16 &index) const {
1954 id = 0;
1955 index = 0;
1956
1957 uint16 counter = 0;
1958 for (int i = startIndex; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1959 const Hotspot &spot = _hotspots[i];
1960
1961 if (!spot.isFilledNew())
1962 // Not filled, ignore
1963 continue;
1964
1965 if (++counter != n)
1966 // Not yet the one we want
1967 continue;
1968
1969 id = spot.id;
1970 index = i;
1971 return true;
1972 }
1973
1974 return false;
1975 }
1976
leaveNthPlain(uint16 n,uint16 startIndex,int16 timeVal,const uint16 * ids,uint16 & id,uint16 & index,int16 & duration)1977 bool Hotspots::leaveNthPlain(uint16 n, uint16 startIndex, int16 timeVal, const uint16 *ids,
1978 uint16 &id, uint16 &index, int16 &duration) {
1979
1980 id = 0;
1981 index = 0;
1982
1983 if (!findNthPlain(n, startIndex, id, index))
1984 // Doesn't exist
1985 return false;
1986
1987 _vm->_inter->storeMouse();
1988
1989 if (getCurrentHotspot() != 0)
1990 // We already handle a hotspot
1991 return false;
1992
1993 setCurrentHotspot(ids, id);
1994
1995 const Hotspot &spot = _hotspots[index];
1996 if (spot.funcLeave != 0) {
1997 // It has a leave function
1998
1999 uint32 startTime, callTime;
2000
2001 // Call the leave and time it
2002 startTime = _vm->_util->getTimeKey();
2003 call(spot.funcLeave);
2004 _vm->_inter->animPalette();
2005 callTime = _vm->_util->getTimeKey() - startTime;
2006
2007 // Remove the time it took from the time we have available
2008 duration = CLIP<int>(timeVal - callTime, 2, timeVal);
2009 }
2010
2011 if (getCurrentHotspot() == 0) {
2012 id = 0;
2013 index = 0;
2014 }
2015
2016 return getCurrentHotspot() != 0;
2017 }
2018
setCurrentHotspot(const uint16 * ids,uint16 id) const2019 void Hotspots::setCurrentHotspot(const uint16 *ids, uint16 id) const {
2020 if (!ids) {
2021 WRITE_VAR(16, 0);
2022 return;
2023 }
2024
2025 if (Hotspot::getState(id) == kStateFilled)
2026 WRITE_VAR(16, ids[id & 0xFFF]);
2027 else
2028 WRITE_VAR(16, id & 0xFFF);
2029 }
2030
getCurrentHotspot() const2031 uint32 Hotspots::getCurrentHotspot() const {
2032 return VAR(16);
2033 }
2034
cleanFloatString(const Hotspot & spot) const2035 void Hotspots::cleanFloatString(const Hotspot &spot) const {
2036 char *to, *from;
2037
2038 to = from = GET_VARO_STR(spot.key);
2039 for (int i = 0; (i < 257) && (*from != '\0'); i++, from++) {
2040 char c = *from;
2041
2042 // Skip spaces
2043 if (c == ' ')
2044 continue;
2045
2046 // Convert decimal separator if necessary
2047 if ((_vm->_global->_language == kLanguageBritish) && (c == '.'))
2048 c = ',';
2049
2050 *to++ = c;
2051 }
2052
2053 *to = '\0';
2054 }
2055
checkStringMatch(const Hotspot & spot,const InputDesc & input,uint16 inputPos) const2056 void Hotspots::checkStringMatch(const Hotspot &spot, const InputDesc &input,
2057 uint16 inputPos) const {
2058
2059 const char *str = input.str;
2060
2061 char tempStr[256];
2062 char spotStr[256];
2063
2064 Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
2065
2066 if (spot.getType() < kTypeInput3NoLeave)
2067 _vm->_util->cleanupStr(tempStr);
2068
2069 uint16 pos = 0;
2070 do {
2071 Common::strlcpy(spotStr, str, 256);
2072
2073 pos += strlen(str) + 1;
2074 str += strlen(str) + 1;
2075
2076 if (spot.getType() < kTypeInput3NoLeave)
2077 _vm->_util->cleanupStr(spotStr);
2078
2079 // Compare the entered string with the string we wanted
2080 if (strcmp(tempStr, spotStr) == 0) {
2081 WRITE_VAR(17, VAR(17) + 1);
2082 WRITE_VAR(17 + inputPos, 1);
2083 break;
2084 }
2085 } while (input.length > pos);
2086 }
2087
matchInputStrings(const InputDesc * inputs) const2088 void Hotspots::matchInputStrings(const InputDesc *inputs) const {
2089 uint16 strInputCount = 0;
2090 uint16 inputIndex = 0;
2091 uint16 inputPos = 1;
2092
2093 for (int i = 0; i < kHotspotCount; i++) {
2094 const Hotspot &spot = _hotspots[i];
2095
2096 // Looking for all enabled inputs
2097 if (spot.isEnd())
2098 continue;
2099 if (!spot.isFilledEnabled())
2100 continue;
2101 if (!spot.isInput())
2102 continue;
2103
2104 if (spot.getType() >= kTypeInputFloatNoLeave)
2105 cleanFloatString(spot);
2106
2107 if ((spot.getType() >= kTypeInput2NoLeave) && (spot.getType() <= kTypeInput3Leave)) {
2108 // Look if we find a match between the wanted and the typed string
2109 checkStringMatch(spot, inputs[inputIndex], inputPos);
2110 strInputCount++;
2111 } else
2112 WRITE_VAR(17 + inputPos, 2);
2113
2114 inputIndex++;
2115 inputPos++;
2116 }
2117
2118 // Notify the scripts if we reached the requested hotspot
2119 WRITE_VAR(17, (uint32) (strInputCount == ((uint16) VAR(17))));
2120 }
2121
convertSpecialKey(uint16 key) const2122 uint16 Hotspots::convertSpecialKey(uint16 key) const {
2123 if (((key & 0xFF) >= ' ') && ((key & 0xFF) <= 0xFF) &&
2124 ((key >> 8) > 1) && ((key >> 8) < 12))
2125 key = '0' + (((key >> 8) - 1) % 10) + (key & 0xFF00);
2126
2127 return key;
2128 }
2129
getTextCursorPos(const Font & font,const char * str,uint32 pos,uint16 x,uint16 y,uint16 width,uint16 height,uint16 & cursorX,uint16 & cursorY,uint16 & cursorWidth,uint16 & cursorHeight) const2130 void Hotspots::getTextCursorPos(const Font &font, const char *str,
2131 uint32 pos, uint16 x, uint16 y, uint16 width, uint16 height,
2132 uint16 &cursorX, uint16 &cursorY, uint16 &cursorWidth, uint16 &cursorHeight) const {
2133
2134 if (!font.isMonospaced()) {
2135 // Cursor to the right of the current character
2136
2137 cursorX = x;
2138 cursorY = y;
2139 cursorWidth = 1;
2140 cursorHeight = height;
2141
2142 // Iterate through the string and add each character's width
2143 for (uint32 i = 0; i < pos; i++)
2144 cursorX += font.getCharWidth(str[i]);
2145
2146 } else {
2147 // Cursor underlining the current character
2148
2149 cursorX = x + font.getCharWidth() * pos;
2150 cursorY = y + height - 1;
2151 cursorWidth = font.getCharWidth();
2152 cursorHeight = 1;
2153 }
2154 }
2155
fillRect(uint16 x,uint16 y,uint16 width,uint16 height,uint16 color) const2156 void Hotspots::fillRect(uint16 x, uint16 y, uint16 width, uint16 height, uint16 color) const {
2157 _vm->_draw->_destSurface = Draw::kBackSurface;
2158 _vm->_draw->_destSpriteX = x;
2159 _vm->_draw->_destSpriteY = y;
2160 _vm->_draw->_spriteRight = width;
2161 _vm->_draw->_spriteBottom = height;
2162 _vm->_draw->_backColor = color;
2163
2164 _vm->_draw->spriteOperation(DRAW_FILLRECT | 0x10);
2165 }
2166
printText(uint16 x,uint16 y,const char * str,uint16 fontIndex,uint16 color) const2167 void Hotspots::printText(uint16 x, uint16 y, const char *str, uint16 fontIndex, uint16 color) const {
2168 _vm->_draw->_destSpriteX = x;
2169 _vm->_draw->_destSpriteY = y;
2170 _vm->_draw->_frontColor = color;
2171 _vm->_draw->_fontIndex = fontIndex;
2172 _vm->_draw->_textToPrint = str;
2173 _vm->_draw->_transparency = 1;
2174
2175 _vm->_draw->spriteOperation(DRAW_PRINTTEXT | 0x10);
2176 }
2177
updateAllTexts(const InputDesc * inputs) const2178 void Hotspots::updateAllTexts(const InputDesc *inputs) const {
2179 uint16 input = 0;
2180
2181 for (int i = 0; i < kHotspotCount; i++) {
2182 const Hotspot &spot = _hotspots[i];
2183
2184 if (spot.isEnd())
2185 // It's an end, we don't want it
2186 continue;
2187
2188 if (!spot.isFilledEnabled())
2189 // This one's either not used or disabled
2190 continue;
2191
2192 if (!spot.isInput())
2193 // Not an input
2194 continue;
2195
2196 // Get its text
2197 char tempStr[256];
2198 Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
2199
2200 // Coordinates
2201 uint16 x = spot.left;
2202 uint16 y = spot.top;
2203 uint16 width = spot.right - spot.left + 1;
2204 uint16 height = spot.bottom - spot.top + 1;
2205 // Clear the background
2206 fillRect(x, y, width, height, inputs[input].backColor);
2207
2208 // Center the text vertically
2209 y += (height - _vm->_draw->_fonts[_vm->_draw->_fontIndex]->getCharHeight()) / 2;
2210
2211 // Draw it
2212 printText(x, y, tempStr, inputs[input].fontIndex, inputs[input].frontColor);
2213
2214 input++;
2215 }
2216 }
2217 } // End of namespace Gob
2218