Files
2026-02-02 04:50:13 +01:00

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