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/system.h"
24 
25 #include "sci/sci.h"
26 #include "sci/engine/features.h"
27 #include "sci/engine/guest_additions.h"
28 #include "sci/engine/kernel.h"
29 #include "sci/engine/savegame.h"
30 #include "sci/engine/selector.h"
31 #include "sci/engine/state.h"
32 #include "sci/console.h"
33 #include "sci/debug.h"	// for g_debug_simulated_key
34 #include "sci/event.h"
35 #include "sci/graphics/coordadjuster.h"
36 #include "sci/graphics/cursor.h"
37 #include "sci/graphics/maciconbar.h"
38 #ifdef ENABLE_SCI32
39 #include "sci/graphics/frameout.h"
40 #endif
41 
42 namespace Sci {
43 
kGetEvent(EngineState * s,int argc,reg_t * argv)44 reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
45 	SciEventType mask = (SciEventType)argv[0].toUint16();
46 	reg_t obj = argv[1];
47 	SciEvent curEvent;
48 	uint16 modifiers = 0;
49 	SegManager *segMan = s->_segMan;
50 	Common::Point mousePos;
51 
52 	// If there's a simkey pending, and the game wants a keyboard event, use the
53 	// simkey instead of a normal event
54 	// TODO: This does not really work as expected for keyup events, since the
55 	// fake event is disposed halfway through the normal event lifecycle.
56 	if (g_debug_simulated_key && (mask & kSciEventKeyDown)) {
57 		// In case we use a simulated event we query the current mouse position
58 		mousePos = g_sci->_gfxCursor->getPosition();
59 
60 		// Limit the mouse cursor position, if necessary
61 		g_sci->_gfxCursor->refreshPosition();
62 
63 		writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventKeyDown);
64 		writeSelectorValue(segMan, obj, SELECTOR(message), g_debug_simulated_key);
65 		writeSelectorValue(segMan, obj, SELECTOR(modifiers), kSciKeyModNumLock);
66 		writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
67 		writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
68 		g_debug_simulated_key = 0;
69 		return TRUE_REG;
70 	}
71 
72 	curEvent = g_sci->getEventManager()->getSciEvent(mask);
73 
74 	// For Mac games with an icon bar, handle possible icon bar events first
75 	if (g_sci->hasMacIconBar()) {
76 		reg_t iconObj = NULL_REG;
77 		if (g_sci->_gfxMacIconBar->handleEvents(curEvent, iconObj)) {
78 			if (!iconObj.isNull()) {
79 				invokeSelector(s, iconObj, SELECTOR(select), argc, argv, 0, NULL);
80 			}
81 
82 			// The mouse press event was handled by the mac icon bar so change
83 			// its type to none so that generic event processing can continue
84 			// without the mouse press being handled twice
85 			curEvent.type = kSciEventNone;
86 		}
87 	}
88 
89 	if (g_sci->_guestAdditions->kGetEventHook()) {
90 		return NULL_REG;
91 	}
92 
93 	// For a real event we use its associated mouse position
94 #ifdef ENABLE_SCI32
95 	if (getSciVersion() >= SCI_VERSION_2) {
96 		mousePos = curEvent.mousePosSci;
97 
98 		// Some games, like LSL6hires (when interacting with the menu bar) and
99 		// Phant2 (when on the "click mouse" screen after restoring a game),
100 		// have unthrottled loops that call kGetEvent but do not call kFrameOut.
101 		// In these cases we still need to call OSystem::updateScreen to update
102 		// the mouse cursor (in SSCI this was not necessary because mouse
103 		// updates were made directly to hardware from an interrupt handler),
104 		// and we need to throttle these calls so the game does not use 100%
105 		// CPU.
106 		// This situation seems to be detectable by looking at how many times
107 		// kGetEvent has been called between calls to kFrameOut. During normal
108 		// game operation, there are usually just 0 or 1 kGetEvent calls between
109 		// kFrameOut calls; any more than that indicates that we are probably in
110 		// one of these ugly loops and should be updating the screen &
111 		// throttling the VM.
112 		if (++s->_eventCounter > 2) {
113 			g_sci->_gfxFrameout->updateScreen();
114 			s->speedThrottler(10); // 10ms is an arbitrary value
115 			s->_throttleTrigger = true;
116 		}
117 	} else {
118 #endif
119 		mousePos = curEvent.mousePos;
120 		// Limit the mouse cursor position, if necessary
121 		g_sci->_gfxCursor->refreshPosition();
122 #ifdef ENABLE_SCI32
123 	}
124 #endif
125 
126 	if (g_sci->getVocabulary())
127 		g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
128 
129 	if (s->_cursorWorkaroundActive) {
130 		// We check if the actual cursor position is inside specific rectangles
131 		// where the cursor itself should be moved to. If this is the case, we
132 		// set the mouse cursor's position to be within the rectangle in
133 		// question. Check GfxCursor::setPosition(), for a more detailed
134 		// explanation and a list of cursor position workarounds.
135 		if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
136 			// For OpenPandora and possibly other platforms, that support analog-stick control + touch screen
137 			// control at the same time: in case the cursor is currently at the coordinate set by the scripts,
138 			// we will count down instead of immediately disabling the workaround.
139 			// On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the
140 			// touch screen. In this case we would sometimes disable the workaround, simply because the touch
141 			// screen hasn't yet overwritten the position and thus the workaround would not work anymore.
142 			// On OpenPandora it would sometimes work and sometimes not without this.
143 			if (s->_cursorWorkaroundPoint == mousePos) {
144 				// Cursor is still at the same spot as set by the scripts
145 				if (s->_cursorWorkaroundPosCount > 0) {
146 					s->_cursorWorkaroundPosCount--;
147 				} else {
148 					// Was for quite a bit of time at that spot, so disable workaround now
149 					s->_cursorWorkaroundActive = false;
150 				}
151 			} else {
152 				// Cursor has moved, but is within the rect -> disable workaround immediately
153 				s->_cursorWorkaroundActive = false;
154 			}
155 		} else {
156 			mousePos.x = s->_cursorWorkaroundPoint.x;
157 			mousePos.y = s->_cursorWorkaroundPoint.y;
158 		}
159 	}
160 
161 	writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
162 	writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
163 
164 	// Get current keyboard modifiers, only keep relevant bits
165 	const int modifierMask = getSciVersion() <= SCI_VERSION_01 ? kSciKeyModAll : kSciKeyModNonSticky;
166 	modifiers = curEvent.modifiers & modifierMask;
167 	if (g_sci->getPlatform() == Common::kPlatformDOS && getSciVersion() <= SCI_VERSION_01) {
168 		// We are supposed to emulate SCI running in DOS
169 
170 		// We set the higher byte of the modifiers to 02h
171 		// Original SCI also did that indirectly, because it asked BIOS for shift status
172 		// via AH=0x02 INT16, which then sets the shift flags in AL
173 		// AH is supposed to be destroyed in that case and it's not defined that 0x02
174 		// is still in it on return. The value of AX was then set into the modifiers selector.
175 		// At least one fan-made game (Betrayed Alliance) requires 0x02 to be in the upper byte,
176 		// otherwise the darts game (script 111) will not work properly.
177 
178 		// It seems Sierra fixed this behaviour (effectively bug) in the SCI1 keyboard driver.
179 		// SCI32 also resets the upper byte.
180 
181 		// This was verified in SSCI itself by creating a SCI game and checking behavior.
182 		modifiers |= 0x0200;
183 	}
184 
185 	switch (curEvent.type) {
186 	case kSciEventQuit:
187 		s->abortScriptProcessing = kAbortQuitGame; // Terminate VM
188 		g_sci->_debugState.seeking = kDebugSeekNothing;
189 		g_sci->_debugState.runningStep = 0;
190 		break;
191 
192 	case kSciEventKeyDown:
193 	case kSciEventKeyUp:
194 		writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
195 		writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.character);
196 		// We only care about the translated character
197 		writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
198 		s->r_acc = TRUE_REG;
199 		break;
200 
201 	case kSciEventMouseRelease:
202 	case kSciEventMousePress:
203 		// track left buttton clicks, if requested
204 		if (curEvent.type == kSciEventMousePress && curEvent.modifiers == 0 && g_debug_track_mouse_clicks) {
205 			g_sci->getSciDebugger()->debugPrintf("Mouse clicked at %d, %d\n",
206 						mousePos.x, mousePos.y);
207 		}
208 
209 		if (mask & curEvent.type) {
210 			writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
211 			writeSelectorValue(segMan, obj, SELECTOR(message), 0);
212 			writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
213 			s->r_acc = TRUE_REG;
214 		}
215 		break;
216 
217 #ifdef ENABLE_SCI32
218 	case kSciEventHotRectangle:
219 		writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
220 		writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.hotRectangleIndex);
221 		s->r_acc = TRUE_REG;
222 		break;
223 #endif
224 
225 	default:
226 		// Return a null event
227 		writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventNone);
228 		writeSelectorValue(segMan, obj, SELECTOR(message), 0);
229 		writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
230 		s->r_acc = NULL_REG;
231 	}
232 
233 	if ((s->r_acc.getOffset()) && (g_sci->_debugState.stopOnEvent)) {
234 		g_sci->_debugState.stopOnEvent = false;
235 
236 		// A SCI event occurred, and we have been asked to stop, so open the debug console
237 		Console *con = g_sci->getSciDebugger();
238 		con->debugPrintf("SCI event occurred: ");
239 		switch (curEvent.type) {
240 		case kSciEventQuit:
241 			con->debugPrintf("quit event\n");
242 			break;
243 		case kSciEventKeyDown:
244 		case kSciEventKeyUp:
245 			con->debugPrintf("keyboard event\n");
246 			break;
247 		case kSciEventMousePress:
248 		case kSciEventMouseRelease:
249 			con->debugPrintf("mouse click event\n");
250 			break;
251 		default:
252 			con->debugPrintf("unknown or no event (event type %d)\n", curEvent.type);
253 		}
254 
255 		con->attach();
256 		con->onFrame();
257 	}
258 
259 	if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) {
260 		// If we're running a sound-SCI0 game, update the sound cues, to
261 		// compensate for the fact that sound-SCI0 does not poll to update
262 		// the sound cues itself, like sound-SCI1 and later do with
263 		// cmdUpdateSoundCues. kGetEvent is called quite often, so emulate
264 		// the sound-SCI1 behavior of cmdUpdateSoundCues with this call
265 		g_sci->_soundCmd->updateSci0Cues();
266 	}
267 
268 	// Wait a bit here, so that the CPU isn't maxed out when the game
269 	// is waiting for user input (e.g. when showing text boxes) - bug
270 	// #5091. Make sure that we're not delaying while the game is
271 	// benchmarking, as that will affect the final benchmarked result -
272 	// check bugs #5326 and #5543
273 	if (s->_gameIsBenchmarking) {
274 		// Game is benchmarking, don't add a delay
275 	} else if (getSciVersion() < SCI_VERSION_2) {
276 		g_system->delayMillis(10);
277 	}
278 
279 	return s->r_acc;
280 }
281 
282 struct KeyDirMapping {
283 	SciKeyCode key;
284 	uint16 direction;
285 };
286 
287 const KeyDirMapping keyToDirMap[] = {
288 	{ kSciKeyHome, 8 }, { kSciKeyUp,     1 }, { kSciKeyPageUp,   2 },
289 	{ kSciKeyLeft, 7 }, { kSciKeyCenter, 0 }, { kSciKeyRight,    3 },
290 	{ kSciKeyEnd,  6 }, { kSciKeyDown,   5 }, { kSciKeyPageDown, 4 },
291 };
292 
kMapKeyToDir(EngineState * s,int argc,reg_t * argv)293 reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
294 	reg_t obj = argv[0];
295 	SegManager *segMan = s->_segMan;
296 
297 	if (readSelectorValue(segMan, obj, SELECTOR(type)) == kSciEventKeyDown) {
298 		uint16 message = readSelectorValue(segMan, obj, SELECTOR(message));
299 		SciEventType eventType = kSciEventDirection;
300 		// It seems with SCI1 Sierra started to add the kSciEventDirection bit instead of setting it directly.
301 		// It was done inside the keyboard driver and is required for the PseudoMouse functionality and class
302 		// to work (script 933).
303 		if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) {
304 			eventType |= kSciEventKeyDown;
305 		}
306 
307 		for (int i = 0; i < ARRAYSIZE(keyToDirMap); i++) {
308 			if (keyToDirMap[i].key == message) {
309 				writeSelectorValue(segMan, obj, SELECTOR(type), eventType);
310 				writeSelectorValue(segMan, obj, SELECTOR(message), keyToDirMap[i].direction);
311 				return TRUE_REG;	// direction mapped
312 			}
313 		}
314 
315 		return NULL_REG;	// unknown direction
316 	}
317 
318 	return s->r_acc;	// no keyboard event to map, leave accumulator unchanged
319 }
320 
kGlobalToLocal(EngineState * s,int argc,reg_t * argv)321 reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
322 	reg_t obj = argv[0];
323 	SegManager *segMan = s->_segMan;
324 
325 	if (obj.getSegment()) {
326 		int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
327 		int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
328 
329 		g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y);
330 
331 		writeSelectorValue(segMan, obj, SELECTOR(x), x);
332 		writeSelectorValue(segMan, obj, SELECTOR(y), y);
333 	}
334 
335 	return s->r_acc;
336 
337 }
338 
kLocalToGlobal(EngineState * s,int argc,reg_t * argv)339 reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) {
340 	reg_t obj = argv[0];
341 	SegManager *segMan = s->_segMan;
342 
343 	if (obj.getSegment()) {
344 		int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
345 		int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
346 
347 		g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y);
348 
349 		writeSelectorValue(segMan, obj, SELECTOR(x), x);
350 		writeSelectorValue(segMan, obj, SELECTOR(y), y);
351 	}
352 
353 	return s->r_acc;
354 }
355 
kJoystick(EngineState * s,int argc,reg_t * argv)356 reg_t kJoystick(EngineState *s, int argc, reg_t *argv) {
357 	// Subfunction 12 sets/gets joystick repeat rate
358 	debug(5, "Unimplemented syscall 'Joystick()'");
359 	return NULL_REG;
360 }
361 
362 #ifdef ENABLE_SCI32
kGlobalToLocal32(EngineState * s,int argc,reg_t * argv)363 reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) {
364 	const reg_t result = argv[0];
365 	const reg_t planeObj = argv[1];
366 
367 	bool visible = true;
368 	Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
369 	if (plane == nullptr) {
370 		plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
371 		visible = false;
372 	}
373 	if (plane == nullptr) {
374 		error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj));
375 	}
376 
377 	const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left;
378 	const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top;
379 
380 	writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
381 	writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
382 
383 	return make_reg(0, visible);
384 }
385 
kLocalToGlobal32(EngineState * s,int argc,reg_t * argv)386 reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
387 	const reg_t result = argv[0];
388 	const reg_t planeObj = argv[1];
389 
390 	bool visible = true;
391 	Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
392 	if (plane == nullptr) {
393 		plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
394 		visible = false;
395 	}
396 	if (plane == nullptr) {
397 		error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj));
398 	}
399 
400 	const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left;
401 	const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top;
402 
403 	writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
404 	writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
405 
406 	return make_reg(0, visible);
407 }
408 
kSetHotRectangles(EngineState * s,int argc,reg_t * argv)409 reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) {
410 	if (argc == 1) {
411 		g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16());
412 		return s->r_acc;
413 	}
414 
415 	const int16 numRects = argv[0].toSint16();
416 	SciArray &hotRects = *s->_segMan->lookupArray(argv[1]);
417 
418 	Common::Array<Common::Rect> rects;
419 	rects.resize(numRects);
420 
421 	for (int16 i = 0; i < numRects; ++i) {
422 		rects[i].left   = hotRects.getAsInt16(i * 4);
423 		rects[i].top    = hotRects.getAsInt16(i * 4 + 1);
424 		rects[i].right  = hotRects.getAsInt16(i * 4 + 2) + 1;
425 		rects[i].bottom = hotRects.getAsInt16(i * 4 + 3) + 1;
426 	}
427 
428 	g_sci->getEventManager()->setHotRectanglesActive(true);
429 	g_sci->getEventManager()->setHotRectangles(rects);
430 	return s->r_acc;
431 }
432 #endif
433 
434 } // End of namespace Sci
435