scummvm/saga/animation.cpp
Torbjörn Andersson c64c7eb4d4 First pass at migrating from the old Reinherit console to the ScummVM
console.

Some things are likely to have broken. For instance, I believe the old
console was could be visible during gameplay, while ScummVM's is modal. So
any place where we output something to the console during gameplay should
probably be removed.

Some things I've stubbed out. Basically any code that registers a variable.

Most of the debugging commands are untested.

Syntax may have changed because of different command-line parsing. (I never
actually used the old console, so I don't know for sure. :-)

Actually, I'm not that interested in reproducing the old console commands
faithfully. What we should do now is to make the immediately useful console
stuff work. Anything that remains unimplemented should probably be removed.

svn-id: r15976
2004-12-03 19:15:44 +00:00

929 lines
20 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2004 The ScummVM project
*
* The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
// Background animation management module
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/console.h"
#include "saga/game_mod.h"
#include "saga/events.h"
#include "saga/render.h"
#include "saga/animation.h"
namespace Saga {
Anim::Anim(SagaEngine *vm) : _vm(vm) {
int i;
_anim_limit = MAX_ANIMATIONS;
_anim_count = 0;
for (i = 0; i < MAX_ANIMATIONS; i++)
_anim_tbl[i] = NULL;
_initialized = true;
}
Anim::~Anim(void) {
uint16 i;
for (i = 0; i < MAX_ANIMATIONS; i++)
free(_anim_tbl[i]);
_initialized = false;
}
int Anim::load(const byte *anim_resdata, size_t anim_resdata_len, uint16 *anim_id_p) {
ANIMATION *new_anim;
uint16 anim_id = 0;
uint16 i;
if (!_initialized) {
warning("Anim::load not initialised");
return FAILURE;
}
// Find an unused animation slot
for (i = 0; i < MAX_ANIMATIONS; i++) {
if (_anim_tbl[i] == NULL) {
anim_id = i;
break;
}
}
if (i == MAX_ANIMATIONS) {
warning("Anim::load could not find unused animation slot");
return FAILURE;
}
new_anim = (ANIMATION *)malloc(sizeof *new_anim);
if (new_anim == NULL) {
warning("Anim::load Allocation failure");
return MEM;
}
new_anim->resdata = anim_resdata;
new_anim->resdata_len = anim_resdata_len;
if (GAME_GetGameType() == GID_ITE) {
if (getNumFrames(anim_resdata, anim_resdata_len, &new_anim->n_frames) != SUCCESS) {
warning("Anim::load Couldn't get animation frame count");
return FAILURE;
}
// Cache frame offsets
new_anim->frame_offsets = (size_t *)malloc(new_anim->n_frames * sizeof *new_anim->frame_offsets);
if (new_anim->frame_offsets == NULL) {
warning("Anim::load Allocation failure");
return MEM;
}
for (i = 0; i < new_anim->n_frames; i++) {
getFrameOffset(anim_resdata, anim_resdata_len, i + 1, &new_anim->frame_offsets[i]);
}
} else {
new_anim->cur_frame_p = anim_resdata + SAGA_FRAME_HEADER_LEN;
new_anim->cur_frame_len = anim_resdata_len - SAGA_FRAME_HEADER_LEN;
getNumFrames(anim_resdata, anim_resdata_len, &new_anim->n_frames);
}
// Set animation data
new_anim->current_frame = 1;
new_anim->end_frame = new_anim->n_frames;
new_anim->stop_frame = new_anim->end_frame;
new_anim->frame_time = DEFAULT_FRAME_TIME;
new_anim->flags = 0;
new_anim->play_flag = 0;
new_anim->link_flag = 0;
new_anim->link_id = 0;
_anim_tbl[anim_id] = new_anim;
*anim_id_p = anim_id;
_anim_count++;
return SUCCESS;
}
int Anim::link(uint16 anim_id1, uint16 anim_id2) {
ANIMATION *anim1;
ANIMATION *anim2;
if ((anim_id1 >= _anim_count) || (anim_id2 >= _anim_count)) {
return FAILURE;
}
anim1 = _anim_tbl[anim_id1];
anim2 = _anim_tbl[anim_id2];
if ((anim1 == NULL) || (anim2 == NULL)) {
return FAILURE;
}
anim1->link_id = anim_id2;
anim1->link_flag = 1;
anim2->frame_time = anim1->frame_time;
return SUCCESS;
}
int Anim::play(uint16 anim_id, int vector_time) {
EVENT event;
ANIMATION *anim;
ANIMATION *link_anim;
uint16 link_anim_id;
BUFFER_INFO buf_info;
byte *display_buf;
const byte *nextf_p;
size_t nextf_len;
uint16 frame;
int result;
GAME_DISPLAYINFO disp_info;
if (anim_id >= _anim_count) {
return FAILURE;
}
GAME_GetDisplayInfo(&disp_info);
_vm->_render->getBufferInfo(&buf_info);
display_buf = buf_info.bg_buf;
anim = _anim_tbl[anim_id];
if (anim == NULL) {
return FAILURE;
}
if (anim->flags & ANIM_PAUSE)
return SUCCESS;
if (anim->play_flag) {
frame = anim->current_frame;
if (GAME_GetGameType() == GID_ITE) {
result = ITE_DecodeFrame(anim->resdata, anim->resdata_len, anim->frame_offsets[frame - 1], display_buf,
disp_info.logical_w * disp_info.logical_h);
if (result != SUCCESS) {
warning("ANIM::play: Error decoding frame %u", anim->current_frame);
anim->play_flag = 0;
return FAILURE;
}
} else {
if (anim->cur_frame_p == NULL) {
warning("ANIM::play: Frames exhausted");
return FAILURE;
}
result = IHNM_DecodeFrame(display_buf, disp_info.logical_w * disp_info.logical_h,
anim->cur_frame_p, anim->cur_frame_len, &nextf_p, &nextf_len);
if (result != SUCCESS) {
warning("ANIM::play: Error decoding frame %u", anim->current_frame);
anim->play_flag = 0;
return FAILURE;
}
anim->cur_frame_p = nextf_p;
anim->cur_frame_len = nextf_len;
}
anim->current_frame++;
}
anim->play_flag = 1;
if (anim->current_frame > anim->n_frames) {
// Animation done playing
if (anim->link_flag) {
// If this animation has a link, follow it
anim->play_flag = 0;
anim->current_frame = 1;
link_anim_id = anim->link_id;
link_anim = _anim_tbl[link_anim_id];
if (link_anim != NULL) {
link_anim->current_frame = 1;
link_anim->play_flag = 1;
}
anim_id = link_anim_id;
} else if (anim->flags & ANIM_LOOP) {
// Loop animation
anim->current_frame = 1;
anim->cur_frame_p = anim->resdata + SAGA_FRAME_HEADER_LEN;
anim->cur_frame_len = anim->resdata_len - SAGA_FRAME_HEADER_LEN;
} else {
// No link, stop playing
anim->current_frame = anim->n_frames;
anim->play_flag = 0;
if (anim->flags & ANIM_ENDSCENE) {
// This animation ends the scene
event.type = ONESHOT_EVENT;
event.code = SCENE_EVENT;
event.op = EVENT_END;
event.time = anim->frame_time + vector_time;
_vm->_events->queue(&event);
}
return SUCCESS;
}
}
event.type = ONESHOT_EVENT;
event.code = ANIM_EVENT;
event.op = EVENT_FRAME;
event.param = anim_id;
event.time = anim->frame_time + vector_time;
_vm->_events->queue(&event);
return SUCCESS;
}
int Anim::reset() {
uint16 i;
for (i = 0; i < MAX_ANIMATIONS; i++) {
freeId(i);
}
_anim_count = 0;
return SUCCESS;
}
int Anim::setFlag(uint16 anim_id, uint16 flag) {
ANIMATION *anim;
if (anim_id > _anim_count) {
return FAILURE;
}
anim = _anim_tbl[anim_id];
if (anim == NULL) {
return FAILURE;
}
anim->flags |= flag;
return SUCCESS;
}
int Anim::clearFlag(uint16 anim_id, uint16 flag) {
ANIMATION *anim;
if (anim_id > _anim_count) {
return FAILURE;
}
anim = _anim_tbl[anim_id];
if (anim == NULL) {
return FAILURE;
}
anim->flags &= ~flag;
return SUCCESS;
}
int Anim::setFrameTime(uint16 anim_id, int time) {
ANIMATION *anim;
if (anim_id > _anim_count) {
return FAILURE;
}
anim = _anim_tbl[anim_id];
if (anim == NULL) {
return FAILURE;
}
anim->frame_time = time;
return SUCCESS;
}
int Anim::freeId(uint16 anim_id) {
ANIMATION *anim;
if (anim_id > _anim_count) {
return FAILURE;
}
anim = _anim_tbl[anim_id];
if (anim == NULL) {
return FAILURE;
}
if (GAME_GetGameType() == GID_ITE) {
free(anim->frame_offsets);
anim->frame_offsets = NULL;
}
free(anim);
_anim_tbl[anim_id] = NULL;
_anim_count--;
return SUCCESS;
}
// The actual number of frames present in an animation resource is
// sometimes less than number present in the .nframes member of the
// animation header. For this reason, the function attempts to find
// the last valid frame number, which it returns via 'n_frames'
int Anim::getNumFrames(const byte *anim_resource, size_t anim_resource_len, uint16 *n_frames) {
ANIMATION_HEADER ah;
size_t offset;
int magic;
int x;
if (!_initialized) {
return FAILURE;
}
MemoryReadStream readS(anim_resource, anim_resource_len);
ah.magic = readS.readUint16LE();
ah.screen_w = readS.readUint16LE();
ah.screen_h = readS.readUint16LE();
ah.unknown06 = readS.readByte();
ah.unknown07 = readS.readByte();
ah.nframes = readS.readByte();
if (GAME_GetGameType() == GID_IHNM) {
*n_frames = ah.nframes;
}
if (ah.magic == 68) {
for (x = ah.nframes; x > 0; x--) {
if (getFrameOffset(anim_resource, anim_resource_len, x, &offset) != SUCCESS) {
return FAILURE;
}
magic = *(anim_resource + offset);
if (magic == SAGA_FRAME_HEADER_MAGIC) {
*n_frames = x;
return SUCCESS;
}
}
return FAILURE;
}
return FAILURE;
}
int Anim::ITE_DecodeFrame(const byte *resdata, size_t resdata_len, size_t frame_offset, byte *buf, size_t buf_len) {
ANIMATION_HEADER ah;
FRAME_HEADER fh;
byte *write_p;
uint16 magic;
uint16 x_start;
uint16 y_start;
uint32 screen_w;
uint32 screen_h;
int mark_byte;
byte data_byte;
int new_row;
uint16 control_ch;
uint16 param_ch;
uint16 runcount;
int x_vector;
uint16 i;
if (!_initialized) {
return FAILURE;
}
MemoryReadStream headerReadS(resdata, resdata_len);
// Read animation header
ah.magic = headerReadS.readUint16LE();
ah.screen_w = headerReadS.readUint16LE();
ah.screen_h = headerReadS.readUint16LE();
ah.unknown06 = headerReadS.readByte();
ah.unknown07 = headerReadS.readByte();
ah.nframes = headerReadS.readByte();
ah.flags = headerReadS.readByte();
ah.unknown10 = headerReadS.readByte();
ah.unknown11 = headerReadS.readByte();
screen_w = ah.screen_w;
screen_h = ah.screen_h;
if ((screen_w * screen_h) > buf_len) {
// Buffer argument is too small to hold decoded frame, abort.
warning("ITE_DecodeFrame: Buffer size inadequate");
return FAILURE;
}
// Read frame header
MemoryReadStream readS(resdata + frame_offset, resdata_len - frame_offset);
// Check for frame magic byte
magic = readS.readByte();
if (magic != SAGA_FRAME_HEADER_MAGIC) {
warning("ITE_DecodeFrame: Invalid frame offset");
return FAILURE;
}
// For some strange reason, the animation header is in little
// endian format, but the actual RLE encoded frame data,
// including the frame header, is in big endian format.
fh.x_start = readS.readUint16BE();
fh.y_start = readS.readByte();
readS.readByte(); /* Skip pad byte */
fh.x_pos = readS.readUint16BE();
fh.y_pos = readS.readUint16BE();
fh.width = readS.readUint16BE();
fh.height = readS.readUint16BE();
x_start = fh.x_start;
y_start = fh.y_start;
// Setup write pointer to the draw origin
write_p = (buf + (y_start * screen_w) + x_start);
// Begin RLE decompression to output buffer
do {
mark_byte = readS.readByte();
switch (mark_byte) {
case 0x10: // Long Unencoded Run
runcount = readS.readSint16BE();
for (i = 0; i < runcount; i++) {
data_byte = readS.readByte();
if (data_byte != 0) {
*write_p = data_byte;
}
write_p++;
}
continue;
break;
case 0x20: // Long encoded run
runcount = readS.readSint16BE();
data_byte = readS.readByte();
for (i = 0; i < runcount; i++) {
*write_p++ = data_byte;
}
continue;
break;
case 0x2F: // End of row
x_vector = readS.readSint16BE();
new_row = readS.readByte();
// Set write pointer to the new draw origin
write_p = buf + ((y_start + new_row) * screen_w) + x_start + x_vector;
continue;
break;
case 0x30: // Reposition command
x_vector = readS.readSint16BE();
write_p += x_vector;
continue;
break;
case 0x3F: // End of frame marker
return SUCCESS;
break;
default:
break;
}
// Mask all but two high order control bits
control_ch = mark_byte & 0xC0U;
param_ch = mark_byte & 0x3FU;
switch (control_ch) {
case 0xC0: // 1100 0000
// Run of empty pixels
runcount = param_ch + 1;
write_p += runcount;
continue;
break;
case 0x80: // 1000 0000
// Run of compressed data
runcount = param_ch + 1;
data_byte = readS.readByte();
for (i = 0; i < runcount; i++) {
*write_p++ = data_byte;
}
continue;
break;
case 0x40: // 0100 0000
// Uncompressed run
runcount = param_ch + 1;
for (i = 0; i < runcount; i++) {
data_byte = readS.readByte();
if (data_byte != 0) {
*write_p = data_byte;
}
write_p++;
}
continue;
break;
default:
// Unknown marker found - abort
warning("ITE_DecodeFrame: Invalid RLE marker encountered");
return FAILURE;
break;
}
} while (mark_byte != 63); // end of frame marker
return SUCCESS;
}
int Anim::IHNM_DecodeFrame(byte *decode_buf, size_t decode_buf_len, const byte *thisf_p,
size_t thisf_len, const byte **nextf_p, size_t *nextf_len) {
int in_ch;
int decoded_data = 0;
int cont_flag = 1;
int control_ch;
int param_ch;
byte data_pixel;
int x_origin = 0;
int y_origin = 0;
int x_vector;
int new_row;
uint16 runcount;
uint16 c;
size_t in_ch_offset;
MemoryReadStream readS(thisf_p, thisf_len);
byte *outbuf_p = decode_buf;
byte *outbuf_endp = (decode_buf + decode_buf_len) - 1;
size_t outbuf_remain = decode_buf_len;
GAME_DISPLAYINFO di;
GAME_GetDisplayInfo(&di);
*nextf_p = NULL;
for (; cont_flag; decoded_data = 1) {
in_ch_offset = readS.pos();
in_ch = readS.readByte();
switch (in_ch) {
case 0x0F: // 15: Frame header
{
int param1;
int param2;
int param3;
int param4;
int param5;
int param6;
if (thisf_len - readS.pos() < 13) {
warning("0x%02X: Input buffer underrun", in_ch);
return FAILURE;
}
param1 = readS.readUint16BE();
param2 = readS.readUint16BE();
readS.readByte(); // skip 1?
param3 = readS.readUint16BE();
param4 = readS.readUint16BE();
param5 = readS.readUint16BE();
param6 = readS.readUint16BE();
x_origin = param1;
y_origin = param2;
outbuf_p = decode_buf + x_origin + (y_origin * di.logical_w);
if (outbuf_p > outbuf_endp) {
warning("0x%02X: (0x%X) Invalid output position. (x: %d, y: %d)",
in_ch, in_ch_offset, x_origin, y_origin);
return FAILURE;
}
outbuf_remain = (outbuf_endp - outbuf_p) + 1;
continue;
}
break;
case 0x10: // Long Unencoded Run
runcount = readS.readSint16BE();
if (thisf_len - readS.pos() < runcount) {
warning("0x%02X: Input buffer underrun", in_ch);
return FAILURE;
}
if (outbuf_remain < runcount) {
warning("0x%02X: Output buffer overrun", in_ch);
return FAILURE;
}
for (c = 0; c < runcount; c++) {
data_pixel = readS.readByte();
if (data_pixel != 0) {
*outbuf_p = data_pixel;
}
outbuf_p++;
}
outbuf_remain -= runcount;
continue;
break;
case 0x1F: // 31: Unusued?
if (thisf_len - readS.pos() < 3) {
warning("0x%02X: Input buffer underrun", in_ch);
return FAILURE;
}
readS.readByte();
readS.readByte();
readS.readByte();
continue;
break;
case 0x20: // Long compressed run
if (thisf_len - readS.pos() <= 3) {
warning("0x%02X: Input buffer underrun", in_ch);
return FAILURE;
}
runcount = readS.readSint16BE();
data_pixel = readS.readByte();
for (c = 0; c < runcount; c++) {
*outbuf_p++ = data_pixel;
}
outbuf_remain -= runcount;
continue;
break;
case 0x2F: // End of row
if (thisf_len - readS.pos() <= 4) {
return FAILURE;
}
x_vector = readS.readSint16BE();
new_row = readS.readSint16BE();
outbuf_p = decode_buf + ((y_origin + new_row) * di.logical_w) + x_origin + x_vector;
outbuf_remain = (outbuf_endp - outbuf_p) + 1;
continue;
break;
case 0x30: // Reposition command
if (thisf_len - readS.pos() < 2) {
return FAILURE;
}
x_vector = readS.readSint16BE();
if (((x_vector > 0) && ((size_t) x_vector > outbuf_remain)) || (-x_vector > outbuf_p - decode_buf)) {
warning("0x30: Invalid x_vector");
return FAILURE;
}
outbuf_p += x_vector;
outbuf_remain -= x_vector;
continue;
break;
case 0x3F: // 68: Frame end marker
debug(1, "0x3F: Frame end marker");
if (decoded_data && (thisf_len - readS.pos() > 0)) {
*nextf_p = thisf_p + readS.pos();
*nextf_len = thisf_len - readS.pos();
} else {
*nextf_p = NULL;
*nextf_len = 0;
}
cont_flag = 0;
continue;
break;
default:
break;
}
control_ch = in_ch & 0xC0;
param_ch = in_ch & 0x3f;
switch (control_ch) {
case 0xC0: // Run of empty pixels
runcount = param_ch + 1;
if (outbuf_remain < runcount) {
return FAILURE;
}
outbuf_p += runcount;
outbuf_remain -= runcount;
continue;
break;
case 0x80: // Run of compressed data
runcount = param_ch + 1;
if ((outbuf_remain < runcount) || (thisf_len - readS.pos() <= 1)) {
return FAILURE;
}
data_pixel = readS.readByte();
for (c = 0; c < runcount; c++) {
*outbuf_p++ = data_pixel;
}
outbuf_remain -= runcount;
continue;
break;
case 0x40: // Uncompressed run
runcount = param_ch + 1;
if ((outbuf_remain < runcount) || (thisf_len - readS.pos() < runcount)) {
return FAILURE;
}
for (c = 0; c < runcount; c++) {
data_pixel = readS.readByte();
if (data_pixel != 0) {
*outbuf_p = data_pixel;
}
outbuf_p++;
}
outbuf_remain -= runcount;
continue;
break;
default:
break;
}
}
return SUCCESS;
}
int Anim::getFrameOffset(const byte *resdata, size_t resdata_len, uint16 find_frame, size_t *frame_offset_p) {
ANIMATION_HEADER ah;
uint16 num_frames;
uint16 current_frame;
byte mark_byte;
uint16 control;
uint16 runcount;
uint16 magic;
int i;
if (!_initialized) {
return FAILURE;
}
MemoryReadStream readS(resdata, resdata_len);
// Read animation header
ah.magic = readS.readUint16LE();
ah.screen_w = readS.readUint16LE();
ah.screen_h = readS.readUint16LE();
ah.unknown06 = readS.readByte();
ah.unknown07 = readS.readByte();
ah.nframes = readS.readByte();
ah.flags = readS.readByte();
ah.unknown10 = readS.readByte();
ah.unknown11 = readS.readByte();
num_frames = ah.nframes;
if ((find_frame < 1) || (find_frame > num_frames)) {
return FAILURE;
}
for (current_frame = 1; current_frame < find_frame; current_frame++) {
magic = readS.readByte();
if (magic != SAGA_FRAME_HEADER_MAGIC) {
// Frame sync failure. Magic Number not found
return FAILURE;
}
// skip header
for (i = 0; i < SAGA_FRAME_HEADER_LEN; i++)
readS.readByte();
// For some strange reason, the animation header is in little
// endian format, but the actual RLE encoded frame data,
// including the frame header, is in big endian format. */
do {
mark_byte = readS.readByte();
switch (mark_byte) {
case 0x3F: // End of frame marker
continue;
break;
case 0x30: // Reposition command
readS.readByte();
readS.readByte();
continue;
break;
case 0x2F: // End of row marker
readS.readByte();
readS.readByte();
readS.readByte();
continue;
break;
case 0x20: // Long compressed run marker
readS.readByte();
readS.readByte();
readS.readByte();
continue;
break;
case 0x10: // (16) 0001 0000
// Long Uncompressed Run
runcount = readS.readSint16BE();
for (i = 0; i < runcount; i++)
readS.readByte();
continue;
break;
default:
break;
}
// Mask all but two high order (control) bits
control = mark_byte & 0xC0;
switch (control) {
case 0xC0:
// Run of empty pixels
continue;
break;
case 0x80:
// Run of compressed data
readS.readByte(); // Skip data byte
continue;
break;
case 0x40:
// Uncompressed run
runcount = (mark_byte & 0x3f) + 1;
for (i = 0; i < runcount; i++)
readS.readByte();
continue;
break;
default:
// Encountered unknown RLE marker, abort
return FAILURE;
break;
}
} while (mark_byte != 63);
}
*frame_offset_p = readS.pos();
return SUCCESS;
}
void Anim::animInfo() {
uint16 anim_ct;
uint16 i;
uint16 idx;
anim_ct = _anim_count;
_vm->_console->DebugPrintf("There are %d animations loaded:\n", anim_ct);
for (idx = 0, i = 0; i < anim_ct; idx++, i++) {
while (_anim_tbl[idx] == NULL) {
idx++;
}
_vm->_console->DebugPrintf("%02d: Frames: %u Flags: %u\n", i, _anim_tbl[idx]->n_frames, _anim_tbl[idx]->flags);
}
}
} // End of namespace Saga