2007-05-30 21:56:52 +00:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
2006-05-23 23:43:52 +00:00
|
|
|
*
|
2007-05-30 21:56:52 +00:00
|
|
|
* 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.
|
2006-05-23 23:43:52 +00:00
|
|
|
*
|
2021-12-26 17:47:58 +00:00
|
|
|
* 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 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
2014-02-18 01:34:17 +00:00
|
|
|
*
|
2006-05-23 23:43:52 +00:00
|
|
|
* 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.
|
2014-02-18 01:34:17 +00:00
|
|
|
*
|
2006-05-23 23:43:52 +00:00
|
|
|
* You should have received a copy of the GNU General Public License
|
2021-12-26 17:47:58 +00:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2006-05-23 23:43:52 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "agi/agi.h"
|
2016-01-29 12:13:40 +00:00
|
|
|
#include "agi/graphics.h"
|
2006-05-23 23:43:52 +00:00
|
|
|
|
|
|
|
namespace Agi {
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
bool AgiEngine::checkPosition(ScreenObjEntry *screenObj) {
|
|
|
|
bool result = true; // position is fine
|
|
|
|
debugC(4, kDebugLevelSprites, "check position @ %d, %d", screenObj->xPos, screenObj->yPos);
|
2007-01-16 12:40:51 +00:00
|
|
|
|
2016-01-29 14:11:07 +00:00
|
|
|
if (screenObj->xPos < 0) {
|
|
|
|
result = false;
|
|
|
|
} else {
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) {
|
|
|
|
result = false;
|
2016-01-29 14:11:07 +00:00
|
|
|
} else {
|
|
|
|
if (screenObj->yPos - screenObj->ySize < -1) {
|
|
|
|
result = false;
|
|
|
|
} else {
|
|
|
|
if (screenObj->yPos >= SCRIPT_HEIGHT) {
|
|
|
|
result = false;
|
|
|
|
} else {
|
|
|
|
if (((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)) {
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-29 12:13:40 +00:00
|
|
|
}
|
2016-01-29 14:11:07 +00:00
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// MH1 needs this, but it breaks LSL1
|
2016-01-29 12:13:40 +00:00
|
|
|
// TODO: *NOT* in disassembly of AGI3 .149, why was this needed?
|
|
|
|
// if (getVersion() >= 0x3000) {
|
|
|
|
// if (screenObj->yPos < screenObj->ySize)
|
|
|
|
// result = false;
|
|
|
|
// }
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (!result) {
|
|
|
|
debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d",
|
2016-02-02 17:43:36 +00:00
|
|
|
screenObj->xPos, screenObj->yPos, screenObj->xSize, screenObj->ySize);
|
2016-01-29 12:13:40 +00:00
|
|
|
}
|
|
|
|
return result;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if there's another object on the way
|
|
|
|
*/
|
2016-01-29 12:13:40 +00:00
|
|
|
bool AgiEngine::checkCollision(ScreenObjEntry *screenObj) {
|
|
|
|
ScreenObjEntry *checkObj;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->flags & fIgnoreObjects)
|
|
|
|
return false;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
for (checkObj = _game.screenObjTable; checkObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; checkObj++) {
|
|
|
|
if ((checkObj->flags & (fAnimated | fDrawn)) != (fAnimated | fDrawn))
|
2006-05-23 23:43:52 +00:00
|
|
|
continue;
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (checkObj->flags & fIgnoreObjects)
|
2006-05-23 23:43:52 +00:00
|
|
|
continue;
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Same object, check next
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->objectNr == checkObj->objectNr)
|
2006-05-23 23:43:52 +00:00
|
|
|
continue;
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// No horizontal overlap, check next
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->xPos + screenObj->xSize < checkObj->xPos || screenObj->xPos > checkObj->xPos + checkObj->xSize)
|
2006-05-23 23:43:52 +00:00
|
|
|
continue;
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Same y, return error!
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->yPos == checkObj->yPos) {
|
|
|
|
debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr);
|
|
|
|
return true;
|
2007-05-06 10:35:47 +00:00
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Crossed the baseline, return error!
|
2016-01-29 12:13:40 +00:00
|
|
|
if ((screenObj->yPos > checkObj->yPos && screenObj->yPos_prev < checkObj->yPos_prev) ||
|
2016-02-02 17:43:36 +00:00
|
|
|
(screenObj->yPos < checkObj->yPos && screenObj->yPos_prev > checkObj->yPos_prev)) {
|
2016-01-29 12:13:40 +00:00
|
|
|
debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", screenObj->objectNr);
|
|
|
|
return true;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-02 17:43:36 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
return false;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
bool AgiEngine::checkPriority(ScreenObjEntry *screenObj) {
|
|
|
|
bool touchedWater = false;
|
|
|
|
bool touchedTrigger = false;
|
|
|
|
bool touchedControl = true;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (!(screenObj->flags & fFixedPriority)) {
|
2009-06-06 17:39:13 +00:00
|
|
|
// Priority bands
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->priority = _gfx->priorityFromY(screenObj->yPos);
|
2012-01-07 16:55:11 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->priority != 0x0f) {
|
|
|
|
touchedWater = true;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2024-03-09 15:45:58 +00:00
|
|
|
int16 curX = screenObj->xPos;
|
|
|
|
int16 curY = screenObj->yPos;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2024-03-09 15:45:58 +00:00
|
|
|
for (int16 celX = 0; celX < screenObj->xSize; celX++, curX++) {
|
|
|
|
byte screenPriority = _gfx->getPriority(curX, curY);
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-02-02 17:43:36 +00:00
|
|
|
if (screenPriority == 0) { // unconditional black. no go at all!
|
2016-02-19 01:00:03 +00:00
|
|
|
touchedControl = false;
|
2016-01-29 12:13:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-02-02 17:43:36 +00:00
|
|
|
if (screenPriority != 3) { // not water surface
|
2016-01-29 12:13:40 +00:00
|
|
|
touchedWater = false;
|
|
|
|
|
2016-02-02 17:43:36 +00:00
|
|
|
if (screenPriority == 1) { // conditional blue
|
2016-01-29 12:13:40 +00:00
|
|
|
if (!(screenObj->flags & fIgnoreBlocks)) {
|
|
|
|
debugC(4, kDebugLevelSprites, "Blocks observed!");
|
|
|
|
touchedControl = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (screenPriority == 2) {
|
|
|
|
debugC(4, kDebugLevelSprites, "stepped on trigger");
|
|
|
|
if (!_debug.ignoretriggers)
|
|
|
|
touchedTrigger = true;
|
|
|
|
}
|
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (touchedControl) {
|
|
|
|
if (!touchedWater) {
|
|
|
|
if (screenObj->flags & fOnWater)
|
|
|
|
touchedControl = false;
|
|
|
|
} else {
|
|
|
|
if (screenObj->flags & fOnLand)
|
|
|
|
touchedControl = false;
|
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-06 10:35:47 +00:00
|
|
|
// Check ego
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->objectNr == 0) {
|
2024-03-09 04:49:17 +00:00
|
|
|
setFlag(VM_FLAG_EGO_TOUCHED_P2, touchedTrigger);
|
|
|
|
setFlag(VM_FLAG_EGO_WATER, touchedWater);
|
2024-03-09 15:47:34 +00:00
|
|
|
|
|
|
|
// WORKAROUND: KQ3 infinite falling, bug #13379
|
|
|
|
// Falling off of the ladder in room 22 or the stairs in room 64 can
|
|
|
|
// cause ego to fall forever in place. In both rooms, and possibly
|
|
|
|
// others, an unrelated black priority line overlaps with fall paths.
|
|
|
|
// This also occurs in the original. Ignore these lines when falling.
|
|
|
|
if (!touchedControl && getGameID() == GID_KQ3 && screenObj->currentViewNr == 11) {
|
|
|
|
touchedControl = true;
|
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
return touchedControl;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-01-16 12:40:51 +00:00
|
|
|
void AgiEngine::updatePosition() {
|
2016-01-31 16:35:13 +00:00
|
|
|
setVar(VM_VAR_BORDER_CODE, 0);
|
|
|
|
setVar(VM_VAR_BORDER_TOUCH_EGO, 0);
|
|
|
|
setVar(VM_VAR_BORDER_TOUCH_OBJECT, 0);
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2024-03-09 15:45:58 +00:00
|
|
|
ScreenObjEntry *screenObj;
|
2016-01-29 12:13:40 +00:00
|
|
|
for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
|
|
|
|
if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
|
2006-05-23 23:43:52 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (screenObj->stepTimeCount > 1) {
|
|
|
|
screenObj->stepTimeCount--;
|
|
|
|
continue;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->stepTimeCount = screenObj->stepTime;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2024-03-09 15:45:58 +00:00
|
|
|
int x = screenObj->xPos;
|
|
|
|
int oldX = x;
|
|
|
|
int y = screenObj->yPos;
|
|
|
|
int oldY = y;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// If object has moved, update its position
|
2016-01-29 12:13:40 +00:00
|
|
|
if (!(screenObj->flags & fUpdatePos)) {
|
2024-03-10 23:16:09 +00:00
|
|
|
const int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
|
|
|
|
const int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
|
2016-01-29 12:13:40 +00:00
|
|
|
x += screenObj->stepSize * dx[screenObj->direction];
|
|
|
|
y += screenObj->stepSize * dy[screenObj->direction];
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Now check if it touched the borders
|
2024-03-09 15:45:58 +00:00
|
|
|
int border = 0;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Check left/right borders
|
2016-01-29 12:13:40 +00:00
|
|
|
if (getVersion() == 0x3086) {
|
|
|
|
// KQ4 interpreter does a different comparison on x
|
|
|
|
// The interpreter before (2.917) and after that (3.098) don't do them that way
|
|
|
|
// This difference is required for at least Sarien bug #192
|
|
|
|
// KQ4: room 135, hen moves from the center of the screen to the left border,
|
|
|
|
// but it doesn't disappear.
|
|
|
|
if (x <= 0) {
|
|
|
|
x = 0;
|
|
|
|
border = 4;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// regular comparison
|
|
|
|
if (x < 0) {
|
|
|
|
x = 0;
|
|
|
|
border = 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// } else if (v->entry == 0 && x == 0 && v->flags & fAdjEgoXY) { // should not be required
|
|
|
|
// // Extra test to walk west clicking the mouse
|
|
|
|
// x = 0;
|
|
|
|
// border = 4;
|
|
|
|
|
|
|
|
if (!border) {
|
|
|
|
if (x + screenObj->xSize > SCRIPT_WIDTH) {
|
|
|
|
x = SCRIPT_WIDTH - screenObj->xSize;
|
|
|
|
border = 2;
|
|
|
|
}
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Check top/bottom borders.
|
2016-01-29 12:13:40 +00:00
|
|
|
if (y - screenObj->ySize < -1) {
|
|
|
|
y = screenObj->ySize - 1;
|
2006-05-23 23:43:52 +00:00
|
|
|
border = 1;
|
2016-01-29 12:13:40 +00:00
|
|
|
} else if (y > SCRIPT_HEIGHT - 1) {
|
|
|
|
y = SCRIPT_HEIGHT - 1;
|
2006-05-23 23:43:52 +00:00
|
|
|
border = 3;
|
2016-01-29 12:13:40 +00:00
|
|
|
} else if ((!(screenObj->flags & fIgnoreHorizon)) && y <= _game.horizon) {
|
2007-01-16 12:40:51 +00:00
|
|
|
debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, _game.horizon);
|
|
|
|
y = _game.horizon + 1;
|
2006-05-23 23:43:52 +00:00
|
|
|
border = 1;
|
|
|
|
}
|
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// Test new position. rollback if test fails
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->xPos = x;
|
|
|
|
screenObj->yPos = y;
|
|
|
|
if (checkCollision(screenObj) || !checkPriority(screenObj)) {
|
|
|
|
screenObj->xPos = oldX;
|
|
|
|
screenObj->yPos = oldY;
|
2006-05-23 23:43:52 +00:00
|
|
|
border = 0;
|
2016-01-29 12:13:40 +00:00
|
|
|
fixPosition(screenObj->objectNr);
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
if (border) {
|
|
|
|
if (isEgoView(screenObj)) {
|
2016-01-31 16:35:13 +00:00
|
|
|
setVar(VM_VAR_BORDER_TOUCH_EGO, border);
|
2006-05-23 23:43:52 +00:00
|
|
|
} else {
|
2016-01-31 16:35:13 +00:00
|
|
|
setVar(VM_VAR_BORDER_CODE, screenObj->objectNr);
|
|
|
|
setVar(VM_VAR_BORDER_TOUCH_OBJECT, border);
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
2016-01-31 00:04:53 +00:00
|
|
|
if (screenObj->motionType == kMotionMoveObj) {
|
2016-01-29 12:13:40 +00:00
|
|
|
motionMoveObjStop(screenObj);
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->flags &= ~fUpdatePos;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2011-05-25 14:31:37 +00:00
|
|
|
* This behavior is also known as the "Budin-Sonneveld effect".
|
2006-05-23 23:43:52 +00:00
|
|
|
*
|
|
|
|
* @param n view table entry number
|
|
|
|
*/
|
2016-01-29 12:13:40 +00:00
|
|
|
void AgiEngine::fixPosition(int16 screenObjNr) {
|
|
|
|
ScreenObjEntry *screenObj = &_game.screenObjTable[screenObjNr];
|
|
|
|
fixPosition(screenObj);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AgiEngine::fixPosition(ScreenObjEntry *screenObj) {
|
|
|
|
debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2009-06-06 17:39:13 +00:00
|
|
|
// test horizon
|
2016-01-29 12:13:40 +00:00
|
|
|
if ((!(screenObj->flags & fIgnoreHorizon)) && screenObj->yPos <= _game.horizon)
|
|
|
|
screenObj->yPos = _game.horizon + 1;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2024-03-09 15:45:58 +00:00
|
|
|
int dir = 0;
|
|
|
|
int count = 1;
|
|
|
|
int size = 1;
|
2006-05-23 23:43:52 +00:00
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
while (!checkPosition(screenObj) || checkCollision(screenObj) || !checkPriority(screenObj)) {
|
2006-05-23 23:43:52 +00:00
|
|
|
switch (dir) {
|
2016-02-02 17:43:36 +00:00
|
|
|
case 0: // west
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->xPos--;
|
2006-05-23 23:43:52 +00:00
|
|
|
if (--count)
|
|
|
|
continue;
|
|
|
|
dir = 1;
|
|
|
|
break;
|
2016-02-02 17:43:36 +00:00
|
|
|
case 1: // south
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->yPos++;
|
2006-05-23 23:43:52 +00:00
|
|
|
if (--count)
|
|
|
|
continue;
|
|
|
|
dir = 2;
|
|
|
|
size++;
|
|
|
|
break;
|
2016-02-02 17:43:36 +00:00
|
|
|
case 2: // east
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->xPos++;
|
2006-05-23 23:43:52 +00:00
|
|
|
if (--count)
|
|
|
|
continue;
|
|
|
|
dir = 3;
|
|
|
|
break;
|
2016-02-02 17:43:36 +00:00
|
|
|
case 3: // north
|
2016-01-29 12:13:40 +00:00
|
|
|
screenObj->yPos--;
|
2006-05-23 23:43:52 +00:00
|
|
|
if (--count)
|
|
|
|
continue;
|
|
|
|
dir = 0;
|
|
|
|
size++;
|
|
|
|
break;
|
2019-10-14 02:54:10 +00:00
|
|
|
default:
|
|
|
|
break;
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
count = size;
|
|
|
|
}
|
|
|
|
|
2016-01-29 12:13:40 +00:00
|
|
|
debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", screenObj->objectNr, screenObj->xPos, screenObj->yPos);
|
2006-05-23 23:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // End of namespace Agi
|