1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 * Main purpose is to process user events.
22 * Also provides a couple of utility functions.
23 */
24
25 #include "common/coroutines.h"
26 #include "tinsel/actors.h"
27 #include "tinsel/background.h"
28 #include "tinsel/config.h"
29 #include "tinsel/cursor.h"
30 #include "tinsel/dw.h"
31 #include "tinsel/events.h"
32 #include "tinsel/handle.h" // For LockMem()
33 #include "tinsel/dialogs.h"
34 #include "tinsel/move.h" // For walking lead actor
35 #include "tinsel/pcode.h" // For Interpret()
36 #include "tinsel/pdisplay.h"
37 #include "tinsel/pid.h"
38 #include "tinsel/polygons.h"
39 #include "tinsel/rince.h" // For walking lead actor
40 #include "tinsel/sched.h"
41 #include "tinsel/scroll.h" // For DontScrollCursor()
42 #include "tinsel/timers.h" // DwGetCurrentTime()
43 #include "tinsel/tinlib.h" // For control()
44 #include "tinsel/tinsel.h"
45 #include "tinsel/token.h"
46
47 namespace Tinsel {
48
49 //----------------- EXTERNAL FUNCTIONS ---------------------
50
51 // in PDISPLAY.C
52 extern int GetTaggedActor();
53 extern HPOLYGON GetTaggedPoly();
54
55 //----------------- EXTERNAL GLOBAL DATA ---------------------
56
57 extern bool g_bEnableMenu;
58
59 //----------------- LOCAL GLOBAL DATA --------------------
60
61 // FIXME: Avoid non-const global vars
62
63 static uint32 g_lastUserEvent = 0; // Time it hapenned
64 static int g_leftEvents = 0; // Single or double, left or right. Or escape key.
65 static int g_escEvents = 1; // Escape key
66 static int g_userEvents = 0; // Whenever a button or a key comes in
67
68 static int g_eCount = 0;
69
70 static int g_controlState;
71 static bool g_bStartOff;
72
73 static int g_controlX, g_controlY;
74 static bool g_bProvNotProcessed = false;
75
76 /**
77 * Gets called before each schedule, only 1 user action per schedule
78 * is allowed.
79 */
ResetEcount()80 void ResetEcount() {
81 g_eCount = 0;
82 }
83
84
IncUserEvents()85 void IncUserEvents() {
86 g_userEvents++;
87 g_lastUserEvent = DwGetCurrentTime();
88 }
89
90 /**
91 * If this is a single click, wait to check it's not the first half of a
92 * double click.
93 * If this is a double click, the process from the waiting single click
94 * gets killed.
95 */
AllowDclick(CORO_PARAM,PLR_EVENT be)96 void AllowDclick(CORO_PARAM, PLR_EVENT be) {
97 CORO_BEGIN_CONTEXT;
98 CORO_END_CONTEXT(_ctx);
99
100 CORO_BEGIN_CODE(_ctx);
101 if (be == PLR_SLEFT) {
102 GetToken(TOKEN_LEFT_BUT);
103 CORO_SLEEP(_vm->_config->_dclickSpeed+1);
104 FreeToken(TOKEN_LEFT_BUT);
105
106 // Prevent activation of 2 events on the same tick
107 if (++g_eCount != 1)
108 CORO_KILL_SELF();
109
110 break;
111
112 } else if (be == PLR_DLEFT) {
113 GetToken(TOKEN_LEFT_BUT);
114 FreeToken(TOKEN_LEFT_BUT);
115 }
116 CORO_END_CODE;
117 }
118
119 /**
120 * Re-enables user control
121 */
ControlOn()122 void ControlOn() {
123 if (!TinselV2) {
124 Control(CONTROL_ON);
125 return;
126 }
127
128 g_bEnableMenu = false;
129
130 if (g_controlState == CONTROL_OFF) {
131 // Control is on
132 g_controlState = CONTROL_ON;
133
134 // Restore cursor to where it was
135 if (g_bStartOff == true)
136 g_bStartOff = false;
137 else
138 SetCursorXY(g_controlX, g_controlY);
139
140 // Re-instate cursor
141 UnHideCursor();
142
143 // Turn tags back on
144 if (!InventoryActive())
145 EnableTags();
146 }
147 }
148
149 /**
150 * Takes control from the user
151 */
ControlOff()152 void ControlOff() {
153 if (!TinselV2) {
154 Control(CONTROL_ON);
155 return;
156 }
157
158 g_bEnableMenu = false;
159
160 if (g_controlState == CONTROL_ON) {
161 // Control is off
162 g_controlState = CONTROL_OFF;
163
164 // Store cursor position
165 GetCursorXY(&g_controlX, &g_controlY, true);
166
167 // Blank out cursor
168 DwHideCursor();
169
170 // Switch off tags
171 DisableTags();
172 }
173 }
174
175 /**
176 * Prevent tags and cursor re-appearing
177 */
ControlStartOff()178 void ControlStartOff() {
179 if (!TinselV2) {
180 Control(CONTROL_STARTOFF);
181 return;
182 }
183
184 g_bEnableMenu = false;
185
186 // Control is off
187 g_controlState = CONTROL_OFF;
188
189 // Blank out cursor
190 DwHideCursor();
191
192 // Switch off tags
193 DisableTags();
194
195 g_bStartOff = true;
196 }
197
198 /**
199 * Take control from player, if the player has it.
200 * Return TRUE if control taken, FALSE if not.
201 */
GetControl(int param)202 bool GetControl(int param) {
203 if (TinselV2)
204 return GetControl();
205
206 else if (TestToken(TOKEN_CONTROL)) {
207 Control(param);
208 return true;
209 } else
210 return false;
211 }
212
GetControl()213 bool GetControl() {
214 if (g_controlState == CONTROL_ON) {
215 ControlOff();
216 return true;
217 } else
218 return false;
219 }
220
ControlIsOn()221 bool ControlIsOn() {
222 if (TinselV2)
223 return (g_controlState == CONTROL_ON);
224
225 return TestToken(TOKEN_CONTROL);
226 }
227
228 //-----------------------------------------------------------------------
229
230 struct WP_INIT {
231 int x; // } Where to walk to
232 int y; // }
233 };
234
235 /**
236 * Perform a walk directly initiated by a click.
237 */
WalkProcess(CORO_PARAM,const void * param)238 static void WalkProcess(CORO_PARAM, const void *param) {
239 // COROUTINE
240 CORO_BEGIN_CONTEXT;
241 PMOVER pMover;
242 int thisWalk;
243 CORO_END_CONTEXT(_ctx);
244
245 const WP_INIT *to = (const WP_INIT *)param; // get the co-ordinates - copied to process when it was created
246
247 CORO_BEGIN_CODE(_ctx);
248
249 _ctx->pMover = GetMover(LEAD_ACTOR);
250
251 if (TinselV2 && MoverIs(_ctx->pMover) && !MoverIsSWalking(_ctx->pMover)) {
252 assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path
253
254 _ctx->thisWalk = SetActorDest(_ctx->pMover, to->x, to->y, false, 0);
255 DontScrollCursor();
256
257 while (MoverMoving(_ctx->pMover) && (_ctx->thisWalk == GetWalkNumber(_ctx->pMover)))
258 CORO_SLEEP(1);
259
260 } else if (!TinselV2 && _ctx->pMover->bActive) {
261 assert(_ctx->pMover->hCpath != NOPOLY); // Lead actor is not in a path
262
263 GetToken(TOKEN_LEAD);
264 SetActorDest(_ctx->pMover, to->x, to->y, false, 0);
265 DontScrollCursor();
266
267 while (MoverMoving(_ctx->pMover))
268 CORO_SLEEP(1);
269
270 FreeToken(TOKEN_LEAD);
271 }
272
273 CORO_END_CODE;
274 }
275
WalkTo(int x,int y)276 void WalkTo(int x, int y) {
277 WP_INIT to = { x, y };
278
279 CoroScheduler.createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
280 }
281
282 /**
283 * Run appropriate actor or polygon glitter code.
284 * If none, and it's a WALKTO event, do a walk.
285 */
ProcessUserEvent(TINSEL_EVENT uEvent,const Common::Point & coOrds,PLR_EVENT be=PLR_NOEVENT)286 static void ProcessUserEvent(TINSEL_EVENT uEvent, const Common::Point &coOrds, PLR_EVENT be = PLR_NOEVENT) {
287 int actor;
288 int aniX, aniY;
289 HPOLYGON hPoly;
290
291 // Prevent activation of 2 events on the same tick
292 if (++g_eCount != 1)
293 return;
294
295 if ((actor = GetTaggedActor()) != 0) {
296 // Event for a tagged actor
297 if (TinselV2)
298 ActorEvent(Common::nullContext, actor, uEvent, false, 0);
299 else
300 ActorEvent(actor, uEvent, be);
301 } else if ((hPoly = GetTaggedPoly()) != NOPOLY) {
302 // Event for active tagged polygon
303 if (!TinselV2)
304 RunPolyTinselCode(hPoly, uEvent, be, false);
305 else if (uEvent != PROV_WALKTO)
306 PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
307
308 } else {
309 GetCursorXY(&aniX, &aniY, true);
310
311 // There could be a poly involved which has no tag.
312 if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY ||
313 (!TinselV2 && ((hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY))) {
314 if (TinselV2 && (uEvent != PROV_WALKTO))
315 PolygonEvent(Common::nullContext, hPoly, uEvent, 0, false, 0);
316 else if (!TinselV2)
317 RunPolyTinselCode(hPoly, uEvent, be, false);
318 } else if ((uEvent == PROV_WALKTO) || (uEvent == WALKTO)) {
319 if (TinselV2)
320 ProcessedProvisional();
321 WalkTo(aniX, aniY);
322 }
323 }
324 }
325
326
327 /**
328 * ProcessButEvent
329 */
ProcessButEvent(PLR_EVENT be)330 void ProcessButEvent(PLR_EVENT be) {
331 if (_vm->_config->_swapButtons) {
332 switch (be) {
333 case PLR_SLEFT:
334 be = PLR_SRIGHT;
335 break;
336 case PLR_DLEFT:
337 be = PLR_DRIGHT;
338 break;
339 case PLR_SRIGHT:
340 be = PLR_SLEFT;
341 break;
342 case PLR_DRIGHT:
343 be = PLR_DLEFT;
344 break;
345 case PLR_DRAG1_START:
346 be = PLR_DRAG2_START;
347 break;
348 case PLR_DRAG1_END:
349 be = PLR_DRAG2_END;
350 break;
351 case PLR_DRAG2_START:
352 be = PLR_DRAG1_START;
353 break;
354 case PLR_DRAG2_END:
355 be = PLR_DRAG1_END;
356 break;
357 default:
358 break;
359 }
360 }
361
362 PlayerEvent(be, _vm->getMousePosition());
363 }
364
365 /**
366 * ProcessKeyEvent
367 */
368
ProcessKeyEvent(PLR_EVENT ke)369 void ProcessKeyEvent(PLR_EVENT ke) {
370 // Pass the keyboard event to the player event handler
371 int xp, yp;
372 GetCursorXYNoWait(&xp, &yp, true);
373 const Common::Point mousePos(xp, yp);
374
375 PlayerEvent(ke, mousePos);
376 }
377
378 #define REAL_ACTION_CHECK if (TinselV2) { \
379 if (DwGetCurrentTime() - lastRealAction < 4) return; \
380 lastRealAction = DwGetCurrentTime(); \
381 }
382
383 /**
384 * Main interface point for specifying player atcions
385 */
PlayerEvent(PLR_EVENT pEvent,const Common::Point & coOrds)386 void PlayerEvent(PLR_EVENT pEvent, const Common::Point &coOrds) {
387 // Logging of player actions
388 const char *actionList[] = {
389 "PLR_PROV_WALKTO", "PLR_WALKTO", "PLR_LOOK", "PLR_ACTION", "PLR_ESCAPE",
390 "PLR_MENU", "PLR_QUIT", "PLR_PGUP", "PLR_PGDN", "PLR_HOME", "PLR_END",
391 "PLR_DRAG1_START", "PLR_DRAG1_END", "PLR_DRAG2_START", "PLR_DRAG2_END",
392 "PLR_JUMP", "PLR_NOEVENT", "PLR_SAVE", "PLR_LOAD", "PLR_WHEEL_UP",
393 "PLR_WHEEL_DOWN"};
394 debugC(DEBUG_BASIC, kTinselDebugActions, "%s - (%d,%d)",
395 actionList[pEvent], coOrds.x, coOrds.y);
396 static uint32 lastRealAction = 0; // FIXME: Avoid non-const global vars
397
398 // This stuff to allow F1 key during startup.
399 if (g_bEnableMenu && pEvent == PLR_MENU)
400 Control(CONTROL_ON);
401 else
402 IncUserEvents();
403
404 if (pEvent == PLR_ESCAPE) {
405 ++g_escEvents;
406 ++g_leftEvents; // Yes, I do mean this
407 } else if ((pEvent == PLR_PROV_WALKTO)
408 || (pEvent == PLR_WALKTO)
409 || (pEvent == PLR_LOOK)
410 || (pEvent == PLR_ACTION)) {
411 ++g_leftEvents;
412 }
413
414 // Only allow events if player control is on
415 if (!ControlIsOn() && (pEvent != PLR_DRAG1_END))
416 return;
417
418 if (TinselV2 && InventoryActive()) {
419 int x, y;
420 PlayfieldGetPos(FIELD_WORLD, &x, &y);
421 EventToInventory(pEvent, Common::Point(coOrds.x - x, coOrds.y - y));
422 return;
423 }
424
425 switch (pEvent) {
426 case PLR_QUIT:
427 OpenMenu(QUIT_MENU);
428 break;
429
430 case PLR_MENU:
431 OpenMenu(MAIN_MENU);
432 break;
433
434 case PLR_JUMP:
435 OpenMenu(HOPPER_MENU1);
436 break;
437
438 case PLR_SAVE:
439 OpenMenu(SAVE_MENU);
440 break;
441
442 case PLR_LOAD:
443 OpenMenu(LOAD_MENU);
444 break;
445
446 case PLR_PROV_WALKTO: // Provisional WALKTO !
447 ProcessUserEvent(PROV_WALKTO, coOrds);
448 break;
449
450 case PLR_WALKTO:
451 REAL_ACTION_CHECK;
452
453 if (TinselV2 || !InventoryActive())
454 ProcessUserEvent(WALKTO, coOrds, PLR_SLEFT);
455 else
456 EventToInventory(PLR_SLEFT, coOrds);
457 break;
458
459 case PLR_ACTION:
460 REAL_ACTION_CHECK;
461
462 if (TinselV2 || !InventoryActive())
463 ProcessUserEvent(ACTION, coOrds, PLR_DLEFT);
464 else
465 EventToInventory(PLR_DLEFT, coOrds);
466 break;
467
468 case PLR_LOOK:
469 REAL_ACTION_CHECK;
470
471 if (TinselV2 || !InventoryActive())
472 ProcessUserEvent(LOOK, coOrds, PLR_SRIGHT);
473 else
474 EventToInventory(PLR_SRIGHT, coOrds);
475 break;
476
477 default:
478 if (InventoryActive())
479 EventToInventory(pEvent, coOrds);
480 break;
481 }
482 }
483
484 /**
485 * For ESCapable Glitter sequences
486 */
GetEscEvents()487 int GetEscEvents() {
488 return g_escEvents;
489 }
490
491 /**
492 * For cutting short talk()s etc.
493 */
GetLeftEvents()494 int GetLeftEvents() {
495 return g_leftEvents;
496 }
497
LeftEventChange(int myleftEvent)498 bool LeftEventChange(int myleftEvent) {
499 if (g_leftEvents != myleftEvent) {
500 ProcessedProvisional();
501 return true;
502 } else
503 return false;
504 }
505
506 /**
507 * For waitkey() Glitter function
508 */
getUserEvents()509 int getUserEvents() {
510 return g_userEvents;
511 }
512
getUserEventTime()513 uint32 getUserEventTime() {
514 return DwGetCurrentTime() - g_lastUserEvent;
515 }
516
resetUserEventTime()517 void resetUserEventTime() {
518 g_lastUserEvent = DwGetCurrentTime();
519 }
520
521 struct PTP_INIT {
522 HPOLYGON hPoly; // Polygon
523 TINSEL_EVENT event; // Trigerring event
524 PLR_EVENT bev; // To allow for double clicks
525 bool take_control; // Set if control should be taken
526 // while code is running.
527 int actor;
528
529 PINT_CONTEXT pic;
530 };
531
532 /**
533 * Runs glitter code associated with a polygon.
534 */
PolyTinselProcess(CORO_PARAM,const void * param)535 void PolyTinselProcess(CORO_PARAM, const void *param) {
536 // COROUTINE
537 CORO_BEGIN_CONTEXT;
538 INT_CONTEXT *pic;
539 bool bTookControl; // Set if this function takes control
540
541 CORO_END_CONTEXT(_ctx);
542
543 const PTP_INIT *to = (const PTP_INIT *)param; // get the stuff copied to process when it was created
544
545 CORO_BEGIN_CODE(_ctx);
546
547 if (TinselV2) {
548
549 // Take control for CONVERSE events
550 if (to->event == CONVERSE) {
551 _ctx->bTookControl = GetControl();
552 HideConversation(true);
553 } else
554 _ctx->bTookControl = false;
555
556 CORO_INVOKE_1(Interpret, to->pic);
557
558 // Restore conv window if applicable
559 if (to->event == CONVERSE) {
560 // Free control if we took it
561 if (_ctx->bTookControl)
562 ControlOn();
563
564 HideConversation(false);
565 }
566
567 } else {
568
569 CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click
570
571 // Control may have gone off during AllowDclick()
572 if (!TestToken(TOKEN_CONTROL)
573 && (to->event == WALKTO || to->event == ACTION || to->event == LOOK))
574 CORO_KILL_SELF();
575
576 // Take control, if requested
577 if (to->take_control)
578 _ctx->bTookControl = GetControl(CONTROL_OFF);
579 else
580 _ctx->bTookControl = false;
581
582 // Hide conversation if appropriate
583 if (to->event == CONVERSE)
584 HideConversation(true);
585
586 // Run the code
587 _ctx->pic = InitInterpretContext(GS_POLYGON, GetPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL);
588 CORO_INVOKE_1(Interpret, _ctx->pic);
589
590 // Free control if we took it
591 if (_ctx->bTookControl)
592 Control(CONTROL_ON);
593
594 // Restore conv window if applicable
595 if (to->event == CONVERSE)
596 HideConversation(false);
597 }
598
599 CORO_END_CODE;
600 }
601
602 /**
603 * Run the Polygon process with the given event
604 */
PolygonEvent(CORO_PARAM,HPOLYGON hPoly,TINSEL_EVENT tEvent,int actor,bool bWait,int myEscape,bool * result)605 void PolygonEvent(CORO_PARAM, HPOLYGON hPoly, TINSEL_EVENT tEvent, int actor, bool bWait,
606 int myEscape, bool *result) {
607 CORO_BEGIN_CONTEXT;
608 Common::PPROCESS pProc;
609 CORO_END_CONTEXT(_ctx);
610
611 CORO_BEGIN_CODE(_ctx);
612
613 PTP_INIT to;
614
615 if (result)
616 *result = false;
617 to.hPoly = -1;
618 to.event = tEvent;
619 to.pic = InitInterpretContext(GS_POLYGON,
620 GetPolyScript(hPoly),
621 tEvent,
622 hPoly, // Polygon
623 actor, // Actor
624 NULL, // No Object
625 myEscape);
626 if (to.pic != NULL) {
627 _ctx->pProc = CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
628 AttachInterpret(to.pic, _ctx->pProc);
629
630 if (bWait)
631 CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result);
632 }
633
634 CORO_END_CODE;
635 }
636
637 /**
638 * Runs glitter code associated with a polygon.
639 */
RunPolyTinselCode(HPOLYGON hPoly,TINSEL_EVENT event,PLR_EVENT be,bool tc)640 void RunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, PLR_EVENT be, bool tc) {
641 PTP_INIT to = { hPoly, event, be, tc, 0, NULL };
642
643 assert(!TinselV2);
644 CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
645 }
646
effRunPolyTinselCode(HPOLYGON hPoly,TINSEL_EVENT event,int actor)647 void effRunPolyTinselCode(HPOLYGON hPoly, TINSEL_EVENT event, int actor) {
648 PTP_INIT to = { hPoly, event, PLR_NOEVENT, false, actor, NULL };
649
650 assert(!TinselV2);
651 CoroScheduler.createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
652 }
653
654 /**
655 * If provisional event was processed, calling this prevents the
656 * subsequent 'real' event.
657 */
ProcessedProvisional()658 void ProcessedProvisional() {
659 g_bProvNotProcessed = false;
660 }
661
662 /**
663 * Resets the bProvNotProcessed flag
664 */
ProvNotProcessed()665 void ProvNotProcessed() {
666 g_bProvNotProcessed = true;
667 }
668
GetProvNotProcessed()669 bool GetProvNotProcessed() {
670 return g_bProvNotProcessed;
671 }
672
673 } // End of namespace Tinsel
674