scummvm/engines/tinsel/events.cpp
Peter Kohaut 25fa525969 TINSEL: Added base of Noir movers
Renamed rince.* files to movers to be more game independent.
Added elementary support for Noir movers which can use different logic.

Allows game to boot to the first interactive scene, but there is no 3D model rendered (that is WIP).
2021-08-08 20:15:18 +02:00

692 lines
16 KiB
C++

/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Main purpose is to process user events.
* Also provides a couple of utility functions.
*/
#include "common/coroutines.h"
#include "tinsel/actors.h"
#include "tinsel/background.h"
#include "tinsel/config.h"
#include "tinsel/cursor.h"
#include "tinsel/dw.h"
#include "tinsel/events.h"
#include "tinsel/handle.h" // For LockMem()
#include "tinsel/dialogs.h"
#include "tinsel/move.h" // For walking lead actor
#include "tinsel/pcode.h" // For Interpret()
#include "tinsel/pdisplay.h"
#include "tinsel/pid.h"
#include "tinsel/polygons.h"
#include "tinsel/movers.h" // For walking lead actor
#include "tinsel/sched.h"
#include "tinsel/scroll.h" // For DontScrollCursor()
#include "tinsel/timers.h" // DwGetCurrentTime()
#include "tinsel/tinlib.h" // For control()
#include "tinsel/tinsel.h"
#include "tinsel/token.h"
namespace Tinsel {
//----------------- EXTERNAL FUNCTIONS ---------------------
// in PDISPLAY.C
extern int GetTaggedActor();
extern HPOLYGON GetTaggedPoly();
//----------------- EXTERNAL GLOBAL DATA ---------------------
extern bool g_bEnableMenu;
//----------------- LOCAL GLOBAL DATA --------------------
// These vars are reset upon engine destruction
static uint32 g_lastUserEvent = 0; // Time it happened
static int g_leftEvents = 0; // Single or double, left or right. Or escape key.
static int g_escEvents = 1; // Escape key
static int g_userEvents = 0; // Whenever a button or a key comes in
static int g_eCount = 0;
static int g_controlState;
static bool g_bStartOff;
static int g_controlX, g_controlY;
static bool g_bProvNotProcessed = false;
static uint32 lastRealAction = 0;
void ResetVarsEvents() {
g_lastUserEvent = 0;
g_leftEvents = 0; // Single or double, left or right. Or escape key.
g_escEvents = 1; // Escape key
g_userEvents = 0; // Whenever a button or a key comes in
g_eCount = 0;
g_controlState = 0;
g_bStartOff = false;
g_controlX = 0;
g_controlY = 0;
g_bProvNotProcessed = false;
lastRealAction = 0;
}
/**
* Gets called before each schedule, only 1 user action per schedule
* is allowed.
*/
void ResetEcount() {
g_eCount = 0;
}
void IncUserEvents() {
g_userEvents++;
g_lastUserEvent = DwGetCurrentTime();
}
/**
* If this is a single click, wait to check it's not the first half of a
* double click.
* If this is a double click, the process from the waiting single click
* gets killed.
*/
void AllowDclick(CORO_PARAM, PLR_EVENT be) {
CORO_BEGIN_CONTEXT;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
if (be == PLR_SLEFT) {
GetToken(TOKEN_LEFT_BUT);
CORO_SLEEP(_vm->_config->_dclickSpeed+1);
FreeToken(TOKEN_LEFT_BUT);
// Prevent activation of 2 events on the same tick
if (++g_eCount != 1)
CORO_KILL_SELF();
break;
} else if (be == PLR_DLEFT) {
GetToken(TOKEN_LEFT_BUT);
FreeToken(TOKEN_LEFT_BUT);
}
CORO_END_CODE;
}
/**
* Re-enables user control
*/
void ControlOn() {
if (!TinselV2) {
Control(CONTROL_ON);
return;
}
g_bEnableMenu = false;
if (g_controlState == CONTROL_OFF) {
// Control is on
g_controlState = CONTROL_ON;
// Restore cursor to where it was
if (g_bStartOff == true)
g_bStartOff = false;
else
_vm->_cursor->SetCursorXY(g_controlX, g_controlY);
// Re-instate cursor
_vm->_cursor->UnHideCursor();
// Turn tags back on
if (!_vm->_dialogs->InventoryActive())
EnableTags();
}
}
/**
* Takes control from the user
*/
void ControlOff() {
if (!TinselV2) {
Control(CONTROL_ON);
return;
}
g_bEnableMenu = false;
if (g_controlState == CONTROL_ON) {
// Control is off
g_controlState = CONTROL_OFF;
// Store cursor position
_vm->_cursor->GetCursorXY(&g_controlX, &g_controlY, true);
// Blank out cursor
_vm->_cursor->DwHideCursor();
// Switch off tags
DisableTags();
}
}
/**
* Prevent tags and cursor re-appearing
*/
void ControlStartOff() {
if (!TinselV2) {
Control(CONTROL_STARTOFF);
return;
}
g_bEnableMenu = false;
// Control is off
g_controlState = CONTROL_OFF;
// Blank out cursor
_vm->_cursor->DwHideCursor();
// Switch off tags
DisableTags();
g_bStartOff = true;
}
/**
* Take control from player, if the player has it.
* Return TRUE if control taken, FALSE if not.
*/
bool GetControl(int param) {
if (TinselV2)
return GetControl();
else if (TestToken(TOKEN_CONTROL)) {
Control(param);
return true;
} else
return false;
}
bool GetControl() {
if (g_controlState == CONTROL_ON) {
ControlOff();
return true;
} else
return false;
}
bool ControlIsOn() {
if (TinselV2)
return (g_controlState == CONTROL_ON);
return TestToken(TOKEN_CONTROL);
}
//-----------------------------------------------------------------------
struct WP_INIT {
int x; // } Where to walk to
int y; // }
};
/**
* Perform a walk directly initiated by a click.
*/
static void WalkProcess(CORO_PARAM, const void *param) {
// COROUTINE
CORO_BEGIN_CONTEXT;
PMOVER pMover;
int thisWalk;
CORO_END_CONTEXT(_ctx);
const WP_INIT *to = (const WP_INIT *)param; // get the co-ordinates - copied to process when it was created
CORO_BEGIN_CODE(_ctx);
_ctx->pMover = GetMover(LEAD_ACTOR);
if (TinselV2 && MoverIs(_ctx->pMover) && !MoverIsSWalking(_ctx->pMover)) {
assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path
_ctx->thisWalk = SetActorDest(_ctx->pMover, to->x, to->y, false, 0);
_vm->_scroll->DontScrollCursor();
while (MoverMoving(_ctx->pMover) && (_ctx->thisWalk == GetWalkNumber(_ctx->pMover)))
CORO_SLEEP(1);
} else if (!TinselV2 && _ctx->pMover->bActive) {
assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path
GetToken(TOKEN_LEAD);
SetActorDest(_ctx->pMover, to->x, to->y, false, 0);
_vm->_scroll->DontScrollCursor();
while (MoverMoving(_ctx->pMover))
CORO_SLEEP(1);
FreeToken(TOKEN_LEAD);
}
CORO_END_CODE;
}
void WalkTo(int x, int y) {
WP_INIT to = { x, y };
CoroScheduler.createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
}
/**
* Run appropriate actor or polygon glitter code.
* If none, and it's a WALKTO event, do a walk.
*/
static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, PLR_EVENT be = PLR_NOEVENT) {
int actor;
int aniX, aniY;
HPOLYGON hPoly;
// Prevent activation of 2 events on the same tick
if (++g_eCount != 1)
return;
if ((actor = GetTaggedActor()) != 0) {
// Event for a tagged actor
if (TinselV2)
ActorEvent(Common::nullContext, actor, uEvent, false, 0);
else
ActorEvent(actor, uEvent, be);
} else if ((hPoly = GetTaggedPoly()) != NOPOLY) {
// Event for active tagged polygon
if (!TinselV2)
RunPolyTinselCode(hPoly, uEvent, be, false);
else if (uEvent != PROV_WALKTO)
PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
} else {
_vm->_cursor->GetCursorXY(&aniX, &aniY, true);
// There could be a poly involved which has no tag.
if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY ||
(!TinselV2 && ((hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY))) {
if (TinselV2 && (uEvent != PROV_WALKTO))
PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
else if (!TinselV2)
RunPolyTinselCode(hPoly, uEvent, be, false);
} else if ((uEvent == PROV_WALKTO) || (uEvent == WALKTO)) {
if (TinselV2)
ProcessedProvisional();
WalkTo(aniX, aniY);
}
}
}
/**
* ProcessButEvent
*/
void ProcessButEvent(PLR_EVENT be) {
if (_vm->_config->_swapButtons) {
switch (be) {
case PLR_SLEFT:
be = PLR_SRIGHT;
break;
case PLR_DLEFT:
be = PLR_DRIGHT;
break;
case PLR_SRIGHT:
be = PLR_SLEFT;
break;
case PLR_DRIGHT:
be = PLR_DLEFT;
break;
case PLR_DRAG1_START:
be = PLR_DRAG2_START;
break;
case PLR_DRAG1_END:
be = PLR_DRAG2_END;
break;
case PLR_DRAG2_START:
be = PLR_DRAG1_START;
break;
case PLR_DRAG2_END:
be = PLR_DRAG1_END;
break;
default:
break;
}
}
PlayerEvent(be, _vm->getMousePosition());
}
/**
* ProcessKeyEvent
*/
void ProcessKeyEvent(PLR_EVENT ke) {
// Pass the keyboard event to the player event handler
int xp, yp;
_vm->_cursor->GetCursorXYNoWait(&xp, &yp, true);
const Common::Point mousePos(xp, yp);
PlayerEvent(ke, mousePos);
}
#define REAL_ACTION_CHECK if (TinselV2) { \
if (DwGetCurrentTime() - lastRealAction < 4) return; \
lastRealAction = DwGetCurrentTime(); \
}
/**
* Main interface point for specifying player atcions
*/
void PlayerEvent(PLR_EVENT pEvent, const Common::Point &coOrds) {
// Logging of player actions
const char *actionList[] = {
"PLR_PROV_WALKTO", "PLR_WALKTO", "PLR_LOOK", "PLR_ACTION", "PLR_ESCAPE",
"PLR_MENU", "PLR_QUIT", "PLR_PGUP", "PLR_PGDN", "PLR_HOME", "PLR_END",
"PLR_DRAG1_START", "PLR_DRAG1_END", "PLR_DRAG2_START", "PLR_DRAG2_END",
"PLR_JUMP", "PLR_NOEVENT", "PLR_SAVE", "PLR_LOAD", "PLR_WHEEL_UP",
"PLR_WHEEL_DOWN"};
debugC(DEBUG_BASIC, kTinselDebugActions, "%s - (%d,%d)",
actionList[pEvent], coOrds.x, coOrds.y);
// This stuff to allow F1 key during startup.
if (g_bEnableMenu && pEvent == PLR_MENU)
Control(CONTROL_ON);
else
IncUserEvents();
if (pEvent == PLR_ESCAPE) {
++g_escEvents;
++g_leftEvents; // Yes, I do mean this
} else if ((pEvent == PLR_PROV_WALKTO)
|| (pEvent == PLR_WALKTO)
|| (pEvent == PLR_LOOK)
|| (pEvent == PLR_ACTION)) {
++g_leftEvents;
}
// Only allow events if player control is on
if (!ControlIsOn() && (pEvent != PLR_DRAG1_END))
return;
if (TinselV2 && _vm->_dialogs->InventoryActive()) {
int x, y;
_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &x, &y);
_vm->_dialogs->EventToInventory(pEvent, Common::Point(coOrds.x - x, coOrds.y - y));
return;
}
switch (pEvent) {
case PLR_QUIT:
_vm->_dialogs->OpenMenu(QUIT_MENU);
break;
case PLR_MENU:
_vm->_dialogs->OpenMenu(MAIN_MENU);
break;
case PLR_JUMP:
_vm->_dialogs->OpenMenu(HOPPER_MENU1);
break;
case PLR_SAVE:
_vm->_dialogs->OpenMenu(SAVE_MENU);
break;
case PLR_LOAD:
_vm->_dialogs->OpenMenu(LOAD_MENU);
break;
case PLR_PROV_WALKTO: // Provisional WALKTO !
ProcessUserEvent(PROV_WALKTO, coOrds);
break;
case PLR_WALKTO:
REAL_ACTION_CHECK;
if (TinselV2 || !_vm->_dialogs->InventoryActive())
ProcessUserEvent(WALKTO, coOrds, PLR_SLEFT);
else
_vm->_dialogs->EventToInventory(PLR_SLEFT, coOrds);
break;
case PLR_ACTION:
REAL_ACTION_CHECK;
if (TinselV2 || !_vm->_dialogs->InventoryActive())
ProcessUserEvent(ACTION, coOrds, PLR_DLEFT);
else
_vm->_dialogs->EventToInventory(PLR_DLEFT, coOrds);
break;
case PLR_LOOK:
REAL_ACTION_CHECK;
if (TinselV2 || !_vm->_dialogs->InventoryActive())
ProcessUserEvent(LOOK, coOrds, PLR_SRIGHT);
else
_vm->_dialogs->EventToInventory(PLR_SRIGHT, coOrds);
break;
default:
if (_vm->_dialogs->InventoryActive())
_vm->_dialogs->EventToInventory(pEvent, coOrds);
break;
}
}
/**
* For ESCapable Glitter sequences
*/
int GetEscEvents() {
return g_escEvents;
}
/**
* For cutting short talk()s etc.
*/
int GetLeftEvents() {
return g_leftEvents;
}
bool LeftEventChange(int myleftEvent) {
if (g_leftEvents != myleftEvent) {
ProcessedProvisional();
return true;
} else
return false;
}
/**
* For waitkey() Glitter function
*/
int getUserEvents() {
return g_userEvents;
}
uint32 getUserEventTime() {
return DwGetCurrentTime() - g_lastUserEvent;
}
void resetUserEventTime() {
g_lastUserEvent = DwGetCurrentTime();
}
struct PTP_INIT {
HPOLYGON hPoly; // Polygon
TINSEL_EVENT event; // Trigerring event
PLR_EVENT bev; // To allow for double clicks
bool take_control; // Set if control should be taken
// while code is running.
int actor;
PINT_CONTEXT pic;
};
/**
* Runs glitter code associated with a polygon.
*/
void PolyTinselProcess(CORO_PARAM, const void *param) {
// COROUTINE
CORO_BEGIN_CONTEXT;
INT_CONTEXT *pic;
bool bTookControl; // Set if this function takes control
CORO_END_CONTEXT(_ctx);
const PTP_INIT *to = (const PTP_INIT *)param; // get the stuff copied to process when it was created
CORO_BEGIN_CODE(_ctx);
if (TinselV2) {
// Take control for CONVERSE events
if (to->event == CONVERSE) {
_ctx->bTookControl = GetControl();
_vm->_dialogs->HideConversation(true);
} else
_ctx->bTookControl = false;
CORO_INVOKE_1(Interpret, to->pic);
// Restore conv window if applicable
if (to->event == CONVERSE) {
// Free control if we took it
if (_ctx->bTookControl)
ControlOn();
_vm->_dialogs->HideConversation(false);
}
} else {
CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click
// Control may have gone off during AllowDclick()
if (!TestToken(TOKEN_CONTROL)
&& (to->event == WALKTO || to->event == ACTION || to->event == LOOK))
CORO_KILL_SELF();
// Take control, if requested
if (to->take_control)
_ctx->bTookControl = GetControl(CONTROL_OFF);
else
_ctx->bTookControl = false;
// Hide conversation if appropriate
if (to->event == CONVERSE)
_vm->_dialogs->HideConversation(true);
// Run the code
_ctx->pic = InitInterpretContext(GS_POLYGON, GetPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL);
CORO_INVOKE_1(Interpret, _ctx->pic);
// Free control if we took it
if (_ctx->bTookControl)
Control(CONTROL_ON);
// Restore conv window if applicable
if (to->event == CONVERSE)
_vm->_dialogs->HideConversation(false);
}
CORO_END_CODE;
}
/**
* Run the Polygon process with the given event
*/
void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bool bWait,
int myEscape, bool *result) {
CORO_BEGIN_CONTEXT;
Common::PPROCESS pProc;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
PTP_INIT to;
if (result)
*result = false;
to.hPoly = -1;
to.event = tEvent;
to.pic = InitInterpretContext(GS_POLYGON,
GetPolyScript(hPoly),
tEvent,
hPoly, // Polygon
actor, // Actor
NULL, // No Object
myEscape);
if (to.pic != NULL) {
_ctx->pProc = CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
AttachInterpret(to.pic, _ctx->pProc);
if (bWait)
CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result);
}
CORO_END_CODE;
}
/**
* Runs glitter code associated with a polygon.
*/
void RunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, PLR_EVENT be, bool tc) {
PTP_INIT to = { hPoly, event, be, tc, 0, NULL };
assert(!TinselV2);
CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
}
void effRunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, int actor) {
PTP_INIT to = { hPoly, event, PLR_NOEVENT, false, actor, NULL };
assert(!TinselV2);
CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
}
/**
* If provisional event was processed, calling this prevents the
* subsequent 'real' event.
*/
void ProcessedProvisional() {
g_bProvNotProcessed = false;
}
/**
* Resets the bProvNotProcessed flag
*/
void ProvNotProcessed() {
g_bProvNotProcessed = true;
}
bool GetProvNotProcessed() {
return g_bProvNotProcessed;
}
} // End of namespace Tinsel