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