388 lines
9.3 KiB
C++
388 lines
9.3 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 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 "mediastation/actors/sprite.h"
|
|
#include "mediastation/debugchannels.h"
|
|
#include "mediastation/mediastation.h"
|
|
|
|
namespace MediaStation {
|
|
|
|
SpriteFrameHeader::SpriteFrameHeader(Chunk &chunk) : BitmapHeader(chunk) {
|
|
_index = chunk.readTypedUint16();
|
|
_offset = chunk.readTypedPoint();
|
|
}
|
|
|
|
SpriteFrame::SpriteFrame(Chunk &chunk, SpriteFrameHeader *header) : Bitmap(chunk, header) {
|
|
_bitmapHeader = header;
|
|
}
|
|
|
|
SpriteFrame::~SpriteFrame() {
|
|
// The base class destructor takes care of deleting the bitmap header.
|
|
}
|
|
|
|
uint32 SpriteFrame::left() {
|
|
return _bitmapHeader->_offset.x;
|
|
}
|
|
|
|
uint32 SpriteFrame::top() {
|
|
return _bitmapHeader->_offset.y;
|
|
}
|
|
|
|
Common::Point SpriteFrame::topLeft() {
|
|
return Common::Point(left(), top());
|
|
}
|
|
|
|
Common::Rect SpriteFrame::boundingBox() {
|
|
return Common::Rect(topLeft(), width(), height());
|
|
}
|
|
|
|
uint32 SpriteFrame::index() {
|
|
return _bitmapHeader->_index;
|
|
}
|
|
|
|
SpriteAsset::~SpriteAsset() {
|
|
for (SpriteFrame *frame : frames) {
|
|
delete frame;
|
|
}
|
|
}
|
|
|
|
SpriteMovieActor::~SpriteMovieActor() {
|
|
unregisterWithStreamManager();
|
|
}
|
|
|
|
void SpriteMovieActor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) {
|
|
switch (paramType) {
|
|
case kActorHeaderChannelIdent:
|
|
_channelIdent = chunk.readTypedChannelIdent();
|
|
registerWithStreamManager();
|
|
_asset = Common::SharedPtr<SpriteAsset>(new SpriteAsset);
|
|
break;
|
|
|
|
case kActorHeaderFrameRate:
|
|
_frameRate = static_cast<uint32>(chunk.readTypedDouble());
|
|
break;
|
|
|
|
case kActorHeaderLoadType:
|
|
_loadType = chunk.readTypedByte();
|
|
break;
|
|
|
|
case kActorHeaderStartup:
|
|
_isVisible = static_cast<bool>(chunk.readTypedByte());
|
|
break;
|
|
|
|
case kActorHeaderSpriteChunkCount: {
|
|
_frameCount = chunk.readTypedUint16();
|
|
|
|
// Set the default clip.
|
|
SpriteClip clip;
|
|
clip.id = DEFAULT_CLIP_ID;
|
|
clip.firstFrameIndex = 0;
|
|
clip.lastFrameIndex = _frameCount - 1;
|
|
_clips.setVal(clip.id, clip);
|
|
setCurrentClip(clip.id);
|
|
break;
|
|
}
|
|
|
|
case kActorHeaderSpriteClip: {
|
|
SpriteClip spriteClip;
|
|
spriteClip.id = chunk.readTypedUint16();
|
|
spriteClip.firstFrameIndex = chunk.readTypedUint16();
|
|
spriteClip.lastFrameIndex = chunk.readTypedUint16();
|
|
_clips.setVal(spriteClip.id, spriteClip);
|
|
break;
|
|
}
|
|
|
|
case kActorHeaderCurrentSpriteClip: {
|
|
uint clipId = chunk.readTypedUint16();
|
|
setCurrentClip(clipId);
|
|
break;
|
|
}
|
|
|
|
case kActorHeaderActorReference: {
|
|
_actorReference = chunk.readTypedUint16();
|
|
Actor *referencedActor = g_engine->getActorById(_actorReference);
|
|
if (referencedActor == nullptr) {
|
|
error("%s: Referenced actor %d doesn't exist or has not been read yet in this title", __func__, _actorReference);
|
|
}
|
|
if (referencedActor->type() != kActorTypeSprite) {
|
|
error("%s: Type mismatch of referenced actor %d", __func__, _actorReference);
|
|
}
|
|
SpriteMovieActor *referencedSprite = static_cast<SpriteMovieActor *>(referencedActor);
|
|
_asset = referencedSprite->_asset;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
SpatialEntity::readParameter(chunk, paramType);
|
|
}
|
|
}
|
|
|
|
ScriptValue SpriteMovieActor::callMethod(BuiltInMethod methodId, Common::Array<ScriptValue> &args) {
|
|
ScriptValue returnValue;
|
|
|
|
switch (methodId) {
|
|
case kSpatialShowMethod: {
|
|
assert(args.empty());
|
|
setVisibility(true);
|
|
return returnValue;
|
|
}
|
|
|
|
case kSpatialHideMethod: {
|
|
assert(args.empty());
|
|
setVisibility(false);
|
|
return returnValue;
|
|
}
|
|
|
|
case kTimePlayMethod: {
|
|
assert(args.empty());
|
|
play();
|
|
return returnValue;
|
|
}
|
|
|
|
case kTimeStopMethod: {
|
|
assert(args.empty());
|
|
stop();
|
|
return returnValue;
|
|
}
|
|
|
|
case kMovieResetMethod: {
|
|
assert(args.empty());
|
|
setCurrentFrameToInitial();
|
|
return returnValue;
|
|
}
|
|
|
|
case kSetCurrentClipMethod: {
|
|
assert(args.size() <= 1);
|
|
uint clipId = DEFAULT_CLIP_ID;
|
|
if (args.size() == 1) {
|
|
clipId = args[0].asParamToken();
|
|
}
|
|
setCurrentClip(clipId);
|
|
return returnValue;
|
|
}
|
|
|
|
case kIncrementFrameMethod: {
|
|
assert(args.size() <= 1);
|
|
bool loopAround = false;
|
|
if (args.size() == 1) {
|
|
loopAround = args[0].asBool();
|
|
}
|
|
|
|
bool moreFrames = activateNextFrame();
|
|
if (!moreFrames) {
|
|
if (loopAround) {
|
|
setCurrentFrameToInitial();
|
|
}
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
case kDecrementFrameMethod: {
|
|
bool shouldSetCurrentFrameToFinal = false;
|
|
if (args.size() == 1) {
|
|
shouldSetCurrentFrameToFinal = args[0].asBool();
|
|
}
|
|
|
|
bool moreFrames = activatePreviousFrame();
|
|
if (!moreFrames) {
|
|
if (shouldSetCurrentFrameToFinal) {
|
|
setCurrentFrameToFinal();
|
|
}
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
case kGetCurrentClipIdMethod: {
|
|
returnValue.setToParamToken(_activeClip.id);
|
|
return returnValue;
|
|
}
|
|
|
|
case kIsPlayingMethod: {
|
|
returnValue.setToBool(_isPlaying);
|
|
return returnValue;
|
|
}
|
|
|
|
default:
|
|
return SpatialEntity::callMethod(methodId, args);
|
|
}
|
|
}
|
|
|
|
bool SpriteMovieActor::activateNextFrame() {
|
|
if (_currentFrameIndex < _activeClip.lastFrameIndex) {
|
|
_currentFrameIndex++;
|
|
dirtyIfVisible();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpriteMovieActor::activatePreviousFrame() {
|
|
if (_currentFrameIndex > _activeClip.firstFrameIndex) {
|
|
_currentFrameIndex--;
|
|
dirtyIfVisible();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SpriteMovieActor::dirtyIfVisible() {
|
|
if (_isVisible) {
|
|
invalidateLocalBounds();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::setVisibility(bool visibility) {
|
|
if (_isVisible != visibility) {
|
|
_isVisible = visibility;
|
|
invalidateLocalBounds();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::play() {
|
|
_isPlaying = true;
|
|
_startTime = g_system->getMillis();
|
|
_lastProcessedTime = 0;
|
|
_nextFrameTime = 0;
|
|
|
|
scheduleNextFrame();
|
|
}
|
|
|
|
void SpriteMovieActor::stop() {
|
|
_nextFrameTime = 0;
|
|
_isPlaying = false;
|
|
}
|
|
|
|
void SpriteMovieActor::setCurrentClip(uint clipId) {
|
|
if (_activeClip.id != clipId) {
|
|
if (_clips.contains(clipId)) {
|
|
_activeClip = _clips.getVal(clipId);
|
|
} else {
|
|
_activeClip.id = clipId;
|
|
warning("%s: Sprite clip %d not found in sprite %d", __func__, clipId, _id);
|
|
}
|
|
}
|
|
|
|
setCurrentFrameToInitial();
|
|
}
|
|
|
|
void SpriteMovieActor::setCurrentFrameToInitial() {
|
|
if (_currentFrameIndex != _activeClip.firstFrameIndex) {
|
|
_currentFrameIndex = _activeClip.firstFrameIndex;
|
|
dirtyIfVisible();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::setCurrentFrameToFinal() {
|
|
if (_currentFrameIndex != _activeClip.lastFrameIndex) {
|
|
_currentFrameIndex = _activeClip.lastFrameIndex;
|
|
dirtyIfVisible();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::process() {
|
|
updateFrameState();
|
|
// Sprites don't have time event handlers, separate timers do time handling.
|
|
}
|
|
|
|
void SpriteMovieActor::readChunk(Chunk &chunk) {
|
|
// Reads one frame from the sprite.
|
|
debugC(5, kDebugLoading, "Sprite::readFrame(): Reading sprite frame (@0x%llx)", static_cast<long long int>(chunk.pos()));
|
|
SpriteFrameHeader *header = new SpriteFrameHeader(chunk);
|
|
SpriteFrame *frame = new SpriteFrame(chunk, header);
|
|
_asset->frames.push_back(frame);
|
|
|
|
// TODO: Are these in exactly reverse order? If we can just reverse the
|
|
// whole thing once.
|
|
Common::sort(_asset->frames.begin(), _asset->frames.end(), [](SpriteFrame *a, SpriteFrame *b) {
|
|
return a->index() < b->index();
|
|
});
|
|
}
|
|
|
|
void SpriteMovieActor::scheduleNextFrame() {
|
|
if (!_isPlaying) {
|
|
return;
|
|
}
|
|
|
|
if (_currentFrameIndex < _activeClip.lastFrameIndex) {
|
|
scheduleNextTimerEvent();
|
|
} else {
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::scheduleNextTimerEvent() {
|
|
uint frameDuration = 1000 / _frameRate;
|
|
_nextFrameTime += frameDuration;
|
|
}
|
|
|
|
void SpriteMovieActor::updateFrameState() {
|
|
if (!_isPlaying) {
|
|
return;
|
|
}
|
|
|
|
uint currentTime = g_system->getMillis() - _startTime;
|
|
bool drawNextFrame = currentTime >= _nextFrameTime;
|
|
debugC(kDebugGraphics, "nextFrameTime: %d; startTime: %d, currentTime: %d", _nextFrameTime, _startTime, currentTime);
|
|
if (drawNextFrame) {
|
|
timerEvent();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::timerEvent() {
|
|
if (!_isPlaying) {
|
|
error("%s: Attempt to activate sprite frame when sprite is not playing", __func__);
|
|
return;
|
|
}
|
|
|
|
bool result = activateNextFrame();
|
|
if (!result) {
|
|
stop();
|
|
} else {
|
|
postMovieEndEventIfNecessary();
|
|
scheduleNextFrame();
|
|
}
|
|
}
|
|
|
|
void SpriteMovieActor::postMovieEndEventIfNecessary() {
|
|
if (_currentFrameIndex != _activeClip.lastFrameIndex) {
|
|
return;
|
|
}
|
|
|
|
_isPlaying = false;
|
|
_startTime = 0;
|
|
_nextFrameTime = 0;
|
|
|
|
ScriptValue value;
|
|
value.setToParamToken(_activeClip.id);
|
|
runEventHandlerIfExists(kSpriteMovieEndEvent, value);
|
|
}
|
|
|
|
void SpriteMovieActor::draw(DisplayContext &displayContext) {
|
|
SpriteFrame *activeFrame = _asset->frames[_currentFrameIndex];
|
|
if (_isVisible) {
|
|
Common::Rect frameBbox = activeFrame->boundingBox();
|
|
frameBbox.translate(_boundingBox.left, _boundingBox.top);
|
|
g_engine->getDisplayManager()->imageBlit(frameBbox.origin(), activeFrame, _dissolveFactor, &displayContext);
|
|
}
|
|
}
|
|
|
|
} // End of namespace MediaStation
|