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