scummvm/engines/agi/checks.cpp

326 lines
7.4 KiB
C++

/* 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$
*
*/
#include "agi/agi.h"
namespace Agi {
int AgiEngine::checkPosition(VtEntry *v) {
debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->xPos, v->yPos);
if (v->xPos < 0 ||
v->xPos + v->xSize > _WIDTH ||
v->yPos - v->ySize + 1 < 0 ||
v->yPos >= _HEIGHT ||
((~v->flags & IGNORE_HORIZON) && v->yPos <= _game.horizon)) {
debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d",
v->xPos, v->yPos, v->xSize, v->ySize);
return 0;
}
/* MH1 needs this, but it breaks LSL1 */
if (agiGetRelease() >= 0x3000) {
if (v->yPos < v->ySize)
return 0;
}
return 1;
}
/**
* Check if there's another object on the way
*/
int AgiEngine::checkCollision(VtEntry *v) {
VtEntry *u;
if (v->flags & IGNORE_OBJECTS)
return 0;
for (u = _game.viewTable; u < &_game.viewTable[MAX_VIEWTABLE]; u++) {
if ((u->flags & (ANIMATED | DRAWN)) != (ANIMATED | DRAWN))
continue;
if (u->flags & IGNORE_OBJECTS)
continue;
/* Same object, check next */
if (v->entry == u->entry)
continue;
/* No horizontal overlap, check next */
if (v->xPos + v->xSize < u->xPos || v->xPos > u->xPos + u->xSize)
continue;
/* Same y, return error! */
if (v->yPos == u->yPos) {
debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry);
return 1;
}
/* Crossed the baseline, return error! */
if ((v->yPos > u->yPos && v->yPos2 < u->yPos2) ||
(v->yPos < u->yPos && v->yPos2 > u->yPos2)) {
debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry);
return 1;
}
}
return 0;
}
int AgiEngine::checkPriority(VtEntry *v) {
int i, trigger, water, pass, pri;
uint8 *p0;
if (~v->flags & FIXED_PRIORITY) {
/* Priority bands */
v->priority = _game.priTable[v->yPos];
}
trigger = 0;
water = 0;
pass = 1;
if (v->priority == 0x0f) {
// Check ego
if (v->entry == 0) {
setflag(fEgoTouchedP2, trigger ? true : false);
setflag(fEgoWater, water ? true : false);
}
return pass;
}
water = 1;
p0 = &_game.sbuf16c[v->xPos + v->yPos * _WIDTH];
for (i = 0; i < v->xSize; i++, p0++) {
pri = *p0 >> 4;
if (pri == 0) { /* unconditional black. no go at all! */
pass = 0;
break;
}
if (pri == 3) /* water surface */
continue;
water = 0;
if (pri == 1) { /* conditional blue */
if (v->flags & IGNORE_BLOCKS)
continue;
debugC(4, kDebugLevelSprites, "Blocks observed!");
pass = 0;
break;
}
if (pri == 2) { /* trigger */
debugC(4, kDebugLevelSprites, "stepped on trigger");
if (!_debug.ignoretriggers)
trigger = 1;
}
}
if (pass) {
if (!water && v->flags & ON_WATER)
pass = 0;
if (water && v->flags & ON_LAND)
pass = 0;
}
// Check ego
if (v->entry == 0) {
setflag(fEgoTouchedP2, trigger ? true : false);
setflag(fEgoWater, water ? true : false);
}
return pass;
}
/*
* Public functions
*/
/**
* Update position of objects
* This function updates the position of all animated, updating view
* table entries according to its motion type, step size, etc. The
* new position must be valid according to the sprite positioning
* rules, otherwise the previous position will be kept.
*/
void AgiEngine::updatePosition() {
VtEntry *v;
int x, y, oldX, oldY, border;
_game.vars[vBorderCode] = 0;
_game.vars[vBorderTouchEgo] = 0;
_game.vars[vBorderTouchObj] = 0;
for (v = _game.viewTable; v < &_game.viewTable[MAX_VIEWTABLE]; v++) {
if ((v->flags & (ANIMATED | UPDATE | DRAWN)) != (ANIMATED | UPDATE | DRAWN)) {
continue;
}
if (v->stepTimeCount != 0) {
if (--v->stepTimeCount != 0)
continue;
}
v->stepTimeCount = v->stepTime;
x = oldX = v->xPos;
y = oldY = v->yPos;
/* If object has moved, update its position */
if (~v->flags & UPDATE_POS) {
int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
x += v->stepSize * dx[v->direction];
y += v->stepSize * dy[v->direction];
}
/* Now check if it touched the borders */
border = 0;
/* Check left/right borders */
if (x < 0) {
x = 0;
border = 4;
} else if (x <= 0 && agiGetRelease() == 0x3086) { /* KQ4 */
x = 0; /* See Sarien bug #590462 */
border = 4;
} else if (v->entry == 0 && x == 0 && v->flags & ADJ_EGO_XY) {
/* Extra test to walk west clicking the mouse */
x = 0;
border = 4;
} else if (x + v->xSize > _WIDTH) {
x = _WIDTH - v->xSize;
border = 2;
}
/* Check top/bottom borders. */
if (y - v->ySize + 1 < 0) {
y = v->ySize - 1;
border = 1;
} else if (y > _HEIGHT - 1) {
y = _HEIGHT - 1;
border = 3;
} else if ((~v->flags & IGNORE_HORIZON) && y <= _game.horizon) {
debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, _game.horizon);
y = _game.horizon + 1;
border = 1;
}
/* Test new position. rollback if test fails */
v->xPos = x;
v->yPos = y;
if (checkCollision(v) || !checkPriority(v)) {
v->xPos = oldX;
v->yPos = oldY;
border = 0;
fixPosition(v->entry);
}
if (border != 0) {
if (isEgoView(v)) {
_game.vars[vBorderTouchEgo] = border;
} else {
_game.vars[vBorderCode] = v->entry;
_game.vars[vBorderTouchObj] = border;
}
if (v->motion == MOTION_MOVE_OBJ) {
inDestination(v);
}
}
v->flags &= ~UPDATE_POS;
}
}
/**
* Adjust position of a sprite
* This function adjusts the position of a sprite moving it until
* certain criteria is matched. According to priority and control line
* data, a sprite may not always appear at the location we specified.
* This behaviour is also known as the "Budin-Sonneveld effect".
*
* @param n view table entry number
*/
void AgiEngine::fixPosition(int n) {
VtEntry *v = &_game.viewTable[n];
int count, dir, size;
debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->xPos, v->yPos);
/* test horizon */
if ((~v->flags & IGNORE_HORIZON) && v->yPos <= _game.horizon)
v->yPos = _game.horizon + 1;
dir = 0;
count = size = 1;
while (!checkPosition(v) || checkCollision(v) || !checkPriority(v)) {
switch (dir) {
case 0: /* west */
v->xPos--;
if (--count)
continue;
dir = 1;
break;
case 1: /* south */
v->yPos++;
if (--count)
continue;
dir = 2;
size++;
break;
case 2: /* east */
v->xPos++;
if (--count)
continue;
dir = 3;
break;
case 3: /* north */
v->yPos--;
if (--count)
continue;
dir = 0;
size++;
break;
}
count = size;
}
debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->xPos, v->yPos);
}
} // End of namespace Agi