/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Handles scrolling */ #include "tinsel/actors.h" #include "tinsel/background.h" #include "tinsel/cursor.h" #include "tinsel/dw.h" #include "tinsel/graphics.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/scroll.h" #include "tinsel/sched.h" #include "tinsel/sysvar.h" #include "tinsel/tinsel.h" namespace Tinsel { //----------------- LOCAL DEFINES -------------------- #define LEFT 'L' #define RIGHT 'R' #define UP 'U' #define DOWN 'D' //----------------- LOCAL GLOBAL DATA -------------------- // FIXME: Avoid non-const global vars static int g_LeftScroll = 0, g_DownScroll = 0; // Number of iterations outstanding static int g_scrollActor = 0; static PMOVER g_pScrollMover = 0; static int g_oldx = 0, g_oldy = 0; /** Boundaries and numbers of boundaries */ static SCROLLDATA g_sd = { { {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }, { {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} }, 0, 0, // DW2 fields 0, 0, 0, 0, 0, 0, 0 }; static int g_ImageH = 0, g_ImageW = 0; static bool g_ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on, // the cursor is kept over that polygon // whilst scrolling static int g_scrollPixelsX = SCROLLPIXELS; static int g_scrollPixelsY = SCROLLPIXELS; /** * Reset the ScrollCursor flag */ void DontScrollCursor() { g_ScrollCursor = false; } /** * Set the ScrollCursor flag */ void DoScrollCursor() { g_ScrollCursor = true; } /** * Configure a no-scroll boundary for a scene. */ void SetNoScroll(int x1, int y1, int x2, int y2) { if (x1 == x2) { /* Vertical line */ assert(g_sd.NumNoH < MAX_HNOSCROLL); g_sd.NoHScroll[g_sd.NumNoH].ln = x1; // X pos of vertical line g_sd.NoHScroll[g_sd.NumNoH].c1 = y1; g_sd.NoHScroll[g_sd.NumNoH].c2 = y2; g_sd.NumNoH++; } else if (y1 == y2) { /* Horizontal line */ assert(g_sd.NumNoV < MAX_VNOSCROLL); g_sd.NoVScroll[g_sd.NumNoV].ln = y1; // Y pos of horizontal line g_sd.NoVScroll[g_sd.NumNoV].c1 = x1; g_sd.NoVScroll[g_sd.NumNoV].c2 = x2; g_sd.NumNoV++; } else { /* No-scroll lines must be horizontal or vertical */ } } /** * Called from scroll process when it thinks that a scroll is in order. * Checks for no-scroll boundaries and sets off a scroll if allowed. */ static void NeedScroll(int direction) { uint i; int BottomLine, RightCol; int Loffset, Toffset; // get background offsets PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); switch (direction) { case LEFT: /* Picture will go left, 'camera' right */ BottomLine = Toffset + (SCREEN_HEIGHT - 1); RightCol = Loffset + (SCREEN_WIDTH - 1); for (i = 0; i < g_sd.NumNoH; i++) { if (RightCol >= g_sd.NoHScroll[i].ln - 1 && RightCol <= g_sd.NoHScroll[i].ln + 1 && ((g_sd.NoHScroll[i].c1 >= Toffset && g_sd.NoHScroll[i].c1 <= BottomLine) || (g_sd.NoHScroll[i].c2 >= Toffset && g_sd.NoHScroll[i].c2 <= BottomLine) || (g_sd.NoHScroll[i].c1 < Toffset && g_sd.NoHScroll[i].c2 > BottomLine))) return; } if (g_LeftScroll <= 0) { if (TinselV2) { g_scrollPixelsX = g_sd.xSpeed; g_LeftScroll += g_sd.xDistance; } else { g_scrollPixelsX = SCROLLPIXELS; g_LeftScroll = RLSCROLL; } } break; case RIGHT: /* Picture will go right, 'camera' left */ BottomLine = Toffset + (SCREEN_HEIGHT - 1); for (i = 0; i < g_sd.NumNoH; i++) { if (Loffset >= g_sd.NoHScroll[i].ln - 1 && Loffset <= g_sd.NoHScroll[i].ln + 1 && ((g_sd.NoHScroll[i].c1 >= Toffset && g_sd.NoHScroll[i].c1 <= BottomLine) || (g_sd.NoHScroll[i].c2 >= Toffset && g_sd.NoHScroll[i].c2 <= BottomLine) || (g_sd.NoHScroll[i].c1 < Toffset && g_sd.NoHScroll[i].c2 > BottomLine))) return; } if (g_LeftScroll >= 0) { if (TinselV2) { g_scrollPixelsX = g_sd.xSpeed; g_LeftScroll -= g_sd.xDistance; } else { g_scrollPixelsX = SCROLLPIXELS; g_LeftScroll = -RLSCROLL; } } break; case UP: /* Picture will go upwards, 'camera' downwards */ BottomLine = Toffset + (SCREEN_HEIGHT - 1); RightCol = Loffset + (SCREEN_WIDTH - 1); for (i = 0; i < g_sd.NumNoV; i++) { if ((BottomLine >= g_sd.NoVScroll[i].ln - 1 && BottomLine <= g_sd.NoVScroll[i].ln + 1) && ((g_sd.NoVScroll[i].c1 >= Loffset && g_sd.NoVScroll[i].c1 <= RightCol) || (g_sd.NoVScroll[i].c2 >= Loffset && g_sd.NoVScroll[i].c2 <= RightCol) || (g_sd.NoVScroll[i].c1 < Loffset && g_sd.NoVScroll[i].c2 > RightCol))) return; } if (g_DownScroll <= 0) { if (TinselV2) { g_scrollPixelsY = g_sd.ySpeed; g_DownScroll += g_sd.yDistance; } else { g_scrollPixelsY = SCROLLPIXELS; g_DownScroll = UDSCROLL; } } break; case DOWN: /* Picture will go downwards, 'camera' upwards */ RightCol = Loffset + (SCREEN_WIDTH - 1); for (i = 0; i < g_sd.NumNoV; i++) { if (Toffset >= g_sd.NoVScroll[i].ln - 1 && Toffset <= g_sd.NoVScroll[i].ln + 1 && ((g_sd.NoVScroll[i].c1 >= Loffset && g_sd.NoVScroll[i].c1 <= RightCol) || (g_sd.NoVScroll[i].c2 >= Loffset && g_sd.NoVScroll[i].c2 <= RightCol) || (g_sd.NoVScroll[i].c1 < Loffset && g_sd.NoVScroll[i].c2 > RightCol))) return; } if (g_DownScroll >= 0) { if (TinselV2) { g_scrollPixelsY = g_sd.ySpeed; g_DownScroll -= g_sd.yDistance; } else { g_scrollPixelsY = SCROLLPIXELS; g_DownScroll = -UDSCROLL; } } break; } } /** * Called from scroll process - Scrolls the image as appropriate. */ static void ScrollImage() { int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag int Loffset, Toffset; int curX, curY; // get background offsets PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); /* * Keeping cursor on a tag? */ if (g_ScrollCursor) { GetCursorXYNoWait(&curX, &curY, true); if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) { OldLoffset = Loffset; OldToffset = Toffset; } else g_ScrollCursor = false; } /* * Horizontal scrolling */ if (g_LeftScroll > 0) { g_LeftScroll -= g_scrollPixelsX; if (g_LeftScroll < 0) { Loffset += g_LeftScroll; g_LeftScroll = 0; } Loffset += g_scrollPixelsX; // Move right if (Loffset > g_ImageW - SCREEN_WIDTH) Loffset = g_ImageW - SCREEN_WIDTH;// Now at extreme right /*** New feature to prop up rickety scroll boundaries ***/ if (TinselV2 && SysVar(SV_MaximumXoffset) && (Loffset > SysVar(SV_MaximumXoffset))) Loffset = SysVar(SV_MaximumXoffset); } else if (g_LeftScroll < 0) { g_LeftScroll += g_scrollPixelsX; if (g_LeftScroll > 0) { Loffset += g_LeftScroll; g_LeftScroll = 0; } Loffset -= g_scrollPixelsX; // Move left if (Loffset < 0) Loffset = 0; // Now at extreme left /*** New feature to prop up rickety scroll boundaries ***/ if (TinselV2 && SysVar(SV_MinimumXoffset) && (Loffset < SysVar(SV_MinimumXoffset))) Loffset = SysVar(SV_MinimumXoffset); } /* * Vertical scrolling */ if (g_DownScroll > 0) { g_DownScroll -= g_scrollPixelsY; if (g_DownScroll < 0) { Toffset += g_DownScroll; g_DownScroll = 0; } Toffset += g_scrollPixelsY; // Move down if (Toffset > g_ImageH - SCREEN_HEIGHT) Toffset = g_ImageH - SCREEN_HEIGHT;// Now at extreme bottom /*** New feature to prop up rickety scroll boundaries ***/ if (TinselV2 && SysVar(SV_MaximumYoffset) && Toffset > SysVar(SV_MaximumYoffset)) Toffset = SysVar(SV_MaximumYoffset); } else if (g_DownScroll < 0) { g_DownScroll += g_scrollPixelsY; if (g_DownScroll > 0) { Toffset += g_DownScroll; g_DownScroll = 0; } Toffset -= g_scrollPixelsY; // Move up if (Toffset < 0) Toffset = 0; // Now at extreme top /*** New feature to prop up rickety scroll boundaries ***/ if (TinselV2 && SysVar(SV_MinimumYoffset) && Toffset < SysVar(SV_MinimumYoffset)) Toffset = SysVar(SV_MinimumYoffset); } /* * Move cursor if keeping cursor on a tag. */ if (g_ScrollCursor) AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset); PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset); } /** * See if the actor on whom the camera is is approaching an edge. * Request a scroll if he is. */ static void MonitorScroll() { int newx, newy; int Loffset, Toffset; /* * Only do it if the actor is there and is visible */ if (!g_pScrollMover || MoverHidden(g_pScrollMover) || !MoverIs(g_pScrollMover)) return; GetActorPos(g_scrollActor, &newx, &newy); if (g_oldx == newx && g_oldy == newy) return; PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); /* * Approaching right side or left side of the screen? */ if (newx > Loffset+SCREEN_WIDTH - RLDISTANCE && Loffset < g_ImageW - SCREEN_WIDTH) { if (newx > g_oldx) NeedScroll(LEFT); } else if (newx < Loffset + RLDISTANCE && Loffset) { if (newx < g_oldx) NeedScroll(RIGHT); } /* * Approaching bottom or top of the screen? */ if (newy > Toffset+SCREEN_HEIGHT-DDISTANCE && Toffset < g_ImageH-SCREEN_HEIGHT) { if (newy > g_oldy) NeedScroll(UP); } else if (Toffset && newy < Toffset + UDISTANCE + GetActorBottom(g_scrollActor) - GetActorTop(g_scrollActor)) { if (newy < g_oldy) NeedScroll(DOWN); } g_oldx = newx; g_oldy = newy; } static void RestoreScrollDefaults() { g_sd.xTrigger = SysVar(SV_SCROLL_XTRIGGER); g_sd.xDistance = SysVar(SV_SCROLL_XDISTANCE); g_sd.xSpeed = SysVar(SV_SCROLL_XSPEED); g_sd.yTriggerTop = SysVar(SV_SCROLL_YTRIGGERTOP); g_sd.yTriggerBottom= SysVar(SV_SCROLL_YTRIGGERBOT); g_sd.yDistance = SysVar(SV_SCROLL_YDISTANCE); g_sd.ySpeed = SysVar(SV_SCROLL_YSPEED); } /** * Does the obvious - called at the end of a scene. */ void DropScroll() { g_sd.NumNoH = g_sd.NumNoV = 0; if (TinselV2) { g_LeftScroll = g_DownScroll = 0; // No iterations outstanding g_oldx = g_oldy = 0; g_scrollPixelsX = g_sd.xSpeed; g_scrollPixelsY = g_sd.ySpeed; RestoreScrollDefaults(); } } /** * Decide when to scroll and scroll when decided to. */ void ScrollProcess(CORO_PARAM, const void *) { // COROUTINE CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); // In Tinsel v2, scenes may play movies, so the background may not always // already be initialized like it is in v1 while (!GetBgObject()) CORO_SLEEP(1); g_ImageH = BgHeight(); // Dimensions g_ImageW = BgWidth(); // of this scene. // Give up if there'll be no purpose in this process if (g_ImageW == SCREEN_WIDTH && g_ImageH == SCREEN_HEIGHT) CORO_KILL_SELF(); if (!TinselV2) { g_LeftScroll = g_DownScroll = 0; // No iterations outstanding g_oldx = g_oldy = 0; g_scrollPixelsX = g_scrollPixelsY = SCROLLPIXELS; } if (!g_scrollActor) g_scrollActor = GetLeadId(); g_pScrollMover = GetMover(g_scrollActor); while (1) { MonitorScroll(); // Set scroll requirement if (g_LeftScroll || g_DownScroll) // Scroll if required ScrollImage(); CORO_SLEEP(1); // allow re-scheduling } CORO_END_CODE; } /** * Change which actor the camera is following. */ void ScrollFocus(int ano) { if (g_scrollActor != ano) { g_oldx = g_oldy = 0; g_scrollActor = ano; g_pScrollMover = ano ? GetMover(g_scrollActor) : NULL; } } /** * Returns the actor which the camera is following */ int GetScrollFocus() { return g_scrollActor; } /** * Scroll to abslote position. */ void ScrollTo(int x, int y, int xIter, int yIter) { int Loffset, Toffset; // for background offsets g_scrollPixelsX = xIter != 0 ? xIter : (TinselV2 ? g_sd.xSpeed : SCROLLPIXELS); g_scrollPixelsY = yIter != 0 ? yIter : (TinselV2 ? g_sd.ySpeed : SCROLLPIXELS); PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets g_LeftScroll = x - Loffset; g_DownScroll = y - Toffset; } /** * Kill of any current scroll. */ void KillScroll() { g_LeftScroll = g_DownScroll = 0; } void GetNoScrollData(SCROLLDATA *ssd) { memcpy(ssd, &g_sd, sizeof(SCROLLDATA)); } void RestoreNoScrollData(SCROLLDATA *ssd) { memcpy(&g_sd, ssd, sizeof(SCROLLDATA)); } /** * SetScrollParameters */ void SetScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop, int yTriggerBottom, int yDistance, int ySpeed) { if (xTrigger == 0 && xDistance == 0 && xSpeed == 0 && yTriggerTop == 0 && yTriggerBottom && yDistance == 0 && ySpeed == 0) { // Restore defaults RestoreScrollDefaults(); } else { if (xTrigger) g_sd.xTrigger = xTrigger; if (xDistance) g_sd.xDistance = xDistance; if (xSpeed) g_sd.xSpeed = xSpeed; if (yTriggerTop) g_sd.yTriggerTop = yTriggerTop; if (yTriggerBottom) g_sd.yTriggerBottom = yTriggerBottom; if (yDistance) g_sd.yDistance = yDistance; if (ySpeed) g_sd.ySpeed = ySpeed; } } bool IsScrolling() { return (g_LeftScroll || g_DownScroll); } } // End of namespace Tinsel