Initial commit
This commit is contained in:
444
engines/sci/engine/kevent.cpp
Normal file
444
engines/sci/engine/kevent.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
#include "sci/sci.h"
|
||||
#include "sci/engine/features.h"
|
||||
#include "sci/engine/guest_additions.h"
|
||||
#include "sci/engine/kernel.h"
|
||||
#include "sci/engine/savegame.h"
|
||||
#include "sci/engine/selector.h"
|
||||
#include "sci/engine/state.h"
|
||||
#include "sci/console.h"
|
||||
#include "sci/debug.h" // for g_debug_simulated_key
|
||||
#include "sci/event.h"
|
||||
#include "sci/graphics/animate.h"
|
||||
#include "sci/graphics/coordadjuster.h"
|
||||
#include "sci/graphics/cursor.h"
|
||||
#include "sci/graphics/maciconbar.h"
|
||||
#ifdef ENABLE_SCI32
|
||||
#include "sci/graphics/frameout.h"
|
||||
#endif
|
||||
|
||||
namespace Sci {
|
||||
|
||||
reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
|
||||
SciEventType mask = (SciEventType)argv[0].toUint16();
|
||||
reg_t obj = argv[1];
|
||||
SciEvent curEvent;
|
||||
uint16 modifiers = 0;
|
||||
SegManager *segMan = s->_segMan;
|
||||
Common::Point mousePos;
|
||||
|
||||
// If there's a simkey pending, and the game wants a keyboard event, use the
|
||||
// simkey instead of a normal event
|
||||
// TODO: This does not really work as expected for keyup events, since the
|
||||
// fake event is disposed halfway through the normal event lifecycle.
|
||||
if (g_debug_simulated_key && (mask & kSciEventKeyDown)) {
|
||||
// In case we use a simulated event we query the current mouse position
|
||||
mousePos = g_sci->_gfxCursor->getPosition();
|
||||
|
||||
// Limit the mouse cursor position, if necessary
|
||||
g_sci->_gfxCursor->refreshPosition();
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventKeyDown);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), g_debug_simulated_key);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), kSciKeyModNumLock);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
|
||||
g_debug_simulated_key = 0;
|
||||
return TRUE_REG;
|
||||
}
|
||||
|
||||
curEvent = g_sci->getEventManager()->getSciEvent(mask);
|
||||
|
||||
// For Mac games with an icon bar, handle possible icon bar events first
|
||||
if (g_sci->hasMacIconBar()) {
|
||||
reg_t iconObj = NULL_REG;
|
||||
if (g_sci->_gfxMacIconBar->handleEvents(curEvent, iconObj)) {
|
||||
if (!iconObj.isNull()) {
|
||||
invokeSelector(s, iconObj, SELECTOR(select), argc, argv, 0, nullptr);
|
||||
}
|
||||
|
||||
// The mouse press event was handled by the mac icon bar so change
|
||||
// its type to none so that generic event processing can continue
|
||||
// without the mouse press being handled twice
|
||||
curEvent.type = kSciEventNone;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_sci->_guestAdditions->kGetEventHook()) {
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
// For a real event we use its associated mouse position
|
||||
#ifdef ENABLE_SCI32
|
||||
if (getSciVersion() >= SCI_VERSION_2) {
|
||||
mousePos = curEvent.mousePosSci;
|
||||
|
||||
// Some games, like LSL6hires (when interacting with the menu bar) and
|
||||
// Phant2 (when on the "click mouse" screen after restoring a game),
|
||||
// have unthrottled loops that call kGetEvent but do not call kFrameOut.
|
||||
// In these cases we still need to call OSystem::updateScreen to update
|
||||
// the mouse cursor (in SSCI this was not necessary because mouse
|
||||
// updates were made directly to hardware from an interrupt handler),
|
||||
// and we need to throttle these calls so the game does not use 100%
|
||||
// CPU.
|
||||
// This situation seems to be detectable by looking at how many times
|
||||
// kGetEvent has been called between calls to kFrameOut. During normal
|
||||
// game operation, there are usually just 0 or 1 kGetEvent calls between
|
||||
// kFrameOut calls; any more than that indicates that we are probably in
|
||||
// one of these ugly loops and should be updating the screen &
|
||||
// throttling the VM.
|
||||
if (++s->_eventCounter > 2) {
|
||||
g_sci->_gfxFrameout->updateScreen();
|
||||
s->speedThrottler(10); // 10ms is an arbitrary value
|
||||
s->_throttleTrigger = true;
|
||||
}
|
||||
} else {
|
||||
#endif
|
||||
mousePos = curEvent.mousePos;
|
||||
// Limit the mouse cursor position, if necessary
|
||||
g_sci->_gfxCursor->refreshPosition();
|
||||
#ifdef ENABLE_SCI32
|
||||
}
|
||||
#endif
|
||||
|
||||
if (g_sci->getVocabulary())
|
||||
g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
|
||||
|
||||
if (s->_cursorWorkaroundActive) {
|
||||
// We check if the actual cursor position is inside specific rectangles
|
||||
// where the cursor itself should be moved to. If this is the case, we
|
||||
// set the mouse cursor's position to be within the rectangle in
|
||||
// question. Check GfxCursor::setPosition(), for a more detailed
|
||||
// explanation and a list of cursor position workarounds.
|
||||
if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) {
|
||||
// For OpenPandora and possibly other platforms, that support analog-stick control + touch screen
|
||||
// control at the same time: in case the cursor is currently at the coordinate set by the scripts,
|
||||
// we will count down instead of immediately disabling the workaround.
|
||||
// On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the
|
||||
// touch screen. In this case we would sometimes disable the workaround, simply because the touch
|
||||
// screen hasn't yet overwritten the position and thus the workaround would not work anymore.
|
||||
// On OpenPandora it would sometimes work and sometimes not without this.
|
||||
if (s->_cursorWorkaroundPoint == mousePos) {
|
||||
// Cursor is still at the same spot as set by the scripts
|
||||
if (s->_cursorWorkaroundPosCount > 0) {
|
||||
s->_cursorWorkaroundPosCount--;
|
||||
} else {
|
||||
// Was for quite a bit of time at that spot, so disable workaround now
|
||||
s->_cursorWorkaroundActive = false;
|
||||
}
|
||||
} else {
|
||||
// Cursor has moved, but is within the rect -> disable workaround immediately
|
||||
s->_cursorWorkaroundActive = false;
|
||||
}
|
||||
} else {
|
||||
mousePos.x = s->_cursorWorkaroundPoint.x;
|
||||
mousePos.y = s->_cursorWorkaroundPoint.y;
|
||||
}
|
||||
}
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), mousePos.x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), mousePos.y);
|
||||
|
||||
// Get current keyboard modifiers, only keep relevant bits
|
||||
const int modifierMask = getSciVersion() <= SCI_VERSION_01 ? kSciKeyModAll : kSciKeyModNonSticky;
|
||||
modifiers = curEvent.modifiers & modifierMask;
|
||||
if (g_sci->getPlatform() == Common::kPlatformDOS && getSciVersion() <= SCI_VERSION_01) {
|
||||
// We are supposed to emulate SCI running in DOS
|
||||
|
||||
// We set the higher byte of the modifiers to 02h
|
||||
// Original SCI also did that indirectly, because it asked BIOS for shift status
|
||||
// via AH=0x02 INT16, which then sets the shift flags in AL
|
||||
// AH is supposed to be destroyed in that case and it's not defined that 0x02
|
||||
// is still in it on return. The value of AX was then set into the modifiers selector.
|
||||
// At least one fan-made game (Betrayed Alliance) requires 0x02 to be in the upper byte,
|
||||
// otherwise the darts game (script 111) will not work properly.
|
||||
|
||||
// It seems Sierra fixed this behaviour (effectively bug) in the SCI1 keyboard driver.
|
||||
// SCI32 also resets the upper byte.
|
||||
|
||||
// This was verified in SSCI itself by creating a SCI game and checking behavior.
|
||||
modifiers |= 0x0200;
|
||||
}
|
||||
|
||||
switch (curEvent.type) {
|
||||
case kSciEventQuit:
|
||||
s->abortScriptProcessing = kAbortQuitGame; // Terminate VM
|
||||
g_sci->_debugState.seeking = kDebugSeekNothing;
|
||||
g_sci->_debugState.runningStep = 0;
|
||||
break;
|
||||
|
||||
case kSciEventKeyDown:
|
||||
case kSciEventKeyUp:
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.character);
|
||||
// We only care about the translated character
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = TRUE_REG;
|
||||
break;
|
||||
|
||||
case kSciEventMouseRelease:
|
||||
case kSciEventMousePress:
|
||||
// track left buttton clicks, if requested
|
||||
if (curEvent.type == kSciEventMousePress && curEvent.modifiers == 0 && g_debug_track_mouse_clicks) {
|
||||
g_sci->getSciDebugger()->debugPrintf("Mouse clicked at %d, %d\n",
|
||||
mousePos.x, mousePos.y);
|
||||
}
|
||||
|
||||
if (mask & curEvent.type) {
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), 0);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = TRUE_REG;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
case kSciEventHotRectangle:
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), curEvent.type);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), curEvent.hotRectangleIndex);
|
||||
s->r_acc = TRUE_REG;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Return a null event
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), kSciEventNone);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), 0);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(modifiers), modifiers);
|
||||
s->r_acc = NULL_REG;
|
||||
}
|
||||
|
||||
if ((s->r_acc.getOffset()) && (g_sci->_debugState.stopOnEvent)) {
|
||||
g_sci->_debugState.stopOnEvent = false;
|
||||
|
||||
// A SCI event occurred, and we have been asked to stop, so open the debug console
|
||||
Console *con = g_sci->getSciDebugger();
|
||||
con->debugPrintf("SCI event occurred: ");
|
||||
switch (curEvent.type) {
|
||||
case kSciEventQuit:
|
||||
con->debugPrintf("quit event\n");
|
||||
break;
|
||||
case kSciEventKeyDown:
|
||||
case kSciEventKeyUp:
|
||||
con->debugPrintf("keyboard event\n");
|
||||
break;
|
||||
case kSciEventMousePress:
|
||||
case kSciEventMouseRelease:
|
||||
con->debugPrintf("mouse click event\n");
|
||||
break;
|
||||
default:
|
||||
con->debugPrintf("unknown or no event (event type %d)\n", curEvent.type);
|
||||
}
|
||||
|
||||
con->attach();
|
||||
con->onFrame();
|
||||
}
|
||||
|
||||
if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) {
|
||||
// If we're running a sound-SCI0 game, update the sound cues, to
|
||||
// compensate for the fact that sound-SCI0 does not poll to update
|
||||
// the sound cues itself, like sound-SCI1 and later do with
|
||||
// cmdUpdateSoundCues. kGetEvent is called quite often, so emulate
|
||||
// the sound-SCI1 behavior of cmdUpdateSoundCues with this call
|
||||
g_sci->_soundCmd->updateSci0Cues();
|
||||
}
|
||||
|
||||
// If we're in a SCI16 unthrottled inner loop then delay a bit.
|
||||
// This prevents the CPU from maxing out and prevents inner loop animations
|
||||
// from running too fast. "Fast cast" games poll kGetEvent from an inner
|
||||
// loop while they display or say a message. This can be detected by testing
|
||||
// the fast cast global. Other inner loops can be detected by counting the
|
||||
// kGetEvent calls since the last kGameIsRestarting(0) or kWait call.
|
||||
// For example, some versions of Dialog:doit poll without calling kWait(1).
|
||||
// See above for similar SCI32 code that counts calls between kFrameout.
|
||||
// Fixes bugs #5091, #5326, #14020
|
||||
if (getSciVersion() <= SCI_VERSION_1_1) {
|
||||
if (++s->_eventCounter > 2 ||
|
||||
(g_sci->_gfxAnimate->isFastCastEnabled() &&
|
||||
!s->variables[VAR_GLOBAL][kGlobalVarFastCast].isNull())) {
|
||||
g_system->delayMillis(10);
|
||||
}
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
struct KeyDirMapping {
|
||||
SciKeyCode key;
|
||||
uint16 direction;
|
||||
};
|
||||
|
||||
const KeyDirMapping keyToDirMap[] = {
|
||||
{ kSciKeyHome, 8 }, { kSciKeyUp, 1 }, { kSciKeyPageUp, 2 },
|
||||
{ kSciKeyLeft, 7 }, { kSciKeyCenter, 0 }, { kSciKeyRight, 3 },
|
||||
{ kSciKeyEnd, 6 }, { kSciKeyDown, 5 }, { kSciKeyPageDown, 4 },
|
||||
};
|
||||
|
||||
reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (readSelectorValue(segMan, obj, SELECTOR(type)) == kSciEventKeyDown) {
|
||||
uint16 message = readSelectorValue(segMan, obj, SELECTOR(message));
|
||||
#ifdef ENABLE_SCI32
|
||||
SciEventType eventType = (getSciVersion() < SCI_VERSION_2) ? kSciEventDirection16 : kSciEventDirection32;
|
||||
#else
|
||||
SciEventType eventType = kSciEventDirection16;
|
||||
#endif
|
||||
// It seems with SCI1 Sierra started to add the kSciEventDirection bit instead of setting it directly.
|
||||
// It was done inside the keyboard driver and is required for the PseudoMouse functionality and class
|
||||
// to work (script 933).
|
||||
if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) {
|
||||
eventType |= kSciEventKeyDown;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(keyToDirMap); i++) {
|
||||
if (keyToDirMap[i].key == message) {
|
||||
writeSelectorValue(segMan, obj, SELECTOR(type), eventType);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(message), keyToDirMap[i].direction);
|
||||
return TRUE_REG; // direction mapped
|
||||
}
|
||||
}
|
||||
|
||||
return NULL_REG; // unknown direction
|
||||
}
|
||||
|
||||
return s->r_acc; // no keyboard event to map, leave accumulator unchanged
|
||||
}
|
||||
|
||||
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (obj.getSegment()) {
|
||||
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
|
||||
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
|
||||
|
||||
g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y);
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), y);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
|
||||
}
|
||||
|
||||
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) {
|
||||
reg_t obj = argv[0];
|
||||
SegManager *segMan = s->_segMan;
|
||||
|
||||
if (obj.getSegment()) {
|
||||
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
|
||||
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
|
||||
|
||||
g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y);
|
||||
|
||||
writeSelectorValue(segMan, obj, SELECTOR(x), x);
|
||||
writeSelectorValue(segMan, obj, SELECTOR(y), y);
|
||||
}
|
||||
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
reg_t kJoystick(EngineState *s, int argc, reg_t *argv) {
|
||||
// Subfunction 12 sets/gets joystick repeat rate
|
||||
debug(5, "Unimplemented syscall 'Joystick()'");
|
||||
return NULL_REG;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCI32
|
||||
reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t result = argv[0];
|
||||
const reg_t planeObj = argv[1];
|
||||
|
||||
bool visible = true;
|
||||
Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
|
||||
if (plane == nullptr) {
|
||||
plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
|
||||
visible = false;
|
||||
}
|
||||
if (plane == nullptr) {
|
||||
error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj));
|
||||
}
|
||||
|
||||
const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left;
|
||||
const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top;
|
||||
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
|
||||
|
||||
return make_reg(0, visible);
|
||||
}
|
||||
|
||||
reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
|
||||
const reg_t result = argv[0];
|
||||
const reg_t planeObj = argv[1];
|
||||
|
||||
bool visible = true;
|
||||
Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
|
||||
if (plane == nullptr) {
|
||||
plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
|
||||
visible = false;
|
||||
}
|
||||
if (plane == nullptr) {
|
||||
error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj));
|
||||
}
|
||||
|
||||
const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left;
|
||||
const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top;
|
||||
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
|
||||
writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
|
||||
|
||||
return make_reg(0, visible);
|
||||
}
|
||||
|
||||
reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv) {
|
||||
if (argc == 1) {
|
||||
g_sci->getEventManager()->setHotRectanglesActive((bool)argv[0].toUint16());
|
||||
return s->r_acc;
|
||||
}
|
||||
|
||||
const int16 numRects = argv[0].toSint16();
|
||||
SciArray &hotRects = *s->_segMan->lookupArray(argv[1]);
|
||||
|
||||
Common::Array<Common::Rect> rects;
|
||||
rects.resize(numRects);
|
||||
|
||||
for (int16 i = 0; i < numRects; ++i) {
|
||||
rects[i].left = hotRects.getAsInt16(i * 4);
|
||||
rects[i].top = hotRects.getAsInt16(i * 4 + 1);
|
||||
rects[i].right = hotRects.getAsInt16(i * 4 + 2) + 1;
|
||||
rects[i].bottom = hotRects.getAsInt16(i * 4 + 3) + 1;
|
||||
}
|
||||
|
||||
g_sci->getEventManager()->setHotRectanglesActive(true);
|
||||
g_sci->getEventManager()->setHotRectangles(rects);
|
||||
return s->r_acc;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // End of namespace Sci
|
||||
Reference in New Issue
Block a user