mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-08 10:51:11 +00:00
902 lines
25 KiB
C++
902 lines
25 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
|
|
* aint32 with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*
|
|
* Based on the original sources
|
|
* Faery Tale II -- The Halls of the Dead
|
|
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
|
|
*/
|
|
|
|
#include "saga2/saga2.h"
|
|
#include "saga2/fta.h"
|
|
#include "saga2/blitters.h"
|
|
#include "saga2/sprite.h"
|
|
#include "saga2/tcoords.h"
|
|
#include "saga2/hresmgr.h"
|
|
|
|
namespace Saga2 {
|
|
|
|
const int maxWeaponSpriteSets = 40;
|
|
|
|
const uint32 spriteGroupID = MKTAG('S', 'P', 'R', 'I'),
|
|
frameGroupID = MKTAG('F', 'R', 'M', 'L'),
|
|
poseGroupID = MKTAG('P', 'O', 'S', 'E'),
|
|
schemeGroupID = MKTAG('S', 'C', 'H', 'M'),
|
|
objectSpriteID = MKTAG('O', 'B', 'J', 'S'),
|
|
mentalSpriteID = MKTAG('M', 'E', 'N', 'T'),
|
|
weaponSpriteBaseID = MKTAG('W', 'P', 'N', 0),
|
|
missileSpriteID = MKTAG('M', 'I', 'S', 'S');
|
|
|
|
extern uint16 rippedRoofID;
|
|
extern void drawTileMask(
|
|
const Point16 &sPos,
|
|
gPixelMap &map,
|
|
TilePoint loc,
|
|
uint16 roofID = rippedRoofID);
|
|
|
|
// Color map ranges
|
|
extern uint8 *ColorMapRanges;
|
|
|
|
/* ===================================================================== *
|
|
Exports
|
|
* ===================================================================== */
|
|
|
|
/* ===================================================================== *
|
|
Locals
|
|
* ===================================================================== */
|
|
|
|
// Remap table for colors which are not remapped.
|
|
|
|
const uint8 fixedColors[] = {
|
|
0, 10, 12, 14, 16, 18, 21, 24,
|
|
101, 104, 130, 132, 197, 199, 228, 230
|
|
};
|
|
|
|
SpriteSet *objectSprites, // object sprites
|
|
*mentalSprites, // intangible object sprites
|
|
*weaponSprites[maxWeaponSpriteSets], // weapon sprites
|
|
*missileSprites; // missile sprites
|
|
|
|
hResContext *spriteRes, // sprite resource handle
|
|
*frameRes, // framelist resource handle
|
|
*poseRes, // poselist resource handle
|
|
*schemeRes; // schemelist resource handle
|
|
|
|
// An array of 32 actor appearances
|
|
static ActorAppearance appearanceTable[32];
|
|
|
|
/* ===================================================================== *
|
|
Quick memory routines
|
|
* ===================================================================== */
|
|
|
|
static uint8 *quickMemBase,
|
|
*quickMemPtr;
|
|
|
|
int32 quickMemSize;
|
|
|
|
void initQuickMem(int32 size) {
|
|
quickMemBase = new uint8[size]();
|
|
if (quickMemBase == nullptr)
|
|
error("Error: Memory allocation size %d failed!", size);
|
|
quickMemSize = size;
|
|
quickMemPtr = quickMemBase;
|
|
}
|
|
|
|
void cleanupQuickMem(void) {
|
|
if (quickMemBase)
|
|
delete[] quickMemBase;
|
|
quickMemBase = nullptr;
|
|
}
|
|
|
|
void *getQuickMem(int32 size) {
|
|
if (quickMemPtr + size > quickMemBase + quickMemSize) {
|
|
error("Error: QuickMem allocation failed, size %d", size);
|
|
}
|
|
quickMemPtr += size;
|
|
return quickMemPtr - size;
|
|
}
|
|
|
|
void freeQuickMem(void *ptr) {
|
|
quickMemPtr = ptr ? (uint8 *)ptr : quickMemBase;
|
|
}
|
|
|
|
/* ===================================================================== *
|
|
Sprite rendering routines
|
|
* ===================================================================== */
|
|
|
|
void DrawCompositeMaskedSprite(
|
|
gPort &port, // destination gPort
|
|
SpriteComponent *scList, // list of components
|
|
int16 numParts, // number of components
|
|
const Point16 &destPoint, // where to render to
|
|
const TilePoint &loc, // location on map
|
|
int16 effects, // effects flags
|
|
bool *obscured) { // set if object obscured by terrain
|
|
SpriteComponent *sc; // sprite component
|
|
int16 xMax = 0, // extent of composite
|
|
xMin = 0,
|
|
yMax = 0,
|
|
yMin = 0;
|
|
Rect16 clip; // clip rect of port
|
|
gPixelMap compMap, // pixel map for composite
|
|
sprMap; // sprite map
|
|
Point16 org;
|
|
int x, y;
|
|
|
|
port.getClip(clip);
|
|
|
|
// First, determine the enclosing rectangle which
|
|
// surrounds all of the sprites.
|
|
|
|
sc = scList;
|
|
|
|
for (int i = 0; i < numParts; i++, sc++) {
|
|
Sprite *sp = sc->sp;
|
|
int16 left,
|
|
right,
|
|
bottom,
|
|
top;
|
|
|
|
// Compute the rectangle of the sprites
|
|
|
|
if (sc->flipped)
|
|
left = destPoint.x + sc->offset.x - sp->size.x - sp->offset.x;
|
|
else left = destPoint.x + sc->offset.x + sp->offset.x;
|
|
|
|
top = destPoint.y + sc->offset.y + sp->offset.y;
|
|
right = left + sp->size.x;
|
|
bottom = top + sp->size.y;
|
|
|
|
if (i == 0) {
|
|
xMin = left;
|
|
xMax = right;
|
|
yMin = top;
|
|
yMax = bottom;
|
|
} else {
|
|
if (left < xMin) xMin = left;
|
|
if (right > xMax) xMax = right;
|
|
if (top < yMin) yMin = top;
|
|
if (bottom > yMax) yMax = bottom;
|
|
}
|
|
}
|
|
|
|
// If the composite area is outside the screen rect, then
|
|
// nothing needs to be done.
|
|
|
|
if (xMax <= clip.x
|
|
|| yMax <= clip.y
|
|
|| xMin >= clip.x + clip.width
|
|
|| yMin >= clip.y + clip.height)
|
|
return;
|
|
|
|
// Justify the x coords to the nearest tile boundary
|
|
|
|
xMin = xMin & ~31;
|
|
xMax = (xMax + 31) & ~31;
|
|
|
|
// Build a temporary bitmap to composite the sprite within
|
|
|
|
compMap.size.x = xMax - xMin;
|
|
compMap.size.y = yMax - yMin;
|
|
compMap.data = (uint8 *)getQuickMem(compMap.bytes());
|
|
memset(compMap.data, 0, compMap.bytes());
|
|
|
|
// Calculate the offset from the upper-left corner of
|
|
// our composite map to the origin point where the sprites
|
|
// should be drawn.
|
|
|
|
org.x = destPoint.x - xMin;
|
|
org.y = destPoint.y - yMin;
|
|
|
|
// First, determine the enclosing rectangle which
|
|
// surrounds all of the sprites.
|
|
sc = scList;
|
|
for (int i = 0; i < numParts; i++, sc++) {
|
|
Sprite *sp = sc->sp;
|
|
|
|
// Create a temp map for the sprite to unpack in
|
|
|
|
sprMap.size = sp->size;
|
|
if (sprMap.size.x <= 0 || sprMap.size.y <= 0) continue;
|
|
sprMap.data = (uint8 *)getQuickMem(compMap.bytes());
|
|
|
|
// Unpack the sprite into the temp map
|
|
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
// Blit the temp map onto the composite map
|
|
|
|
if (sc->flipped) {
|
|
compositePixelsRvs(
|
|
&compMap,
|
|
&sprMap,
|
|
org.x + sc->offset.x - sp->offset.x,
|
|
org.y + sc->offset.y + sp->offset.y,
|
|
sc->colorTable);
|
|
} else {
|
|
compositePixels(
|
|
&compMap,
|
|
&sprMap,
|
|
org.x + sc->offset.x + sp->offset.x,
|
|
org.y + sc->offset.y + sp->offset.y,
|
|
sc->colorTable);
|
|
}
|
|
freeQuickMem(sprMap.data);
|
|
}
|
|
|
|
// do terrain masking
|
|
if (effects & sprFXTerrainMask) {
|
|
if (!(effects & sprFXGhostIfObscured)) {
|
|
drawTileMask(
|
|
Point16(xMin, yMin),
|
|
compMap,
|
|
loc);
|
|
} else {
|
|
gPixelMap tempMap;
|
|
int32 compMapBytes = compMap.bytes(),
|
|
visiblePixels;
|
|
bool isObscured;
|
|
|
|
tempMap.size = compMap.size;
|
|
tempMap.data = (uint8 *)getQuickMem(compMapBytes);
|
|
|
|
memcpy(tempMap.data, compMap.data, compMapBytes);
|
|
|
|
drawTileMask(
|
|
Point16(xMin, yMin),
|
|
compMap,
|
|
loc);
|
|
|
|
visiblePixels = 0;
|
|
for (int i = 0; i < compMapBytes; i++) {
|
|
if (compMap.data[i] != 0) {
|
|
visiblePixels++;
|
|
if (visiblePixels > 10) break;
|
|
}
|
|
}
|
|
|
|
isObscured = visiblePixels <= 10;
|
|
if (isObscured) {
|
|
memcpy(compMap.data, tempMap.data, compMapBytes);
|
|
effects |= sprFXGhosted;
|
|
}
|
|
|
|
if (obscured != nullptr) *obscured = isObscured;
|
|
|
|
freeQuickMem(tempMap.data);
|
|
}
|
|
}
|
|
|
|
// Check if location is underwater
|
|
if (loc.z < 0) {
|
|
uint8 *submergedArea = &compMap.data[(-loc.z < compMap.size.y ?
|
|
(compMap.size.y + loc.z)
|
|
* compMap.size.x :
|
|
0)];
|
|
|
|
uint16 submergedSize = &compMap.data[compMap.bytes()] -
|
|
submergedArea;
|
|
|
|
memset(submergedArea, 0, submergedSize);
|
|
}
|
|
|
|
// Add in "ghost" effects
|
|
if (effects & sprFXGhosted) {
|
|
uint32 *dstRow = (uint32 *)compMap.data;
|
|
|
|
uint32 mask = (yMin & 1) ? 0xff00ff00 : 0x00ff00ff;
|
|
|
|
for (y = 0; y < compMap.size.y; y++) {
|
|
for (x = 0; x < compMap.size.x; x += 4) {
|
|
*dstRow++ &= mask;
|
|
}
|
|
mask = ~mask;
|
|
}
|
|
}
|
|
|
|
// Blit to the port
|
|
|
|
TBlit(port.map, &compMap, xMin, yMin);
|
|
|
|
freeQuickMem(compMap.data);
|
|
}
|
|
|
|
void DrawSprite(
|
|
gPort &port, // destination gPort
|
|
const Point16 &destPoint, // where to render to
|
|
Sprite *sp) { // sprite pointer
|
|
gPixelMap sprMap; // sprite map
|
|
|
|
// Create a temp map for the sprite to unpack in
|
|
sprMap.size = sp->size;
|
|
sprMap.data = (uint8 *)getQuickMem(sprMap.bytes());
|
|
|
|
// Unpack the sprite into the temp map
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
// Blit to the port
|
|
port.setMode(drawModeMatte);
|
|
port.bltPixels(sprMap,
|
|
0, 0,
|
|
destPoint.x + sp->offset.x,
|
|
destPoint.y + sp->offset.y,
|
|
sprMap.size.x, sprMap.size.y);
|
|
|
|
freeQuickMem(sprMap.data);
|
|
}
|
|
|
|
// Draw a single sprite with no masking, but with color mapping.
|
|
|
|
void DrawColorMappedSprite(
|
|
gPort &port, // destination gPort
|
|
const Point16 &destPoint, // where to render to
|
|
Sprite *sp, // sprite pointer
|
|
uint8 *colorTable) { // color remapping table
|
|
gPixelMap sprMap, // sprite map
|
|
sprReMap; // remapped sprite map
|
|
|
|
// Create a temp map for the sprite to unpack in
|
|
sprMap.size = sp->size;
|
|
sprMap.data = (uint8 *)getQuickMem(sprMap.bytes());
|
|
sprReMap.size = sp->size;
|
|
sprReMap.data = (uint8 *)getQuickMem(sprReMap.bytes());
|
|
|
|
// Unpack the sprite into the temp map
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
memset(sprReMap.data, 0, sprReMap.bytes());
|
|
|
|
// remap the sprite to the color table given
|
|
compositePixels(
|
|
&sprReMap,
|
|
&sprMap,
|
|
0,
|
|
0,
|
|
colorTable);
|
|
|
|
// Blit to the port
|
|
port.setMode(drawModeMatte);
|
|
port.bltPixels(sprReMap,
|
|
0, 0,
|
|
destPoint.x + sp->offset.x,
|
|
destPoint.y + sp->offset.y,
|
|
sprReMap.size.x, sprReMap.size.y);
|
|
|
|
freeQuickMem(sprReMap.data);
|
|
freeQuickMem(sprMap.data);
|
|
}
|
|
|
|
// Draw a single sprite with no masking, but with color mapping.
|
|
|
|
void ExpandColorMappedSprite(
|
|
gPixelMap &map, // destination gPixelMap
|
|
Sprite *sp, // sprite pointer
|
|
uint8 *colorTable) { // color remapping table
|
|
gPixelMap sprMap, // sprite map
|
|
sprReMap; // remapped sprite map
|
|
|
|
// Create a temp map for the sprite to unpack in
|
|
sprMap.size = sp->size;
|
|
sprMap.data = (uint8 *)getQuickMem(sprMap.bytes());
|
|
|
|
// Unpack the sprite into the temp map
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
// remap the sprite to the color table given
|
|
compositePixels(
|
|
&map,
|
|
&sprMap,
|
|
0,
|
|
0,
|
|
colorTable);
|
|
|
|
freeQuickMem(sprMap.data);
|
|
}
|
|
|
|
// Unpacks a sprite for a moment and returns the value of a
|
|
// specific pixel in the sprite. This is used to do hit testing
|
|
// against sprites.
|
|
|
|
uint8 GetSpritePixel(
|
|
Sprite *sp, // sprite pointer
|
|
int16 flipped, // true if sprite was flipped
|
|
const Point16 &testPoint) { // where to render to
|
|
gPixelMap sprMap; // sprite map
|
|
uint8 result;
|
|
|
|
// Create a temp map for the sprite to unpack in
|
|
sprMap.size = sp->size;
|
|
sprMap.data = (uint8 *)getQuickMem(sprMap.bytes());
|
|
|
|
// Unpack the sprite into the temp map
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
// Map the coords to the bitmap and return the pixel
|
|
if (flipped) {
|
|
result = sprMap.data[testPoint.y * sprMap.size.x
|
|
+ sprMap.size.x - testPoint.x];
|
|
} else {
|
|
result = sprMap.data[testPoint.y * sprMap.size.x + testPoint.x];
|
|
}
|
|
freeQuickMem(sprMap.data);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// Return the number of visible pixels in a sprite after terrain masking
|
|
uint16 visiblePixelsInSprite(
|
|
Sprite *sp, // sprite pointer
|
|
bool flipped, // is sprite flipped
|
|
ColorTable colors, // sprite's color table
|
|
Point16 drawPos, // XY position of sprite
|
|
TilePoint loc, // UVZ coordinates of sprite
|
|
uint16 roofID) { // ID of ripped roof
|
|
|
|
Point16 org;
|
|
int16 xMin, // extent of sprite
|
|
xMax,
|
|
yMin,
|
|
yMax;
|
|
gPixelMap sprMap, // sprite map
|
|
compMap;
|
|
uint16 compBytes,
|
|
i,
|
|
visiblePixels;
|
|
|
|
// Determine the extent of the sprite
|
|
xMin = drawPos.x + sp->offset.x;
|
|
yMin = drawPos.y + sp->offset.y;
|
|
xMax = xMin + sp->size.x;
|
|
yMax = yMin + sp->size.y;
|
|
|
|
// Justify the x coords to the nearest tile boundary
|
|
xMin &= ~31;
|
|
xMax = (xMax + 31) & ~31;
|
|
|
|
// Build a temporary bitmap to composite the sprite within
|
|
compMap.size.x = xMax - xMin;
|
|
compMap.size.y = yMax - yMin;
|
|
compMap.data = (uint8 *)getQuickMem(compBytes = compMap.bytes());
|
|
memset(compMap.data, 0, compBytes);
|
|
|
|
// Build bitmap in which to unpack the sprite
|
|
sprMap.size = sp->size;
|
|
sprMap.data = (uint8 *)getQuickMem(sprMap.bytes());
|
|
|
|
unpackSprite(&sprMap, sp->_data, sp->_dataSize);
|
|
|
|
org.x = drawPos.x - xMin;
|
|
org.y = drawPos.y - yMin;
|
|
|
|
// Blit the sprite onto the composite map
|
|
if (!flipped) {
|
|
compositePixels(
|
|
&compMap,
|
|
&sprMap,
|
|
org.x + sp->offset.x,
|
|
org.y + sp->offset.y,
|
|
colors);
|
|
} else {
|
|
compositePixelsRvs(
|
|
&compMap,
|
|
&sprMap,
|
|
org.x - sp->offset.x,
|
|
org.y + sp->offset.y,
|
|
colors);
|
|
}
|
|
|
|
// do terrain masking
|
|
drawTileMask(
|
|
Point16(xMin, yMin),
|
|
compMap,
|
|
loc,
|
|
roofID);
|
|
|
|
// count the visible pixels in the composite map
|
|
for (i = 0, visiblePixels = 0; i < compBytes; i++)
|
|
if (compMap.data[i]) visiblePixels++;
|
|
|
|
#if DEBUG*0
|
|
WriteStatusF(8, "Visible pixels = %u", visiblePixels);
|
|
#endif
|
|
|
|
freeQuickMem(sprMap.data);
|
|
|
|
freeQuickMem(compMap.data);
|
|
|
|
return visiblePixels;
|
|
}
|
|
|
|
/* ===================================================================== *
|
|
Color table assembly
|
|
* ===================================================================== */
|
|
|
|
void buildColorTable(
|
|
uint8 *colorTable, // color table to build
|
|
uint8 *colorOptions, // colors ranges chosen
|
|
int16 numOptions) {
|
|
uint32 *src,
|
|
*dst;
|
|
|
|
memcpy(colorTable, fixedColors, sizeof fixedColors);
|
|
dst = (uint32 *)(colorTable + sizeof fixedColors);
|
|
|
|
while (numOptions--) {
|
|
src = (uint32 *)&ColorMapRanges[*colorOptions * 8];
|
|
colorOptions++;
|
|
*dst++ = *src++;
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
|
|
/* ===================================================================== *
|
|
Load actor appearance
|
|
* ===================================================================== */
|
|
|
|
void ActorAppearance::loadSpriteBanks(int16 banksNeeded) {
|
|
int16 bank;
|
|
|
|
WriteStatusF(2, "Loading Banks: %x", banksNeeded);
|
|
|
|
// Make this one the most recently used entry
|
|
g_vm->_appearanceLRU.push_back(this);
|
|
|
|
// Load in additional sprite banks if requested...
|
|
for (bank = 0; bank < (long)ARRAYSIZE(spriteBanks); bank++) {
|
|
// Load the sprite handle...
|
|
if (spriteBanks[bank] == nullptr && (banksNeeded & (1 << bank))) {
|
|
Common::SeekableReadStream *stream = loadResourceToStream(spriteRes, id + MKTAG(0, 0, 0, bank), "sprite bank");
|
|
if (stream) {
|
|
spriteBanks[bank] = new SpriteSet(stream);
|
|
delete stream;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ActorAnimation::ActorAnimation(Common::SeekableReadStream *stream) {
|
|
for (int i = 0; i < numPoseFacings; i++)
|
|
start[i] = stream->readUint16LE();
|
|
|
|
for (int i = 0; i < numPoseFacings; i++)
|
|
count[i] = stream->readUint16LE();
|
|
|
|
for (int i = 0; i < numPoseFacings; i++)
|
|
debugC(2, kDebugLoading, "anim%d: start: %d count: %d", i, start[i], count[i]);
|
|
}
|
|
|
|
ActorPose::ActorPose() {
|
|
flags = 0;
|
|
|
|
actorFrameIndex = actorFrameBank = leftObjectIndex = rightObjectIndex = 0;
|
|
}
|
|
|
|
|
|
ActorPose::ActorPose(Common::SeekableReadStream *stream) {
|
|
load(stream);
|
|
}
|
|
|
|
void ActorPose::load(Common::SeekableReadStream *stream) {
|
|
flags = stream->readUint16LE();
|
|
|
|
actorFrameIndex = stream->readByte();
|
|
actorFrameBank = stream->readByte();
|
|
leftObjectIndex = stream->readByte();
|
|
rightObjectIndex = stream->readByte();
|
|
|
|
leftObjectOffset.load(stream);
|
|
rightObjectOffset.load(stream);
|
|
}
|
|
|
|
void ActorPose::write(Common::MemoryWriteStreamDynamic *out) {
|
|
out->writeUint16LE(flags);
|
|
|
|
out->writeByte(actorFrameIndex);
|
|
out->writeByte(actorFrameBank);
|
|
out->writeByte(leftObjectIndex);
|
|
out->writeByte(rightObjectIndex);
|
|
|
|
leftObjectOffset.write(out);
|
|
rightObjectOffset.write(out);
|
|
}
|
|
|
|
|
|
ColorScheme::ColorScheme(Common::SeekableReadStream *stream) {
|
|
for (int i = 0; i < 11; ++i)
|
|
bank[i] = stream->readByte();
|
|
|
|
speechColor = stream->readByte();
|
|
|
|
for (int i = 0; i < 32; ++i)
|
|
name[i] = stream->readSByte();
|
|
}
|
|
|
|
ColorSchemeList::ColorSchemeList(int count, Common::SeekableReadStream *stream) {
|
|
_count = count;
|
|
|
|
_schemes = (ColorScheme **)malloc(_count * sizeof(ColorScheme *));
|
|
for (int i = 0; i < _count; ++i)
|
|
_schemes[i] = new ColorScheme(stream);
|
|
}
|
|
|
|
ColorSchemeList::~ColorSchemeList() {
|
|
for (int i = 0; i < _count; ++i)
|
|
delete _schemes[i];
|
|
|
|
free(_schemes);
|
|
}
|
|
|
|
ActorAppearance *LoadActorAppearance(uint32 id, int16 banksNeeded) {
|
|
int16 bank;
|
|
int schemeListSize;
|
|
Common::SeekableReadStream *stream;
|
|
|
|
// Search the table for either a matching appearance,
|
|
// or for an empty one.
|
|
for (Common::List<ActorAppearance *>::iterator it = g_vm->_appearanceLRU.begin(); it != g_vm->_appearanceLRU.end(); ++it) {
|
|
if ((*it)->id == id // If has same ID
|
|
&& (*it)->poseList != nullptr) { // and frames not dumped
|
|
// then use this one!
|
|
(*it)->useCount++;
|
|
(*it)->loadSpriteBanks(banksNeeded);
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
// If we couldn't find an extact match, search for an
|
|
// empty one.
|
|
ActorAppearance *aa = nullptr;
|
|
// Search from LRU end of list.
|
|
for (Common::List<ActorAppearance *>::iterator it = g_vm->_appearanceLRU.begin(); it != g_vm->_appearanceLRU.end(); ++it) {
|
|
if ((*it)->useCount == 0) { // If not in use
|
|
aa = *it; // then use this one!
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If none available, that's fatal...
|
|
if (aa == nullptr) {
|
|
error("All ActorAppearance records are in use!");
|
|
}
|
|
|
|
// Dump the sprites being stored
|
|
for (bank = 0; bank < (long)ARRAYSIZE(aa->spriteBanks); bank++) {
|
|
if (aa->spriteBanks[bank])
|
|
delete aa->spriteBanks[bank];
|
|
aa->spriteBanks[bank] = nullptr;
|
|
}
|
|
|
|
if (aa->poseList) {
|
|
for (uint i = 0; i < aa->poseList->numPoses; i++)
|
|
delete aa->poseList->poses[i];
|
|
|
|
free(aa->poseList->poses);
|
|
|
|
for (uint i = 0; i < aa->poseList->numAnimations; i++)
|
|
delete aa->poseList->animations[i];
|
|
|
|
free(aa->poseList->animations);
|
|
|
|
delete aa->poseList;
|
|
}
|
|
aa->poseList = nullptr;
|
|
|
|
if (aa->schemeList) {
|
|
delete aa->schemeList;
|
|
}
|
|
aa->schemeList = nullptr;
|
|
|
|
// Set ID and use count
|
|
aa->id = id;
|
|
aa->useCount = 1;
|
|
|
|
// Load in new frame lists and sprite banks
|
|
aa->loadSpriteBanks(banksNeeded);
|
|
|
|
Common::SeekableReadStream *poseStream = loadResourceToStream(poseRes, id, "pose list");
|
|
|
|
if (poseStream == nullptr) {
|
|
warning("LoadActorAppearance: Could not load pose list");
|
|
} else {
|
|
ActorAnimSet *as = new ActorAnimSet;
|
|
aa->poseList = as;
|
|
as->numAnimations = poseStream->readUint32LE();
|
|
as->poseOffset = poseStream->readUint32LE();
|
|
|
|
// compute number of ActorPoses
|
|
uint32 poseBytes = poseStream->size() - as->poseOffset;
|
|
const int poseSize = 14;
|
|
|
|
debugC(1, kDebugLoading, "Pose List: bytes: %ld numAnimations: %d poseOffset: %d calculated offset: %d numPoses: %d",
|
|
poseStream->size(), as->numAnimations, as->poseOffset, 8 + as->numAnimations * 32, poseBytes / poseSize);
|
|
|
|
if (poseBytes % poseSize != 0)
|
|
warning("Incorrect number of poses, %d bytes more", poseBytes % poseSize);
|
|
|
|
as->numPoses = poseBytes / poseSize;
|
|
|
|
as->animations = (ActorAnimation **)malloc(as->numAnimations * sizeof(ActorAnimation *));
|
|
|
|
for (uint i = 0; i < as->numAnimations; i++)
|
|
as->animations[i] = new ActorAnimation(poseStream);
|
|
|
|
as->poses = (ActorPose **)malloc(as->numPoses * sizeof(ActorPose *));
|
|
|
|
for (uint i = 0; i < as->numPoses; i++)
|
|
as->poses[i] = new ActorPose(poseStream);
|
|
|
|
delete poseStream;
|
|
}
|
|
|
|
if (schemeRes->seek(id) == 0) {
|
|
warning("LoadActorAppearance: Could not load scheme list");
|
|
} else {
|
|
const int colorSchemeSize = 44;
|
|
|
|
if (schemeRes->size(id) % colorSchemeSize != 0)
|
|
warning("Incorrect number of colorschemes, %d bytes more", schemeRes->size(id) % colorSchemeSize);
|
|
|
|
schemeListSize = schemeRes->size(id) / colorSchemeSize;
|
|
stream = loadResourceToStream(schemeRes, id, "scheme list");
|
|
aa->schemeList = new ColorSchemeList(schemeListSize, stream);
|
|
|
|
delete stream;
|
|
}
|
|
|
|
return aa;
|
|
}
|
|
|
|
void ReleaseActorAppearance(ActorAppearance *aa) {
|
|
aa->useCount--;
|
|
}
|
|
|
|
/* ===================================================================== *
|
|
Sprite initialization routines
|
|
* ===================================================================== */
|
|
|
|
Sprite::Sprite(Common::SeekableReadStream *stream) {
|
|
size.load(stream);
|
|
offset.load(stream);
|
|
|
|
_dataSize = size.x * size.y;
|
|
_data = (byte *)malloc(_dataSize);
|
|
stream->read(_data, _dataSize);
|
|
}
|
|
|
|
Sprite::~Sprite() {
|
|
free(_data);
|
|
}
|
|
|
|
SpriteSet::SpriteSet(Common::SeekableReadStream *stream) {
|
|
count = stream->readUint32LE();
|
|
_sprites = (Sprite **)malloc(count * sizeof(Sprite *));
|
|
|
|
for (uint i = 0; i < count; ++i) {
|
|
stream->seek(4 + i * 4);
|
|
uint32 offset = stream->readUint32LE();
|
|
stream->seek(offset);
|
|
_sprites[i] = new Sprite(stream);
|
|
}
|
|
}
|
|
|
|
SpriteSet::~SpriteSet() {
|
|
for (uint i = 0; i < count; ++i) {
|
|
if (_sprites[i])
|
|
delete _sprites[i];
|
|
}
|
|
|
|
free(_sprites);
|
|
}
|
|
|
|
void initSprites(void) {
|
|
int i;
|
|
Common::SeekableReadStream *stream = nullptr;
|
|
|
|
spriteRes = resFile->newContext(spriteGroupID, "sprite resources");
|
|
if (!spriteRes->_valid)
|
|
error("Error accessing sprite resource group.");
|
|
|
|
frameRes = resFile->newContext(frameGroupID, "frame resources");
|
|
assert(frameRes && frameRes->_valid);
|
|
|
|
poseRes = resFile->newContext(poseGroupID, "pose resources");
|
|
assert(poseRes && poseRes->_valid);
|
|
|
|
schemeRes = resFile->newContext(schemeGroupID, "scheme resources");
|
|
assert(schemeRes && schemeRes->_valid);
|
|
|
|
// object sprites
|
|
stream = loadResourceToStream(spriteRes, objectSpriteID, "object sprites");
|
|
objectSprites = new SpriteSet(stream);
|
|
delete stream;
|
|
assert(objectSprites);
|
|
|
|
// intagible object sprites
|
|
stream = loadResourceToStream(spriteRes, mentalSpriteID, "mental sprites");
|
|
mentalSprites = new SpriteSet(stream);
|
|
delete stream;
|
|
assert(mentalSprites);
|
|
|
|
for (i = 0; i < maxWeaponSpriteSets; i++) {
|
|
hResID weaponSpriteID;
|
|
|
|
weaponSpriteID = weaponSpriteBaseID + MKTAG(0, 0, 0, i);
|
|
|
|
if (spriteRes->size(weaponSpriteID) == 0) {
|
|
weaponSprites[i] = nullptr;
|
|
continue;
|
|
}
|
|
|
|
stream = loadResourceToStream(spriteRes, weaponSpriteID, "weapon sprite set");
|
|
weaponSprites[i] = new SpriteSet(stream);
|
|
delete stream;
|
|
}
|
|
|
|
stream = loadResourceToStream(spriteRes, missileSpriteID, "missle sprites");
|
|
missileSprites = new SpriteSet(stream);
|
|
delete stream;
|
|
|
|
initQuickMem(0x10000);
|
|
|
|
// Initialize actor appearance table
|
|
for (i = 0; i < ARRAYSIZE(appearanceTable); i++) {
|
|
ActorAppearance *aa = &appearanceTable[i];
|
|
|
|
aa->useCount = 0;
|
|
g_vm->_appearanceLRU.push_front(aa);
|
|
}
|
|
}
|
|
|
|
void cleanupSprites(void) {
|
|
int i;
|
|
|
|
cleanupQuickMem();
|
|
|
|
if (objectSprites)
|
|
delete objectSprites;
|
|
objectSprites = nullptr;
|
|
|
|
if (mentalSprites)
|
|
delete mentalSprites;
|
|
mentalSprites = nullptr;
|
|
|
|
for (i = 0; i < maxWeaponSpriteSets; i++) {
|
|
if (weaponSprites[i]) {
|
|
delete weaponSprites[i];
|
|
weaponSprites[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
if (schemeRes) resFile->disposeContext(schemeRes);
|
|
schemeRes = nullptr;
|
|
|
|
if (poseRes) resFile->disposeContext(poseRes);
|
|
poseRes = nullptr;
|
|
|
|
if (frameRes) resFile->disposeContext(frameRes);
|
|
frameRes = nullptr;
|
|
|
|
if (spriteRes) resFile->disposeContext(spriteRes);
|
|
spriteRes = nullptr;
|
|
}
|
|
|
|
} // end of namespace Saga2
|