mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-27 04:07:05 +00:00
338 lines
8.1 KiB
C++
338 lines
8.1 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.
|
|
*
|
|
*/
|
|
|
|
#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 & fIgnoreHorizon) && 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 (getVersion() >= 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 & fIgnoreObjects)
|
|
return 0;
|
|
|
|
for (u = _game.viewTable; u < &_game.viewTable[MAX_VIEWTABLE]; u++) {
|
|
if ((u->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn))
|
|
continue;
|
|
|
|
if (u->flags & fIgnoreObjects)
|
|
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 & fFixedPriority) {
|
|
// 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;
|
|
|
|
// Check if any picture is loaded before checking for priority below.
|
|
// If no picture has been loaded, the priority buffer won't be initialized,
|
|
// thus the check below will always fail. This case causes an infinite loop
|
|
// in the fanmade game Nick's Quest (bug #3451122), as the game attempts to
|
|
// draw a sprite (view 4, floating Nick) before it loads any picture. This
|
|
// causes the checks below to always fail, and the engine keeps readjusting
|
|
// the sprite's position in fixPosition() forever, as there is no valid
|
|
// position to place it (the default visual and priority screen is set to
|
|
// zero, i.e. unconditional black). To remedy this situation, we always
|
|
// return true here if no picture has been loaded and no priority screen
|
|
// has been set up.
|
|
if (!_game._vm->_picture->isPictureLoaded()) {
|
|
warning("checkPriority: no picture loaded");
|
|
return pass;
|
|
}
|
|
|
|
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 & fIgnoreBlocks)
|
|
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 & fOnWater)
|
|
pass = 0;
|
|
if (water && v->flags & fOnLand)
|
|
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 & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
|
|
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 & fUpdatePos) {
|
|
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 && getVersion() == 0x3086) { // KQ4
|
|
x = 0; // See Sarien bug #590462
|
|
border = 4;
|
|
} else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) {
|
|
// 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 & fIgnoreHorizon) && 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 == kMotionMoveObj) {
|
|
inDestination(v);
|
|
}
|
|
}
|
|
|
|
v->flags &= ~fUpdatePos;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 behavior 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 & fIgnoreHorizon) && 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
|