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