scummvm/engines/tinsel/events.cpp
Max Horn 1dbf8d73d5 TINSEL: Mark all (?) global vars with a FIXME comment
Use of global vars is what prevents RTL from working in Tinsel (and
probably in other engines). More specifically, the fact that many
global vars are not explicitly inited when the engine is (re)launched.

svn-id: r54262
2010-11-16 09:53:55 +00:00

676 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.
*
* $URL$
* $Id$
*
* Main purpose is to process user events.
* Also provides a couple of utility functions.
*/
#include "tinsel/actors.h"
#include "tinsel/background.h"
#include "tinsel/config.h"
#include "tinsel/coroutine.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 bEnableMenu;
//----------------- LOCAL GLOBAL DATA --------------------
// FIXME: Avoid non-const global vars
static uint32 lastUserEvent = 0; // Time it hapenned
static int leftEvents = 0; // Single or double, left or right. Or escape key.
static int escEvents = 1; // Escape key
static int userEvents = 0; // Whenever a button or a key comes in
static int eCount = 0;
static int controlState;
static bool bStartOff;
static int controlX, controlY;
static bool bProvNotProcessed = false;
/**
* Gets called before each schedule, only 1 user action per schedule
* is allowed.
*/
void ResetEcount() {
eCount = 0;
}
void IncUserEvents() {
userEvents++;
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 (++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;
}
bEnableMenu = false;
if (controlState == CONTROL_OFF) {
// Control is on
controlState = CONTROL_ON;
// Restore cursor to where it was
if (bStartOff == true)
bStartOff = false;
else
SetCursorXY(controlX, 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;
}
bEnableMenu = false;
if (controlState == CONTROL_ON) {
// Control is off
controlState = CONTROL_OFF;
// Store cursor position
GetCursorXY(&controlX, &controlY, true);
// Blank out cursor
DwHideCursor();
// Switch off tags
DisableTags();
}
}
/**
* Prevent tags and cursor re-appearing
*/
void ControlStartOff() {
if (!TinselV2) {
Control(CONTROL_STARTOFF);
return;
}
bEnableMenu = false;
// Control is off
controlState = CONTROL_OFF;
// Blank out cursor
DwHideCursor();
// Switch off tags
DisableTags();
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 (controlState == CONTROL_ON) {
ControlOff();
return true;
} else
return false;
}
bool ControlIsOn() {
if (TinselV2)
return (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 };
g_scheduler->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 (++eCount != 1)
return;
if ((actor = GetTaggedActor()) != 0) {
// Event for a tagged actor
if (TinselV2)
ActorEvent(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(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(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 (bEnableMenu && pEvent == PLR_MENU)
Control(CONTROL_ON);
else
IncUserEvents();
if (pEvent == PLR_ESCAPE) {
++escEvents;
++leftEvents; // Yes, I do mean this
} else if ((pEvent == PLR_PROV_WALKTO)
|| (pEvent == PLR_WALKTO)
|| (pEvent == PLR_LOOK)
|| (pEvent == PLR_ACTION)) {
++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 escEvents;
}
/**
* For cutting short talk()s etc.
*/
int GetLeftEvents() {
return leftEvents;
}
bool LeftEventChange(int myleftEvent) {
if (leftEvents != myleftEvent) {
ProcessedProvisional();
return true;
} else
return false;
}
/**
* For waitkey() Glitter function
*/
int getUserEvents() {
return userEvents;
}
uint32 getUserEventTime() {
return DwGetCurrentTime() - lastUserEvent;
}
void resetUserEventTime() {
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;
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 = g_scheduler->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);
g_scheduler->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);
g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
}
/**
* If provisional event was processed, calling this prevents the
* subsequent 'real' event.
*/
void ProcessedProvisional() {
bProvNotProcessed = false;
}
/**
* Resets the bProvNotProcessed flag
*/
void ProvNotProcessed() {
bProvNotProcessed = true;
}
bool GetProvNotProcessed() {
return bProvNotProcessed;
}
} // End of namespace Tinsel