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 	}
1233 }
1234 
evaluateNew(uint16 i,uint16 * ids,InputDesc * inputs,uint16 & inputId,bool & hasInput,uint16 & inputCount)1235 void Hotspots::evaluateNew(uint16 i, uint16 *ids, InputDesc *inputs,
1236 		uint16 &inputId, bool &hasInput, uint16 &inputCount) {
1237 
1238 	ids[i] = 0;
1239 
1240 	// Type and window
1241 	byte type      = _vm->_game->_script->readByte();
1242 	byte windowNum = 0;
1243 
1244 	if ((type & 0x40) != 0) {
1245 		// Got a window ID
1246 
1247 		type     -= 0x40;
1248 		windowNum = _vm->_game->_script->readByte();
1249 	}
1250 
1251 	// Coordinates
1252 	uint16 left, top, width, height, right, bottom;
1253 	uint32 funcPos = 0;
1254 	if ((type & 0x80) != 0) {
1255 		// Complex coordinate expressions
1256 		funcPos = _vm->_game->_script->pos();
1257 		left    = _vm->_game->_script->readValExpr();
1258 		top     = _vm->_game->_script->readValExpr();
1259 		width   = _vm->_game->_script->readValExpr();
1260 		height  = _vm->_game->_script->readValExpr();
1261 	} else {
1262 		// Immediate values
1263 		funcPos = 0;
1264 		left    = _vm->_game->_script->readUint16();
1265 		top     = _vm->_game->_script->readUint16();
1266 		width   = _vm->_game->_script->readUint16();
1267 		height  = _vm->_game->_script->readUint16();
1268 	}
1269 	type &= 0x7F;
1270 
1271 	// Draw a border around the hotspot
1272 	if (_vm->_draw->_renderFlags & RENDERFLAG_BORDERHOTSPOTS) {
1273 		Surface &surface = *_vm->_draw->_spritesArray[_vm->_draw->_destSurface];
1274 
1275 		_vm->_video->dirtyRectsAll();
1276 
1277 		if (windowNum == 0) {
1278 			// The hotspot is not inside a window, just draw border it
1279 			surface.drawRect(left, top, left + width - 1, top + height - 1, 0);
1280 
1281 		} else {
1282 			// The hotspot is inside a window, only draw it if it's the topmost window
1283 
1284 			if ((_vm->_draw->_fascinWin[windowNum].id != -1) &&
1285 			    (_vm->_draw->_fascinWin[windowNum].id == (_vm->_draw->_winCount - 1))) {
1286 
1287 				const uint16 wLeft = left + _vm->_draw->_fascinWin[windowNum].left;
1288 				const uint16 wTop  = top  + _vm->_draw->_fascinWin[windowNum].top;
1289 
1290 				surface.drawRect(wLeft, wTop, wLeft + width - 1, wTop + height - 1, 0);
1291 			}
1292 		}
1293 	}
1294 
1295 	// Apply global drawing offset
1296 	if ((_vm->_draw->_renderFlags & RENDERFLAG_CAPTUREPOP) && (left != 0xFFFF)) {
1297 		left += _vm->_draw->_backDeltaX;
1298 		top  += _vm->_draw->_backDeltaY;
1299 	}
1300 
1301 	right  = left + width  - 1;
1302 	bottom = top  + height - 1;
1303 
1304 	// Enabling the hotspots again
1305 	if ((type == kTypeEnable2) || (type == kTypeEnable1)) {
1306 		uint8 wantedState = 0;
1307 		if (type == kTypeEnable2)
1308 			wantedState = kStateFilledDisabled | kStateType2;
1309 		else
1310 			wantedState = kStateFilledDisabled | kStateType1;
1311 
1312 		_vm->_game->_script->skip(6);
1313 
1314 		for (int j = 0; j < kHotspotCount; j++) {
1315 			Hotspot &spot = _hotspots[j];
1316 
1317 			if (spot.getState() == wantedState) {
1318 				spot.enable();
1319 				spot.funcEnter = _vm->_game->_script->pos();
1320 				spot.funcLeave = _vm->_game->_script->pos();
1321 			}
1322 		}
1323 
1324 		_vm->_game->_script->skipBlock();
1325 
1326 		return;
1327 	}
1328 
1329 	int16 key   = 0;
1330 	int16 flags = 0;
1331 	Font *font = 0;
1332 	uint32 funcEnter = 0, funcLeave = 0;
1333 
1334 	if ((windowNum != 0) && (type != 0) && (type != 2))
1335 		debugC(0, kDebugHotspots, "evaluateNew - type %d, win %d",type, windowNum);
1336 
1337 	// Evaluate parameters for the new hotspot
1338 	switch (type) {
1339 	case kTypeNone:
1340 		_vm->_game->_script->skip(6);
1341 
1342 		funcEnter = _vm->_game->_script->pos();
1343 		_vm->_game->_script->skipBlock();
1344 
1345 		funcLeave = _vm->_game->_script->pos();
1346 		_vm->_game->_script->skipBlock();
1347 
1348 		key   = i + ((kStateFilled | kStateType2) << 12);
1349 		flags = type + (windowNum << 8);
1350 		break;
1351 
1352 	case kTypeMove:
1353 		key    = _vm->_game->_script->readInt16();
1354 		ids[i] = _vm->_game->_script->readInt16();
1355 		flags  = _vm->_game->_script->readInt16();
1356 
1357 		funcEnter = _vm->_game->_script->pos();
1358 		_vm->_game->_script->skipBlock();
1359 
1360 		funcLeave = _vm->_game->_script->pos();
1361 		_vm->_game->_script->skipBlock();
1362 
1363 		if (key == 0)
1364 			key = i + ((kStateFilled | kStateType2) << 12);
1365 
1366 		flags = type + (windowNum << 8) + (flags << 4);
1367 		break;
1368 
1369 	case kTypeInput1NoLeave:
1370 	case kTypeInput1Leave:
1371 	case kTypeInput2NoLeave:
1372 	case kTypeInput2Leave:
1373 	case kTypeInput3NoLeave:
1374 	case kTypeInput3Leave:
1375 	case kTypeInputFloatNoLeave:
1376 	case kTypeInputFloatLeave:
1377 		hasInput = true;
1378 
1379 		_vm->_util->clearKeyBuf();
1380 
1381 		// Input text parameters
1382 		key                           = _vm->_game->_script->readVarIndex();
1383 		inputs[inputCount].fontIndex  = _vm->_game->_script->readInt16();
1384 		inputs[inputCount].backColor  = _vm->_game->_script->readByte();
1385 		inputs[inputCount].frontColor = _vm->_game->_script->readByte();
1386 		inputs[inputCount].length     = 0;
1387 		inputs[inputCount].str        = 0;
1388 
1389 		if ((type >= kTypeInput2NoLeave) && (type <= kTypeInput3Leave)) {
1390 			inputs[inputCount].length = _vm->_game->_script->readUint16();
1391 
1392 			inputs[inputCount].str =
1393 				(const char *)(_vm->_game->_script->getData() + _vm->_game->_script->pos());
1394 
1395 			_vm->_game->_script->skip(inputs[inputCount].length);
1396 		}
1397 
1398 		if (left == 0xFFFF) {
1399 			if (!(type & 1))
1400 				// No coordinates but a leave block => skip it
1401 				_vm->_game->_script->skipBlock();
1402 			break;
1403 		}
1404 
1405 		font = _vm->_draw->_fonts[inputs[inputCount].fontIndex];
1406 		if (font->isMonospaced())
1407 			right = left + width * font->getCharWidth() - 1;
1408 
1409 		funcEnter = 0;
1410 		funcPos   = 0;
1411 		funcLeave = 0;
1412 		if (!(type & 1)) {
1413 			// Got a leave
1414 			funcLeave = _vm->_game->_script->pos();
1415 			_vm->_game->_script->skipBlock();
1416 		}
1417 
1418 		flags = type;
1419 
1420 		inputCount++;
1421 		break;
1422 
1423 	case 20:
1424 		inputId = i;
1425 		// fall through
1426 	case kTypeClick:
1427 		key    = _vm->_game->_script->readInt16();
1428 		ids[i] = _vm->_game->_script->readInt16();
1429 		flags  = _vm->_game->_script->readInt16();
1430 
1431 		if (flags > 3)
1432 			warning("evaluateNew: Warning, use of type 2 or 20. flags = %d, should be %d", flags, flags&3);
1433 
1434 		funcEnter = 0;
1435 
1436 		funcLeave = _vm->_game->_script->pos();
1437 		_vm->_game->_script->skipBlock();
1438 
1439 		flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
1440 		break;
1441 
1442 	case kTypeClickEnter:
1443 		key    = _vm->_game->_script->readInt16();
1444 		ids[i] = _vm->_game->_script->readInt16();
1445 		flags  = _vm->_game->_script->readInt16() & 3;
1446 
1447 		funcEnter = _vm->_game->_script->pos();
1448 		_vm->_game->_script->skipBlock();
1449 
1450 		funcLeave = 0;
1451 
1452 		flags = ((uint16) kTypeClick) + (windowNum << 8) + (flags << 4);
1453 		break;
1454 	}
1455 
1456 	// Add the new hotspot
1457 	add(i | (kStateFilled << 12), left, top, right, bottom,
1458 			flags, key, funcEnter, funcLeave, funcPos);
1459 }
1460 
evaluateFind(uint16 key,int16 timeVal,const uint16 * ids,uint16 leaveWindowIndex,uint16 hotspotIndex1,uint16 hotspotIndex2,uint16 endIndex,int16 & duration,uint16 & id,uint16 & index,bool & finished)1461 bool Hotspots::evaluateFind(uint16 key, int16 timeVal, const uint16 *ids,
1462 		uint16 leaveWindowIndex, uint16 hotspotIndex1, uint16 hotspotIndex2,
1463 		uint16 endIndex, int16 &duration, uint16 &id, uint16 &index, bool &finished) {
1464 
1465 	bool fascinCheck = false;
1466 
1467 	if (id != 0)
1468 		// We already found a hotspot, nothing to do
1469 		return true;
1470 
1471 	if (key != 0) {
1472 		// We've got a key
1473 
1474 		// Find the hotspot with that key associated
1475 		findKey(key, id, index);
1476 		if (id != 0)
1477 			// Found it
1478 			return true;
1479 
1480 		// Try it case insensitively
1481 		findKeyCaseInsensitive(key, id, index);
1482 		if (id != 0)
1483 			// Found it
1484 			return true;
1485 
1486 		return false;
1487 	}
1488 	if ((_vm->getGameType() == kGameTypeFascination) && (getCurrentHotspot()))
1489 		fascinCheck = true;
1490 
1491 	if ((duration != 0) && (!fascinCheck)) {
1492 		// We've got a time duration
1493 
1494 		if        (hotspotIndex1 != 0) {
1495 			finished =
1496 				leaveNthPlain(hotspotIndex1, endIndex, timeVal, ids, id, index, duration);
1497 		} else if (hotspotIndex2 != 0) {
1498 			findNthPlain(hotspotIndex2, endIndex, id, index);
1499 		} else {
1500 			// Enter the first hotspot
1501 			for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1502 				Hotspot &spot = _hotspots[i];
1503 				if (spot.isFilledNew()) {
1504 					id    = spot.id;
1505 					index = i;
1506 					break;
1507 				}
1508 			}
1509 
1510 			// Leave the current hotspot
1511 			if ((_currentKey != 0) && (_hotspots[_currentIndex].funcLeave != 0))
1512 				call(_hotspots[_currentIndex].funcLeave);
1513 
1514 			_currentKey = 0;
1515 		}
1516 
1517 		if (id != 0)
1518 			return true;
1519 
1520 		return false;
1521 	} else {
1522 		if (leaveWindowIndex != 0)
1523 			findNthPlain(leaveWindowIndex, endIndex, id, index);
1524 
1525 		if (id != 0)
1526 			return true;
1527 	}
1528 
1529 	return false;
1530 }
1531 
evaluate()1532 void Hotspots::evaluate() {
1533 	InputDesc inputs[20];
1534 	uint16 ids[kHotspotCount];
1535 
1536 	// Push all local hotspots
1537 	push(0);
1538 
1539 	// Find the current end of the hotspot block
1540 	uint16 endIndex = 0;
1541 	while (!_hotspots[endIndex].isEnd())
1542 		endIndex++;
1543 
1544 	_shouldPush = false;
1545 
1546 	_vm->_game->_script->skip(1);
1547 
1548 	// Number of new hotspots
1549 	byte count = _vm->_game->_script->readByte();
1550 
1551 	// Parameters of this block
1552 	_vm->_game->_handleMouse = _vm->_game->_script->peekByte(0);
1553 	int16 duration           = _vm->_game->_script->peekByte(1);
1554 
1555 	byte leaveWindowIndex = 0;
1556 	if (_vm->getGameType() == kGameTypeFascination)
1557 		leaveWindowIndex = _vm->_game->_script->peekByte(2);
1558 
1559 	byte hotspotIndex1       = _vm->_game->_script->peekByte(3);
1560 	byte hotspotIndex2       = _vm->_game->_script->peekByte(4);
1561 	bool needRecalculation   = _vm->_game->_script->peekByte(5) != 0;
1562 
1563 	// Seconds -> Milliseconds
1564 	duration *= 1000;
1565 
1566 	if ((hotspotIndex1 != 0) || (hotspotIndex2 != 0)) {
1567 		duration /= 100;
1568 		if (_vm->_game->_script->peekByte(1) == 100)
1569 			duration = 2;
1570 	}
1571 
1572 	int16 timeVal = duration;
1573 
1574 	_vm->_game->_script->skip(6);
1575 
1576 	setCurrentHotspot(0, 0);
1577 
1578 	bool finishedDuration = false;
1579 
1580 	uint16 id      = 0;
1581 	uint16 inputId = 0xFFFF;
1582 	uint16 index   = 0;
1583 
1584 	bool   hasInput   = false;
1585 	uint16 inputCount = 0;
1586 
1587 	// Adding new hotspots
1588 	for (uint16 i = 0; i < count; i++)
1589 		evaluateNew(i, ids, inputs, inputId, hasInput, inputCount);
1590 
1591 	// Recalculate all hotspots if requested
1592 	if (needRecalculation)
1593 		recalculate(true);
1594 
1595 	_vm->_game->_forceHandleMouse = 0;
1596 	_vm->_util->clearKeyBuf();
1597 
1598 	while ((id == 0) && !_vm->_inter->_terminate && !_vm->shouldQuit()) {
1599 		uint16 key = 0;
1600 		if (hasInput) {
1601 			// Input
1602 
1603 			uint16 curInput = 0;
1604 
1605 			key = handleInputs(duration, inputCount, curInput, inputs, id, index);
1606 
1607 			// Notify the script of the current input index
1608 			WRITE_VAR(17 + 38, curInput);
1609 			if (key == kKeyReturn) {
1610 				// Return pressed, invoke input leave
1611 				findFirstInputLeave(id, inputId, index);
1612 				break;
1613 			}
1614 		} else
1615 			// Normal move or click check
1616 			key = check(_vm->_game->_handleMouse, -duration, id, index);
1617 
1618 		key = convertSpecialKey(key);
1619 
1620 		// Try to find a fitting hotspot
1621 		evaluateFind(key, timeVal, ids, leaveWindowIndex, hotspotIndex1, hotspotIndex2, endIndex,
1622 				duration, id, index, finishedDuration);
1623 
1624 		if (finishedDuration)
1625 			break;
1626 
1627 		if ((id == 0) || (_hotspots[index].funcLeave != 0))
1628 			// We don't have a new ID, but haven't yet handled the leave function
1629 			continue;
1630 
1631 		_vm->_inter->storeMouse();
1632 
1633 		setCurrentHotspot(ids, id);
1634 
1635 		// Enter it
1636 		if (_hotspots[index].funcEnter != 0)
1637 			call(_hotspots[index].funcEnter);
1638 
1639 		setCurrentHotspot(0, 0);
1640 		id = 0;
1641 	}
1642 
1643 	if ((id & 0xFFF) == inputId)
1644 		matchInputStrings(inputs);
1645 
1646 	if (_vm->_game->_handleMouse == 1)
1647 		_vm->_draw->blitCursor();
1648 
1649 	if (!_vm->_inter->_terminate && (!finishedDuration)) {
1650 		_vm->_game->_script->seek(_hotspots[index].funcLeave);
1651 
1652 		_vm->_inter->storeMouse();
1653 		if (getCurrentHotspot() == 0) {
1654 			// No hotspot currently handled, now we'll handle the newly found one
1655 
1656 			setCurrentHotspot(ids, id);
1657 		}
1658 	} else
1659 		_vm->_game->_script->setFinished(true);
1660 
1661 	for (int i = 0; i < count; i++)
1662 		// Remove all local hotspots
1663 		remove(i + (kStateFilled << 12));
1664 
1665 	for (int i = 0; i < kHotspotCount; i++) {
1666 		Hotspot &spot = _hotspots[i];
1667 
1668 		// Disable the ones still there
1669 		if ((spot.getState() == (kStateFilled | kStateType1)) ||
1670 				(spot.getState() == (kStateFilled | kStateType2)))
1671 				spot.disable();
1672 	}
1673 
1674 }
1675 
findCursor(uint16 x,uint16 y) const1676 int16 Hotspots::findCursor(uint16 x, uint16 y) const {
1677 	int16 cursor = 0;
1678 
1679 	int16 deltax = 0;
1680 	int16 deltay = 0;
1681 
1682 	// Fascination uses hard-coded windows
1683 	if (_vm->getGameType() == kGameTypeFascination) {
1684 		cursor = windowCursor(deltax, deltay);
1685 
1686 		// We're in a window and in an area that forces a specific cursor
1687 		if (cursor > 0)
1688 			return cursor;
1689 
1690 		// We're somewhere else inside a window
1691 		if (cursor < 0) {
1692 			int16 curType = -cursor * 256;
1693 			cursor = 0;
1694 
1695 			for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1696 				const Hotspot &spot = _hotspots[i];
1697 				// this check is /really/ Fascination specific.
1698 				// It's illogical, so if it's to be reused in Adi games... Be careful!
1699 				if ((spot.flags & 0xFF00) == curType)
1700 					if (spot.isIn(x - deltax, y - deltay)) {
1701 						if (spot.getType() < kTypeInput1NoLeave)
1702 							cursor = 1;
1703 						else
1704 							cursor = 3;
1705 						break;
1706 					}
1707 			}
1708 
1709 			if (_vm->_draw->_cursorAnimLow[cursor] == -1)
1710 			// If the cursor is invalid... there's a generic "click" cursor
1711 				cursor = 1;
1712 
1713 			return cursor;
1714 		}
1715 
1716 	}
1717 
1718 	// Normal, non-window cursor handling
1719 	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1720 		const Hotspot &spot = _hotspots[i];
1721 
1722 		if ((spot.getWindow() != 0) || spot.isDisabled())
1723 			// Ignore disabled and non-main-windowed hotspots
1724 			continue;
1725 
1726 		if (!spot.isIn(x, y))
1727 			// We're not in that hotspot, ignore it
1728 			continue;
1729 
1730 		if (spot.getCursor() == 0) {
1731 			// Hotspot doesn't itself specify a cursor...
1732 			if (spot.getType() >= kTypeInput1NoLeave) {
1733 				// ...but the type has a generic one
1734 				cursor = 3;
1735 				break;
1736 			} else if ((spot.getButton() != kMouseButtonsRight) && (cursor == 0))
1737 				// ...but there's a generic "click" cursor
1738 				cursor = 1;
1739 		} else if (cursor == 0)
1740 			// Hotspot had an attached cursor index
1741 			cursor = spot.getCursor();
1742 	}
1743 
1744 	return cursor;
1745 }
1746 
oPlaytoons_F_1B()1747 void Hotspots::oPlaytoons_F_1B() {
1748 	int16 shortId;
1749 	int16 longId;
1750 	int16 var2;
1751 	int16 fontIndex;
1752 	int16 var4;
1753 
1754 	uint16 left;
1755 	uint16 top;
1756 	uint16 right;
1757 	uint16 bottom;
1758 
1759 	shortId = _vm->_game->_script->readValExpr();
1760 	var2 = _vm->_game->_script->readValExpr();
1761 
1762 	_vm->_game->_script->evalExpr(0);
1763 
1764 	fontIndex = _vm->_game->_script->readValExpr();
1765 	var4 = _vm->_game->_script->readValExpr();
1766 
1767 //  this variable is always set to 0 in Playtoons
1768 //	var_4 += unk_var;
1769 
1770 	for (int i = 0; i < kHotspotCount; i++) {
1771 		if (_hotspots[i].isEnd())
1772 			return;
1773 
1774 		if ((_hotspots[i].id == 0xD000 + shortId) || (_hotspots[i].id == 0xB000 + shortId) ||
1775 			(_hotspots[i].id == 0x4000 + shortId)) {
1776 			longId = _hotspots[i].id;
1777 			warning("oPlaytoons_F_1B: shortId %d, var2 %d fontIndex %d var4 %d - longId %d", shortId, var2, fontIndex, var4, longId);
1778 
1779 			left = _hotspots[i].left;
1780 			top = _hotspots[i].top;
1781 			right = _hotspots[i].right;
1782 			bottom = _hotspots[i].bottom;
1783 
1784 			left += 2;
1785 			top += 2;
1786 			right -= 2;
1787 			bottom -= 2;
1788 			if ((_vm->_draw->_needAdjust != 2) && (_vm->_draw->_needAdjust != 10)) {
1789 				left += 2;
1790 				top += 2;
1791 				right -= 2;
1792 				bottom -= 2;
1793 			}
1794 			_vm->_draw->oPlaytoons_sub_F_1B(0x8000+ var2, left, top, right, bottom, _vm->_game->_script->getResultStr(), fontIndex, var4, shortId);
1795 			return;
1796 		}
1797 	}
1798 	warning("shortId not found %d", shortId);
1799 	return;
1800 }
1801 
inputToHotspot(uint16 input) const1802 uint16 Hotspots::inputToHotspot(uint16 input) const {
1803 	uint16 inputIndex = 0;
1804 	for (int i = 0; i < kHotspotCount; i++) {
1805 		const Hotspot &spot = _hotspots[i];
1806 
1807 		if (!spot.isActiveInput())
1808 			// Not an active input
1809 			continue;
1810 
1811 		if (inputIndex == input)
1812 			// We've found our input
1813 			return i;
1814 
1815 		// Next one
1816 		inputIndex++;
1817 	}
1818 
1819 	// None found
1820 	return 0xFFFF;
1821 }
1822 
hotspotToInput(uint16 hotspot) const1823 uint16 Hotspots::hotspotToInput(uint16 hotspot) const {
1824 	uint16 input = 0;
1825 
1826 	for (int i = 0; i < kHotspotCount; i++) {
1827 		const Hotspot &spot = _hotspots[i];
1828 
1829 		if (!spot.isActiveInput())
1830 			// Not an active input
1831 			continue;
1832 
1833 		if (i == hotspot)
1834 			// We've found our hotspot
1835 			break;
1836 
1837 		// Next one
1838 		input++;
1839 	}
1840 
1841 	return input;
1842 }
1843 
findClickedInput(uint16 index) const1844 uint16 Hotspots::findClickedInput(uint16 index) const {
1845 	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1846 		const Hotspot &spot = _hotspots[i];
1847 
1848 		if (spot.getWindow() != 0)
1849 			// Ignore other windows
1850 			continue;
1851 
1852 		if (spot.isDisabled())
1853 			// Ignore disabled hotspots
1854 			continue;
1855 
1856 		if (!spot.isIn(_vm->_global->_inter_mouseX, _vm->_global->_inter_mouseY))
1857 			// This one wasn't it
1858 			continue;
1859 
1860 		if (spot.getCursor() != 0)
1861 			// This one specifies a cursor, so we don't want it
1862 			continue;
1863 
1864 		if (!spot.isInput())
1865 			// It's no input
1866 			continue;
1867 
1868 		index = i;
1869 		break;
1870 	}
1871 
1872 	return index;
1873 }
1874 
findFirstInputLeave(uint16 & id,uint16 & inputId,uint16 & index) const1875 bool Hotspots::findFirstInputLeave(uint16 &id, uint16 &inputId, uint16 &index) const {
1876 	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1877 		const Hotspot &spot = _hotspots[i];
1878 
1879 		if (!spot.isFilledEnabled())
1880 			// Not filled or disabled
1881 			continue;
1882 
1883 		if (!spot.isInputLeave())
1884 			// Not an input with a leave function
1885 			continue;
1886 
1887 		id      = spot.id;
1888 		inputId = spot.id & 0x7FFF;
1889 		index   = i;
1890 		return true;
1891 	}
1892 
1893 	return false;
1894 }
1895 
findKey(uint16 key,uint16 & id,uint16 & index) const1896 bool Hotspots::findKey(uint16 key, uint16 &id, uint16 &index) const {
1897 	id    = 0;
1898 	index = 0;
1899 
1900 	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1901 		const Hotspot &spot = _hotspots[i];
1902 
1903 		if (!spot.isFilledEnabled())
1904 			// Not filled or disabled
1905 			continue;
1906 
1907 		//      Key match              Catch all
1908 		if ((spot.key == key) || (spot.key == 0x7FFF)) {
1909 			id    = spot.id;
1910 			index = i;
1911 			return true;
1912 		}
1913 	}
1914 
1915 	return false;
1916 }
1917 
findKeyCaseInsensitive(uint16 key,uint16 & id,uint16 & index) const1918 bool Hotspots::findKeyCaseInsensitive(uint16 key, uint16 &id, uint16 &index) const {
1919 	id    = 0;
1920 	index = 0;
1921 
1922 	for (int i = 0; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1923 		const Hotspot &spot = _hotspots[i];
1924 
1925 		if (!spot.isFilledEnabled())
1926 			// Not filled or disabled, ignore
1927 			continue;
1928 
1929 		if ((spot.key & 0xFF00) != 0)
1930 			continue;
1931 
1932 		if (spot.key == 0)
1933 			// No associated key, ignore
1934 			continue;
1935 
1936 		// Compare
1937 		if (toupper(key & 0xFF) == toupper(spot.key)) {
1938 			id    = spot.id;
1939 			index = i;
1940 			return true;
1941 		}
1942 	}
1943 
1944 	return false;
1945 }
1946 
findNthPlain(uint16 n,uint16 startIndex,uint16 & id,uint16 & index) const1947 bool Hotspots::findNthPlain(uint16 n, uint16 startIndex, uint16 &id, uint16 &index) const {
1948 	id    = 0;
1949 	index = 0;
1950 
1951 	uint16 counter = 0;
1952 	for (int i = startIndex; (i < kHotspotCount) && !_hotspots[i].isEnd(); i++) {
1953 		const Hotspot &spot = _hotspots[i];
1954 
1955 		if (!spot.isFilledNew())
1956 			// Not filled, ignore
1957 			continue;
1958 
1959 		if (++counter != n)
1960 			// Not yet the one we want
1961 			continue;
1962 
1963 		id    = spot.id;
1964 		index = i;
1965 		return true;
1966 	}
1967 
1968 	return false;
1969 }
1970 
leaveNthPlain(uint16 n,uint16 startIndex,int16 timeVal,const uint16 * ids,uint16 & id,uint16 & index,int16 & duration)1971 bool Hotspots::leaveNthPlain(uint16 n, uint16 startIndex, int16 timeVal, const uint16 *ids,
1972 		uint16 &id, uint16 &index, int16 &duration) {
1973 
1974 	id    = 0;
1975 	index = 0;
1976 
1977 	if (!findNthPlain(n, startIndex, id, index))
1978 		// Doesn't exist
1979 		return false;
1980 
1981 	_vm->_inter->storeMouse();
1982 
1983 	if (getCurrentHotspot() != 0)
1984 		// We already handle a hotspot
1985 		return false;
1986 
1987 	setCurrentHotspot(ids, id);
1988 
1989 	const Hotspot &spot = _hotspots[index];
1990 	if (spot.funcLeave != 0) {
1991 		// It has a leave function
1992 
1993 		uint32 startTime, callTime;
1994 
1995 		// Call the leave and time it
1996 		startTime = _vm->_util->getTimeKey();
1997 		call(spot.funcLeave);
1998 		_vm->_inter->animPalette();
1999 		callTime = _vm->_util->getTimeKey() - startTime;
2000 
2001 		// Remove the time it took from the time we have available
2002 		duration = CLIP<int>(timeVal - callTime, 2, timeVal);
2003 	}
2004 
2005 	if (getCurrentHotspot() == 0) {
2006 		id    = 0;
2007 		index = 0;
2008 	}
2009 
2010 	return getCurrentHotspot() != 0;
2011 }
2012 
setCurrentHotspot(const uint16 * ids,uint16 id) const2013 void Hotspots::setCurrentHotspot(const uint16 *ids, uint16 id) const {
2014 	if (!ids) {
2015 		WRITE_VAR(16, 0);
2016 		return;
2017 	}
2018 
2019 	if (Hotspot::getState(id) == kStateFilled)
2020 		WRITE_VAR(16, ids[id & 0xFFF]);
2021 	else
2022 		WRITE_VAR(16, id & 0xFFF);
2023 }
2024 
getCurrentHotspot() const2025 uint32 Hotspots::getCurrentHotspot() const {
2026 	return VAR(16);
2027 }
2028 
cleanFloatString(const Hotspot & spot) const2029 void Hotspots::cleanFloatString(const Hotspot &spot) const {
2030 	char *to, *from;
2031 
2032 	to = from = GET_VARO_STR(spot.key);
2033 	for (int i = 0; (i < 257) && (*from != '\0'); i++, from++) {
2034 		char c = *from;
2035 
2036 		// Skip spaces
2037 		if (c == ' ')
2038 			continue;
2039 
2040 		// Convert decimal separator if necessary
2041 		if ((_vm->_global->_language == kLanguageBritish) && (c == '.'))
2042 			c = ',';
2043 
2044 		*to++ = c;
2045 	}
2046 
2047 	*to = '\0';
2048 }
2049 
checkStringMatch(const Hotspot & spot,const InputDesc & input,uint16 inputPos) const2050 void Hotspots::checkStringMatch(const Hotspot &spot, const InputDesc &input,
2051 		uint16 inputPos) const {
2052 
2053 	const char *str = input.str;
2054 
2055 	char tempStr[256];
2056 	char spotStr[256];
2057 
2058 	Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
2059 
2060 	if (spot.getType() < kTypeInput3NoLeave)
2061 		_vm->_util->cleanupStr(tempStr);
2062 
2063 	uint16 pos = 0;
2064 	do {
2065 		Common::strlcpy(spotStr, str, 256);
2066 
2067 		pos += strlen(str) + 1;
2068 		str += strlen(str) + 1;
2069 
2070 		if (spot.getType() < kTypeInput3NoLeave)
2071 			_vm->_util->cleanupStr(spotStr);
2072 
2073 		// Compare the entered string with the string we wanted
2074 		if (strcmp(tempStr, spotStr) == 0) {
2075 			WRITE_VAR(17, VAR(17) + 1);
2076 			WRITE_VAR(17 + inputPos, 1);
2077 			break;
2078 		}
2079 	} while (input.length > pos);
2080 }
2081 
matchInputStrings(const InputDesc * inputs) const2082 void Hotspots::matchInputStrings(const InputDesc *inputs) const {
2083 	uint16 strInputCount = 0;
2084 	uint16 inputIndex    = 0;
2085 	uint16 inputPos      = 1;
2086 
2087 	for (int i = 0; i < kHotspotCount; i++) {
2088 		const Hotspot &spot = _hotspots[i];
2089 
2090 		// Looking for all enabled inputs
2091 		if (spot.isEnd())
2092 			continue;
2093 		if (!spot.isFilledEnabled())
2094 			continue;
2095 		if (!spot.isInput())
2096 			continue;
2097 
2098 		if (spot.getType() >= kTypeInputFloatNoLeave)
2099 			cleanFloatString(spot);
2100 
2101 		if ((spot.getType() >= kTypeInput2NoLeave) && (spot.getType() <= kTypeInput3Leave)) {
2102 			// Look if we find a match between the wanted and the typed string
2103 			checkStringMatch(spot, inputs[inputIndex], inputPos);
2104 			strInputCount++;
2105 		} else
2106 			WRITE_VAR(17 + inputPos, 2);
2107 
2108 		inputIndex++;
2109 		inputPos++;
2110 	}
2111 
2112 	// Notify the scripts if we reached the requested hotspot
2113 	WRITE_VAR(17, (uint32) (strInputCount == ((uint16) VAR(17))));
2114 }
2115 
convertSpecialKey(uint16 key) const2116 uint16 Hotspots::convertSpecialKey(uint16 key) const {
2117 	if (((key & 0xFF) >= ' ') && ((key & 0xFF) <= 0xFF) &&
2118 			((key >> 8) > 1) && ((key >> 8) < 12))
2119 		key = '0' + (((key >> 8) - 1) % 10) + (key & 0xFF00);
2120 
2121 	return key;
2122 }
2123 
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) const2124 void Hotspots::getTextCursorPos(const Font &font, const char *str,
2125 		uint32 pos, uint16 x, uint16 y, uint16 width, uint16 height,
2126 		uint16 &cursorX, uint16 &cursorY, uint16 &cursorWidth, uint16 &cursorHeight) const {
2127 
2128 	if (!font.isMonospaced()) {
2129 		// Cursor to the right of the current character
2130 
2131 		cursorX      = x;
2132 		cursorY      = y;
2133 		cursorWidth  = 1;
2134 		cursorHeight = height;
2135 
2136 		// Iterate through the string and add each character's width
2137 		for (uint32 i = 0; i < pos; i++)
2138 			cursorX += font.getCharWidth(str[i]);
2139 
2140 	} else {
2141 		// Cursor underlining the current character
2142 
2143 		cursorX      = x + font.getCharWidth() * pos;
2144 		cursorY      = y + height - 1;
2145 		cursorWidth  = font.getCharWidth();
2146 		cursorHeight = 1;
2147 	}
2148 }
2149 
fillRect(uint16 x,uint16 y,uint16 width,uint16 height,uint16 color) const2150 void Hotspots::fillRect(uint16 x, uint16 y, uint16 width, uint16 height, uint16 color) const {
2151 	_vm->_draw->_destSurface  = Draw::kBackSurface;
2152 	_vm->_draw->_destSpriteX  = x;
2153 	_vm->_draw->_destSpriteY  = y;
2154 	_vm->_draw->_spriteRight  = width;
2155 	_vm->_draw->_spriteBottom = height;
2156 	_vm->_draw->_backColor    = color;
2157 
2158 	_vm->_draw->spriteOperation(DRAW_FILLRECT | 0x10);
2159 }
2160 
printText(uint16 x,uint16 y,const char * str,uint16 fontIndex,uint16 color) const2161 void Hotspots::printText(uint16 x, uint16 y, const char *str, uint16 fontIndex, uint16 color) const {
2162 	_vm->_draw->_destSpriteX  = x;
2163 	_vm->_draw->_destSpriteY  = y;
2164 	_vm->_draw->_frontColor   = color;
2165 	_vm->_draw->_fontIndex    = fontIndex;
2166 	_vm->_draw->_textToPrint  = str;
2167 	_vm->_draw->_transparency = 1;
2168 
2169 	_vm->_draw->spriteOperation(DRAW_PRINTTEXT | 0x10);
2170 }
2171 
updateAllTexts(const InputDesc * inputs) const2172 void Hotspots::updateAllTexts(const InputDesc *inputs) const {
2173 	uint16 input = 0;
2174 
2175 	for (int i = 0; i < kHotspotCount; i++) {
2176 		const Hotspot &spot = _hotspots[i];
2177 
2178 		if (spot.isEnd())
2179 			// It's an end, we don't want it
2180 			continue;
2181 
2182 		if (!spot.isFilledEnabled())
2183 			// This one's either not used or disabled
2184 			continue;
2185 
2186 		if (!spot.isInput())
2187 			// Not an input
2188 			continue;
2189 
2190 		// Get its text
2191 		char tempStr[256];
2192 		Common::strlcpy(tempStr, GET_VARO_STR(spot.key), 256);
2193 
2194 		// Coordinates
2195 		uint16 x      = spot.left;
2196 		uint16 y      = spot.top;
2197 		uint16 width  = spot.right  - spot.left + 1;
2198 		uint16 height = spot.bottom - spot.top  + 1;
2199 		// Clear the background
2200 		fillRect(x, y, width, height, inputs[input].backColor);
2201 
2202 		// Center the text vertically
2203 		y += (height - _vm->_draw->_fonts[_vm->_draw->_fontIndex]->getCharHeight()) / 2;
2204 
2205 		// Draw it
2206 		printText(x, y, tempStr, inputs[input].fontIndex, inputs[input].frontColor);
2207 
2208 		input++;
2209 	}
2210 }
2211 } // End of namespace Gob
2212