scummvm/engines/agi/sprite.cpp
Martin Kiewitz e1c36a52b5 AGI: Add support for upscaling and Hercules hires font
- User option to force Hercules hires font for any rendering mode
- Also change mouse cursor hotspots from 1,1 to 0,0
- Fix inaccuracy in mouse controlled game menu
- Change render_Block(), drawBox(), drawDisplayRect() to use
  upper Y instead of lower Y. Original AGI uses lower Y, but
  upper Y makes upscaling way easier.
2016-02-27 21:44:21 +01:00

554 lines
16 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"
#include "agi/sprite.h"
#include "agi/graphics.h"
#include "agi/text.h"
namespace Agi {
SpritesMgr::SpritesMgr(AgiEngine *agi, GfxMgr *gfx) {
_vm = agi;
_gfx = gfx;
}
SpritesMgr::~SpritesMgr() {
_spriteRegularList.clear();
_spriteStaticList.clear();
}
static bool sortSpriteHelper(const Sprite &entry1, const Sprite &entry2) {
if (entry1.sortOrder == entry2.sortOrder) {
// If sort-order is the same, we sort according to given order
// which makes this sort stable.
return entry1.givenOrderNr < entry2.givenOrderNr;
}
return entry1.sortOrder < entry2.sortOrder;
}
void SpritesMgr::buildRegularSpriteList() {
ScreenObjEntry *screenObj = NULL;
uint16 givenOrderNr = 0;
freeList(_spriteRegularList);
for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fUpdate | fDrawn)) {
buildSpriteListAdd(givenOrderNr, screenObj, _spriteRegularList);
givenOrderNr++;
}
}
// Now sort this list
Common::sort(_spriteRegularList.begin(), _spriteRegularList.end(), sortSpriteHelper);
// warning("buildRegular: %d", _spriteRegularList.size());
}
void SpritesMgr::buildStaticSpriteList() {
ScreenObjEntry *screenObj = NULL;
uint16 givenOrderNr = 0;
freeList(_spriteStaticList);
for (screenObj = _vm->_game.screenObjTable; screenObj < &_vm->_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) == (fAnimated | fDrawn)) { // DIFFERENCE IN HERE!
buildSpriteListAdd(givenOrderNr, screenObj, _spriteStaticList);
givenOrderNr++;
}
}
// Now sort this list
Common::sort(_spriteStaticList.begin(), _spriteStaticList.end(), sortSpriteHelper);
}
void SpritesMgr::buildAllSpriteLists() {
buildStaticSpriteList();
buildRegularSpriteList();
}
void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenObj, SpriteList &spriteList) {
Sprite spriteEntry;
// Check, if screen object points to currently loaded view, if not don't add it
if (!(_vm->_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))
return;
spriteEntry.givenOrderNr = givenOrderNr;
// warning("sprite add objNr %d", screenObjPtr->objectNr);
if (screenObj->flags & fFixedPriority) {
spriteEntry.sortOrder = _gfx->priorityToY(screenObj->priority);
// warning(" - priorityToY (fixed) %d -> %d", screenObj->priority, spriteEntry.sortOrder);
} else {
spriteEntry.sortOrder = screenObj->yPos;
// warning(" - Ypos %d -> %d", screenObjPtr->yPos, spriteEntry.sortOrder);
}
spriteEntry.screenObjPtr = screenObj;
spriteEntry.xPos = screenObj->xPos;
spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1;
spriteEntry.xSize = screenObj->xSize;
spriteEntry.ySize = screenObj->ySize;
// Checking, if xPos/yPos/right/bottom are valid and do not go outside of playscreen (visual screen)
// Original AGI did not do this (but it then resulted in memory corruption)
if (spriteEntry.xPos < 0) {
warning("buildSpriteListAdd(): ignoring screen obj %d, b/c xPos (%d) < 0", screenObj->objectNr, spriteEntry.xPos);
return;
}
if (spriteEntry.yPos < 0) {
warning("buildSpriteListAdd(): ignoring screen obj %d, b/c yPos (%d) < 0", screenObj->objectNr, spriteEntry.yPos);
return;
}
int16 xRight = spriteEntry.xPos + spriteEntry.xSize;
if (xRight > SCRIPT_HEIGHT) {
warning("buildSpriteListAdd(): ignoring screen obj %d, b/c rightPos (%d) > %d", screenObj->objectNr, xRight, SCRIPT_WIDTH);
return;
}
int16 yBottom = spriteEntry.yPos + spriteEntry.ySize;
if (yBottom > SCRIPT_HEIGHT) {
warning("buildSpriteListAdd(): ignoring screen obj %d, b/c bottomPos (%d) > %d", screenObj->objectNr, yBottom, SCRIPT_HEIGHT);
return;
}
// warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize);
spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data
assert(spriteEntry.backgroundBuffer);
spriteList.push_back(spriteEntry);
}
void SpritesMgr::freeList(SpriteList &spriteList) {
SpriteList::iterator iter;
for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
Sprite &sprite = *iter;
free(sprite.backgroundBuffer);
}
spriteList.clear();
}
void SpritesMgr::freeRegularSprites() {
freeList(_spriteRegularList);
}
void SpritesMgr::freeStaticSprites() {
freeList(_spriteStaticList);
}
void SpritesMgr::freeAllSprites() {
freeList(_spriteRegularList);
freeList(_spriteStaticList);
}
void SpritesMgr::eraseSprites(SpriteList &spriteList) {
SpriteList::iterator iter;
// warning("eraseSprites - count %d", spriteList.size());
for (iter = spriteList.reverse_begin(); iter != spriteList.end(); iter--) {
Sprite &sprite = *iter;
_gfx->block_restore(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
}
freeList(spriteList);
}
/**
* Erase updating sprites.
* This function follows the list of all updating sprites and restores
* the visible and priority data of their background buffers back to
* the AGI screen.
*
* @see erase_nonupd_sprites()
* @see erase_both()
*/
void SpritesMgr::eraseRegularSprites() {
eraseSprites(_spriteRegularList);
}
void SpritesMgr::eraseStaticSprites() {
eraseSprites(_spriteStaticList);
}
void SpritesMgr::eraseSprites() {
eraseSprites(_spriteRegularList);
eraseSprites(_spriteStaticList);
}
/**
* Draw all sprites in the given list.
*/
void SpritesMgr::drawSprites(SpriteList &spriteList) {
SpriteList::iterator iter;
// warning("drawSprites");
for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
Sprite &sprite = *iter;
ScreenObjEntry *screenObj = sprite.screenObjPtr;
_gfx->block_save(sprite.xPos, sprite.yPos, sprite.xSize, sprite.ySize, sprite.backgroundBuffer);
//debugC(8, kDebugLevelSprites, "drawSprites(): s->v->entry = %d (prio %d)", s->viewPtr->entry, s->viewPtr->priority);
// warning("sprite %d (view %d), priority %d, sort %d, givenOrder %d", screenObj->objectNr, screenObj->currentView, screenObj->priority, sprite.sortOrder, sprite.givenOrderNr);
drawCel(screenObj);
}
}
/**
* Blit updating sprites.
* This function follows the list of all updating sprites and blits
* them on the AGI screen.
*
* @see blit_nonupd_sprites()
* @see blit_both()
*/
void SpritesMgr::drawRegularSpriteList() {
debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
drawSprites(_spriteRegularList);
}
void SpritesMgr::drawStaticSpriteList() {
//debugC(7, kDebugLevelSprites, "drawRegularSpriteList()");
drawSprites(_spriteStaticList);
}
void SpritesMgr::drawAllSpriteLists() {
drawSprites(_spriteStaticList);
drawSprites(_spriteRegularList);
}
void SpritesMgr::drawCel(ScreenObjEntry *screenObj) {
int16 curX = screenObj->xPos;
int16 baseX = screenObj->xPos;
int16 curY = screenObj->yPos;
AgiViewCel *celPtr = screenObj->celData;
byte *celDataPtr = celPtr->rawBitmap;
uint8 remainingCelHeight = celPtr->height;
uint8 celWidth = celPtr->width;
byte celClearKey = celPtr->clearKey;
byte viewPriority = screenObj->priority;
byte screenPriority = 0;
byte curColor = 0;
byte isViewHidden = true;
// Adjust vertical position, given yPos is lower left, but we need upper left
curY = curY - celPtr->height + 1;
while (remainingCelHeight) {
for (int16 loopX = 0; loopX < celWidth; loopX++) {
curColor = *celDataPtr++;
if (curColor != celClearKey) {
screenPriority = _gfx->getPriority(curX, curY);
if (screenPriority <= 2) {
// control data found
if (_gfx->checkControlPixel(curX, curY, viewPriority)) {
_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_VISUAL, curColor, 0);
isViewHidden = false;
}
} else if (screenPriority <= viewPriority) {
_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_ALL, curColor, viewPriority);
isViewHidden = false;
}
}
curX++;
}
// go to next vertical position
remainingCelHeight--;
curX = baseX;
curY++;
}
if (screenObj->objectNr == 0) { // if ego, update if ego is visible at the moment
_vm->setFlag(VM_FLAG_EGO_INVISIBLE, isViewHidden);
}
}
void SpritesMgr::showSprite(ScreenObjEntry *screenObj) {
int16 x = 0;
int16 y = 0;
int16 width = 0;
int16 height = 0;
int16 view_height_prev = 0;
int16 view_width_prev = 0;
int16 y2 = 0;
int16 height1 = 0;
int16 height2 = 0;
int16 x2 = 0;
int16 width1 = 0;
int16 width2 = 0;
if (!_vm->_game.pictureShown)
return;
view_height_prev = screenObj->ySize_prev;
view_width_prev = screenObj->xSize_prev;
screenObj->ySize_prev = screenObj->ySize;
screenObj->xSize_prev = screenObj->xSize;
if (screenObj->yPos < screenObj->yPos_prev) {
y = screenObj->yPos_prev;
y2 = screenObj->yPos;
height1 = view_height_prev;
height2 = screenObj->ySize;
} else {
y = screenObj->yPos;
y2 = screenObj->yPos_prev;
height1 = screenObj->ySize;
height2 = view_height_prev;
}
if ((y2 - height2) > (y - height1)) {
height = height1;
} else {
height = y - y2 + height2;
}
if (screenObj->xPos > screenObj->xPos_prev) {
x = screenObj->xPos_prev;
x2 = screenObj->xPos;
width1 = view_width_prev;
width2 = screenObj->xSize;
} else {
x = screenObj->xPos;
x2 = screenObj->xPos_prev;
width1 = screenObj->xSize;
width2 = view_width_prev;
}
if ((x2 + width2) < (x + width1)) {
width = width1;
} else {
width = width2 + x2 - x;
}
if ((x + width) > 161) {
width = 161 - x;
}
if (1 < (height - y)) {
height = y + 1;
}
// render this block
int16 upperY = y - height + 1;
_gfx->render_Block(x, upperY, width, height);
}
void SpritesMgr::showSprites(SpriteList &spriteList) {
SpriteList::iterator iter;
ScreenObjEntry *screenObjPtr = NULL;
for (iter = spriteList.begin(); iter != spriteList.end(); ++iter) {
Sprite &sprite = *iter;
screenObjPtr = sprite.screenObjPtr;
showSprite(screenObjPtr);
if (screenObjPtr->stepTimeCount == screenObjPtr->stepTime) {
if ((screenObjPtr->xPos == screenObjPtr->xPos_prev) && (screenObjPtr->yPos == screenObjPtr->yPos_prev)) {
screenObjPtr->flags |= fDidntMove;
} else {
screenObjPtr->xPos_prev = screenObjPtr->xPos;
screenObjPtr->yPos_prev = screenObjPtr->yPos;
screenObjPtr->flags &= ~fDidntMove;
}
}
}
g_system->updateScreen();
//g_system->delayMillis(20);
}
void SpritesMgr::showRegularSpriteList() {
debugC(7, kDebugLevelSprites, "showRegularSpriteList()");
showSprites(_spriteRegularList);
}
void SpritesMgr::showStaticSpriteList() {
debugC(7, kDebugLevelSprites, "showStaticSpriteList()");
showSprites(_spriteStaticList);
}
void SpritesMgr::showAllSpriteLists() {
showSprites(_spriteStaticList);
showSprites(_spriteRegularList);
}
/**
* Show object and description
* This function shows an object from the player's inventory, displaying
* a message box with the object description.
* @param n Number of the object to show
*/
void SpritesMgr::showObject(int16 viewNr) {
ScreenObjEntry screenObj;
uint8 *backgroundBuffer = NULL;
_vm->agiLoadResource(RESOURCETYPE_VIEW, viewNr);
_vm->setView(&screenObj, viewNr);
screenObj.ySize_prev = screenObj.celData->height;
screenObj.xSize_prev = screenObj.celData->width;
screenObj.xPos_prev = ((SCRIPT_WIDTH - 1) - screenObj.xSize) / 2;
screenObj.xPos = screenObj.xPos_prev;
screenObj.yPos_prev = SCRIPT_HEIGHT - 1;
screenObj.yPos = screenObj.yPos_prev;
screenObj.priority = 15;
screenObj.flags = fFixedPriority; // Original AGI did "| fFixedPriority" on uninitialized memory
screenObj.objectNr = 255; // ???
backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data
_gfx->block_save(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
drawCel(&screenObj);
showSprite(&screenObj);
_vm->_text->messageBox((char *)_vm->_game.views[viewNr].description);
_gfx->block_restore(screenObj.xPos, (screenObj.yPos - screenObj.ySize + 1), screenObj.xSize, screenObj.ySize, backgroundBuffer);
showSprite(&screenObj);
free(backgroundBuffer);
}
/**
* Add view to picture.
* This function is used to implement the add.to.pic AGI command. It
* copies the specified cel from a view resource on the current picture.
* This cel is not a sprite, it can't be moved or removed.
* @param view number of view resource
* @param loop number of loop in the specified view resource
* @param cel number of cel in the specified loop
* @param x x coordinate to place the view
* @param y y coordinate to place the view
* @param pri priority to use
* @param mar if < 4, create a margin around the the base of the cel
*/
void SpritesMgr::addToPic(int16 viewNr, int16 loopNr, int16 celNr, int16 xPos, int16 yPos, int16 priority, int16 border) {
debugC(3, kDebugLevelSprites, "addToPic(view=%d, loop=%d, cel=%d, x=%d, y=%d, pri=%d, border=%d)", viewNr, loopNr, celNr, xPos, yPos, priority, border);
_vm->recordImageStackCall(ADD_VIEW, viewNr, loopNr, celNr, xPos, yPos, priority, border);
ScreenObjEntry *screenObj = &_vm->_game.addToPicView;
screenObj->objectNr = -1; // addToPic-view
_vm->setView(screenObj, viewNr);
_vm->setLoop(screenObj, loopNr);
_vm->setCel(screenObj, celNr);
screenObj->xSize_prev = screenObj->xSize;
screenObj->ySize_prev = screenObj->ySize;
screenObj->xPos_prev = xPos;
screenObj->xPos = xPos;
screenObj->yPos_prev = yPos;
screenObj->yPos = yPos;
screenObj->flags = fIgnoreObjects | fIgnoreHorizon | fFixedPriority;
screenObj->priority = 15;
_vm->fixPosition(screenObj);
if (priority == 0) {
screenObj->flags = fIgnoreHorizon;
}
screenObj->priority = priority;
eraseSprites();
// bugs related to this code: required by Gold Rush (see Sarien bug #587558)
if (screenObj->priority == 0) {
screenObj->priority = _gfx->priorityFromY(screenObj->yPos);
}
drawCel(screenObj);
if (border <= 3) {
// Create priority-box
addToPicDrawPriorityBox(screenObj, border);
}
buildAllSpriteLists();
drawAllSpriteLists();
showSprite(screenObj);
}
// bugs previously related to this:
// Sarien bug #247)
void SpritesMgr::addToPicDrawPriorityBox(ScreenObjEntry *screenObj, int16 border) {
int16 priorityFromY = _gfx->priorityFromY(screenObj->yPos);
int16 priorityHeight = 0;
int16 curY = 0;
int16 curX = 0;
int16 height = 0;
int16 width = 0;
int16 offsetX = 0;
// Figure out the height of the box
curY = screenObj->yPos;
do {
priorityHeight++;
if (curY <= 0)
break;
curY--;
} while (_gfx->priorityFromY(curY) == priorityFromY);
// box height may not be larger than the actual view
if (screenObj->ySize < priorityHeight)
priorityHeight = screenObj->ySize;
// now actually draw lower horizontal line
curY = screenObj->yPos;
curX = screenObj->xPos;
width = screenObj->xSize;
while (width) {
_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
curX++;
width--;
}
if (priorityHeight > 1) {
// Actual rectangle is needed
curY = screenObj->yPos;
curX = screenObj->xPos;
offsetX = screenObj->xSize - 1;
height = priorityHeight - 1;
while (height) {
curY--;
height--;
_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // left line
_gfx->putPixel(curX + offsetX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border); // right line
}
// and finally the upper horizontal line
width = screenObj->xSize - 2;
curX++;
while (width > 0) {
_gfx->putPixel(curX, curY, GFX_SCREEN_MASK_PRIORITY, 0, border);
curX++;
width--;
}
}
}
} // End of namespace Agi