MTROPOLIS: mToon fixes (get Obsidian file cabinets working)

This commit is contained in:
elasota 2022-05-13 23:14:53 -04:00 committed by Eugene Sandulenko
parent 4d8f8bc43b
commit ab07cd3e56
8 changed files with 399 additions and 61 deletions

View File

@ -327,6 +327,9 @@ void CachedMToon::loadRLEFrames(const Common::Array<uint8> &data) {
_dataRLE8[i].data.resize(frameDataSize);
memcpy(&_dataRLE8[i].data[0], &data[baseOffset + 20], frameDataSize);
} else if (bpp == 16) {
// RLE16 data size excludes the header
frameDataSize -= 20;
uint32 numDWords = frameDataSize / 2;
_dataRLE16[i].data.resize(numDWords);
memcpy(&_dataRLE16[i].data[0], &data[baseOffset + 20], numDWords * 2);
@ -448,7 +451,8 @@ void CachedMToon::rleReformat(const TSrcFrame &srcFrame, const Graphics::PixelFo
destFrame.version = srcFrame.version;
while (offset < srcFrame.data.size()) {
const uint32 rleCode = srcFrame.data[offset];
uint32 rleCodeOffset = offset;
const uint32 rleCode = srcFrame.data[rleCodeOffset];
if (rleCode == 0) {
destFrame.data[offset] = 0;
offset++;

View File

@ -22,6 +22,7 @@
#include "mtropolis/elements.h"
#include "mtropolis/assets.h"
#include "mtropolis/element_factory.h"
#include "mtropolis/miniscript.h"
#include "mtropolis/render.h"
#include "video/video_decoder.h"
@ -450,7 +451,7 @@ void ImageElement::render(Window *window) {
}
}
MToonElement::MToonElement() : _cel1Based(1), _renderedFrame(0), _flushPriority(0) {
MToonElement::MToonElement() : _frame(0), _renderedFrame(0), _flushPriority(0), _celStartTimeMSec(0), _isPlaying(false), _playRange(IntRange::create(1, 1)) {
}
MToonElement::~MToonElement() {
@ -475,11 +476,17 @@ bool MToonElement::load(ElementLoaderContext &context, const Data::MToonElement
bool MToonElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "cel") {
result.setInt(_cel1Based);
result.setInt(_frame + 1);
return true;
} else if (attrib == "flushpriority") {
result.setInt(_flushPriority);
return true;
} else if (attrib == "rate") {
result.setFloat(_rateTimes10000 / 10000.0);
return true;
} else if (attrib == "range") {
result.setIntRange(_playRange);
return true;
}
return VisualElement::readAttribute(thread, result, attrib);
@ -488,7 +495,7 @@ bool MToonElement::readAttribute(MiniscriptThread *thread, DynamicValue &result,
MiniscriptInstructionOutcome MToonElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
if (attrib == "cel") {
// TODO proper support
DynamicValueWriteIntegerHelper<uint32>::create(&_cel1Based, result);
DynamicValueWriteFuncHelper<MToonElement, &MToonElement::scriptSetCel>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
} else if (attrib == "flushpriority") {
DynamicValueWriteIntegerHelper<int32>::create(&_flushPriority, result);
@ -496,11 +503,37 @@ MiniscriptInstructionOutcome MToonElement::writeRefAttribute(MiniscriptThread *t
} else if (attrib == "maintainrate") {
DynamicValueWriteBoolHelper::create(&_maintainRate, result);
return kMiniscriptInstructionOutcomeContinue;
} else if (attrib == "rate") {
DynamicValueWriteFuncHelper<MToonElement, &MToonElement::scriptSetRate>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
} else if (attrib == "range") {
DynamicValueWriteFuncHelper<MToonElement, &MToonElement::scriptSetRange>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
return VisualElement::writeRefAttribute(thread, result, attrib);
}
VThreadState MToonElement::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties>& msg) {
if (Event::create(EventIDs::kPlay, 0).respondsTo(msg->getEvent())) {
StartPlayingTaskData *startPlayingTaskData = runtime->getVThread().pushTask("MToonElement::startPlayingTask", this, &MToonElement::startPlayingTask);
startPlayingTaskData->runtime = runtime;
ChangeFlagTaskData *becomeVisibleTaskData = runtime->getVThread().pushTask("MToonElement::changeVisibilityTask", static_cast<VisualElement *>(this), &MToonElement::changeVisibilityTask);
becomeVisibleTaskData->desiredFlag = true;
becomeVisibleTaskData->runtime = runtime;
return kVThreadReturn;
}
if (Event::create(EventIDs::kStop, 0).respondsTo(msg->getEvent())) {
// Works differently from movies: Needs to hide the element and pause
warning("mToon element stops are not implemented");
return kVThreadReturn;
}
return kVThreadReturn;
}
void MToonElement::activate() {
Project *project = _runtime->getProject();
Common::SharedPtr<Asset> asset = project->getAssetByID(_assetID).lock();
@ -519,6 +552,7 @@ void MToonElement::activate() {
_metadata = _cachedMToon->getMetadata();
_playMediaSignaller = project->notifyOnPlayMedia(this);
_playRange = IntRange::create(1, _metadata->frames.size());
}
void MToonElement::deactivate() {
@ -533,17 +567,9 @@ void MToonElement::deactivate() {
void MToonElement::render(Window *window) {
if (_cachedMToon) {
_cachedMToon->optimize(_runtime);
uint32 frame = 0;
if (_cel1Based < 1)
frame = 0;
else if (_cel1Based > _metadata->frames.size())
frame = _metadata->frames.size() - 1;
else
frame = _cel1Based - 1;
_cachedMToon->getOrRenderFrame(_renderedFrame, frame, _renderSurface);
_renderedFrame = frame;
_cachedMToon->getOrRenderFrame(_renderedFrame, _frame, _renderSurface);
_renderedFrame = _frame;
}
if (_renderSurface) {
@ -553,9 +579,202 @@ void MToonElement::render(Window *window) {
}
}
void MToonElement::playMedia(Runtime *runtime, Project *project) {
VThreadState MToonElement::startPlayingTask(const StartPlayingTaskData &taskData) {
_frame = _playRange.min;
_paused = false;
_isPlaying = false; // Reset play state, it starts for real in playMedia
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
taskData.runtime->sendMessageOnVThread(dispatch);
return kVThreadReturn;
}
VThreadState MToonElement::changeFrameTask(const ChangeFrameTaskData &taskData) {
if (taskData.frame == _frame)
return kVThreadReturn;
uint32 minFrame = _playRange.min;
uint32 maxFrame = _playRange.max;
_frame = taskData.frame;
if (_frame == minFrame) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtFirstCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
taskData.runtime->sendMessageOnVThread(dispatch);
}
if (_frame == maxFrame) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtLastCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
taskData.runtime->sendMessageOnVThread(dispatch);
}
return kVThreadReturn;
}
void MToonElement::playMedia(Runtime *runtime, Project *project) {
uint32 targetFrame = _frame;
if (_paused)
return;
uint32 minFrame = _playRange.min - 1;
uint32 maxFrame = _playRange.max - 1;
uint64 playTime = runtime->getPlayTime();
if (!_isPlaying) {
_isPlaying = true;
_celStartTimeMSec = runtime->getPlayTime();
}
if (_rateTimes10000 < 0) {
warning("Playing mToons backwards is not implemented yet");
_rateTimes10000 = 0;
return;
}
// Might be possible due to drift?
if (playTime < _celStartTimeMSec)
return;
uint64 timeSinceCelStart = playTime - _celStartTimeMSec;
uint64 framesAdvanced = timeSinceCelStart * static_cast<uint64>(_rateTimes10000) / static_cast<uint64>(10000000);
if (framesAdvanced > 0) {
// This needs to be handled correctly: Reaching the last frame triggers At Last Cel,
// but going PAST the last frame triggers automatic stop and pause.
// The Obsidian bureau filing cabinets depend on this, since they reset the cel when
// reaching the last cel but do not unpause.
bool ranPastEnd = false;
size_t framesRemainingToOnePastEnd = (maxFrame + 1) -_frame;
if (framesRemainingToOnePastEnd <= framesAdvanced) {
ranPastEnd = true;
if (_loop)
targetFrame = minFrame;
else
targetFrame = maxFrame;
} else
targetFrame = _frame + framesAdvanced;
if (_frame != targetFrame) {
_frame = targetFrame;
if (_frame == maxFrame) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtLastCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
}
if (_frame == minFrame) {
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kAtFirstCel, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
}
}
if (ranPastEnd && !_loop) {
_paused = true;
// Unlike movies, reaching the end of an mToon fires "Paused" not "Stopped"
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(EventIDs::kPause, 0), DynamicValue(), getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
runtime->queueMessage(dispatch);
}
if (_maintainRate)
_celStartTimeMSec = playTime;
else
_celStartTimeMSec += (static_cast<uint64>(10000000) * framesAdvanced) / _rateTimes10000;
}
}
MiniscriptInstructionOutcome MToonElement::scriptSetCel(MiniscriptThread *thread, const DynamicValue &value) {
int32 asInteger = 0;
if (!value.roundToInt(asInteger)) {
thread->error("Attempted to set mToon cel to an invalid value");
return kMiniscriptInstructionOutcomeFailed;
}
if (asInteger < _playRange.min)
asInteger = _playRange.min;
else if (asInteger > _playRange.max)
asInteger = _playRange.max;
uint32 frame = asInteger - 1;
_celStartTimeMSec = thread->getRuntime()->getPlayTime();
if (frame != _frame) {
ChangeFrameTaskData *taskData = thread->getRuntime()->getVThread().pushTask("MToonElement::changeFrameTask", this, &MToonElement::changeFrameTask);
taskData->runtime = _runtime;
taskData->frame = frame;
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome MToonElement::scriptSetRange(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kIntegerRange) {
thread->error("Invalid type for mToon range");
return kMiniscriptInstructionOutcomeFailed;
}
IntRange intRange = value.getIntRange();
size_t numFrames = _metadata->frames.size();
if (intRange.min < 1)
intRange.min = 1;
if (intRange.max > numFrames)
intRange.max = numFrames;
if (intRange.max < intRange.min)
intRange.min = intRange.max;
_playRange = intRange;
uint32 targetFrame = _frame;
uint32 minFrame = intRange.min - 1;
uint32 maxFrame = intRange.max - 1;
if (targetFrame < minFrame)
targetFrame = minFrame;
else if (targetFrame > maxFrame)
targetFrame = maxFrame;
if (targetFrame != _frame) {
ChangeFrameTaskData *taskData = thread->getRuntime()->getVThread().pushTask("MToonElement::changeFrameTask", this, &MToonElement::changeFrameTask);
taskData->frame = targetFrame;
taskData->runtime = _runtime;
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
}
return kMiniscriptInstructionOutcomeContinue;
}
void MToonElement::onPauseStateChanged() {
_celStartTimeMSec = _runtime->getPlayTime();
}
MiniscriptInstructionOutcome MToonElement::scriptSetRate(MiniscriptThread *thread, const DynamicValue &value) {
switch (value.getType()) {
case DynamicValueTypes::kFloat:
_rateTimes10000 = static_cast<int32>(round(value.getFloat()) * 10000.0);
break;
case DynamicValueTypes::kInteger:
_rateTimes10000 = value.getInt() * 10000;
break;
default:
thread->error("Invalid type for Miniscript rate");
return kMiniscriptInstructionOutcomeFailed;
}
_celStartTimeMSec = thread->getRuntime()->getPlayTime();
return kMiniscriptInstructionOutcomeContinue;
}
TextLabelElement::TextLabelElement() : _needsRender(false), _isBitmap(false) {
}

View File

@ -158,6 +158,8 @@ public:
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) override;
VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
void deactivate() override;
@ -169,7 +171,24 @@ public:
#endif
private:
struct StartPlayingTaskData {
Runtime *runtime;
};
struct ChangeFrameTaskData {
Runtime *runtime;
uint32 frame;
};
VThreadState startPlayingTask(const StartPlayingTaskData &taskData);
VThreadState changeFrameTask(const ChangeFrameTaskData &taskData);
void playMedia(Runtime *runtime, Project *project) override;
MiniscriptInstructionOutcome scriptSetRate(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetCel(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetRange(MiniscriptThread *thread, const DynamicValue &value);
void onPauseStateChanged();
bool _cacheBitmap;
@ -177,9 +196,11 @@ private:
bool _maintainRate;
uint32 _assetID;
uint32 _rateTimes10000;
uint32 _cel1Based;
int32 _rateTimes10000;
uint32 _frame;
int32 _flushPriority;
uint32 _celStartTimeMSec;
bool _isPlaying; // Is actually rolling media, this is only set by playMedia because it needs to start after scene transition
Runtime *_runtime;
Common::SharedPtr<Graphics::Surface> _renderSurface;
@ -188,6 +209,8 @@ private:
Common::SharedPtr<MToonMetadata> _metadata;
Common::SharedPtr<CachedMToon> _cachedMToon;
Common::SharedPtr<PlayMediaSignaller> _playMediaSignaller;
IntRange _playRange;
};
class TextLabelElement : public VisualElement {

View File

@ -1280,6 +1280,64 @@ MiniscriptInstructionOutcome PointCreate::execute(MiniscriptThread *thread) cons
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome RangeCreate::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0, false);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1, false);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &yVal = thread->getStackValueFromTop(0).value;
DynamicValue &xValDest = thread->getStackValueFromTop(1).value;
int32 coords[2];
DynamicValue *coordInputs[2] = {&xValDest, &yVal};
for (int i = 0; i < 2; i++) {
DynamicValue *v = coordInputs[i];
DynamicValue listContents;
if (v->getType() == DynamicValueTypes::kList) {
// Yes this is actually allowed
const Common::SharedPtr<DynamicList> &list = v->getList();
if (list->getSize() != 1 || !list->getAtIndex(0, listContents)) {
thread->error("Can't convert list to integer");
return kMiniscriptInstructionOutcomeFailed;
}
v = &listContents;
}
switch (v->getType()) {
case DynamicValueTypes::kFloat:
coords[i] = static_cast<int32>(floor(v->getFloat() + 0.5)) & 0xffff;
break;
case DynamicValueTypes::kInteger:
coords[i] = v->getInt();
break;
case DynamicValueTypes::kBoolean:
coords[i] = (v->getBool()) ? 1 : 0;
break;
default:
thread->error("Invalid input for point creation");
return kMiniscriptInstructionOutcomeFailed;
}
}
xValDest.setIntRange(IntRange::create(coords[0], coords[1]));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
GetChild::GetChild(uint32 attribute, bool isLValue, bool isIndexed)
: _attribute(attribute), _isLValue(isLValue), _isIndexed(isIndexed) {
}
@ -1873,10 +1931,6 @@ VThreadState MiniscriptThread::resume(const ResumeTaskData &taskData) {
if (instrsArray.size() == 0)
return kVThreadReturn;
if (_modifier->getStaticGUID() == 0x31ae0e) {
int n = 0;
}
MiniscriptInstruction *const *instrs = &instrsArray[0];
size_t numInstrs = instrsArray.size();

View File

@ -265,7 +265,9 @@ namespace MiniscriptInstructions {
MiniscriptInstructionOutcome execute(MiniscriptThread *thread) const override;
};
class RangeCreate : public UnimplementedInstruction {
class RangeCreate : public MiniscriptInstruction {
private:
MiniscriptInstructionOutcome execute(MiniscriptThread *thread) const override;
};
class VectorCreate : public UnimplementedInstruction {

View File

@ -746,9 +746,6 @@ Common::SharedPtr<Modifier> TimerMessengerModifier::shallowClone() const {
}
void TimerMessengerModifier::trigger(Runtime *runtime) {
if (getStaticGUID() == 0xd9550) {
int n = 0;
}
debug(3, "Timer %x '%s' triggered", getStaticGUID(), getName().c_str());
if (_looping) {
uint32 realMilliseconds = _milliseconds;

View File

@ -2411,45 +2411,17 @@ bool Structural::readAttribute(MiniscriptThread *thread, DynamicValue &result, c
result.clear();
return true;
} else if (attrib == "previous") {
Structural *parent = getParent();
if (parent) {
const Common::Array<Common::SharedPtr<Structural> > &neighborhood = parent->getChildren();
bool found = false;
size_t foundIndex = 0;
for (size_t i = 0; i < neighborhood.size(); i++) {
if (neighborhood[i].get() == this) {
foundIndex = i;
found = true;
break;
}
}
if (found && foundIndex > 0)
result.setObject(neighborhood[foundIndex - 1]->getSelfReference());
else
result.clear();
} else
Structural *sibling = findPrevSibling();
if (sibling)
result.setObject(sibling->getSelfReference());
else
result.clear();
return true;
} else if (attrib == "next") {
Structural *parent = getParent();
if (parent) {
const Common::Array<Common::SharedPtr<Structural> > &neighborhood = parent->getChildren();
bool found = false;
size_t foundIndex = 0;
for (size_t i = 0; i < neighborhood.size(); i++) {
if (neighborhood[i].get() == this) {
foundIndex = i;
found = true;
break;
}
}
if (found && foundIndex < neighborhood.size() - 1)
result.setObject(neighborhood[foundIndex + 1]->getSelfReference());
else
result.clear();
} else
Structural *sibling = findNextSibling();
if (sibling)
result.setObject(sibling->getSelfReference());
else
result.clear();
return true;
} else if (attrib == "scene") {
@ -2511,6 +2483,22 @@ MiniscriptInstructionOutcome Structural::writeRefAttribute(MiniscriptThread *thr
} else {
return kMiniscriptInstructionOutcomeFailed;
}
} else if (attrib == "next") {
Structural *sibling = findNextSibling();
if (sibling) {
DynamicValueWriteObjectHelper::create(sibling, result);
return kMiniscriptInstructionOutcomeContinue;
} else {
return kMiniscriptInstructionOutcomeFailed;
}
} else if (attrib == "previous") {
Structural *sibling = findPrevSibling();
if (sibling) {
DynamicValueWriteObjectHelper::create(sibling, result);
return kMiniscriptInstructionOutcomeContinue;
} else {
return kMiniscriptInstructionOutcomeFailed;
}
} else if (attrib == "loop") {
DynamicValueWriteFuncHelper<Structural, &Structural::scriptSetLoop>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
@ -2557,6 +2545,48 @@ Structural *Structural::getParent() const {
return _parent;
}
Structural *Structural::findNextSibling() const {
Structural *parent = getParent();
if (parent) {
const Common::Array<Common::SharedPtr<Structural> > &neighborhood = parent->getChildren();
bool found = false;
size_t foundIndex = 0;
for (size_t i = 0; i < neighborhood.size(); i++) {
if (neighborhood[i].get() == this) {
foundIndex = i;
found = true;
break;
}
}
if (found && foundIndex < neighborhood.size() - 1)
return neighborhood[foundIndex + 1].get();
}
return nullptr;
}
Structural *Structural::findPrevSibling() const {
Structural *parent = getParent();
if (parent) {
const Common::Array<Common::SharedPtr<Structural> > &neighborhood = parent->getChildren();
bool found = false;
size_t foundIndex = 0;
for (size_t i = 0; i < neighborhood.size(); i++) {
if (neighborhood[i].get() == this) {
foundIndex = i;
found = true;
break;
}
}
if (found && foundIndex > 0)
return neighborhood[foundIndex - 1].get();
}
return nullptr;
}
void Structural::setParent(Structural *parent) {
_parent = parent;
}

View File

@ -340,6 +340,13 @@ struct IntRange {
return !((*this) == other);
}
inline static IntRange create(int32 min, int32 max) {
IntRange result;
result.min = min;
result.max = max;
return result;
}
bool refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib);
Common::String toString() const;
};
@ -1831,6 +1838,8 @@ public:
void holdAssets(const Common::Array<Common::SharedPtr<Asset> > &assets);
Structural *getParent() const;
Structural *findNextSibling() const;
Structural *findPrevSibling() const;
void setParent(Structural *parent);
// Helper that finds the scene containing the structural object, or itself if it is the scene