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