scummvm/engines/tinsel/events.cpp

673 lines
15 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/rince.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 --------------------
// FIXME: Avoid non-const global vars
static uint32 g_lastUserEvent = 0; // Time it hapenned
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;
/**
* 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
SetCursorXY(g_controlX, g_controlY);
// Re-instate cursor
UnHideCursor();
// Turn tags back on
if (!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
GetCursorXY(&g_controlX, &g_controlY, true);
// Blank out 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
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);
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);
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 {
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;
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"};
debugC(DEBUG_BASIC, kTinselDebugActions, "%s - (%d,%d)",
actionList[pEvent], coOrds.x, coOrds.y);
static uint32 lastRealAction = 0; // FIXME: Avoid non-const global vars
// 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 && InventoryActive()) {
int x, y;
PlayfieldGetPos(FIELD_WORLD, &x, &y);
EventToInventory(pEvent, Common::Point(coOrds.x - x, coOrds.y - y));
return;
}
switch (pEvent) {
case PLR_QUIT:
OpenMenu(QUIT_MENU);
break;
case PLR_MENU:
OpenMenu(MAIN_MENU);
break;
case PLR_JUMP:
OpenMenu(HOPPER_MENU1);
break;
case PLR_SAVE:
OpenMenu(SAVE_MENU);
break;
case PLR_LOAD:
OpenMenu(LOAD_MENU);
break;
case PLR_PROV_WALKTO: // Provisional WALKTO !
ProcessUserEvent(PROV_WALKTO, coOrds);
break;
case PLR_WALKTO:
REAL_ACTION_CHECK;
if (TinselV2 || !InventoryActive())
ProcessUserEvent(WALKTO, coOrds, PLR_SLEFT);
else
EventToInventory(PLR_SLEFT, coOrds);
break;
case PLR_ACTION:
REAL_ACTION_CHECK;
if (TinselV2 || !InventoryActive())
ProcessUserEvent(ACTION, coOrds, PLR_DLEFT);
else
EventToInventory(PLR_DLEFT, coOrds);
break;
case PLR_LOOK:
REAL_ACTION_CHECK;
if (TinselV2 || !InventoryActive())
ProcessUserEvent(LOOK, coOrds, PLR_SRIGHT);
else
EventToInventory(PLR_SRIGHT, coOrds);
break;
default:
if (InventoryActive())
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();
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();
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)
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)
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