mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-23 10:19:27 +00:00
648add8aa8
svn-id: r7160
349 lines
11 KiB
C++
349 lines
11 KiB
C++
/* ScummVM - Scumm Interpreter
|
|
* Copyright (C) 2003 The ScummVM project
|
|
* Copyright (C) 2003 Robert "LavosSpawn" Goeffringmann
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* $Header$
|
|
*
|
|
*/
|
|
|
|
#include "autoroute.h"
|
|
|
|
#define ROUTE_GRID_WIDTH ((GAME_SCREEN_WIDTH/8)+2)
|
|
#define ROUTE_GRID_HEIGHT ((GAME_SCREEN_HEIGHT/8)+2)
|
|
#define ROUTE_GRID_SIZE (ROUTE_GRID_WIDTH*ROUTE_GRID_HEIGHT*2)
|
|
#define WALK_JUMP 8 // walk in blocks of 8
|
|
|
|
SkyAutoRoute::SkyAutoRoute(SkyGrid *pGrid) {
|
|
|
|
_grid = pGrid;
|
|
_routeGrid = (uint16*)malloc(ROUTE_GRID_SIZE);
|
|
}
|
|
|
|
SkyAutoRoute::~SkyAutoRoute(void) {
|
|
|
|
free(_routeGrid);
|
|
}
|
|
|
|
uint16 SkyAutoRoute::checkBlock(uint16 *blockPos) {
|
|
|
|
uint16 fieldVal, retVal = 0;
|
|
fieldVal = blockPos[1]; // field to the right
|
|
if ((!(fieldVal & 0x8000)) && (fieldVal != 0)) retVal = fieldVal;
|
|
fieldVal = (blockPos - 1)[0]; // field to the left
|
|
if ((!(fieldVal & 0x8000)) && (fieldVal != 0) && (fieldVal > retVal)) retVal = fieldVal;
|
|
fieldVal = (blockPos + ROUTE_GRID_WIDTH)[0]; // upper field
|
|
if ((!(fieldVal & 0x8000)) && (fieldVal != 0) && (fieldVal > retVal)) retVal = fieldVal;
|
|
fieldVal = (blockPos - ROUTE_GRID_WIDTH)[0]; // upper field
|
|
if ((!(fieldVal & 0x8000)) && (fieldVal != 0) && (fieldVal > retVal)) retVal = fieldVal;
|
|
return retVal;
|
|
}
|
|
|
|
uint16 SkyAutoRoute::autoRoute(Compact *cpt, uint16 **pSaveRoute) {
|
|
|
|
if (!cpt->extCompact)
|
|
error("SkyAutoRoute::autoRoute: fatal error. cpt->extCompact == NULL!\n");
|
|
uint16* routeData = (uint16*)cpt->extCompact->animScratch;
|
|
uint8* screenGrid = _grid->giveGrid(cpt->screen) - 4;
|
|
// ^^ this is actually a pointer to the last dword of the grid
|
|
uint16 *routeCalc = _routeGrid + (ROUTE_GRID_SIZE >> 1) - 1;
|
|
|
|
uint8 stretch1, stretch2; // bl / bh
|
|
stretch1 = 0;
|
|
//FIXME: don't know if this is uint16 or uint32 (endian problem)
|
|
stretch2 = (uint8)*(uint32 *)SkyCompact::getCompactElem(cpt, C_GRID_WIDTH + cpt->extCompact->megaSet);
|
|
|
|
uint16 cnt;
|
|
//First clear the bottom line and right hand edge of next line
|
|
for (cnt = 0; cnt < ROUTE_GRID_WIDTH + 1; cnt++)
|
|
_routeGrid[(ROUTE_GRID_SIZE>>1) - 1 - cnt] = 0;
|
|
|
|
uint16 gridCntX = ROUTE_GRID_WIDTH-2; // ch
|
|
uint16 gridCntY = ROUTE_GRID_HEIGHT-2; // ebp
|
|
uint16 bitsLeft = 32;
|
|
uint32 gridData = screenGrid[0] | (screenGrid[1] << 8) |
|
|
(screenGrid[2] << 16) | (screenGrid[3] << 24);
|
|
screenGrid -= 4;
|
|
do {
|
|
//stretch:
|
|
uint8 shiftBit = (uint8)gridData&1;
|
|
gridData >>= 1;
|
|
if (shiftBit) {
|
|
//bit_set:
|
|
routeCalc[0] = 0xFFFF;
|
|
stretch1 = stretch2; // set up stretch factor
|
|
} else {
|
|
if (stretch1) {
|
|
//still_stretching:
|
|
stretch1--;
|
|
routeCalc[0] = 0xFFFF;
|
|
} else {
|
|
routeCalc[0] = 0; // this block is free
|
|
}
|
|
}
|
|
// next_stretch:
|
|
routeCalc--;
|
|
bitsLeft--;
|
|
if (!bitsLeft) {
|
|
gridData = screenGrid[0] | (screenGrid[1] << 8) |
|
|
(screenGrid[2] << 16) | (screenGrid[3] << 24);
|
|
screenGrid -= 4;
|
|
bitsLeft = 32;
|
|
}
|
|
// still bits:
|
|
gridCntX--;
|
|
if (gridCntX == 0) {
|
|
routeCalc--;
|
|
routeCalc[0] = routeCalc[1] = 0; // do edges
|
|
routeCalc--;
|
|
gridCntX = ROUTE_GRID_WIDTH-2;
|
|
stretch1 = 0; // clear stretch factor
|
|
gridCntY--;
|
|
}
|
|
} while(gridCntX || gridCntY);
|
|
for (cnt = 0; cnt < ROUTE_GRID_WIDTH-1; cnt++)
|
|
_routeGrid[cnt] = 0; // clear top line (right hand edge already done
|
|
|
|
// the grid has been initialised
|
|
|
|
// calculate start and end points
|
|
int16 initX = 0, initY = 0, postX = 0, postY = 0;
|
|
uint8 initBlockY; // bh
|
|
uint8 initBlockX; // bl
|
|
uint8 postBlockY; // ch
|
|
uint8 postBlockX; // cl
|
|
|
|
if (cpt->ycood < TOP_LEFT_Y) {
|
|
initY = cpt->ycood - TOP_LEFT_Y;
|
|
initBlockY = 0;
|
|
} else if (cpt->ycood - TOP_LEFT_Y >= GAME_SCREEN_HEIGHT) { // no_init_y1
|
|
initY = cpt->ycood - TOP_LEFT_Y - GAME_SCREEN_HEIGHT;
|
|
initBlockY = (GAME_SCREEN_HEIGHT - 1) >> 3; // convert to blocks
|
|
} else { // no_init_y2
|
|
initBlockY = (cpt->ycood - TOP_LEFT_Y) >> 3; // convert to blocks
|
|
}
|
|
|
|
if (cpt->xcood < TOP_LEFT_X) {
|
|
initX = cpt->xcood - TOP_LEFT_X;
|
|
initBlockX = 0;
|
|
} else if (cpt->xcood - TOP_LEFT_X >= GAME_SCREEN_WIDTH) { // no_init_x1
|
|
initX = cpt->xcood - TOP_LEFT_X - (GAME_SCREEN_WIDTH - 1); // -1 to match amiga
|
|
initBlockX = (GAME_SCREEN_WIDTH - 1) >> 3;
|
|
} else { // no_init_x2
|
|
initBlockX = (cpt->xcood - TOP_LEFT_X) >> 3;
|
|
}
|
|
|
|
// destination coords:
|
|
|
|
if (cpt->extCompact->arTargetY < TOP_LEFT_Y) {
|
|
postY = cpt->extCompact->arTargetY - TOP_LEFT_Y;
|
|
postBlockY = 0;
|
|
} else if (cpt->extCompact->arTargetY - TOP_LEFT_Y >= GAME_SCREEN_HEIGHT) { // no_post_y1
|
|
postY = cpt->extCompact->arTargetY - TOP_LEFT_Y - (GAME_SCREEN_HEIGHT - 1);
|
|
postBlockY = (GAME_SCREEN_HEIGHT - 1) >> 3;
|
|
} else { // no_post_y2
|
|
postBlockY = (cpt->extCompact->arTargetY - TOP_LEFT_Y) >> 3;
|
|
}
|
|
|
|
if (cpt->extCompact->arTargetX < TOP_LEFT_X) {
|
|
postX = cpt->extCompact->arTargetX - TOP_LEFT_X;
|
|
postBlockX = 0;
|
|
} else if (cpt->extCompact->arTargetX - TOP_LEFT_X >= GAME_SCREEN_WIDTH) {
|
|
postX = cpt->extCompact->arTargetX - TOP_LEFT_X - (GAME_SCREEN_WIDTH - 1);
|
|
postBlockX = (GAME_SCREEN_WIDTH - 1) >> 3;
|
|
} else {
|
|
postBlockX = (cpt->extCompact->arTargetX - TOP_LEFT_X) >> 3;
|
|
}
|
|
if ((postBlockX == initBlockX) && (postBlockY == initBlockY)) {
|
|
// empty route
|
|
routeData[0] = 0;
|
|
return 1;
|
|
}
|
|
|
|
int32 directionX, directionY;
|
|
uint8 numLines, numCols; // number of lines / columns to go
|
|
if (initBlockY > postBlockY) {
|
|
directionY = -ROUTE_GRID_WIDTH;
|
|
numLines = initBlockY;
|
|
} else { // go_down:
|
|
directionY = ROUTE_GRID_WIDTH;
|
|
numLines = (ROUTE_GRID_HEIGHT-1)-initBlockY;
|
|
}
|
|
if (initBlockX > postBlockX) {
|
|
directionX = -1;
|
|
numCols = initBlockX+2;
|
|
} else {
|
|
directionX = 1;
|
|
numCols = (ROUTE_GRID_WIDTH - 1) - initBlockX;
|
|
}
|
|
// calculate destination address
|
|
//postBlockX <<= 1; postBlockY <<= 1; // multiply by 2
|
|
uint16 *routeDestCalc;
|
|
routeDestCalc = postBlockY * ROUTE_GRID_WIDTH + postBlockX + _routeGrid;
|
|
routeDestCalc += ROUTE_GRID_WIDTH+1; // skip blank edges (what?)
|
|
|
|
//initBlockX <<= 1; initBlockY <<= 1;
|
|
uint16 *routeSrcCalc;
|
|
routeSrcCalc = initBlockY * ROUTE_GRID_WIDTH + initBlockX + _routeGrid;
|
|
routeSrcCalc += ROUTE_GRID_WIDTH+1; // skip blank edges
|
|
routeSrcCalc[0] = 1; //start this one off
|
|
// means: mark the block we start from as accessible
|
|
|
|
// if we are on the edge, move diagonally from start
|
|
if (numLines < ROUTE_GRID_HEIGHT-3)
|
|
routeSrcCalc -= directionY;
|
|
|
|
if (numCols < ROUTE_GRID_WIDTH-2)
|
|
routeSrcCalc -= directionX;
|
|
|
|
if (routeDestCalc[0]) {
|
|
// If destination is a wall then we have no route
|
|
// By the way, we could improve this algorithm by moving as close to the
|
|
// wall as possible. The original pathfinding of SKY sucked, if I remember correctly
|
|
return 2;
|
|
}
|
|
uint8 cnty; // ch
|
|
uint8 cntx; // cl
|
|
// numLines = dh, numCols = dl
|
|
uint16 blockRet;
|
|
bool gridChanged, foundRoute;
|
|
do { // wallow_y
|
|
gridChanged = false;
|
|
cnty = numLines;
|
|
uint16 *yPushedSrc = routeSrcCalc;
|
|
do { // wallow_x
|
|
cntx = numCols;
|
|
uint16 *xPushedSrc = routeSrcCalc;
|
|
do { // wallow
|
|
if (!routeSrcCalc[0]) {
|
|
// block wasn't yet done
|
|
blockRet = checkBlock(routeSrcCalc);
|
|
if (blockRet > 0) {
|
|
// this block is accessible
|
|
routeSrcCalc[0] = blockRet;
|
|
gridChanged = true;
|
|
}
|
|
}
|
|
routeSrcCalc += directionX;
|
|
cntx--;
|
|
} while (cntx);
|
|
routeSrcCalc = xPushedSrc + directionY;
|
|
cnty--;
|
|
} while (cnty);
|
|
routeSrcCalc = yPushedSrc;
|
|
|
|
foundRoute = false;
|
|
if (!routeDestCalc[0]) {
|
|
// we have done a section, see if we want to shift backwards (what?)
|
|
if (numLines < ROUTE_GRID_HEIGHT-4) {
|
|
routeSrcCalc -= directionY;
|
|
numLines++;
|
|
}
|
|
if (numCols < ROUTE_GRID_WIDTH-4) {
|
|
routeSrcCalc -= directionX;
|
|
numCols++;
|
|
}
|
|
} else foundRoute = true;
|
|
} while ((!foundRoute) && gridChanged);
|
|
|
|
if (!routeDestCalc[0]) {
|
|
// no route exists from routeSrc to routeDest
|
|
return 2;
|
|
}
|
|
// ok, we know now that it's possible to get from the start position to the desired
|
|
// destination. Let's see how.
|
|
uint16 *saveRoute = routeData+(ROUTE_SPACE >> 1)-1; // route_space is given in bytes so >> 1
|
|
saveRoute[0] = 0; // route is null terminated
|
|
uint16 lastVal;
|
|
lastVal = routeDestCalc[0];
|
|
lastVal--;
|
|
bool routeDone = false;
|
|
do {
|
|
// check_dir:
|
|
if (lastVal == (routeDestCalc-1)[0]) {
|
|
// look_left
|
|
saveRoute -= 2;
|
|
saveRoute[1] = RIGHTY;
|
|
saveRoute[0] = 0;
|
|
while ((lastVal == (routeDestCalc-1)[0]) && (!routeDone)) {
|
|
lastVal--;
|
|
if (lastVal == 0) routeDone = true;
|
|
else {
|
|
routeDestCalc--; // keep checking left
|
|
saveRoute[0] += WALK_JUMP;
|
|
}
|
|
}
|
|
} else if (lastVal == routeDestCalc[1]) {
|
|
// look_right
|
|
saveRoute -= 2;
|
|
saveRoute[1] = LEFTY;
|
|
saveRoute[0] = 0;
|
|
while ((lastVal == routeDestCalc[1]) && (!routeDone)) {
|
|
lastVal--;
|
|
if (lastVal == 0) routeDone = true;
|
|
else {
|
|
routeDestCalc++; // keep checking right
|
|
saveRoute[0] += WALK_JUMP;
|
|
}
|
|
}
|
|
} else if (lastVal == (routeDestCalc - ROUTE_GRID_WIDTH)[0]) {
|
|
// look_up
|
|
saveRoute -= 2;
|
|
saveRoute[1] = DOWNY;
|
|
saveRoute[0] = 0;
|
|
while ((lastVal == (routeDestCalc - ROUTE_GRID_WIDTH)[0]) && (!routeDone)) {
|
|
lastVal--;
|
|
if (lastVal == 0) routeDone = true;
|
|
else {
|
|
routeDestCalc -= ROUTE_GRID_WIDTH; // keep checking up
|
|
saveRoute[0] += WALK_JUMP;
|
|
}
|
|
}
|
|
} else if (lastVal == (routeDestCalc + ROUTE_GRID_WIDTH)[0]) {
|
|
// look_down
|
|
saveRoute -= 2;
|
|
saveRoute[1] = UPY;
|
|
saveRoute[0] = 0;
|
|
while ((lastVal == (routeDestCalc + ROUTE_GRID_WIDTH)[0]) && (!routeDone)) {
|
|
lastVal--;
|
|
if (lastVal == 0) routeDone = true;
|
|
else {
|
|
routeDestCalc += ROUTE_GRID_WIDTH; // keep checking right
|
|
saveRoute[0] += WALK_JUMP;
|
|
}
|
|
}
|
|
} else {
|
|
error("AutoRoute:: Can't find way backwards through _routeGrid");
|
|
}
|
|
} while (!routeDone);
|
|
// the route is done. if there was an initial x/y movement tag it onto the start
|
|
if (initX < 0) {
|
|
saveRoute -= 4;
|
|
saveRoute[1] = RIGHTY;
|
|
saveRoute[0] = ((-initX)+7)&0xFFF8;
|
|
} else if (initX > 0) {
|
|
saveRoute -= 4;
|
|
saveRoute[1] = LEFTY;
|
|
saveRoute[0] = (initX+7)&0xFFF8;
|
|
}
|
|
// I wonder why initY isn't checked
|
|
// saveRoute should now point to routeData
|
|
if (routeData > saveRoute) error("Autoroute: Internal pointer error! routeData overflow.");
|
|
*pSaveRoute = saveRoute;
|
|
return 1;
|
|
}
|
|
|