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  * Handles scrolling
22  */
23 
24 #include "tinsel/actors.h"
25 #include "tinsel/background.h"
26 #include "tinsel/cursor.h"
27 #include "tinsel/dw.h"
28 #include "tinsel/graphics.h"
29 #include "tinsel/polygons.h"
30 #include "tinsel/rince.h"
31 #include "tinsel/scroll.h"
32 #include "tinsel/sched.h"
33 #include "tinsel/sysvar.h"
34 #include "tinsel/tinsel.h"
35 
36 namespace Tinsel {
37 
38 //----------------- LOCAL DEFINES --------------------
39 
40 #define LEFT	'L'
41 #define RIGHT	'R'
42 #define UP	'U'
43 #define DOWN	'D'
44 
45 
46 
47 //----------------- LOCAL GLOBAL DATA --------------------
48 
49 // FIXME: Avoid non-const global vars
50 
51 
52 static int g_LeftScroll = 0, g_DownScroll = 0;	// Number of iterations outstanding
53 
54 static int g_scrollActor = 0;
55 static PMOVER g_pScrollMover = 0;
56 static int g_oldx = 0, g_oldy = 0;
57 
58 /** Boundaries and numbers of boundaries */
59 static SCROLLDATA g_sd = {
60 		{
61 			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
62 			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
63 		},
64 		{
65 			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
66 			{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
67 		},
68 		0,
69 		0,
70 		// DW2 fields
71 		0,
72 		0,
73 		0,
74 		0,
75 		0,
76 		0,
77 		0
78 	};
79 
80 static int g_ImageH = 0, g_ImageW = 0;
81 
82 static bool g_ScrollCursor = 0;	// If a TAG or EXIT polygon is clicked on,
83 				// the cursor is kept over that polygon
84 				// whilst scrolling
85 
86 static int g_scrollPixelsX = SCROLLPIXELS;
87 static int g_scrollPixelsY = SCROLLPIXELS;
88 
89 
90 /**
91  * Reset the ScrollCursor flag
92  */
DontScrollCursor()93 void DontScrollCursor() {
94 	g_ScrollCursor = false;
95 }
96 
97 /**
98  * Set the ScrollCursor flag
99  */
DoScrollCursor()100 void DoScrollCursor() {
101 	g_ScrollCursor = true;
102 }
103 
104 /**
105  * Configure a no-scroll boundary for a scene.
106  */
SetNoScroll(int x1,int y1,int x2,int y2)107 void SetNoScroll(int x1, int y1, int x2, int y2) {
108 	if (x1 == x2) {
109 		/* Vertical line */
110 		assert(g_sd.NumNoH < MAX_HNOSCROLL);
111 
112 		g_sd.NoHScroll[g_sd.NumNoH].ln = x1;	// X pos of vertical line
113 		g_sd.NoHScroll[g_sd.NumNoH].c1 = y1;
114 		g_sd.NoHScroll[g_sd.NumNoH].c2 = y2;
115 		g_sd.NumNoH++;
116 	} else if (y1 == y2) {
117 		/* Horizontal line */
118 		assert(g_sd.NumNoV < MAX_VNOSCROLL);
119 
120 		g_sd.NoVScroll[g_sd.NumNoV].ln = y1;	// Y pos of horizontal line
121 		g_sd.NoVScroll[g_sd.NumNoV].c1 = x1;
122 		g_sd.NoVScroll[g_sd.NumNoV].c2 = x2;
123 		g_sd.NumNoV++;
124 	} else {
125 		/* No-scroll lines must be horizontal or vertical */
126 	}
127 }
128 
129 /**
130  * Called from scroll process when it thinks that a scroll is in order.
131  * Checks for no-scroll boundaries and sets off a scroll if allowed.
132  */
NeedScroll(int direction)133 static void NeedScroll(int direction) {
134 	uint	i;
135 	int	BottomLine, RightCol;
136 	int	Loffset, Toffset;
137 
138 	// get background offsets
139 	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
140 
141 	switch (direction) {
142 	case LEFT:	  /* Picture will go left, 'camera' right */
143 
144 		BottomLine = Toffset + (SCREEN_HEIGHT - 1);
145 		RightCol = Loffset + (SCREEN_WIDTH - 1);
146 
147 		for (i = 0; i < g_sd.NumNoH; i++) {
148 			if (RightCol >= g_sd.NoHScroll[i].ln - 1 && RightCol <= g_sd.NoHScroll[i].ln + 1 &&
149 					((g_sd.NoHScroll[i].c1 >= Toffset && g_sd.NoHScroll[i].c1 <= BottomLine) ||
150 					(g_sd.NoHScroll[i].c2 >= Toffset && g_sd.NoHScroll[i].c2 <= BottomLine) ||
151 					(g_sd.NoHScroll[i].c1 < Toffset && g_sd.NoHScroll[i].c2 > BottomLine)))
152 				return;
153 		}
154 
155 		if (g_LeftScroll <= 0) {
156 			if (TinselV2) {
157 				g_scrollPixelsX = g_sd.xSpeed;
158 				g_LeftScroll += g_sd.xDistance;
159 			} else {
160 				g_scrollPixelsX = SCROLLPIXELS;
161 				g_LeftScroll = RLSCROLL;
162 			}
163 		}
164 		break;
165 
166 	case RIGHT:	 /* Picture will go right, 'camera' left */
167 
168 		BottomLine = Toffset + (SCREEN_HEIGHT - 1);
169 
170 		for (i = 0; i < g_sd.NumNoH; i++) {
171 			if (Loffset >= g_sd.NoHScroll[i].ln - 1 && Loffset <= g_sd.NoHScroll[i].ln + 1 &&
172 					((g_sd.NoHScroll[i].c1 >= Toffset && g_sd.NoHScroll[i].c1 <= BottomLine) ||
173 					(g_sd.NoHScroll[i].c2 >= Toffset && g_sd.NoHScroll[i].c2 <= BottomLine) ||
174 					(g_sd.NoHScroll[i].c1 < Toffset && g_sd.NoHScroll[i].c2 > BottomLine)))
175 				return;
176 		}
177 
178 		if (g_LeftScroll >= 0) {
179 			if (TinselV2) {
180 				g_scrollPixelsX = g_sd.xSpeed;
181 				g_LeftScroll -= g_sd.xDistance;
182 			} else {
183 				g_scrollPixelsX = SCROLLPIXELS;
184 				g_LeftScroll = -RLSCROLL;
185 			}
186 		}
187 		break;
188 
189 	case UP:		/* Picture will go upwards, 'camera' downwards  */
190 
191 		BottomLine = Toffset + (SCREEN_HEIGHT - 1);
192 		RightCol = Loffset + (SCREEN_WIDTH - 1);
193 
194 		for (i = 0; i < g_sd.NumNoV; i++) {
195 			if ((BottomLine >= g_sd.NoVScroll[i].ln - 1 && BottomLine <= g_sd.NoVScroll[i].ln + 1) &&
196 					((g_sd.NoVScroll[i].c1 >= Loffset && g_sd.NoVScroll[i].c1 <= RightCol) ||
197 					(g_sd.NoVScroll[i].c2 >= Loffset && g_sd.NoVScroll[i].c2 <= RightCol) ||
198 					(g_sd.NoVScroll[i].c1 < Loffset && g_sd.NoVScroll[i].c2 > RightCol)))
199 				return;
200 			}
201 
202 		if (g_DownScroll <= 0) {
203 			if (TinselV2) {
204 				g_scrollPixelsY = g_sd.ySpeed;
205 				g_DownScroll += g_sd.yDistance;
206 			} else {
207 				g_scrollPixelsY = SCROLLPIXELS;
208 				g_DownScroll = UDSCROLL;
209 			}
210 		}
211 		break;
212 
213 	case DOWN:	  /* Picture will go downwards, 'camera' upwards  */
214 
215 		RightCol = Loffset + (SCREEN_WIDTH - 1);
216 
217 		for (i = 0; i < g_sd.NumNoV; i++) {
218 			if (Toffset >= g_sd.NoVScroll[i].ln - 1  && Toffset <= g_sd.NoVScroll[i].ln + 1  &&
219 					((g_sd.NoVScroll[i].c1 >= Loffset && g_sd.NoVScroll[i].c1 <= RightCol) ||
220 					(g_sd.NoVScroll[i].c2 >= Loffset && g_sd.NoVScroll[i].c2 <= RightCol) ||
221 					(g_sd.NoVScroll[i].c1 < Loffset && g_sd.NoVScroll[i].c2 > RightCol)))
222 				return;
223 		}
224 
225 		if (g_DownScroll >= 0) {
226 			if (TinselV2) {
227 				g_scrollPixelsY = g_sd.ySpeed;
228 				g_DownScroll -= g_sd.yDistance;
229 			} else {
230 				g_scrollPixelsY = SCROLLPIXELS;
231 				g_DownScroll = -UDSCROLL;
232 			}
233 		}
234 		break;
235 	}
236 }
237 
238 /**
239  * Called from scroll process - Scrolls the image as appropriate.
240  */
ScrollImage()241 static void ScrollImage() {
242 	int OldLoffset = 0, OldToffset = 0;	// Used when keeping cursor on a tag
243 	int Loffset, Toffset;
244 	int curX, curY;
245 
246 	// get background offsets
247 	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
248 
249 	/*
250 	 * Keeping cursor on a tag?
251 	 */
252 	if (g_ScrollCursor) {
253 		GetCursorXYNoWait(&curX, &curY, true);
254 		if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) {
255 			OldLoffset = Loffset;
256 			OldToffset = Toffset;
257 		} else
258 			g_ScrollCursor = false;
259 	}
260 
261 	/*
262 	 * Horizontal scrolling
263 	 */
264 	if (g_LeftScroll > 0) {
265 		g_LeftScroll -= g_scrollPixelsX;
266 		if (g_LeftScroll < 0) {
267 			Loffset += g_LeftScroll;
268 			g_LeftScroll = 0;
269 		}
270 		Loffset += g_scrollPixelsX;		// Move right
271 		if (Loffset > g_ImageW - SCREEN_WIDTH)
272 			Loffset = g_ImageW - SCREEN_WIDTH;// Now at extreme right
273 
274 		/*** New feature to prop up rickety scroll boundaries ***/
275 		if (TinselV2 && SysVar(SV_MaximumXoffset) &&  (Loffset > SysVar(SV_MaximumXoffset)))
276 			Loffset = SysVar(SV_MaximumXoffset);
277 
278 	} else if (g_LeftScroll < 0) {
279 		g_LeftScroll += g_scrollPixelsX;
280 		if (g_LeftScroll > 0) {
281 			Loffset += g_LeftScroll;
282 			g_LeftScroll = 0;
283 		}
284 		Loffset -= g_scrollPixelsX;	// Move left
285 		if (Loffset < 0)
286 			Loffset = 0;		// Now at extreme left
287 
288 		/*** New feature to prop up rickety scroll boundaries ***/
289 		if (TinselV2 && SysVar(SV_MinimumXoffset) &&  (Loffset < SysVar(SV_MinimumXoffset)))
290 			Loffset = SysVar(SV_MinimumXoffset);
291 	}
292 
293 	/*
294 	 * Vertical scrolling
295 	 */
296 	if (g_DownScroll > 0) {
297 		g_DownScroll -= g_scrollPixelsY;
298 		if (g_DownScroll < 0) {
299 			Toffset += g_DownScroll;
300 			g_DownScroll = 0;
301 		}
302 		Toffset += g_scrollPixelsY;		// Move down
303 
304 		if (Toffset > g_ImageH - SCREEN_HEIGHT)
305 			Toffset = g_ImageH - SCREEN_HEIGHT;// Now at extreme bottom
306 
307 		/*** New feature to prop up rickety scroll boundaries ***/
308 		if (TinselV2 && SysVar(SV_MaximumYoffset) &&  Toffset > SysVar(SV_MaximumYoffset))
309 			Toffset = SysVar(SV_MaximumYoffset);
310 
311 	} else if (g_DownScroll < 0) {
312 		g_DownScroll += g_scrollPixelsY;
313 		if (g_DownScroll > 0) {
314 			Toffset += g_DownScroll;
315 			g_DownScroll = 0;
316 		}
317 		Toffset -= g_scrollPixelsY;		// Move up
318 
319 		if (Toffset < 0)
320 			Toffset = 0;			// Now at extreme top
321 
322 		/*** New feature to prop up rickety scroll boundaries ***/
323 		if (TinselV2 && SysVar(SV_MinimumYoffset) &&  Toffset < SysVar(SV_MinimumYoffset))
324 			Toffset = SysVar(SV_MinimumYoffset);
325 	}
326 
327 	/*
328 	 * Move cursor if keeping cursor on a tag.
329 	 */
330 	if (g_ScrollCursor)
331 		AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset);
332 
333 	PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset);
334 }
335 
336 
337 /**
338  * See if the actor on whom the camera is is approaching an edge.
339  * Request a scroll if he is.
340  */
MonitorScroll()341 static void MonitorScroll() {
342 	int newx, newy;
343 	int Loffset, Toffset;
344 
345 	/*
346 	 * Only do it if the actor is there and is visible
347 	 */
348 	if (!g_pScrollMover || MoverHidden(g_pScrollMover) || !MoverIs(g_pScrollMover))
349 		return;
350 
351 	GetActorPos(g_scrollActor, &newx, &newy);
352 
353 	if (g_oldx == newx && g_oldy == newy)
354 		return;
355 
356 	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
357 
358 	/*
359 	 * Approaching right side or left side of the screen?
360 	 */
361 	if (newx > Loffset+SCREEN_WIDTH - RLDISTANCE && Loffset < g_ImageW - SCREEN_WIDTH) {
362 		if (newx > g_oldx)
363 				NeedScroll(LEFT);
364 	} else if (newx < Loffset + RLDISTANCE  &&  Loffset) {
365 		if (newx < g_oldx)
366 				NeedScroll(RIGHT);
367 	}
368 
369 	/*
370 	 * Approaching bottom or top of the screen?
371 	 */
372 	if (newy > Toffset+SCREEN_HEIGHT-DDISTANCE && Toffset < g_ImageH-SCREEN_HEIGHT) {
373 		if (newy > g_oldy)
374 				NeedScroll(UP);
375 	} else if (Toffset && newy < Toffset + UDISTANCE + GetActorBottom(g_scrollActor) - GetActorTop(g_scrollActor)) {
376 		if (newy < g_oldy)
377 				NeedScroll(DOWN);
378 	}
379 
380 	g_oldx = newx;
381 	g_oldy = newy;
382 }
383 
RestoreScrollDefaults()384 static void RestoreScrollDefaults() {
385 	g_sd.xTrigger		= SysVar(SV_SCROLL_XTRIGGER);
386 	g_sd.xDistance	= SysVar(SV_SCROLL_XDISTANCE);
387 	g_sd.xSpeed		= SysVar(SV_SCROLL_XSPEED);
388 	g_sd.yTriggerTop	= SysVar(SV_SCROLL_YTRIGGERTOP);
389 	g_sd.yTriggerBottom= SysVar(SV_SCROLL_YTRIGGERBOT);
390 	g_sd.yDistance	= SysVar(SV_SCROLL_YDISTANCE);
391 	g_sd.ySpeed		= SysVar(SV_SCROLL_YSPEED);
392 }
393 
394 /**
395  * Does the obvious - called at the end of a scene.
396  */
DropScroll()397 void DropScroll() {
398 	g_sd.NumNoH = g_sd.NumNoV = 0;
399 	if (TinselV2) {
400 		g_LeftScroll = g_DownScroll = 0;		// No iterations outstanding
401 		g_oldx = g_oldy = 0;
402 		g_scrollPixelsX = g_sd.xSpeed;
403 		g_scrollPixelsY = g_sd.ySpeed;
404 		RestoreScrollDefaults();
405 	}
406 }
407 
408 /**
409  * Decide when to scroll and scroll when decided to.
410  */
ScrollProcess(CORO_PARAM,const void *)411 void ScrollProcess(CORO_PARAM, const void *) {
412 	// COROUTINE
413 	CORO_BEGIN_CONTEXT;
414 	CORO_END_CONTEXT(_ctx);
415 
416 	CORO_BEGIN_CODE(_ctx);
417 
418 	// In Tinsel v2, scenes may play movies, so the background may not always
419 	// already be initialized like it is in v1
420 	while (!GetBgObject())
421 		CORO_SLEEP(1);
422 
423 	g_ImageH = BgHeight();		// Dimensions
424 	g_ImageW = BgWidth();		//  of this scene.
425 
426 	// Give up if there'll be no purpose in this process
427 	if (g_ImageW == SCREEN_WIDTH  &&  g_ImageH == SCREEN_HEIGHT)
428 		CORO_KILL_SELF();
429 
430 	if (!TinselV2) {
431 		g_LeftScroll = g_DownScroll = 0;		// No iterations outstanding
432 		g_oldx = g_oldy = 0;
433 		g_scrollPixelsX = g_scrollPixelsY = SCROLLPIXELS;
434 	}
435 
436 	if (!g_scrollActor)
437 		g_scrollActor = GetLeadId();
438 
439 	g_pScrollMover = GetMover(g_scrollActor);
440 
441 	while (1) {
442 		MonitorScroll();		// Set scroll requirement
443 
444 		if (g_LeftScroll || g_DownScroll)	// Scroll if required
445 			ScrollImage();
446 
447 		CORO_SLEEP(1);		// allow re-scheduling
448 	}
449 
450 	CORO_END_CODE;
451 }
452 
453 /**
454  * Change which actor the camera is following.
455  */
ScrollFocus(int ano)456 void ScrollFocus(int ano) {
457 	if (g_scrollActor != ano) {
458 		g_oldx = g_oldy = 0;
459 		g_scrollActor = ano;
460 
461 		g_pScrollMover = ano ? GetMover(g_scrollActor) : NULL;
462 	}
463 }
464 
465 /**
466  * Returns the actor which the camera is following
467  */
GetScrollFocus()468 int GetScrollFocus() {
469 	return g_scrollActor;
470 }
471 
472 
473 /**
474  * Scroll to abslote position.
475  */
ScrollTo(int x,int y,int xIter,int yIter)476 void ScrollTo(int x, int y, int xIter, int yIter) {
477 	int Loffset, Toffset;		// for background offsets
478 
479 	g_scrollPixelsX = xIter != 0 ? xIter : (TinselV2 ? g_sd.xSpeed : SCROLLPIXELS);
480 	g_scrollPixelsY = yIter != 0 ? yIter : (TinselV2 ? g_sd.ySpeed : SCROLLPIXELS);
481 
482 	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);	// get background offsets
483 
484 	g_LeftScroll = x - Loffset;
485 	g_DownScroll = y - Toffset;
486 }
487 
488 /**
489  * Kill of any current scroll.
490  */
KillScroll()491 void KillScroll() {
492 	g_LeftScroll = g_DownScroll = 0;
493 }
494 
495 
GetNoScrollData(SCROLLDATA * ssd)496 void GetNoScrollData(SCROLLDATA *ssd) {
497 	memcpy(ssd, &g_sd, sizeof(SCROLLDATA));
498 }
499 
RestoreNoScrollData(SCROLLDATA * ssd)500 void RestoreNoScrollData(SCROLLDATA *ssd) {
501 	memcpy(&g_sd, ssd, sizeof(SCROLLDATA));
502 }
503 
504 /**
505  * SetScrollParameters
506  */
SetScrollParameters(int xTrigger,int xDistance,int xSpeed,int yTriggerTop,int yTriggerBottom,int yDistance,int ySpeed)507 void SetScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop,
508 		int yTriggerBottom, int yDistance, int ySpeed) {
509 	if (xTrigger == 0 && xDistance == 0 && xSpeed == 0
510 	 && yTriggerTop == 0 && yTriggerBottom && yDistance == 0 && ySpeed == 0) {
511 		// Restore defaults
512 		RestoreScrollDefaults();
513 	} else {
514 		if (xTrigger)
515 			g_sd.xTrigger = xTrigger;
516 		if (xDistance)
517 			g_sd.xDistance = xDistance;
518 		if (xSpeed)
519 			g_sd.xSpeed = xSpeed;
520 		if (yTriggerTop)
521 			g_sd.yTriggerTop = yTriggerTop;
522 		if (yTriggerBottom)
523 			g_sd.yTriggerBottom = yTriggerBottom;
524 		if (yDistance)
525 			g_sd.yDistance = yDistance;
526 		if (ySpeed)
527 			g_sd.ySpeed = ySpeed;
528 	}
529 }
530 
IsScrolling()531 bool IsScrolling() {
532 	return (g_LeftScroll || g_DownScroll);
533 }
534 
535 } // End of namespace Tinsel
536