Initial commit
This commit is contained in:
740
engines/agi/view.cpp
Normal file
740
engines/agi/view.cpp
Normal file
@@ -0,0 +1,740 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "agi/agi.h"
|
||||
#include "agi/graphics.h"
|
||||
#include "agi/sprite.h"
|
||||
|
||||
namespace Agi {
|
||||
|
||||
// Apple II V2+ views use different color values.
|
||||
static const byte apple2ViewColorMap[16] = {
|
||||
/*00*/ 0x00,
|
||||
/*01*/ 0x04,
|
||||
/*02*/ 0x01,
|
||||
/*03*/ 0x05,
|
||||
/*04*/ 0x02,
|
||||
/*05*/ 0x08,
|
||||
/*06*/ 0x09,
|
||||
/*07*/ 0x0b,
|
||||
/*08*/ 0x06,
|
||||
/*09*/ 0x0d,
|
||||
/*0a*/ 0x07,
|
||||
/*0b*/ 0x0c,
|
||||
/*0c*/ 0x0a,
|
||||
/*0d*/ 0x0e,
|
||||
/*0e*/ 0x03,
|
||||
/*0f*/ 0x0f
|
||||
};
|
||||
|
||||
void AgiEngine::updateView(ScreenObjEntry *screenObj) {
|
||||
if (screenObj->flags & fDontUpdate) {
|
||||
screenObj->flags &= ~fDontUpdate;
|
||||
return;
|
||||
}
|
||||
|
||||
int16 celNr = screenObj->currentCelNr;
|
||||
int16 lastCelNr = screenObj->celCount - 1;
|
||||
|
||||
switch (screenObj->cycle) {
|
||||
case kCycleNormal:
|
||||
celNr++;
|
||||
if (celNr > lastCelNr)
|
||||
celNr = 0;
|
||||
break;
|
||||
case kCycleEndOfLoop:
|
||||
if (celNr < lastCelNr) {
|
||||
debugC(5, kDebugLevelResources, "cel %d (last = %d)", celNr + 1, lastCelNr);
|
||||
if (++celNr != lastCelNr)
|
||||
break;
|
||||
}
|
||||
if (!screenObj->ignoreLoopFlag) {
|
||||
setFlag(screenObj->loop_flag, true);
|
||||
} else {
|
||||
warning("kCycleEndOfLoop: skip setting flag %d", screenObj->loop_flag);
|
||||
}
|
||||
screenObj->flags &= ~fCycling;
|
||||
screenObj->direction = 0;
|
||||
screenObj->cycle = kCycleNormal;
|
||||
break;
|
||||
case kCycleRevLoop:
|
||||
if (celNr) {
|
||||
celNr--;
|
||||
if (celNr)
|
||||
break;
|
||||
}
|
||||
if (!screenObj->ignoreLoopFlag) {
|
||||
setFlag(screenObj->loop_flag, true);
|
||||
} else {
|
||||
warning("kCycleRevLoop: skip setting flag %d", screenObj->loop_flag);
|
||||
}
|
||||
screenObj->flags &= ~fCycling;
|
||||
screenObj->direction = 0;
|
||||
screenObj->cycle = kCycleNormal;
|
||||
break;
|
||||
case kCycleReverse:
|
||||
if (celNr == 0) {
|
||||
celNr = lastCelNr;
|
||||
} else {
|
||||
celNr--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
setCel(screenObj, celNr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an AGI view resource.
|
||||
* This function decodes the raw data of the specified AGI view resource
|
||||
* and fills the corresponding views array element.
|
||||
* @param viewNr number of view resource to decode
|
||||
*/
|
||||
int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr) {
|
||||
AgiView *viewData = &_game.views[viewNr];
|
||||
|
||||
debugC(5, kDebugLevelResources, "decodeView(%d)", viewNr);
|
||||
|
||||
if (resourceSize < 5)
|
||||
error("unexpected end of view data for view %d", viewNr);
|
||||
|
||||
if (getVersion() < 0x2000) {
|
||||
viewData->headerStepSize = resourceData[0];
|
||||
viewData->headerCycleTime = resourceData[1];
|
||||
} else {
|
||||
viewData->headerStepSize = 0;
|
||||
viewData->headerCycleTime = 0;
|
||||
}
|
||||
|
||||
bool isAGI256Data = false;
|
||||
if (getFeatures() & GF_AGI256) {
|
||||
uint16 headerId = READ_LE_UINT16(resourceData);
|
||||
isAGI256Data = (headerId == 0xF00F); // AGI 256-2 view detected, 256 color view
|
||||
}
|
||||
|
||||
// Apple II V2+ views stopped including the first two bytes
|
||||
int headerLoopCountOffset = 2;
|
||||
int viewHeaderSize = 5;
|
||||
const bool apple2 = (getPlatform() == Common::kPlatformApple2) && getVersion() >= 0x2000;
|
||||
if (apple2) {
|
||||
headerLoopCountOffset = 0;
|
||||
viewHeaderSize = 3;
|
||||
}
|
||||
|
||||
byte headerLoopCount = resourceData[headerLoopCountOffset];
|
||||
uint16 headerDescriptionOffset = READ_LE_UINT16(resourceData + headerLoopCountOffset + 1);
|
||||
|
||||
viewData->loopCount = headerLoopCount;
|
||||
viewData->description = nullptr;
|
||||
viewData->loop = nullptr;
|
||||
|
||||
if (headerDescriptionOffset) {
|
||||
// Figure out length of description
|
||||
uint16 descriptionPos = headerDescriptionOffset;
|
||||
uint16 descriptionLen = 0;
|
||||
while (descriptionPos < resourceSize) {
|
||||
if (resourceData[descriptionPos] == 0)
|
||||
break;
|
||||
descriptionPos++;
|
||||
descriptionLen++;
|
||||
}
|
||||
// Allocate memory for description
|
||||
viewData->description = new byte[descriptionLen + 1];
|
||||
// Copy description over
|
||||
memcpy(viewData->description, resourceData + headerDescriptionOffset, descriptionLen);
|
||||
viewData->description[descriptionLen] = 0; // set terminator
|
||||
}
|
||||
|
||||
if (!viewData->loopCount) // no loops, exit now
|
||||
return errOK;
|
||||
|
||||
// Check, if at least the loop-offsets are available
|
||||
if (resourceSize < viewHeaderSize + (headerLoopCount * 2))
|
||||
error("unexpected end of view data for view %d", viewNr);
|
||||
|
||||
// Allocate space for loop-information
|
||||
AgiViewLoop *loopData = new AgiViewLoop[headerLoopCount];
|
||||
viewData->loop = loopData;
|
||||
|
||||
for (int16 loopNr = 0; loopNr < headerLoopCount; loopNr++) {
|
||||
uint16 loopOffset = READ_LE_UINT16(resourceData + viewHeaderSize + (loopNr * 2));
|
||||
|
||||
// Check, if at least the loop-header is available
|
||||
if (resourceSize < (loopOffset + 1))
|
||||
error("unexpected end of view data for view %d", viewNr);
|
||||
|
||||
// loop-header:
|
||||
// celCount:BYTE [ upper nibble used as mirror data in 2.230 ]
|
||||
// relativeCelOffset[0]:WORD
|
||||
// relativeCelOffset[1]:WORD
|
||||
// etc.
|
||||
byte loopHeaderCelCountByte = resourceData[loopOffset];
|
||||
const bool isMirrorDataInLoopHeader = (getVersion() == 0x2230);
|
||||
if (isMirrorDataInLoopHeader) {
|
||||
loopData->celCount = loopHeaderCelCountByte & 0x0f;
|
||||
} else {
|
||||
loopData->celCount = loopHeaderCelCountByte;
|
||||
}
|
||||
loopData->cel = nullptr;
|
||||
|
||||
// Check, if at least the cel-offsets for current loop are available
|
||||
if (resourceSize < (loopOffset + 1 + (loopData->celCount * 2)))
|
||||
error("unexpected end of view data for view %d", viewNr);
|
||||
|
||||
if (loopData->celCount) {
|
||||
// Allocate space for cel-information of current loop
|
||||
AgiViewCel *celData = new AgiViewCel[loopData->celCount];
|
||||
loopData->cel = celData;
|
||||
|
||||
for (int16 celNr = 0; celNr < loopData->celCount; celNr++) {
|
||||
uint16 celOffset = READ_LE_UINT16(resourceData + loopOffset + 1 + (celNr * 2));
|
||||
celOffset += loopOffset; // cel offset is relative to loop offset, so adjust accordingly
|
||||
|
||||
// Check, if at least the cel-header is available
|
||||
if (resourceSize < (celOffset + 3))
|
||||
error("unexpected end of view data for view %d", viewNr);
|
||||
|
||||
// cel-header:
|
||||
// width:BYTE
|
||||
// height:BYTE
|
||||
// Transparency + Mirroring:BYTE
|
||||
// celData follows
|
||||
byte celHeaderWidth = resourceData[celOffset + 0];
|
||||
byte celHeaderHeight = resourceData[celOffset + 1];
|
||||
byte celHeaderTransparencyMirror = resourceData[celOffset + 2];
|
||||
if (apple2) {
|
||||
// Apple II views switched the transparency and mirror bits
|
||||
celHeaderTransparencyMirror = (celHeaderTransparencyMirror << 4) | (celHeaderTransparencyMirror >> 4);
|
||||
}
|
||||
|
||||
byte celHeaderClearKey;
|
||||
bool celHeaderMirrored = false;
|
||||
if (!isAGI256Data) {
|
||||
// regular AGI view data
|
||||
// Transparency + Mirroring byte is as follows:
|
||||
// Bit 0-3 - clear key
|
||||
// Bit 4-6 - original loop, that is not supposed to be mirrored in any case
|
||||
// Bit 7 - apply mirroring
|
||||
celHeaderClearKey = celHeaderTransparencyMirror & 0x0F; // bit 0-3 is the clear key
|
||||
if (apple2) {
|
||||
// Apple II views use different color values
|
||||
celHeaderClearKey = apple2ViewColorMap[celHeaderClearKey];
|
||||
}
|
||||
|
||||
if (isMirrorDataInLoopHeader) {
|
||||
// 2.230 (early version of xmascard): mirror data is in loop header.
|
||||
if (loopHeaderCelCountByte & 0x80) {
|
||||
// mirror bit is set
|
||||
// there is a second mirror bit whose purpose is currently unknown;
|
||||
// both bits are set in every xmascard loop with mirror data.
|
||||
byte celHeaderMirrorLoop = (loopHeaderCelCountByte >> 4) & 0x03;
|
||||
if (celHeaderMirrorLoop != loopNr) {
|
||||
// only set to mirrored in case we are not the original loop
|
||||
celHeaderMirrored = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 2.272+: mirror data is in cel header
|
||||
if (celHeaderTransparencyMirror & 0x80) {
|
||||
// mirror bit is set
|
||||
byte celHeaderMirrorLoop = (celHeaderTransparencyMirror >> 4) & 0x07;
|
||||
if (celHeaderMirrorLoop != loopNr) {
|
||||
// only set to mirrored in case we are not the original loop
|
||||
celHeaderMirrored = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// AGI256-2 view data
|
||||
celHeaderClearKey = celHeaderTransparencyMirror; // full 8 bits for clear key
|
||||
}
|
||||
|
||||
celData->width = celHeaderWidth;
|
||||
celData->height = celHeaderHeight;
|
||||
celData->clearKey = celHeaderClearKey;
|
||||
celData->mirrored = celHeaderMirrored;
|
||||
|
||||
// Now decompress cel-data
|
||||
if ((celHeaderWidth == 0) && (celHeaderHeight == 0))
|
||||
error("view cel is 0x0");
|
||||
|
||||
byte *celCompressedData = resourceData + celOffset + 3;
|
||||
uint16 celCompressedSize = resourceSize - (celOffset + 3);
|
||||
|
||||
if (celCompressedSize == 0)
|
||||
error("compressed size of loop within view %d is 0 bytes", viewNr);
|
||||
|
||||
if (!isAGI256Data) {
|
||||
unpackViewCelData(celData, celCompressedData, celCompressedSize, viewNr);
|
||||
} else {
|
||||
unpackViewCelDataAGI256(celData, celCompressedData, celCompressedSize, viewNr);
|
||||
}
|
||||
celData++;
|
||||
}
|
||||
}
|
||||
|
||||
loopData++;
|
||||
}
|
||||
|
||||
return errOK;
|
||||
}
|
||||
|
||||
void AgiEngine::unpackViewCelData(AgiViewCel *celData, byte *compressedData, uint16 compressedSize, int16 viewNr) {
|
||||
byte *rawBitmap = new byte[celData->width * celData->height];
|
||||
int16 remainingHeight = celData->height;
|
||||
int16 remainingWidth = celData->width;
|
||||
int16 adjustPreChangeSingle = 0;
|
||||
int16 adjustAfterChangeSingle = +1;
|
||||
const bool apple2 = (getPlatform() == Common::kPlatformApple2) && getVersion() >= 0x2000;
|
||||
|
||||
celData->rawBitmap = rawBitmap;
|
||||
|
||||
if (celData->mirrored) {
|
||||
adjustPreChangeSingle = -1;
|
||||
adjustAfterChangeSingle = 0;
|
||||
rawBitmap += celData->width;
|
||||
}
|
||||
|
||||
while (remainingHeight) {
|
||||
if (!compressedSize)
|
||||
error("unexpected end of data, while unpacking view %d", viewNr);
|
||||
|
||||
byte curByte = *compressedData++;
|
||||
compressedSize--;
|
||||
|
||||
byte curColor;
|
||||
byte curChunkLen;
|
||||
if (curByte == 0) {
|
||||
curColor = celData->clearKey;
|
||||
curChunkLen = remainingWidth;
|
||||
} else {
|
||||
curColor = curByte >> 4;
|
||||
curChunkLen = curByte & 0x0F;
|
||||
if (curChunkLen > remainingWidth)
|
||||
error("invalid chunk in view %d", viewNr);
|
||||
if (apple2) {
|
||||
curColor = apple2ViewColorMap[curColor];
|
||||
}
|
||||
}
|
||||
|
||||
switch (curChunkLen) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
rawBitmap += adjustPreChangeSingle;
|
||||
*rawBitmap = curColor;
|
||||
rawBitmap += adjustAfterChangeSingle;
|
||||
break;
|
||||
default:
|
||||
if (celData->mirrored)
|
||||
rawBitmap -= curChunkLen;
|
||||
memset(rawBitmap, curColor, curChunkLen);
|
||||
if (!celData->mirrored)
|
||||
rawBitmap += curChunkLen;
|
||||
break;
|
||||
}
|
||||
|
||||
remainingWidth -= curChunkLen;
|
||||
|
||||
// Each row is terminated by a zero byte; any remaining pixels are transparent.
|
||||
// Apple II views don't use terminators, instead they explicitly draw remaining
|
||||
// transparent pixels with a normal chunk byte, and rows end when they're full.
|
||||
// The Apple II method uses one less byte on full rows.
|
||||
if (curByte == 0 || (apple2 && remainingWidth == 0)) {
|
||||
remainingWidth = celData->width;
|
||||
remainingHeight--;
|
||||
|
||||
if (celData->mirrored)
|
||||
rawBitmap += celData->width * 2;
|
||||
}
|
||||
}
|
||||
|
||||
// for CGA rendering, apply dithering
|
||||
switch (_renderMode) {
|
||||
case Common::kRenderCGA: {
|
||||
uint16 totalPixels = celData->width * celData->height;
|
||||
|
||||
// dither clear key
|
||||
celData->clearKey = _gfx->getCGAMixtureColor(celData->clearKey);
|
||||
|
||||
rawBitmap = celData->rawBitmap;
|
||||
for (uint16 pixelNr = 0; pixelNr < totalPixels; pixelNr++) {
|
||||
byte curColor = *rawBitmap;
|
||||
*rawBitmap = _gfx->getCGAMixtureColor(curColor);
|
||||
rawBitmap++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AgiEngine::unpackViewCelDataAGI256(AgiViewCel *celData, byte *compressedData, uint16 compressedSize, int16 viewNr) {
|
||||
byte *rawBitmap = new byte[celData->width * celData->height];
|
||||
int16 remainingHeight = celData->height;
|
||||
int16 remainingWidth = celData->width;
|
||||
|
||||
celData->rawBitmap = rawBitmap;
|
||||
|
||||
while (remainingHeight) {
|
||||
if (!compressedSize)
|
||||
error("unexpected end of data, while unpacking AGI256 view %d", viewNr);
|
||||
|
||||
byte curByte = *compressedData++;
|
||||
compressedSize--;
|
||||
|
||||
if (curByte == 0) {
|
||||
// Go to next vertical position
|
||||
if (remainingWidth) {
|
||||
// fill remaining bytes with clear key
|
||||
memset(rawBitmap, celData->clearKey, remainingWidth);
|
||||
rawBitmap += remainingWidth;
|
||||
remainingWidth = 0;
|
||||
}
|
||||
} else {
|
||||
if (!remainingWidth) {
|
||||
error("broken view data, while unpacking AGI256 view %d", viewNr);
|
||||
break;
|
||||
}
|
||||
*rawBitmap = curByte;
|
||||
rawBitmap++;
|
||||
remainingWidth--;
|
||||
}
|
||||
|
||||
if (curByte == 0) {
|
||||
remainingWidth = celData->width;
|
||||
remainingHeight--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all data in a view resource
|
||||
* @param viewNr number of view resource
|
||||
*/
|
||||
void AgiEngine::unloadView(int16 viewNr) {
|
||||
AgiView *viewData = &_game.views[viewNr];
|
||||
|
||||
debugC(5, kDebugLevelResources, "discard view %d", viewNr);
|
||||
if (!(_game.dirView[viewNr].flags & RES_LOADED))
|
||||
return;
|
||||
|
||||
// Rebuild sprite list, see Sarien bug #779302
|
||||
_sprites->eraseSprites();
|
||||
|
||||
// free data
|
||||
for (int16 loopNr = 0; loopNr < viewData->loopCount; loopNr++) {
|
||||
AgiViewLoop *loopData = &viewData->loop[loopNr];
|
||||
for (int16 celNr = 0; celNr < loopData->celCount; celNr++) {
|
||||
AgiViewCel *celData = &loopData->cel[celNr];
|
||||
|
||||
delete[] celData->rawBitmap;
|
||||
}
|
||||
delete[] loopData->cel;
|
||||
}
|
||||
delete[] viewData->loop;
|
||||
delete[] viewData->description;
|
||||
|
||||
viewData->headerCycleTime = 0;
|
||||
viewData->headerStepSize = 0;
|
||||
viewData->description = nullptr;
|
||||
viewData->loop = nullptr;
|
||||
viewData->loopCount = 0;
|
||||
|
||||
// Mark this view as not loaded anymore
|
||||
_game.dirView[viewNr].flags &= ~RES_LOADED;
|
||||
|
||||
_sprites->buildAllSpriteLists();
|
||||
_sprites->drawAllSpriteLists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a view table entry to use the specified view resource.
|
||||
* @param screenObj pointer to screen object
|
||||
* @param viewNr number of AGI view resource
|
||||
*/
|
||||
void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) {
|
||||
if (!(_game.dirView[viewNr].flags & RES_LOADED)) {
|
||||
// View resource currently not loaded, this is probably a game bug
|
||||
// Load the resource now to fix the issue, and give out a warning
|
||||
// This happens in at least Larry 1 for Apple IIgs right after getting beaten up by taxi driver
|
||||
// Original interpreter bombs out in this situation saying "view not loaded, Press ESC to quit"
|
||||
warning("setView() called on screen object %d to use view %d, but view not loaded", screenObj->objectNr, viewNr);
|
||||
warning("probably game script bug, trying to load view into memory");
|
||||
if (loadResource(RESOURCETYPE_VIEW, viewNr) != errOK) {
|
||||
// loading failed, we better error() out now
|
||||
error("setView() called to set view %d for screen object %d, which is not loaded atm and loading failed", viewNr, screenObj->objectNr);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
screenObj->viewResource = &_game.views[viewNr];
|
||||
screenObj->currentViewNr = viewNr;
|
||||
screenObj->loopCount = screenObj->viewResource->loopCount;
|
||||
screenObj->viewReplaced = true;
|
||||
|
||||
if (getVersion() < 0x2000) {
|
||||
screenObj->stepSize = screenObj->viewResource->headerStepSize;
|
||||
screenObj->cycleTime = screenObj->viewResource->headerCycleTime;
|
||||
screenObj->cycleTimeCount = 0;
|
||||
}
|
||||
if (screenObj->currentLoopNr >= screenObj->loopCount) {
|
||||
setLoop(screenObj, 0);
|
||||
} else {
|
||||
setLoop(screenObj, screenObj->currentLoopNr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a view table entry to use the specified loop of the current view.
|
||||
* @param screenObj pointer to screen object
|
||||
* @param loopNr number of loop
|
||||
*/
|
||||
void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) {
|
||||
if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) {
|
||||
error("setLoop() called on screen object %d, which has no loaded view resource assigned to it", screenObj->objectNr);
|
||||
return;
|
||||
}
|
||||
assert(screenObj->viewResource);
|
||||
|
||||
if (screenObj->loopCount == 0) {
|
||||
warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (loopNr >= screenObj->loopCount) {
|
||||
// requested loop not existent
|
||||
// instead of error()ing out, we instead clip it
|
||||
// At least required for possibly Manhunter 1 according to previous comment when leaving the arcade machine
|
||||
// TODO: Check MH1
|
||||
// WORKAROUND: This code caused an issue in KQ1 when bowing to the king in room 53. Bug #7045
|
||||
// When ego finishes bowing, the script sets his view to 0 and loop to 1 so that he faces left,
|
||||
// but it does this by setting the loop first and then the view. The previous view is 71 and only
|
||||
// has one loop. This code treated that as an invalid set.loop and would clip it to 0, but that
|
||||
// caused ego to face away from the king. For now, we detect this and set the view to 0 first.
|
||||
if (getGameID() == GID_KQ1 && screenObj->currentViewNr == 71 && loopNr == 1) {
|
||||
setView(screenObj, 0);
|
||||
} else {
|
||||
int16 requestedLoopNr = loopNr;
|
||||
|
||||
loopNr = screenObj->loopCount - 1;
|
||||
|
||||
warning("Non-existent loop requested for screen object %d", screenObj->objectNr);
|
||||
warning("view %d, requested loop %d -> clipped to loop %d", screenObj->currentViewNr, requestedLoopNr, loopNr);
|
||||
}
|
||||
}
|
||||
|
||||
AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[loopNr];
|
||||
|
||||
screenObj->currentLoopNr = loopNr;
|
||||
screenObj->loopData = curViewLoop;
|
||||
screenObj->celCount = curViewLoop->celCount;
|
||||
|
||||
if (screenObj->currentCelNr >= screenObj->celCount) {
|
||||
setCel(screenObj, 0);
|
||||
} else {
|
||||
setCel(screenObj, screenObj->currentCelNr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a view table entry to use the specified cel of the current loop.
|
||||
* @param screenObj pointer to screen object
|
||||
* @param celNr number of cel
|
||||
*/
|
||||
void AgiEngine::setCel(ScreenObjEntry *screenObj, int16 celNr) {
|
||||
if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) {
|
||||
error("setCel() called on screen object %d, which has no loaded view resource assigned to it", screenObj->objectNr);
|
||||
return;
|
||||
}
|
||||
assert(screenObj->viewResource);
|
||||
|
||||
if (screenObj->loopCount == 0) {
|
||||
warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr);
|
||||
return;
|
||||
}
|
||||
|
||||
AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[screenObj->currentLoopNr];
|
||||
|
||||
// Added by Amit Vainsencher <amitv@subdimension.com> to prevent
|
||||
// crash in KQ1 -- not in the Sierra interpreter
|
||||
if (curViewLoop->celCount == 0) {
|
||||
warning("setCel() called on screen object %d, which has no cels (view %d)", screenObj->objectNr, screenObj->currentViewNr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (celNr >= screenObj->celCount) {
|
||||
// requested cel not existent
|
||||
// instead of error()ing out, we instead clip it
|
||||
// At least required for King's Quest 3 on Apple IIgs - walking the planks death cutscene
|
||||
// see bug #5832, which is a game bug!
|
||||
int16 requestedCelNr = celNr;
|
||||
|
||||
celNr = screenObj->celCount - 1;
|
||||
|
||||
warning("Non-existent cel requested for screen object %d", screenObj->objectNr);
|
||||
warning("view %d, loop %d, requested cel %d -> clipped to cel %d", screenObj->currentViewNr, screenObj->currentLoopNr, requestedCelNr, celNr);
|
||||
}
|
||||
|
||||
screenObj->currentCelNr = celNr;
|
||||
|
||||
AgiViewCel *curViewCel;
|
||||
curViewCel = &curViewLoop->cel[celNr];
|
||||
screenObj->celData = curViewCel;
|
||||
screenObj->xSize = curViewCel->width;
|
||||
screenObj->ySize = curViewCel->height;
|
||||
|
||||
// If position isn't appropriate, update it accordingly
|
||||
clipViewCoordinates(screenObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict view table entry's position so it stays wholly inside the screen.
|
||||
* Also take horizon into account when clipping if not set to ignore it.
|
||||
* @param v pointer to view table entry
|
||||
*/
|
||||
void AgiEngine::clipViewCoordinates(ScreenObjEntry *screenObj) {
|
||||
if (screenObj->xPos + screenObj->xSize > SCRIPT_WIDTH) {
|
||||
screenObj->flags |= fUpdatePos;
|
||||
screenObj->xPos = SCRIPT_WIDTH - screenObj->xSize;
|
||||
}
|
||||
if (screenObj->yPos - screenObj->ySize + 1 < 0) {
|
||||
screenObj->flags |= fUpdatePos;
|
||||
screenObj->yPos = screenObj->ySize - 1;
|
||||
}
|
||||
if (screenObj->yPos <= _game.horizon && (~screenObj->flags & fIgnoreHorizon)) {
|
||||
screenObj->flags |= fUpdatePos;
|
||||
screenObj->yPos = _game.horizon + 1;
|
||||
}
|
||||
|
||||
if (getVersion() < 0x2000) {
|
||||
screenObj->flags |= fDontUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view table entry as updating.
|
||||
* @param viewPtr pointer to view table entry
|
||||
*/
|
||||
void AgiEngine::startUpdate(ScreenObjEntry *viewPtr) {
|
||||
if (~viewPtr->flags & fUpdate) {
|
||||
_sprites->eraseSprites();
|
||||
viewPtr->flags |= fUpdate;
|
||||
_sprites->buildAllSpriteLists();
|
||||
_sprites->drawAllSpriteLists();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view table entry as non-updating.
|
||||
* @param viewPtr pointer to view table entry
|
||||
*/
|
||||
void AgiEngine::stopUpdate(ScreenObjEntry *viewPtr) {
|
||||
if (viewPtr->flags & fUpdate) {
|
||||
_sprites->eraseSprites();
|
||||
viewPtr->flags &= ~fUpdate;
|
||||
_sprites->buildAllSpriteLists();
|
||||
_sprites->drawAllSpriteLists();
|
||||
}
|
||||
}
|
||||
|
||||
// loops to use according to direction and number of loops in
|
||||
// the view resource
|
||||
static const int loopTable2[] = {
|
||||
0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01
|
||||
};
|
||||
|
||||
static const int loopTable4[] = {
|
||||
0x04, 0x03, 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0x01
|
||||
};
|
||||
|
||||
/**
|
||||
* Update view table entries.
|
||||
* This function is called at the end of each interpreter cycle
|
||||
* to update the view table entries and blit the sprites.
|
||||
*/
|
||||
void AgiEngine::updateScreenObjTable() {
|
||||
ScreenObjEntry *screenObj;
|
||||
int16 changeCount, loopNr;
|
||||
|
||||
changeCount = 0;
|
||||
for (screenObj = _game.screenObjTable; screenObj < &_game.screenObjTable[SCREENOBJECTS_MAX]; screenObj++) {
|
||||
if ((screenObj->flags & (fAnimated | fUpdate | fDrawn)) != (fAnimated | fUpdate | fDrawn)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changeCount++;
|
||||
|
||||
loopNr = 4;
|
||||
if (!(screenObj->flags & fFixLoop)) {
|
||||
switch (screenObj->loopCount) {
|
||||
case 2:
|
||||
case 3:
|
||||
loopNr = loopTable2[screenObj->direction];
|
||||
break;
|
||||
case 4:
|
||||
loopNr = loopTable4[screenObj->direction];
|
||||
break;
|
||||
default:
|
||||
// for KQ4
|
||||
if (getVersion() == 0x3086 || getGameID() == GID_KQ4)
|
||||
loopNr = loopTable4[screenObj->direction];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// AGI 2.272 (ddp, xmas) doesn't test step_time_count!
|
||||
if (loopNr != 4 && loopNr != screenObj->currentLoopNr) {
|
||||
if (getVersion() <= 0x2272 || screenObj->stepTimeCount == 1) {
|
||||
setLoop(screenObj, loopNr);
|
||||
}
|
||||
}
|
||||
|
||||
if (screenObj->flags & fCycling) {
|
||||
if (screenObj->cycleTimeCount) {
|
||||
screenObj->cycleTimeCount--;
|
||||
if (screenObj->cycleTimeCount == 0) {
|
||||
updateView(screenObj);
|
||||
screenObj->cycleTimeCount = screenObj->cycleTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changeCount) {
|
||||
_sprites->eraseRegularSprites();
|
||||
updatePosition();
|
||||
_sprites->buildRegularSpriteList();
|
||||
_sprites->drawRegularSpriteList();
|
||||
_sprites->showRegularSpriteList();
|
||||
|
||||
_game.screenObjTable[SCREENOBJECTS_EGO_ENTRY].flags &= ~(fOnWater | fOnLand);
|
||||
}
|
||||
}
|
||||
|
||||
bool AgiEngine::isEgoView(const ScreenObjEntry *screenObj) {
|
||||
return screenObj == _game.screenObjTable;
|
||||
}
|
||||
|
||||
} // End of namespace Agi
|
||||
Reference in New Issue
Block a user