/* 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