/* 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. * * $URL$ * $Id$ * * Handles walking and use of the path system. * * Contains the dodgiest code in the whole system. */ #include "tinsel/actors.h" #include "tinsel/anim.h" #include "tinsel/background.h" #include "tinsel/cursor.h" #include "tinsel/dw.h" #include "tinsel/graphics.h" #include "tinsel/move.h" #include "tinsel/multiobj.h" // multi-part object defintions etc. #include "tinsel/object.h" #include "tinsel/polygons.h" #include "tinsel/rince.h" #include "tinsel/scroll.h" #include "tinsel/tinlib.h" // For stand() namespace Tinsel { //----------------- DEVELOPMENT OPTIONS -------------------- #define SLOW_RINCE_DOWN 0 //----------------- EXTERNAL FUNCTIONS --------------------- // in BG.C extern int BackgroundWidth(void); extern int BackgroundHeight(void); // in POLYGONS.C // Deliberatley defined here, and not in polygons.h HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta); //----------------- LOCAL DEFINES -------------------- #define XMDIST 4 #define XHMDIST 2 #define YMDIST 2 #define YHMDIST 2 #define XTHERE 1 #define XRESTRICT 2 #define YTHERE 4 #define YRESTRICT 8 #define STUCK 16 #define LEAVING_PATH 0x100 #define ENTERING_BLOCK 0x200 #define ENTERING_MBLOCK 0x400 #define ALL_SORTED 1 #define NOT_SORTED 0 //----------------- LOCAL GLOBAL DATA -------------------- #if SLOW_RINCE_DOWN static int Interlude = 0; // For slowing down walking, for testing static int BogusVar = 0; // For slowing down walking, for testing #endif static int32 DefaultRefer = 0; static int hSlowVar = 0; // used by MoveActor() //----------------- FORWARD REFERENCES -------------------- static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p, bool bOver, bool bBodge, PMACTOR pActor, PMACTOR *collisionActor = 0); #if SLOW_RINCE_DOWN /** * AddInterlude */ void AddInterlude(int n) { Interlude += n; if (Interlude < 0) Interlude = 0; } #endif /** * Given (x, y) of a click within a path polygon, checks that the * co-ordinates are not within a blocking polygon. If it is not, the * destination is the click point, otherwise tries to find a legal point * below or above the click point. * Returns: * NOT_SORTED - if a destination is worked out (movement required) * ALL_SORTED - no destination found (so no movement required) */ static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { int Loffset, Toffset; int i; /*-------------------------------------- Clicked within a path, go to where requested unless blocked. --------------------------------------*/ if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) { // Not in a blocking polygon - go to where requested. *ptgtX = clickX; *ptgtY = clickY; } else { /*------------------------------------------------------ In a Blocking polygon - try searching down and up. If still nowhere (for now) give up! ------------------------------------------------------*/ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) { // Don't leave the path system if (InPolygon(clickX, i, PATH) == NOPOLY) { i = SCREEN_HEIGHT; break; } if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; } } if (i == SCREEN_HEIGHT) { for (i = clickY-1; i >= Toffset; i--) { // Don't leave the path system if (InPolygon(clickX, i, PATH) == NOPOLY) { i = -1; break; } if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; } } } if (i < 0) { return ALL_SORTED; } } return NOT_SORTED; } /** * Given (x, y) of a click within a referral polygon, works out the * destination according to the referral type. * Returns: * NOT_SORTED - if a destination is worked out (movement required) * ALL_SORTED - no destination found (so no movement required) */ static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) { int i; int end; // Extreme of the scene int Loffset, Toffset; PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); *ptgtX = *ptgtY = -1; switch (PolySubtype(hRefpoly)) { case REF_POINT: // Go to specified node getPolyNode(hRefpoly, ptgtX, ptgtY); assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point break; case REF_DOWN: // Search downwards end = BackgroundHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY && InPolygon(clickX, i, BLOCKING) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; } break; case REF_UP: // Search upwards for (i = clickY-1; i >= 0; i--) if (InPolygon(clickX, i, PATH) != NOPOLY && InPolygon(clickX, i, BLOCKING) == NOPOLY) { *ptgtX = clickX; *ptgtY = i; break; } break; case REF_RIGHT: // Search to the right end = BackgroundWidth(); for (i = clickX+1; i < end; i++) if (InPolygon(i, clickY, PATH) != NOPOLY && InPolygon(i, clickY, BLOCKING) == NOPOLY) { *ptgtX = i; *ptgtY = clickY; break; } break; case REF_LEFT: // Search to the left for (i = clickX-1; i >= 0; i--) if (InPolygon(i, clickY, PATH) != NOPOLY && InPolygon(i, clickY, BLOCKING) == NOPOLY) { *ptgtX = i; *ptgtY = clickY; break; } break; } if (*ptgtX != -1 && *ptgtY != -1) { return NOT_SORTED; } else return ALL_SORTED; } /** * Given (x, y) of a click, works out the destination according to the * default referral type. * Returns: * NOT_SORTED - if a destination is worked out (movement required) * ALL_SORTED - no destination found (so no movement required) */ static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { int i; int end; // Extreme of the scene int Loffset, Toffset; PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); switch (DefaultRefer) { case REF_DEFAULT: // Try searching down and up (onscreen). for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } for (i = clickY-1; i >= Toffset; i--) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } // Try searching down and up (offscreen). end = BackgroundHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } for (i = clickY-1; i >= 0; i--) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } break; case REF_UP: for (i = clickY-1; i >= 0; i--) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } break; case REF_DOWN: end = BackgroundHeight(); for (i = clickY+1; i < end; i++) if (InPolygon(clickX, i, PATH) != NOPOLY) { return ClickedOnPath(clickX, i, ptgtX, ptgtY); } break; case REF_LEFT: for (i = clickX-1; i >= 0; i--) if (InPolygon(i, clickY, PATH) != NOPOLY) { return ClickedOnPath(i, clickY, ptgtX, ptgtY); } break; case REF_RIGHT: end = BackgroundWidth(); for (i = clickX + 1; i < end; i++) if (InPolygon(i, clickY, PATH) != NOPOLY) { return ClickedOnPath(i, clickY, ptgtX, ptgtY); } break; } // Going nowhere! return ALL_SORTED; } /** * Given (x, y) of the click, ascertains whether the click is within a * path, within a referral poly, or niether. The appropriate function * then gets called to give us a revised destination. * Returns: * NOT_SORTED - if a destination is worked out (movement required) * ALL_SORTED - no destination found (so no movement required) */ static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) { HPOLYGON hPoly; /*-------------------------------------- Clicked within a path? if not, within a referral poly? if not, try and sort something out. ---------------------------------------*/ if (InPolygon(clickX, clickY, PATH) != NOPOLY) { return ClickedOnPath(clickX, clickY, ptgtX, ptgtY); } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) { return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY); } else { return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY); } } /** * Work out which reel to adopt for a section of movement. */ static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) { int xchange = 0, ychange = 0; enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir; enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir; DIRREEL reel = lastreel; // Leave alone if can't decide /* * Determine size and direction of X movement. * i.e. left, right, none or not allowed. */ if (getPolyReelType(hPath) == REEL_VERT) xdir = X_NO; else if (tox == -1) xdir = X_NONE; else { xchange = tox - fromx; if (xchange > 0) xdir = X_RIGHT; else if (xchange < 0) { xchange = -xchange; xdir = X_LEFT; } else xdir = X_NONE; } /* * Determine size and direction of Y movement. * i.e. up, down, none or not allowed. */ if (getPolyReelType(hPath) == REEL_HORIZ) ydir = Y_NO; else if (toy == -1) ydir = Y_NONE; else { ychange = toy - fromy; if (ychange > 0) ydir = Y_DOWN; else if (ychange < 0) { ychange = -ychange; ydir = Y_UP; } else ydir = Y_NONE; } /* * Some adjustment to allow for different x and y pixell sizes. */ ychange += ychange; // Double y distance to cover /* * Determine which reel to use. */ if (xdir == X_NO) { // Forced to be FORWARD or AWAY switch (ydir) { case Y_DOWN: reel = FORWARD; break; case Y_UP: reel = AWAY; break; default: if (reel != AWAY) // No gratuitous turn reel = FORWARD; break; } } else if (ydir == Y_NO) { // Forced to be LEFTREEL or RIGHTREEL switch (xdir) { case X_LEFT: reel = LEFTREEL; break; case X_RIGHT: reel = RIGHTREEL; break; default: if (reel != LEFTREEL) // No gratuitous turn reel = RIGHTREEL; break; } } else if (xdir != X_NONE || ydir != Y_NONE) { if (xdir == X_NONE) reel = (ydir == Y_DOWN) ? FORWARD : AWAY; else if (ydir == Y_NONE) reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; else { bool DontBother = false; if (xchange <= 4 && ychange <= 4) { switch (reel) { case LEFTREEL: if (xdir == X_LEFT) DontBother = true; break; case RIGHTREEL: if (xdir == X_RIGHT) DontBother = true; break; case FORWARD: if (ydir == Y_DOWN) DontBother = true; break; case AWAY: if (ydir == Y_UP) DontBother = true; break; } } if (!DontBother) { if (xchange > ychange) reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; else reel = (ydir == Y_DOWN) ? FORWARD : AWAY; } } } return reel; } /** * Haven't moved, look towards the cursor. */ static void GotThereWithoutMoving(PMACTOR pActor) { int curX, curY; DIRREEL reel; if (!pActor->TagReelRunning) { GetCursorXYNoWait(&curX, &curY, true); reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath); if (reel != pActor->dirn) SetMActorWalkReel(pActor, reel, pActor->scale, false); } } /** * Arrived at final destination. */ static void GotThere(PMACTOR pActor) { pActor->targetX = pActor->targetY = -1; // 4/1/95 pActor->ItargetX = pActor->ItargetY = -1; pActor->UtargetX = pActor->UtargetY = -1; // Perhaps we have'nt moved. if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy) GotThereWithoutMoving(pActor); ReTagActor(pActor->actorID); // Tag allowed while stationary SetMActorStanding(pActor); pActor->bMoving = false; } enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY }; /** * Can we get straight there? */ static cgt CanGetThere(PMACTOR pActor, int tx, int ty) { int s1, s2; // s2 not used here! HPOLYGON hS2p; // nor is s2p! int nextx, nexty; int targetX = tx; int targetY = ty; // Ultimate destination int x = pActor->objx; int y = pActor->objy; // Present position while (targetX != -1 || targetY != -1) { NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, &s1, &s2, &hS2p, pActor->over, false, pActor); if (s1 == (XTHERE | YTHERE)) { return GT_OK; // Can get there directly. } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { return GT_MAY; // Can't get there directly. } else if (s1 & STUCK) { if (s2 == LEAVING_PATH) return GT_NOTL; // Can't get there. else return GT_NOTB; // Can't get there. } else if (x == nextx && y == nexty) { return GT_NOT2; // Can't get there. } x = nextx; y = nexty; } return GT_MAY; } /** * Set final destination. */ static void SetMoverUltDest(PMACTOR pActor, int x, int y) { pActor->UtargetX = x; pActor->UtargetY = y; pActor->hUpath = InPolygon(x, y, PATH); assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination } /** * Set intermediate destination. * * If in final destination path, go straight to target. * If in a neighbouring path to the final destination, if the target path * is a follow nodes path, head for the end node, otherwise head straight * for the target. * Otherwise, head towards the pseudo-centre or end node of the first * en-route path. */ static void SetMoverIntDest(PMACTOR pActor, int x, int y) { HPOLYGON hIpath, hTpath; int node; hTpath = InPolygon(x, y, PATH); // Target path #ifdef DEBUG if (!pActor->bIgPath) assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path #endif if (pActor->hCpath == hTpath || pActor->bIgPath || IsInPolygon(pActor->objx, pActor->objy, hTpath)) { // In destination path - head straight for the target. pActor->ItargetX = x; pActor->ItargetY = y; pActor->hIpath = hTpath; } else if (IsAdjacentPath(pActor->hCpath, hTpath)) { // In path adjacent to target if (PolySubtype(hTpath) != NODE) { // Target path is normal - head for target. // Added 26/01/95, innroom if (CanGetThere(pActor, x, y) == GT_NOTL) { NearestCorner(&x, &y, pActor->hCpath, hTpath); } pActor->ItargetX = x; pActor->ItargetY = y; } else { // Target path is node - head for end node. node = NearestEndNode(hTpath, pActor->objx, pActor->objy); getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY); } pActor->hIpath = hTpath; } else { assert(hTpath != NOPOLY); // Error 701 hIpath = getPathOnTheWay(pActor->hCpath, hTpath); if (hIpath != NOPOLY) { /* Head for an en-route path */ if (PolySubtype(hIpath) != NODE) { /* En-route path is normal - head for pseudo centre. */ if (CanGetThere(pActor, x, y) == GT_OK) { pActor->ItargetX = x; pActor->ItargetY = y; } else { pActor->ItargetX = PolyCentreX(hIpath); pActor->ItargetY = PolyCentreY(hIpath); } } else { /* En-route path is node - head for end node. */ node = NearestEndNode(hIpath, pActor->objx, pActor->objy); getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY); } pActor->hIpath = hIpath; } } pActor->InDifficulty = NO_PROB; } /** * Set short-term destination and adopt the appropriate reel. */ static void SetMoverDest(PMACTOR pActor, int x, int y) { int scale; DIRREEL reel; // Set the co-ordinates requested. pActor->targetX = x; pActor->targetY = y; pActor->InDifficulty = NO_PROB; reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath); scale = GetScale(pActor->hCpath, pActor->objy); if (scale != pActor->scale || reel != pActor->dirn) { SetMActorWalkReel(pActor, reel, scale, false); } } /** * SetNextDest */ static void SetNextDest(PMACTOR pActor) { int targetX, targetY; // Ultimate destination int x, y; // Present position int nextx, nexty; int s1, lstatus = 0; int s2; HPOLYGON hS2p; int i; HPOLYGON hNpoly; HPOLYGON hPath; int znode; int nx, ny; int sx, sy; HPOLYGON hEb; int ss1, ss2; HPOLYGON shS2p; PMACTOR collisionActor; #if 1 int sTargetX, sTargetY; #endif /* * Desired destination (Itarget) is already set */ x = pActor->objx; // Current position y = pActor->objy; targetX = pActor->ItargetX; // Desired position targetY = pActor->ItargetY; /* * If we're where we're headed, end it all (the moving). */ // if (x == targetX && y == targetY) if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) { if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) { // Desired position GotThere(pActor); return; } else { assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); } } if (pActor->bNoPath || pActor->bIgPath) { /* Can get there directly. */ SetMoverDest(pActor, targetX, targetY); pActor->over = false; return; } /*---------------------------------------------------------------------- | Some work to do here if we're in a follow nodes polygon - basically | head for the next node. ----------------------------------------------------------------------*/ hNpoly = pActor->hFnpath; // The node path we're in (if any) switch (pActor->npstatus) { case NOT_IN: break; case ENTERING: znode = NearestEndNode(hNpoly, x, y); /* Hang on, we're probably here already! */ if (znode) { pActor->npstatus = GOING_DOWN; pActor->line = znode-1; getNpathNode(hNpoly, znode - 1, &nx, &ny); } else { pActor->npstatus = GOING_UP; pActor->line = znode; getNpathNode(hNpoly, 1, &nx, &ny); } SetMoverDest(pActor, nx, ny); // Test for pseudo-one-node npaths if (numNodes(hNpoly) == 2 && ABS(pActor->objx - pActor->targetX) < XMDIST && ABS(pActor->objy - pActor->targetY) < YMDIST) { // That's enough, we're leaving pActor->npstatus = LEAVING; } else { // Normal situation pActor->over = true; return; } // Fall through for LEAVING case LEAVING: assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); targetX = pActor->ItargetX; // Desired position targetY = pActor->ItargetY; break; case GOING_UP: i = pActor->line; // The line we're on // Is this the final target line? if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) { // The final leg of the journey pActor->line = i+1; SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); pActor->over = false; return; } else { // Go to the next node unless we're at the last one i++; // The node we're at if (++i < numNodes(hNpoly)) { getNpathNode(hNpoly, i, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->line = i-1; if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && ABS(pActor->UtargetY - pActor->targetY) < YMDIST) pActor->over = false; else pActor->over = true; return; } else { // Last node - we're off pActor->npstatus = LEAVING; assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); targetX = pActor->ItargetX; // Desired position targetY = pActor->ItargetY; break; } } case GOING_DOWN: i = pActor->line; // The line we're on and the node we're at // Is this the final target line? if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) { // The final leg of the journey SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); pActor->line = i-1; pActor->over = false; return; } else { // Go to the next node unless we're at the last one if (--i >= 0) { getNpathNode(hNpoly, i, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->line--; /* The next node to head for */ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && ABS(pActor->UtargetY - pActor->targetY) < YMDIST) pActor->over = false; else pActor->over = true; return; } else { // Last node - we're off pActor->npstatus = LEAVING; assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); targetX = pActor->ItargetX; // Desired position targetY = pActor->ItargetY; break; } } } /*------------------------------------------------------ | See if it can get there directly. There may be an | intermediate destination to head for. ------------------------------------------------------*/ while (targetX != -1 || targetY != -1) { #if 1 // 'push' the target sTargetX = targetX; sTargetY = targetY; #endif NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor); if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) { ss1 = s1; ss2 = s2; shS2p = hS2p; #if 1 // 'pop' the target targetX = sTargetX; targetY = sTargetY; #endif // Note: this aint right - targetX/Y (may) have been // nobbled by that last call to NewCoOrdinates() // Re-instating them (can) leads to oscillation NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor); if (x == nextx && y == nexty) { s1 = ss1; s2 = ss2; hS2p = shS2p; } } if (s1 == (XTHERE | YTHERE)) { /* Can get there directly. */ SetMoverDest(pActor, nextx, nexty); pActor->over = false; break; } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT) || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { /*------------------------------------------------- Can't go any further in this direction. | If it's because of a blocking polygon, try to do | something about it. | -------------------------------------------------*/ if (s2 & ENTERING_BLOCK) { x = pActor->objx; // Current position y = pActor->objy; // Go to the nearest corner of the blocking polygon concerned BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY); SetMoverDest(pActor, x, y); pActor->over = false; } else if (s2 & ENTERING_MBLOCK) { if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { // The best we're going to achieve SetMoverUltDest(pActor, x, y); SetMoverDest(pActor, x, y); } else { sx = pActor->objx; sy = pActor->objy; // pActor->objx = x; // pActor->objy = y; hEb = InitExtraBlock(pActor, collisionActor); x = pActor->objx; y = pActor->objy; BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY); pActor->objx = sx; pActor->objy = sy; SetMoverDest(pActor, x, y); pActor->over = false; } } else { /*---------------------------------------- Currently, this is as far as we can go. | Definitely room for improvement here! | ----------------------------------------*/ hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH); if (hPath != pActor->hIpath) { if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath)) hPath = pActor->hIpath; } assert(hPath == pActor->hIpath); if (pActor->InDifficulty == NO_PROB) { x = PolyCentreX(hPath); y = PolyCentreY(hPath); SetMoverDest(pActor, x, y); pActor->InDifficulty = TRY_CENTRE; pActor->over = false; } else if (pActor->InDifficulty == TRY_CENTRE) { NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); SetMoverDest(pActor, x, y); pActor->InDifficulty = TRY_CORNER; pActor->over = false; } else if (pActor->InDifficulty == TRY_CORNER) { NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); SetMoverDest(pActor, x, y); pActor->InDifficulty = TRY_NEXTCORNER; pActor->over = false; } } break; } else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT)) || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) { /*----------------------------------------------- A restriction in a direction has been removed. | Use this as an intermediate destination. | -----------------------------------------------*/ SetMoverDest(pActor, nextx, nexty); pActor->over = false; break; } x = nextx; y = nexty; /*------------------------- Change of path polygon? | -------------------------*/ hPath = InPolygon(x, y, PATH); if (pActor->hCpath != hPath && !IsInPolygon(x, y, pActor->hCpath) && !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) { /*---------------------------------------------------------- If just entering a follow nodes polygon, go to first node.| Else if just going to pass through, go to pseudo-centre. | ----------------------------------------------------------*/ if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) { int node = NearestEndNode(hPath, x, y); getNpathNode(hPath, node, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->over = true; } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) && !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) { SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath)); pActor->over = true; } else { SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY); } break; } lstatus = s1; } } /** * Work out where the next position should be. * Check that it's in a path and not in a blocking polygon. */ static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p, bool bOver, bool bBodge, PMACTOR pActor, PMACTOR *collisionActor) { HPOLYGON hPoly; int sidem, depthm; int sidesteps, depthsteps; PMACTOR ma; *s1 = *s2 = 0; /*------------------------------------------------ Don't overrun if this is the final destination. | ------------------------------------------------*/ if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) || *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX)) bOver = false; /*---------------------------------------------------- Decide how big a step to attempt in each direction. | ----------------------------------------------------*/ sidesteps = *targetX == -1 ? 0 : *targetX - fromx; sidesteps = ABS(sidesteps); depthsteps = *targetY == -1 ? 0 : *targetY - fromy; depthsteps = ABS(depthsteps); if (sidesteps && depthsteps > sidesteps) { depthm = YMDIST; sidem = depthm * sidesteps/depthsteps; if (!sidem) sidem = 1; } else if (depthsteps && sidesteps > depthsteps) { sidem = XMDIST; depthm = sidem * depthsteps/sidesteps; if (!depthm) { if (bBodge) depthm = 1; } else if (depthm > YMDIST) depthm = YMDIST; } else { sidem = sidesteps ? XMDIST : 0; depthm = depthsteps ? YMDIST : 0; } *newx = fromx; *newy = fromy; /*------------------------------------------------------------ If Left-Right movement is required - then make the move, | but don't overshoot, and do notice when we're already there | ------------------------------------------------------------*/ if (*targetX == -1) *s1 |= XTHERE; else { if (*targetX > fromx) { /* To the right? */ *newx += sidem; // Move to the right... if (*newx == *targetX) *s1 |= XTHERE; else if (*newx > *targetX) { // ...but don't overshoot if (!bOver) *newx = *targetX; else *targetX = *newx; *s1 |= XTHERE; } } else if (*targetX < fromx) { /* To the left? */ *newx -= sidem; // Move to the left... if (*newx == *targetX) *s1 |= XTHERE; else if (*newx < *targetX) { // ...but don't overshoot if (!bOver) *newx = *targetX; else *targetX = *newx; *s1 |= XTHERE; } } else { *targetX = -1; // We're already there! *s1 |= XTHERE; } } /*-------------------------------------------------------------- If Up-Down movement is required - then make the move, but don't overshoot, and do notice when we're already there --------------------------------------------------------------*/ if (*targetY == -1) *s1 |= YTHERE; else { if (*targetY > fromy) { /* Downwards? */ *newy += depthm; // Move down... if (*newy == *targetY) // ...but don't overshoot *s1 |= YTHERE; else if (*newy > *targetY) { // ...but don't overshoot if (!bOver) *newy = *targetY; else *targetY = *newy; *s1 |= YTHERE; } } else if (*targetY < fromy) { /* Upwards? */ *newy -= depthm; // Move up... if (*newy == *targetY) // ...but don't overshoot *s1 |= YTHERE; else if (*newy < *targetY) { // ...but don't overshoot if (!bOver) *newy = *targetY; else *targetY = *newy; *s1 |= YTHERE; } } else { *targetY = -1; // We're already there! *s1 |= YTHERE; } } /* Give over if this is it */ if (*s1 == (XTHERE | YTHERE)) return; /*------------------------------------------------------ Have worked out where an optimum step would take us. Must now check if it's in a legal spot. ------------------------------------------------------*/ if (!pActor->bNoPath && !pActor->bIgPath) { /*------------------------------ Must stay in a path polygon. -------------------------------*/ hPoly = InPolygon(*newx, *newy, PATH); if (hPoly == NOPOLY) { *s2 = LEAVING_PATH; // Trying to leave the path polygons if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) { *newy = fromy; *s1 |= YRESTRICT; } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { *newx = fromx; *newy = fromy; #if 1 *targetX = *targetY = -1; #endif *s1 |= STUCK; return; } } /*-------------------------------------- Must stay out of blocking polygons. --------------------------------------*/ hPoly = InPolygon(*newx, *newy, BLOCKING); if (hPoly != NOPOLY) { *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly *hS2p = hPoly; if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { *newy = fromy; *s1 |= YRESTRICT; } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { *newx = fromx; *newy = fromy; #if 1 *targetX = *targetY = -1; #endif *s1 |= STUCK; } } /*------------------------------------------------------ Must stay out of moving actors' blocking polygons. ------------------------------------------------------*/ ma = InMActorBlock(pActor, *newx, *newy); if (ma != NULL) { // Ignore if already in it (it may have just appeared) if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) { *s2 = ENTERING_MBLOCK; // Trying to walk through an actor *hS2p = -1; if (collisionActor) *collisionActor = ma; if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { *newy = fromy; *s1 |= YRESTRICT; } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { *newx = fromx; *s1 |= XRESTRICT; } else { *newx = fromx; *newy = fromy; #if 1 *targetX = *targetY = -1; #endif *s1 |= STUCK; } } } } } /** * SetOffWithinNodePath */ static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath, int targetX, int targetY) { int endnode; HPOLYGON hIpath; int nx, ny; int x, y; if (StartPath == DestPath) { if (pActor->line == pActor->Tline) { SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); pActor->over = false; } else if (pActor->line < pActor->Tline) { getNpathNode(StartPath, pActor->line+1, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->npstatus = GOING_UP; } else if (pActor->line > pActor->Tline) { getNpathNode(StartPath, pActor->line, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->npstatus = GOING_DOWN; } } else { /* * Leaving this path - work out * which end of this path to head for. */ assert(DestPath != NOPOLY); // Error 702 if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) { // This should never happen! // It's the old code that didn't always work. endnode = NearestEndNode(StartPath, targetX, targetY); } else { if (PolySubtype(hIpath) != NODE) { x = PolyCentreX(hIpath); y = PolyCentreY(hIpath); endnode = NearestEndNode(StartPath, x, y); } else { endnode = NearEndNode(StartPath, hIpath); } } #if 1 if ((pActor->npstatus == LEAVING) && endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) { // Leave it be } else #endif { if (endnode) { getNpathNode(StartPath, pActor->line+1, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->npstatus = GOING_UP; } else { getNpathNode(StartPath, pActor->line, &nx, &ny); SetMoverDest(pActor, nx, ny); pActor->npstatus = GOING_DOWN; } } } } /** * Restore a movement, called from restoreMovement() in ACTORS.CPP */ void SSetActorDest(PMACTOR pActor) { if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { stand(pActor->actorID, pActor->objx, pActor->objy, 0); if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY, pActor->bIgPath, 0); } } else { stand(pActor->actorID, pActor->objx, pActor->objy, 0); } } /** * Initiate a movement, called from WalkTo_Event() */ void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) { HPOLYGON StartPath, DestPath = 0; int targetX, targetY; if (pActor->actorID == LeadId()) // Now only for lead actor UnTagActor(pActor->actorID); // Tag not allowed while moving pActor->ticket++; pActor->stop = false; pActor->over = false; pActor->fromx = pActor->objx; pActor->fromy = pActor->objy; pActor->bMoving = true; pActor->bIgPath = igPath; // Use the supplied reel or restore the normal actor. if (film != 0) AlterMActor(pActor, film, AR_WALKREEL); else AlterMActor(pActor, 0, AR_NORMAL); if (igPath) { targetX = clickX; targetY = clickY; } else { if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) { GotThere(pActor); return; } assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination! assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination! } /***** Now have a destination to aim for. *****/ /*---------------------------------- | Don't move if it's not worth it. ----------------------------------*/ if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) { GotThere(pActor); return; } /*------------------------------------------------------ | If the destiation is within a follow nodes polygon, | set destination as the nearest node. ------------------------------------------------------*/ if (!igPath) { DestPath = InPolygon(targetX, targetY, PATH); if (PolySubtype(DestPath) == NODE) { // Find the nearest point on a line, or nearest node FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline); } } assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 SetMoverUltDest(pActor, targetX, targetY); SetMoverIntDest(pActor, targetX, targetY); /*------------------------------------------------------------------- | If in a follow nodes path, need to set off in the right direction! | -------------------------------------------------------------------*/ if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) { SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY); } else { // Set off! SetNextDest(pActor); } } /** * Change scale if appropriate. */ static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) { int scale; scale = GetScale(hPath, ypos); if (scale != pActor->scale) { SetMActorWalkReel(pActor, pActor->dirn, scale, false); } } /** * Not going anywhere - Kick off again if not at final destination. */ static void NotMoving(PMACTOR pActor, int x, int y) { pActor->targetX = pActor->targetY = -1; // if (x == pActor->UtargetX && y == pActor->UtargetY) if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) { GotThere(pActor); return; } if (pActor->ItargetX != -1 || pActor->ItargetY != -1) { SetNextDest(pActor); } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) { assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); SetNextDest(pActor); } } /** * Does the necessary business when entering a different path polygon. */ static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { int firstnode; // First node to go to int lastnode; // Last node to go to HPOLYGON hIpath; int nx, ny; int nxl, nyl; pActor->hCpath = hPath; // current path if (hPath == NOPOLY) { // Not proved this ever happens, but just in case pActor->hFnpath = NOPOLY; pActor->npstatus = NOT_IN; return; } // Is new path a node path? if (PolySubtype(hPath) == NODE) { // Node path - usually go to nearest end node firstnode = NearestEndNode(hPath, x, y); lastnode = -1; // If this is not the destination path, // find which end nodfe we wish to leave via if (hPath != pActor->hUpath) { if (pActor->bIgPath) { lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY); } else { assert(pActor->hUpath != NOPOLY); // Error 703 hIpath = getPathOnTheWay(hPath, pActor->hUpath); assert(hIpath != NOPOLY); // No path on the way if (PolySubtype(hIpath) != NODE) { lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath)); } else { lastnode = NearEndNode(hPath, hIpath); } } } // Test for pseudo-one-node npaths if (lastnode != -1 && numNodes(hPath) == 2) { getNpathNode(hPath, firstnode, &nx, &ny); getNpathNode(hPath, lastnode, &nxl, &nyl); if (nxl == nx && nyl == ny) firstnode = lastnode; } // If leaving by same node as entering, don't bother. if (lastnode == firstnode) { pActor->hFnpath = NOPOLY; pActor->npstatus = NOT_IN; assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); SetNextDest(pActor); } else { // Head for first node pActor->over = true; pActor->npstatus = ENTERING; pActor->hFnpath = hPath; pActor->line = firstnode ? firstnode - 1 : firstnode; if (pActor->line == pActor->Tline && hPath == pActor->hUpath) { assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008 SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); } else { // This doesn't seem right getNpathNode(hPath, firstnode, &nx, &ny); if (ABS(pActor->objx - nx) < XMDIST && ABS(pActor->objy - ny) < YMDIST) { pActor->npstatus = ENTERING; pActor->hFnpath = hPath; SetNextDest(pActor); } else { getNpathNode(hPath, firstnode, &nx, &ny); SetMoverDest(pActor, nx, ny); } } } return; } else { pActor->hFnpath = NOPOLY; pActor->npstatus = NOT_IN; assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009 // Added 26/01/95 if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY)) return; SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); SetNextDest(pActor); } } /** * Move */ void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) { MultiSetAniXY(pActor->actorObj, newx, newy); MAsetZPos(pActor, newy, getPolyZfactor(hPath)); if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) { // The end of a scale-change reel // Revert to normal walking reel pActor->walkReel = false; pActor->scount = 0; SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); } pActor->objx = newx; pActor->objy = newy; // Synchronised walking reels if (++pActor->scount >= 6) pActor->scount = 0; } /** * Called from MActorProcess() on every tick. * * Moves the actor as appropriate. */ void MoveActor(PMACTOR pActor) { int newx, newy; HPOLYGON hPath; int status, s2; // s2 not used here! HPOLYGON hS2p; // nor is s2p! HPOLYGON hEb; PMACTOR ma; int sTargetX, sTargetY; bool bNewPath = false; // Only do anything if the actor needs to move! if (pActor->targetX == -1 && pActor->targetY == -1) return; if (pActor->stop) { GotThere(pActor); pActor->stop = false; SetMActorStanding(pActor); return; } #if SLOW_RINCE_DOWN /**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code /**/ return; // /**/ BogusVar = 0; // #endif // During swalk()s, movement while hidden may be slowed down. if (pActor->aHidden) { if (++hSlowVar < pActor->SlowFactor) return; hSlowVar = 0; } // 'push' the target sTargetX = pActor->targetX; sTargetY = pActor->targetY; NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor); if (newx == pActor->objx && newy == pActor->objy) { // 'pop' the target pActor->targetX = sTargetX; pActor->targetY = sTargetY; NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy, &status, &s2, &hS2p, pActor->over, true, pActor); if (newx == pActor->objx && newy == pActor->objy) { NotMoving(pActor, newx, newy); return; } } // Find out which path we're in now hPath = InPolygon(newx, newy, PATH); if (hPath == NOPOLY) { if (pActor->bNoPath) { Move(pActor, newx, newy, pActor->hCpath); return; } else { // May be marginally outside! // OR bIgPath may be set. hPath = pActor->hCpath; } } else if (pActor->bNoPath) { pActor->bNoPath = false; bNewPath = true; } else if (hPath != pActor->hCpath) { if (IsInPolygon(newx, newy, pActor->hCpath)) hPath = pActor->hCpath; } CheckScale(pActor, hPath, newy); /* * Must stay out of moving actors' blocking polygons. */ ma = InMActorBlock(pActor, newx, newy); if (ma != NULL) { // Stop if there's no chance of arriving if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { GotThere(pActor); return; } if (InMActorBlock(pActor, pActor->objx, pActor->objy)) ; else { hEb = InitExtraBlock(pActor, ma); newx = pActor->objx; newy = pActor->objy; BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY); SetMoverDest(pActor, newx, newy); return; } } /*-------------------------------------- This is where it actually gets moved. --------------------------------------*/ Move(pActor, newx, newy, hPath); // Entering a new path polygon? if (hPath != pActor->hCpath || bNewPath) EnteringNewPath(pActor, hPath, newx, newy); } /** * Store the default refer type for the current scene. */ void SetDefaultRefer(int32 defRefer) { DefaultRefer = defRefer; } /** * DoMoveActor */ void DoMoveActor(PMACTOR pActor) { int wasx, wasy; int i; #define NUMBER 1 wasx = pActor->objx; wasy = pActor->objy; MoveActor(pActor); if ((pActor->targetX != -1 || pActor->targetY != -1) && (wasx == pActor->objx && wasy == pActor->objy)) { for (i=0; i < NUMBER; i++) { MoveActor(pActor); if (wasx != pActor->objx || wasy != pActor->objy) break; } // assert(i