/* 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", "PLR_SAVE", "PLR_LOAD", "PLR_WHEEL_UP", "PLR_WHEEL_DOWN"}; 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