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  * CursorPositionProcess()
22  * TagProcess()
23  * PointProcess()
24  */
25 
26 #include "common/coroutines.h"
27 #include "tinsel/actors.h"
28 #include "tinsel/background.h"
29 #include "tinsel/cursor.h"
30 #include "tinsel/dw.h"
31 #include "tinsel/events.h"
32 #include "tinsel/font.h"
33 #include "tinsel/graphics.h"
34 #include "tinsel/multiobj.h"
35 #include "tinsel/object.h"
36 #include "tinsel/pcode.h"
37 #include "tinsel/polygons.h"
38 #include "tinsel/rince.h"
39 #include "tinsel/sched.h"
40 #include "tinsel/strres.h"
41 #include "tinsel/text.h"
42 #include "tinsel/tinsel.h"
43 
44 #include "common/textconsole.h"
45 
46 namespace Tinsel {
47 
48 //----------------- EXTERNAL GLOBAL DATA --------------------
49 
50 #ifdef DEBUG
51 //extern int Overrun;		// The overrun counter, in DOS_DW.C
52 
53 extern int g_newestString;	// The overrun counter, in STRRES.C
54 #endif
55 
56 
57 //----------------- LOCAL DEFINES --------------------
58 
59 #define LPOSX	295		// X-co-ord of lead actor's position display
60 #define CPOSX	24		// X-co-ord of cursor's position display
61 #define OPOSX	SCRN_CENTER_X	// X-co-ord of overrun counter's display
62 #define SPOSX	SCRN_CENTER_X	// X-co-ord of string numbner's display
63 
64 #define POSY	0		// Y-co-ord of these position displays
65 
66 enum HotSpotTag {
67 	NO_HOTSPOT_TAG,
68 	POLY_HOTSPOT_TAG,
69 	ACTOR_HOTSPOT_TAG
70 };
71 
72 //----------------- LOCAL GLOBAL DATA --------------------
73 
74 // FIXME: Avoid non-const global vars
75 
76 static bool g_DispPath = false;
77 static bool g_bShowString = false;
78 
79 static int	g_TaggedActor = 0;
80 static HPOLYGON	g_hTaggedPolygon = NOPOLY;
81 
82 static bool g_bTagsActive = true;
83 
84 static bool g_bPointingActive = true;
85 
86 static char g_tagBuffer[64];
87 
88 #ifdef DEBUG
89 /**
90  * Displays the cursor and lead actor's co-ordinates and the overrun
91  * counter. Also which path polygon the cursor is in, if required.
92  *
93  * This process is only started up if a Glitter showpos() call is made.
94  * Obviously, this is for testing purposes only...
95  */
CursorPositionProcess(CORO_PARAM,const void *)96 void CursorPositionProcess(CORO_PARAM, const void *) {
97 	// COROUTINE
98 	CORO_BEGIN_CONTEXT;
99 		int prevsX, prevsY;	// Last screen top left
100 		int prevcX, prevcY;	// Last displayed cursor position
101 		int prevlX, prevlY;	// Last displayed lead actor position
102 //		int prevOver;		// Last displayed overrun
103 		int prevString;		// Last displayed string number
104 
105 		OBJECT *cpText;		// cursor position text object pointer
106 		OBJECT *cpathText;	// cursor path text object pointer
107 		OBJECT *rpText;		// text object pointer
108 //		OBJECT *opText;		// text object pointer
109 		OBJECT *spText;		// string number text object pointer
110 	CORO_END_CONTEXT(_ctx);
111 
112 	CORO_BEGIN_CODE(_ctx);
113 
114 	_ctx->prevsX = -1;
115 	_ctx->prevsY = -1;
116 	_ctx->prevcX = -1;
117 	_ctx->prevcY = -1;
118 	_ctx->prevlX = -1;
119 	_ctx->prevlY = -1;
120 //	_ctx->prevOver = -1;
121 	_ctx->prevString = -1;
122 
123 	_ctx->cpText = NULL;
124 	_ctx->cpathText = NULL;
125 	_ctx->rpText = NULL;
126 //	_ctx->opText = NULL;
127 	_ctx->spText = NULL;
128 
129 
130 	int aniX, aniY;			// cursor/lead actor position
131 	int Loffset, Toffset;		// Screen top left
132 
133 	char PositionString[64];	// sprintf() things into here
134 
135 	PMOVER pActor;		// Lead actor
136 
137 	while (1) {
138 		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
139 
140 		/*-----------------------------------*\
141 		| Cursor's position and path display. |
142 		\*-----------------------------------*/
143 		GetCursorXY(&aniX, &aniY, false);
144 
145 		// Change in cursor position?
146 		if (aniX != _ctx->prevcX || aniY != _ctx->prevcY ||
147 				Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
148 			// kill current text objects
149 			if (_ctx->cpText) {
150 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText);
151 			}
152 			if (_ctx->cpathText) {
153 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText);
154 				_ctx->cpathText = NULL;
155 			}
156 
157 			// New text objects
158 			sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset);
159 			_ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
160 						0, CPOSX, POSY, GetTagFontHandle(), TXT_CENTER);
161 			if (g_DispPath) {
162 				HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH);
163 				if (hp == NOPOLY)
164 					sprintf(PositionString, "No path");
165 				else
166 					sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d",
167 						PolyCornerX(hp, 0), PolyCornerY(hp, 0),
168 						PolyCornerX(hp, 1), PolyCornerY(hp, 1),
169 						PolyCornerX(hp, 2), PolyCornerY(hp, 2),
170 						PolyCornerX(hp, 3), PolyCornerY(hp, 3));
171 				_ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
172 							0, 4, POSY+ 10, GetTagFontHandle(), 0);
173 			}
174 
175 			// update previous position
176 			_ctx->prevcX = aniX;
177 			_ctx->prevcY = aniY;
178 		}
179 
180 #if 0
181 		/*------------------------*\
182 		| Overrun counter display. |
183 		\*------------------------*/
184 		if (Overrun != _ctx->prevOver) {
185 			// kill current text objects
186 			if (_ctx->opText) {
187 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText);
188 			}
189 
190 			sprintf(PositionString, "%d", Overrun);
191 			_ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
192 						0, OPOSX, POSY, GetTagFontHandle(), TXT_CENTER);
193 
194 			// update previous value
195 			_ctx->prevOver = Overrun;
196 		}
197 #endif
198 
199 		/*----------------------*\
200 		| Lead actor's position. |
201 		\*----------------------*/
202 		pActor = GetMover(LEAD_ACTOR);
203 		if (pActor && getMActorState(pActor)) {
204 			// get lead's animation position
205 			GetActorPos(LEAD_ACTOR, &aniX, &aniY);
206 
207 			// Change in position?
208 			if (aniX != _ctx->prevlX || aniY != _ctx->prevlY ||
209 					Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
210 				// Kill current text objects
211 				if (_ctx->rpText) {
212 					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText);
213 				}
214 
215 				// create new text object list
216 				sprintf(PositionString, "%d %d", aniX, aniY);
217 				_ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
218 								0, LPOSX, POSY,	GetTagFontHandle(), TXT_CENTER);
219 
220 				// update previous position
221 				_ctx->prevlX = aniX;
222 				_ctx->prevlY = aniY;
223 			}
224 		}
225 
226 		/*-------------*\
227 		| String number	|
228 		\*-------------*/
229 		if (g_bShowString && g_newestString != _ctx->prevString) {
230 			// kill current text objects
231 			if (_ctx->spText) {
232 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText);
233 			}
234 
235 			sprintf(PositionString, "String: %d", g_newestString);
236 			_ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
237 						0, SPOSX, POSY+10, GetTalkFontHandle(), TXT_CENTER);
238 
239 			// update previous value
240 			_ctx->prevString = g_newestString;
241 		}
242 
243 		// update previous playfield position
244 		_ctx->prevsX = Loffset;
245 		_ctx->prevsY = Toffset;
246 
247 		CORO_SLEEP(1);		// allow re-scheduling
248 	}
249 	CORO_END_CODE;
250 }
251 #endif
252 
253 /**
254  * While inventory/menu is open.
255  */
DisablePointing()256 void DisablePointing() {
257 	int	i;
258 	HPOLYGON hPoly;		// Polygon handle
259 
260 	g_bPointingActive = false;
261 
262 	for (i = 0; i < MAX_POLY; i++)	{
263 		hPoly = GetPolyHandle(i);
264 
265 		if (hPoly != NOPOLY && PolyType(hPoly) == TAG && PolyIsPointedTo(hPoly)) {
266 			SetPolyPointedTo(hPoly, false);
267 			SetPolyTagWanted(hPoly, false, false, 0);
268 			PolygonEvent(Common::nullContext, hPoly, UNPOINT, 0, false, 0);
269 		}
270 	}
271 
272 	// For each tagged actor
273 	for (i = 0; (i = NextTaggedActor(i)) != 0; ) {
274 		if (ActorIsPointedTo(i)) {
275 			SetActorPointedTo(i, false);
276 			SetActorTagWanted(i, false, false, 0);
277 
278 			ActorEvent(Common::nullContext, i, UNPOINT, false, 0);
279 		}
280 	}
281 }
282 
283 /**
284  * EnablePointing()
285  */
EnablePointing()286 void EnablePointing() {
287 	g_bPointingActive = true;
288 }
289 
290 /**
291  * Tag process keeps us updated as to which tagged actor is currently tagged
292  * (if one is). Tag process asks us for this information, as does ProcessUserEvent().
293  */
SaveTaggedActor(int ano)294 static void SaveTaggedActor(int ano) {
295 	g_TaggedActor = ano;
296 }
297 
298 /**
299  * Tag process keeps us updated as to which tagged actor is currently tagged
300  * (if one is). Tag process asks us for this information, as does ProcessUserEvent().
301  */
GetTaggedActor()302 int GetTaggedActor() {
303 	return g_TaggedActor;
304 }
305 
306 /**
307  * Tag process keeps us updated as to which polygon is currently tagged
308  * (if one is). Tag process asks us for this information, as does ProcessUserEvent().
309  */
SaveTaggedPoly(HPOLYGON hp)310 static void SaveTaggedPoly(HPOLYGON hp) {
311 	g_hTaggedPolygon = hp;
312 }
313 
GetTaggedPoly()314 HPOLYGON GetTaggedPoly() {
315 	return g_hTaggedPolygon;
316 }
317 
318 /**
319  * Given cursor position and an actor number, ascertains whether the
320  * cursor is within the actor's tag area.
321  * Returns TRUE for a positive result, FALSE for negative.
322  * If TRUE, the mid-top co-ordinates of the actor's tag area are also
323  * returned.
324  */
InHotSpot(int ano,int aniX,int aniY,int * pxtext,int * pytext)325 static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) {
326 	int	Top, Bot;		// Top and bottom limits of active area
327 	int	left, right;	// left and right of active area
328 	int	qrt = 0;		// 1/4 of height (sometimes 1/2)
329 
330 	// First check if within x-range
331 	if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) {
332 		Top = GetActorTop(ano);
333 		Bot = GetActorBottom(ano);
334 
335 		// y-range varies according to tag-type
336 		switch (TagType(ano)) {
337 		case TAG_DEF:
338 			// Next to bottom 1/4 of the actor's area
339 			qrt = (Bot - Top) >> 1;		// Half actor's height
340 			Top += qrt;			// Top = mid-height
341 
342 			qrt = qrt >> 1;			// Quarter height
343 			Bot -= qrt;			// Bot = 1/4 way up
344 			break;
345 
346 		case TAG_Q1TO3:
347 			// Top 3/4 of the actor's area
348 			qrt = (Bot - Top) >> 2;		// 1/4 actor's height
349 			Bot -= qrt;			// Bot = 1/4 way up
350 			break;
351 
352 		case TAG_Q1TO4:
353 			// All the actor's area
354 			break;
355 
356 		default:
357 			error("illegal tag area type");
358 		}
359 
360 		// Now check if within y-range
361 		if (aniY >= Top && aniY <= Bot) {
362 			if (TagType(ano) == TAG_Q1TO3)
363 				*pytext = Top + qrt;
364 			else
365 				*pytext = Top;
366 			*pxtext = (left + right) / 2;
367 			return true;
368 		}
369 	}
370 	return false;
371 }
372 
373 /**
374  * See if the cursor is over a tagged actor's hot-spot. If so, display
375  * the tag or, if tag already displayed, maintain the tag's position on
376  * the screen.
377  */
ActorTag(int curX,int curY,HotSpotTag * pTag,OBJECT ** ppText)378 static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) {
379 	// FIXME: Avoid non-const global vars
380 	static int tagX = 0, tagY = 0;	// Values when tag was displayed
381 	int	newX, newY;		// new values, to keep tag in place
382 	int	ano;
383 	int	xtext, ytext;
384 	bool	newActor;
385 
386 	if (TinselV2) {
387 		// Tinsel 2 version
388 		// Get the foremost pointed to actor
389 		int actor = FrontTaggedActor();
390 
391 		if (actor == 0) {
392 			SaveTaggedActor(0);
393 			return false;
394 		}
395 
396 		// If new actor
397 		// or actor has suddenly decided it wants tagging...
398 		if (actor != GetTaggedActor() || (ActorTagIsWanted(actor) && !*ppText)) {
399 			// Put up actor tag
400 			SaveTaggedActor(actor);		// This actor tagged
401 			SaveTaggedPoly(NOPOLY);		// No tagged polygon
402 
403 			if (*ppText)
404 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
405 
406 			if (ActorTagIsWanted(actor)) {
407 				GetActorTagPos(actor, &tagX, &tagY, false);
408 				LoadStringRes(GetActorTagHandle(actor), g_tagBuffer, sizeof(g_tagBuffer));
409 
410 				// May have buggered cursor
411 				EndCursorFollowed();
412 				*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), g_tagBuffer,
413 						0, tagX, tagY, GetTagFontHandle(), TXT_CENTER, 0);
414 				assert(*ppText);
415 				MultiSetZPosition(*ppText, Z_TAG_TEXT);
416 			} else
417 				*ppText = NULL;
418 		} else if (*ppText) {
419 			// Same actor, maintain tag position
420 			GetActorTagPos(actor, &newX, &newY, false);
421 
422 			if (newX != tagX || newY != tagY) {
423 				MultiMoveRelXY(*ppText, newX - tagX, newY - tagY);
424 				tagX = newX;
425 				tagY = newY;
426 			}
427 		}
428 
429 		return true;
430 	}
431 
432 	// Tinsel 1 version
433 	// For each actor with a tag....
434 	FirstTaggedActor();
435 	while ((ano = NextTaggedActor()) != 0) {
436 		if (InHotSpot(ano, curX, curY, &xtext, &ytext)) {
437 			// Put up or maintain actor tag
438 			if (*pTag != ACTOR_HOTSPOT_TAG)
439 				newActor = true;
440 			else if (ano != GetTaggedActor())
441 				newActor = true;	// Different actor
442 			else
443 				newActor = false;	// Same actor
444 
445 			if (newActor) {
446 				// Display actor's tag
447 
448 				if (*ppText)
449 					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
450 
451 				*pTag = ACTOR_HOTSPOT_TAG;
452 				SaveTaggedActor(ano);	// This actor tagged
453 				SaveTaggedPoly(NOPOLY);	// No tagged polygon
454 
455 				PlayfieldGetPos(FIELD_WORLD, &tagX, &tagY);
456 				LoadStringRes(GetActorTag(ano), TextBufferAddr(), TBUFSZ);
457 				*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
458 							0, xtext - tagX, ytext - tagY, GetTagFontHandle(), TXT_CENTER);
459 				assert(*ppText); // Actor tag string produced NULL text
460 				MultiSetZPosition(*ppText, Z_TAG_TEXT);
461 			} else {
462 				// Maintain actor tag's position
463 
464 				PlayfieldGetPos(FIELD_WORLD, &newX, &newY);
465 				if (newX != tagX || newY != tagY) {
466 					MultiMoveRelXY(*ppText, tagX - newX, tagY - newY);
467 					tagX = newX;
468 					tagY = newY;
469 				}
470 			}
471 			return true;
472 		}
473 	}
474 
475 	// No tagged actor
476 	if (*pTag == ACTOR_HOTSPOT_TAG) {
477 		*pTag = NO_HOTSPOT_TAG;
478 		SaveTaggedActor(0);
479 	}
480 	return false;
481 }
482 
483 /**
484  * Perhaps some comment in due course.
485  *
486  * Under control of PointProcess(), when the cursor is over a TAG or
487  * EXIT polygon, its pointState flag is set to POINTING. If its Glitter
488  * code contains a printtag() call, its tagState flag gets set to TAG_ON.
489  */
PolyTag(HotSpotTag * pTag,OBJECT ** ppText)490 static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) {
491 	// FIXME: Avoid non-const global vars
492 	static int	Loffset = 0, Toffset = 0;	// Values when tag was displayed
493 	static int curX = 0, curY = 0;
494 	int		nLoff, nToff;		// new values, to keep tag in place
495 	HPOLYGON	hp;
496 	bool	newPoly;
497 	int	shift;
498 
499 	int	tagx, tagy;	// Tag display co-ordinates
500 	SCNHANDLE hTagtext;	// Tag text
501 
502 	// For each polgon with a tag....
503 	for (int i = 0; i < MAX_POLY; i++) {
504 		hp = GetPolyHandle(i);
505 
506 		if (TinselV2 && (hp == NOPOLY))
507 			continue;
508 
509 		// Added code for un-tagged tags
510 		if ((hp != NOPOLY) && (PolyPointState(hp) == PS_POINTING) && (PolyTagState(hp) != TAG_ON)) {
511 			// This poly is entitled to be tagged
512 			if (hp != GetTaggedPoly()) {
513 				if (*ppText) {
514 					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
515 					*ppText = NULL;
516 				}
517 				*pTag = POLY_HOTSPOT_TAG;
518 				SaveTaggedActor(0);	// No tagged actor
519 				SaveTaggedPoly(hp);	// This polygon tagged
520 			}
521 			return true;
522 		} else if ((TinselV2 && PolyTagIsWanted(hp)) ||
523 			(!TinselV2 && hp != NOPOLY && PolyTagState(hp) == TAG_ON)) {
524 			// Put up or maintain polygon tag
525 			newPoly = false;
526 			if (TinselV2) {
527 				if (hp != GetTaggedPoly())
528 					newPoly = true;		// Different polygon
529 			} else {
530 				if (*pTag != POLY_HOTSPOT_TAG)
531 					newPoly = true;		// A new polygon (no current)
532 				else if (hp != GetTaggedPoly())
533 					newPoly = true;		// Different polygon
534 			}
535 
536 			if (newPoly) {
537 				if (*ppText)
538 					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
539 
540 				if (!TinselV2)
541 					*pTag = POLY_HOTSPOT_TAG;
542 				SaveTaggedActor(0);	// No tagged actor
543 				SaveTaggedPoly(hp);	// This polygon tagged
544 
545 				PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
546 				GetTagTag(hp, &hTagtext, &tagx, &tagy);
547 
548 				int strLen;
549 				if (GetPolyTagHandle(hp) != 0)
550 					strLen = LoadStringRes(GetPolyTagHandle(hp), TextBufferAddr(), TBUFSZ);
551 				else
552 					strLen = LoadStringRes(hTagtext, TextBufferAddr(), TBUFSZ);
553 
554 				if (strLen == 0)
555 					// No valid string returned, so leave ppText as NULL
556 					ppText = NULL;
557 				else if (TinselV2 && !PolyTagFollowsCursor(hp)) {
558 					// May have buggered cursor
559 					EndCursorFollowed();
560 
561 					*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
562 							TextBufferAddr(), 0, tagx - Loffset, tagy - Toffset,
563 							GetTagFontHandle(), TXT_CENTER, 0);
564 				} else if (TinselV2) {
565 					// Bugger cursor
566 					const char *tagPtr = TextBufferAddr();
567 					if (tagPtr[0] < ' ' && tagPtr[1] == EOS_CHAR)
568 						StartCursorFollowed();
569 
570 					GetCursorXYNoWait(&curX, &curY, false);
571 					*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
572 							0, curX, curY, GetTagFontHandle(), TXT_CENTER, 0);
573 				} else {
574 					// Handle displaying the tag text on-screen
575 					*ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
576 							0, tagx - Loffset, tagy - Toffset,
577 							GetTagFontHandle(), TXT_CENTER);
578 					assert(*ppText); // Polygon tag string produced NULL text
579 				}
580 
581 				// DW1 has some tags without text, e.g. the "equals" button when talking to the guard in act 3
582 				if (ppText) {
583 					MultiSetZPosition(*ppText, Z_TAG_TEXT);
584 
585 					/*
586 					* New feature: Don't go off the side of the background
587 					*/
588 					shift = MultiRightmost(*ppText) + Loffset + 2;
589 					if (shift >= BgWidth())			// Not off right
590 						MultiMoveRelXY(*ppText, BgWidth() - shift, 0);
591 					shift = MultiLeftmost(*ppText) + Loffset - 1;
592 					if (shift <= 0)					// Not off left
593 						MultiMoveRelXY(*ppText, -shift, 0);
594 					shift = MultiLowest(*ppText) + Toffset;
595 					if (shift > BgHeight())			// Not off bottom
596 						MultiMoveRelXY(*ppText, 0, BgHeight() - shift);
597 				}
598 			} else if (TinselV2 && (*ppText)) {
599 				if (!PolyTagFollowsCursor(hp)) {
600 					PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
601 					if (nLoff != Loffset || nToff != Toffset) {
602 						MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
603 						Loffset = nLoff;
604 						Toffset = nToff;
605 					}
606 				} else {
607 					GetCursorXY(&tagx, &tagy, false);
608 					if (tagx != curX || tagy != curY) {
609 						MultiMoveRelXY(*ppText, tagx - curX, tagy - curY);
610 						curX = tagx;
611 						curY = tagy;
612 					}
613 				}
614 			} else if (!TinselV2) {
615 				PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
616 				if (nLoff != Loffset || nToff != Toffset) {
617 					MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
618 					Loffset = nLoff;
619 					Toffset = nToff;
620 				}
621 			}
622 			return true;
623 		}
624 	}
625 
626 	// No tagged polygon
627 	if (TinselV2)
628 		SaveTaggedPoly(NOPOLY);
629 	else if (*pTag == POLY_HOTSPOT_TAG) {
630 		*pTag = NO_HOTSPOT_TAG;
631 		SaveTaggedPoly(NOPOLY);
632 	}
633 	return false;
634 }
635 
636 /**
637  * Handle display of tagged actor and polygon tags.
638  * Tagged actor's get priority over polygons.
639  */
TagProcess(CORO_PARAM,const void *)640 void TagProcess(CORO_PARAM, const void *) {
641 	// COROUTINE
642 	CORO_BEGIN_CONTEXT;
643 		OBJECT	*pText;	// text object pointer
644 		HotSpotTag Tag;
645 	CORO_END_CONTEXT(_ctx);
646 
647 	CORO_BEGIN_CODE(_ctx);
648 
649 	_ctx->pText = NULL;
650 	_ctx->Tag = NO_HOTSPOT_TAG;
651 
652 	SaveTaggedActor(0);		// No tagged actor yet
653 	SaveTaggedPoly(NOPOLY);		// No tagged polygon yet
654 
655 	while (1) {
656 		if (g_bTagsActive) {
657 			int	curX, curY;	// cursor position
658 			while (!GetCursorXYNoWait(&curX, &curY, true))
659 				CORO_SLEEP(1);
660 
661 			if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText)
662 					&& !PolyTag(&_ctx->Tag, &_ctx->pText)) {
663 				// Nothing tagged. Remove tag, if there is one
664 				if (_ctx->pText) {
665 					MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
666 					_ctx->pText = NULL;
667 
668 					if (TinselV2)
669 						// May have buggered cursor
670 						EndCursorFollowed();
671 				}
672 			}
673 		} else {
674 			SaveTaggedActor(0);
675 			SaveTaggedPoly(NOPOLY);
676 
677 			// Remove tag, if there is one
678 			if (_ctx->pText) {
679 				// kill current text objects
680 				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
681 				_ctx->pText = NULL;
682 				_ctx->Tag = NO_HOTSPOT_TAG;
683 			}
684 		}
685 
686 		CORO_SLEEP(1);		// allow re-scheduling
687 	}
688 
689 	CORO_END_CODE;
690 }
691 
692 /**
693  * Called from PointProcess() as appropriate.
694  */
enteringpoly(CORO_PARAM,HPOLYGON hp)695 static void enteringpoly(CORO_PARAM, HPOLYGON hp) {
696 	CORO_BEGIN_CONTEXT;
697 	CORO_END_CONTEXT(_ctx);
698 
699 	CORO_BEGIN_CODE(_ctx);
700 
701 	SetPolyPointState(hp, PS_POINTING);
702 
703 	if (TinselV2)
704 		CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, hp, POINTED, 0, false, 0));
705 	else
706 		RunPolyTinselCode(hp, POINTED, PLR_NOEVENT, false);
707 
708 	CORO_END_CODE;
709 }
710 
711 /**
712  * Called from PointProcess() as appropriate.
713  */
leavingpoly(CORO_PARAM,HPOLYGON hp)714 static void leavingpoly(CORO_PARAM, HPOLYGON hp) {
715 	CORO_BEGIN_CONTEXT;
716 	CORO_END_CONTEXT(_ctx);
717 
718 	CORO_BEGIN_CODE(_ctx);
719 
720 	SetPolyPointState(hp, PS_NOT_POINTING);
721 
722 	if (TinselV2) {
723 		CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, hp, UNPOINT, 0, false, 0));
724 		SetPolyTagWanted(hp, false, false, 0);
725 
726 	} else if (PolyTagState(hp) == TAG_ON) {
727 		// Delete this tag entry
728 		SetPolyTagState(hp, TAG_OFF);
729 	}
730 
731 	CORO_END_CODE;
732 }
733 
734 /**
735  * For TAG and EXIT polygons, monitor cursor entering and leaving.
736  * Maintain the polygons' pointState and tagState flags accordingly.
737  * Also run the polygon's Glitter code when the cursor enters.
738  */
PointProcess(CORO_PARAM,const void *)739 void PointProcess(CORO_PARAM, const void *) {
740 	// COROUTINE
741 	CORO_BEGIN_CONTEXT;
742 		HPOLYGON hPoly;
743 		int i;
744 		int curX, curY;	// cursor/tagged actor position
745 	CORO_END_CONTEXT(_ctx);
746 
747 	CORO_BEGIN_CODE(_ctx);
748 
749 	if (TinselV2)
750 		EnablePointing();
751 
752 	while (1) {
753 		while (!GetCursorXYNoWait(&_ctx->curX, &_ctx->curY, true))
754 			CORO_SLEEP(1);
755 
756 		/*----------------------------------*\
757 		| For polygons of type TAG and EXIT. |
758 		\*----------------------------------*/
759 		for (_ctx->i = 0; _ctx->i < MAX_POLY; _ctx->i++) {
760 			_ctx->hPoly = GetPolyHandle(_ctx->i);
761 			if ((_ctx->hPoly == NOPOLY) || ((PolyType(_ctx->hPoly) != TAG) &&
762 				(PolyType(_ctx->hPoly) != EXIT)))
763 				continue;
764 
765 			if (!PolyIsPointedTo(_ctx->hPoly)) {
766 				if (IsInPolygon(_ctx->curX, _ctx->curY, _ctx->hPoly)) {
767 					if (TinselV2) {
768 						SetPolyPointedTo(_ctx->hPoly, true);
769 						CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->hPoly, POINTED, 0, false, 0));
770 					} else {
771 						CORO_INVOKE_1(enteringpoly, _ctx->hPoly);
772 					}
773 				}
774 			} else {
775 				if (!IsInPolygon(_ctx->curX, _ctx->curY, _ctx->hPoly)) {
776 					if (TinselV2) {
777 						SetPolyPointedTo(_ctx->hPoly, false);
778 						SetPolyTagWanted(_ctx->hPoly, false, false, 0);
779 						CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, _ctx->hPoly, UNPOINT, 0, false, 0));
780 					} else {
781 						CORO_INVOKE_1(leavingpoly, _ctx->hPoly);
782 					}
783 				}
784 			}
785 		}
786 
787 		if (TinselV2) {
788 			// For each tagged actor
789 			for (_ctx->i = 0; (_ctx->i = NextTaggedActor(_ctx->i)) != 0; ) {
790 				if (!ActorIsPointedTo(_ctx->i)) {
791 					if (InHotSpot(_ctx->i, _ctx->curX, _ctx->curY)) {
792 						SetActorPointedTo(_ctx->i, true);
793 						CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->i, POINTED, false, 0));
794 					}
795 				} else {
796 					if (!InHotSpot(_ctx->i, _ctx->curX, _ctx->curY)) {
797 						SetActorPointedTo(_ctx->i, false);
798 						SetActorTagWanted(_ctx->i, false, false, 0);
799 						CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->i, UNPOINT, false, 0));
800 					}
801 				}
802 			}
803 
804 			// allow re-scheduling
805 			do {
806 				CORO_SLEEP(1);
807 			} while (!g_bPointingActive);
808 		} else {
809 			// allow re-scheduling
810 			CORO_SLEEP(1);
811 		}
812 	}
813 
814 	CORO_END_CODE;
815 }
816 
DisableTags()817 void DisableTags() {
818 	g_bTagsActive = false;
819 }
820 
EnableTags()821 void EnableTags() {
822 	g_bTagsActive = true;
823 }
824 
DisableTagsIfEnabled()825 bool DisableTagsIfEnabled() {
826 	if (g_bTagsActive) {
827 		DisableTags();
828 		return true;
829 	} else
830 		return false;
831 }
832 
833 /**
834  * For testing purposes only.
835  * Causes CursorPositionProcess() to display, or not, the path that the
836  * cursor is in.
837  */
TogglePathDisplay()838 void TogglePathDisplay() {
839 	g_DispPath ^= 1;	// Toggle path display (XOR with true)
840 }
841 
842 
setshowstring()843 void setshowstring() {
844 	g_bShowString = true;
845 }
846 
847 } // End of namespace Tinsel
848