mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
10406 lines
336 KiB
C++
10406 lines
336 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 "common/debug.h"
|
|
#include "common/file.h"
|
|
#include "common/hash-ptr.h"
|
|
#include "common/macresman.h"
|
|
#include "common/random.h"
|
|
#include "common/substream.h"
|
|
#include "common/system.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
#include "graphics/managed_surface.h"
|
|
#include "graphics/paletteman.h"
|
|
#include "graphics/surface.h"
|
|
#include "graphics/wincursor.h"
|
|
#include "graphics/maccursor.h"
|
|
#include "graphics/macgui/macfontmanager.h"
|
|
|
|
#include "audio/mixer.h"
|
|
|
|
#include "mtropolis/runtime.h"
|
|
#include "mtropolis/coroutine_manager.h"
|
|
#include "mtropolis/coroutines.h"
|
|
#include "mtropolis/data.h"
|
|
#include "mtropolis/vthread.h"
|
|
#include "mtropolis/asset_factory.h"
|
|
#include "mtropolis/element_factory.h"
|
|
#include "mtropolis/miniscript.h"
|
|
#include "mtropolis/modifier_factory.h"
|
|
#include "mtropolis/modifiers.h"
|
|
#include "mtropolis/render.h"
|
|
|
|
namespace MTropolis {
|
|
|
|
int32 displayModeToBitDepth(ColorDepthMode displayMode) {
|
|
switch (displayMode) {
|
|
case kColorDepthMode1Bit:
|
|
return 1;
|
|
case kColorDepthMode2Bit:
|
|
return 2;
|
|
case kColorDepthMode4Bit:
|
|
return 4;
|
|
case kColorDepthMode8Bit:
|
|
return 8;
|
|
case kColorDepthMode16Bit:
|
|
return 16;
|
|
case kColorDepthMode32Bit:
|
|
return 32;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ColorDepthMode bitDepthToDisplayMode(int32 bits) {
|
|
switch (bits) {
|
|
case 1:
|
|
return kColorDepthMode1Bit;
|
|
case 2:
|
|
return kColorDepthMode2Bit;
|
|
case 4:
|
|
return kColorDepthMode4Bit;
|
|
case 8:
|
|
return kColorDepthMode8Bit;
|
|
case 16:
|
|
return kColorDepthMode16Bit;
|
|
case 32:
|
|
return kColorDepthMode32Bit;
|
|
default:
|
|
return kColorDepthModeInvalid;
|
|
}
|
|
}
|
|
|
|
class MainWindow : public Window {
|
|
public:
|
|
MainWindow(const WindowParameters &windowParams);
|
|
|
|
void onMouseDown(int32 x, int32 y, int mouseButton) override;
|
|
void onMouseMove(int32 x, int32 y) override;
|
|
void onMouseUp(int32 x, int32 y, int mouseButton) override;
|
|
void onKeyboardEvent(const Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) override;
|
|
void onAction(MTropolis::Actions::Action action) override;
|
|
|
|
private:
|
|
bool _mouseButtonStates[Actions::kMouseButtonCount];
|
|
};
|
|
|
|
MainWindow::MainWindow(const WindowParameters &windowParams) : Window(windowParams) {
|
|
for (int i = 0; i < Actions::kMouseButtonCount; i++)
|
|
_mouseButtonStates[i] = false;
|
|
}
|
|
|
|
void MainWindow::onMouseDown(int32 x, int32 y, int mouseButton) {
|
|
if (!_mouseButtonStates[mouseButton]) {
|
|
_mouseButtonStates[mouseButton] = true;
|
|
|
|
if (mouseButton == Actions::kMouseButtonLeft) {
|
|
_runtime->queueOSEvent(Common::SharedPtr<OSEvent>(new MouseInputEvent(kOSEventTypeMouseDown, x, y, static_cast<Actions::MouseButton>(mouseButton))));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::onMouseMove(int32 x, int32 y) {
|
|
_runtime->queueOSEvent(Common::SharedPtr<OSEvent>(new MouseInputEvent(kOSEventTypeMouseMove, x, y, Actions::kMouseButtonLeft)));
|
|
}
|
|
|
|
void MainWindow::onMouseUp(int32 x, int32 y, int mouseButton) {
|
|
if (_mouseButtonStates[mouseButton]) {
|
|
_mouseButtonStates[mouseButton] = false;
|
|
|
|
if (mouseButton == Actions::kMouseButtonLeft) {
|
|
_runtime->queueOSEvent(Common::SharedPtr<OSEvent>(new MouseInputEvent(kOSEventTypeMouseUp, x, y, static_cast<Actions::MouseButton>(mouseButton))));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::onKeyboardEvent(const Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) {
|
|
_runtime->queueOSEvent(Common::SharedPtr<OSEvent>(new KeyboardInputEvent(kOSEventTypeKeyboard, evtType, repeat, keyEvt)));
|
|
}
|
|
|
|
void MainWindow::onAction(MTropolis::Actions::Action action) {
|
|
_runtime->queueOSEvent(Common::SharedPtr<OSEvent>(new ActionEvent(kOSEventTypeAction, action)));
|
|
}
|
|
|
|
|
|
class ModifierInnerScopeBuilder : public IStructuralReferenceVisitor {
|
|
public:
|
|
ModifierInnerScopeBuilder(Runtime *runtime, Modifier *modifier, ObjectLinkingScope *scope);
|
|
|
|
void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
|
|
void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
|
|
void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
|
|
void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
|
|
|
|
private:
|
|
ObjectLinkingScope *_scope;
|
|
Modifier *_modifier;
|
|
Runtime *_runtime;
|
|
};
|
|
|
|
ModifierInnerScopeBuilder::ModifierInnerScopeBuilder(Runtime *runtime, Modifier *modifier, ObjectLinkingScope *scope)
|
|
: _scope(scope), _modifier(modifier), _runtime(runtime) {
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
assert(false);
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
uint32 oldStaticGUID = modifier->getStaticGUID();
|
|
|
|
_runtime->instantiateIfAlias(modifier, _modifier->getSelfReference());
|
|
_scope->addObject(oldStaticGUID, modifier->getName(), modifier);
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
// Do nothing
|
|
}
|
|
|
|
class ModifierChildMaterializer : public IStructuralReferenceVisitor {
|
|
public:
|
|
ModifierChildMaterializer(Runtime *runtime, ObjectLinkingScope *outerScope);
|
|
|
|
void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
|
|
void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
|
|
void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
|
|
void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
|
|
|
|
private:
|
|
Runtime *_runtime;
|
|
ObjectLinkingScope *_outerScope;
|
|
};
|
|
|
|
ModifierChildMaterializer::ModifierChildMaterializer(Runtime *runtime, ObjectLinkingScope *outerScope)
|
|
: _runtime(runtime), _outerScope(outerScope) {
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
assert(false);
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
modifier->materialize(_runtime, _outerScope);
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
// Do nothing
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
// Do nothing
|
|
}
|
|
|
|
class ModifierChildCloner : public IStructuralReferenceVisitor {
|
|
public:
|
|
ModifierChildCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent);
|
|
|
|
void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
|
|
void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
|
|
void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
|
|
void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
|
|
|
|
private:
|
|
Runtime *_runtime;
|
|
Common::WeakPtr<RuntimeObject> _relinkParent;
|
|
};
|
|
|
|
ModifierChildCloner::ModifierChildCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent)
|
|
: _runtime(runtime), _relinkParent(relinkParent) {
|
|
}
|
|
|
|
void ModifierChildCloner::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
assert(false);
|
|
}
|
|
|
|
void ModifierChildCloner::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
uint32 oldGUID = modifier->getStaticGUID();
|
|
modifier = modifier->shallowClone();
|
|
assert(modifier->getStaticGUID() == oldGUID);
|
|
|
|
(void)oldGUID;
|
|
|
|
modifier->setSelfReference(modifier);
|
|
modifier->setParent(_relinkParent);
|
|
|
|
ModifierChildCloner recursiveCloner(_runtime, modifier);
|
|
modifier->visitInternalReferences(&recursiveCloner);
|
|
}
|
|
|
|
void ModifierChildCloner::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
// Do nothing
|
|
}
|
|
|
|
void ModifierChildCloner::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
// Do nothing
|
|
}
|
|
|
|
class ObjectCloner : public IStructuralReferenceVisitor {
|
|
public:
|
|
ObjectCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent, Common::HashMap<RuntimeObject *, RuntimeObject *> *objectRemaps);
|
|
|
|
void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
|
|
void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
|
|
void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
|
|
void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
|
|
|
|
private:
|
|
Runtime *_runtime;
|
|
Common::WeakPtr<RuntimeObject> _relinkParent;
|
|
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *> *_objectRemaps;
|
|
};
|
|
|
|
ObjectCloner::ObjectCloner(Runtime *runtime, const Common::WeakPtr<RuntimeObject> &relinkParent, Common::HashMap<RuntimeObject *, RuntimeObject *> *objectRemaps)
|
|
: _runtime(runtime), _relinkParent(relinkParent), _objectRemaps(objectRemaps) {
|
|
}
|
|
|
|
void ObjectCloner::visitChildStructuralRef(Common::SharedPtr<Structural> &structuralRef) {
|
|
uint32 oldGUID = structuralRef->getStaticGUID();
|
|
Common::SharedPtr<Structural> cloned = structuralRef->shallowClone();
|
|
assert(cloned->getStaticGUID() == oldGUID);
|
|
|
|
(void)oldGUID;
|
|
|
|
if (_objectRemaps)
|
|
(*_objectRemaps)[structuralRef.get()] = cloned.get();
|
|
|
|
assert(!_relinkParent.expired() && _relinkParent.lock()->isStructural());
|
|
|
|
cloned->setSelfReference(cloned);
|
|
cloned->setRuntimeGUID(_runtime->allocateRuntimeGUID());
|
|
cloned->setParent(static_cast<Structural *>(_relinkParent.lock().get()));
|
|
|
|
ObjectCloner recursiveCloner(_runtime, cloned, _objectRemaps);
|
|
cloned->visitInternalReferences(&recursiveCloner);
|
|
|
|
structuralRef = cloned;
|
|
}
|
|
|
|
void ObjectCloner::visitChildModifierRef(Common::SharedPtr<Modifier> &modifierRef) {
|
|
uint32 oldGUID = modifierRef->getStaticGUID();
|
|
Common::SharedPtr<Modifier> cloned = modifierRef->shallowClone();
|
|
assert(cloned->getStaticGUID() == oldGUID);
|
|
|
|
(void)oldGUID;
|
|
|
|
if (_objectRemaps)
|
|
(*_objectRemaps)[modifierRef.get()] = cloned.get();
|
|
|
|
cloned->setSelfReference(cloned);
|
|
cloned->setRuntimeGUID(_runtime->allocateRuntimeGUID());
|
|
cloned->setParent(_relinkParent);
|
|
|
|
ObjectCloner recursiveCloner(_runtime, cloned, _objectRemaps);
|
|
cloned->visitInternalReferences(&recursiveCloner);
|
|
|
|
modifierRef = cloned;
|
|
}
|
|
|
|
void ObjectCloner::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
// Do nothing
|
|
}
|
|
|
|
void ObjectCloner::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
// Do nothing
|
|
}
|
|
|
|
|
|
|
|
class ObjectRefRemapper : public IStructuralReferenceVisitor {
|
|
public:
|
|
explicit ObjectRefRemapper(const Common::HashMap<RuntimeObject *, RuntimeObject *> &objectRemaps);
|
|
|
|
void visitChildStructuralRef(Common::SharedPtr<Structural> &structural) override;
|
|
void visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) override;
|
|
void visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) override;
|
|
void visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) override;
|
|
|
|
private:
|
|
const Common::HashMap<RuntimeObject *, RuntimeObject *> &_objectRemaps;
|
|
};
|
|
|
|
ObjectRefRemapper::ObjectRefRemapper(const Common::HashMap<RuntimeObject *, RuntimeObject *> &objectRemaps) : _objectRemaps(objectRemaps) {
|
|
}
|
|
|
|
void ObjectRefRemapper::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
RuntimeObject *obj = structural.get();
|
|
if (obj) {
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *> ::const_iterator it = _objectRemaps.find(obj);
|
|
if (it != _objectRemaps.end())
|
|
structural = it->_value->getSelfReference().lock().staticCast<Structural>();
|
|
}
|
|
}
|
|
|
|
void ObjectRefRemapper::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
RuntimeObject *obj = modifier.get();
|
|
if (obj) {
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
|
|
if (it != _objectRemaps.end())
|
|
modifier = it->_value->getSelfReference().lock().staticCast<Modifier>();
|
|
}
|
|
}
|
|
|
|
void ObjectRefRemapper::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
RuntimeObject *obj = structural.lock().get();
|
|
if (obj) {
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
|
|
if (it != _objectRemaps.end())
|
|
structural = it->_value->getSelfReference().staticCast<Structural>();
|
|
}
|
|
}
|
|
|
|
void ObjectRefRemapper::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
RuntimeObject *obj = modifier.lock().get();
|
|
if (obj) {
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *>::const_iterator it = _objectRemaps.find(obj);
|
|
if (it != _objectRemaps.end())
|
|
modifier = it->_value->getSelfReference().staticCast<Modifier>();
|
|
}
|
|
}
|
|
|
|
char invariantToLower(char c) {
|
|
if (c >= 'A' && c <= 'Z')
|
|
return static_cast<char>(c - 'A' + 'a');
|
|
return c;
|
|
}
|
|
|
|
Common::String toCaseInsensitive(const Common::String &str) {
|
|
uint strLen = str.size();
|
|
if (strLen == 0)
|
|
return str;
|
|
|
|
// TODO: Figure out how this is supposed to behave with respect to non-ASCII characters
|
|
Common::Array<char> lowered;
|
|
lowered.resize(strLen);
|
|
|
|
for (uint i = 0; i < strLen; i++)
|
|
lowered[i] = invariantToLower(str[i]);
|
|
|
|
return Common::String(&lowered[0], strLen);
|
|
}
|
|
|
|
bool caseInsensitiveEqual(const Common::String& str1, const Common::String& str2) {
|
|
size_t length1 = str1.size();
|
|
size_t length2 = str2.size();
|
|
if (length1 != length2)
|
|
return false;
|
|
|
|
for (size_t i = 0; i < length1; i++) {
|
|
if (invariantToLower(str1[i]) != invariantToLower(str2[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t caseInsensitiveFind(const Common::String &strToSearch, const Common::String &stringToFind) {
|
|
if (stringToFind.size() > strToSearch.size())
|
|
return Common::String::npos;
|
|
|
|
size_t lastValidStart = strToSearch.size() - stringToFind.size();
|
|
size_t searchLength = stringToFind.size();
|
|
|
|
for (size_t startIndex = 0; startIndex <= lastValidStart; startIndex++) {
|
|
bool matches = true;
|
|
for (size_t i = 0; i < searchLength; i++) {
|
|
char ca = strToSearch[i + startIndex];
|
|
char cb = stringToFind[i];
|
|
if (ca != cb && invariantToLower(ca) != invariantToLower(cb)) {
|
|
matches = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matches)
|
|
return startIndex;
|
|
}
|
|
|
|
return Common::String::npos;
|
|
}
|
|
|
|
bool SceneTransitionTypes::loadFromData(SceneTransitionType &transType, int32 data) {
|
|
switch (data) {
|
|
case Data::SceneTransitionTypes::kNone:
|
|
transType = kNone;
|
|
break;
|
|
case Data::SceneTransitionTypes::kPatternDissolve:
|
|
transType = kPatternDissolve;
|
|
break;
|
|
case Data::SceneTransitionTypes::kRandomDissolve:
|
|
transType = kRandomDissolve;
|
|
break;
|
|
case Data::SceneTransitionTypes::kFade:
|
|
transType = kFade;
|
|
break;
|
|
case Data::SceneTransitionTypes::kSlide:
|
|
transType = kSlide;
|
|
break;
|
|
case Data::SceneTransitionTypes::kPush:
|
|
transType = kPush;
|
|
break;
|
|
case Data::SceneTransitionTypes::kZoom:
|
|
transType = kZoom;
|
|
break;
|
|
case Data::SceneTransitionTypes::kWipe:
|
|
transType = kWipe;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SceneTransitionDirections::loadFromData(SceneTransitionDirection &transDir, int32 data) {
|
|
switch (data) {
|
|
case Data::SceneTransitionDirections::kUp:
|
|
transDir = kUp;
|
|
break;
|
|
case Data::SceneTransitionDirections::kDown:
|
|
transDir = kDown;
|
|
break;
|
|
case Data::SceneTransitionDirections::kLeft:
|
|
transDir = kLeft;
|
|
break;
|
|
case Data::SceneTransitionDirections::kRight:
|
|
transDir = kRight;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EventIDs::isCommand(EventID eventID) {
|
|
switch (eventID) {
|
|
case kElementShow:
|
|
case kElementHide:
|
|
case kElementSelect:
|
|
case kElementDeselect:
|
|
case kElementToggleSelect:
|
|
case kElementEnableEdit:
|
|
case kElementDisableEdit:
|
|
case kElementUpdatedCalculated:
|
|
case kElementScrollUp:
|
|
case kElementScrollDown:
|
|
case kElementScrollLeft:
|
|
case kElementScrollRight:
|
|
case kPreloadMedia:
|
|
case kFlushMedia:
|
|
case kPrerollMedia:
|
|
case kClone:
|
|
case kKill:
|
|
case kPlay:
|
|
case kStop:
|
|
case kPause:
|
|
case kUnpause:
|
|
case kTogglePause:
|
|
case kCloseProject:
|
|
case kFlushAllMedia:
|
|
case kAttribGet:
|
|
case kAttribSet:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
MiniscriptInstructionOutcome pointWriteRefAttrib(Common::Point &point, MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&point.x, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "y") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&point.y, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
Common::String pointToString(const Common::Point &point) {
|
|
return Common::String::format("(%i,%i)", point.x, point.y);
|
|
}
|
|
|
|
IntRange::IntRange() : min(0), max(0) {
|
|
}
|
|
|
|
IntRange::IntRange(int32 pmin, int32 pmax) : min(pmin), max(pmax) {
|
|
}
|
|
|
|
bool IntRange::load(const Data::IntRange &range) {
|
|
max = range.max;
|
|
min = range.min;
|
|
|
|
return true;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome IntRange::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "start") {
|
|
DynamicValueWriteIntegerHelper<int32>::create(&min, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "end") {
|
|
DynamicValueWriteIntegerHelper<int32>::create(&max, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
thread->error("Couldn't reference int range attribute '" + attrib + "'");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
Common::String IntRange::toString() const {
|
|
return Common::String::format("(%i thru %i)", min, max);
|
|
}
|
|
|
|
Label::Label() : superGroupID(0), id(0) {
|
|
}
|
|
|
|
Label::Label(int32 psuperGroupID, int32 pid) : superGroupID(psuperGroupID), id(pid) {
|
|
}
|
|
|
|
bool Label::load(const Data::Label &label) {
|
|
id = label.labelID;
|
|
superGroupID = label.superGroupID;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ColorRGB8::load(const Data::ColorRGB16 &color) {
|
|
this->r = (color.red * 0xff * 2 + 1) / (0xffff * 2);
|
|
this->g = (color.green * 0xff * 2 + 1) / (0xffff * 2);
|
|
this->b = (color.blue * 0xff * 2 + 1) / (0xffff * 2);
|
|
|
|
return true;
|
|
}
|
|
|
|
ColorRGB8::ColorRGB8() : r(0), g(0), b(0) {
|
|
}
|
|
|
|
ColorRGB8::ColorRGB8(uint8 pr, uint8 pg, uint8 pb) : r(pr), g(pg), b(pb) {
|
|
}
|
|
|
|
|
|
MessageFlags::MessageFlags() : relay(true), cascade(true), immediate(true) {
|
|
}
|
|
|
|
DynamicValueWriteProxyPOD DynamicValueWriteProxyPOD::createDefault() {
|
|
DynamicValueWriteProxyPOD proxy;
|
|
proxy.ifc = nullptr;
|
|
proxy.objectRef = nullptr;
|
|
proxy.ptrOrOffset = 0;
|
|
return proxy;
|
|
}
|
|
|
|
DynamicValueWriteProxy::DynamicValueWriteProxy() : pod(DynamicValueWriteProxyPOD::createDefault()) {
|
|
}
|
|
|
|
Common::Point Point16POD::toScummVMPoint() const {
|
|
return Common::Point(x, y);
|
|
}
|
|
|
|
DynamicListContainerBase::~DynamicListContainerBase() {
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(int32 &value) {
|
|
value = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(double &value) {
|
|
value = 0.0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Common::Point &value) {
|
|
value.x = 0;
|
|
value.y = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(IntRange &value) {
|
|
value.min = 0;
|
|
value.max = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(bool &value) {
|
|
value = false;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(AngleMagVector &value) {
|
|
value.angleDegrees = 0.0;
|
|
value.magnitude = 0.0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Label &value) {
|
|
value.id = 0;
|
|
value.superGroupID = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Event &value) {
|
|
value.eventType = EventIDs::EventID::kNothing;
|
|
value.eventInfo = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Common::String &value) {
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Common::SharedPtr<DynamicList> &value) {
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(ObjectReference &value) {
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const int32 *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kInteger)
|
|
return false;
|
|
outPtr = &dynValue.getInt();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const double *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kFloat)
|
|
return false;
|
|
outPtr = &dynValue.getFloat();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const Common::Point *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kPoint)
|
|
return false;
|
|
outPtr = &dynValue.getPoint();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const IntRange *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kIntegerRange)
|
|
return false;
|
|
outPtr = &dynValue.getIntRange();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const bool *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kBoolean)
|
|
return false;
|
|
outPtr = &dynValue.getBool();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const AngleMagVector *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kVector)
|
|
return false;
|
|
outPtr = &dynValue.getVector();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const Label *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kLabel)
|
|
return false;
|
|
outPtr = &dynValue.getLabel();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const Event *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kEvent)
|
|
return false;
|
|
outPtr = &dynValue.getEvent();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const Common::String *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kString)
|
|
return false;
|
|
outPtr = &dynValue.getString();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const Common::SharedPtr<DynamicList> *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kList)
|
|
return false;
|
|
outPtr = &dynValue.getList();
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListValueImporter::importValue(const DynamicValue &dynValue, const ObjectReference *&outPtr) {
|
|
if (dynValue.getType() != DynamicValueTypes::kObject)
|
|
return false;
|
|
outPtr = &dynValue.getObject();
|
|
return true;
|
|
}
|
|
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const int32 &value) {
|
|
dynValue.setInt(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const double &value) {
|
|
dynValue.setFloat(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const Common::Point &value) {
|
|
dynValue.setPoint(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const IntRange &value) {
|
|
dynValue.setIntRange(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const bool &value) {
|
|
dynValue.setBool(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const AngleMagVector &value) {
|
|
dynValue.setVector(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const Label &value) {
|
|
dynValue.setLabel(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const Event &value) {
|
|
dynValue.setEvent(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const Common::String &value) {
|
|
dynValue.setString(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const Common::SharedPtr<DynamicList> &value) {
|
|
dynValue.setList(value);
|
|
}
|
|
|
|
void DynamicListValueExporter::exportValue(DynamicValue &dynValue, const ObjectReference &value) {
|
|
dynValue.setObject(value);
|
|
}
|
|
|
|
DynamicListContainer<void>::DynamicListContainer() : _size(0) {
|
|
}
|
|
|
|
bool DynamicListContainer<void>::setAtIndex(size_t index, const DynamicValue &dynValue) {
|
|
return true;
|
|
}
|
|
|
|
void DynamicListContainer<void>::truncateToSize(size_t sz) {
|
|
}
|
|
|
|
bool DynamicListContainer<void>::expandToMinimumSize(size_t sz) {
|
|
return false;
|
|
}
|
|
|
|
bool DynamicListContainer<void>::getAtIndex(size_t index, DynamicValue &dynValue) const {
|
|
dynValue.clear();
|
|
return true;
|
|
}
|
|
|
|
void DynamicListContainer<void>::setFrom(const DynamicListContainerBase &other) {
|
|
_size = other.getSize(); // ... the only thing we have anyway...
|
|
}
|
|
|
|
const void *DynamicListContainer<void>::getConstArrayPtr() const {
|
|
return nullptr;
|
|
}
|
|
|
|
void *DynamicListContainer<void>::getArrayPtr() {
|
|
return nullptr;
|
|
}
|
|
|
|
size_t DynamicListContainer<void>::getSize() const {
|
|
return _size;
|
|
}
|
|
|
|
bool DynamicListContainer<void>::compareEqual(const DynamicListContainerBase &other) const {
|
|
return true;
|
|
}
|
|
|
|
DynamicListContainerBase *DynamicListContainer<void>::clone() const {
|
|
return new DynamicListContainer<void>(*this);
|
|
}
|
|
DynamicList::DynamicList() : _type(DynamicValueTypes::kUnspecified), _container(nullptr) {
|
|
}
|
|
|
|
DynamicList::DynamicList(const DynamicList &other) : _type(DynamicValueTypes::kUnspecified), _container(nullptr) {
|
|
initFromOther(other);
|
|
}
|
|
|
|
DynamicList::~DynamicList() {
|
|
destroyContainer();
|
|
}
|
|
|
|
DynamicValueTypes::DynamicValueType DynamicList::getType() const {
|
|
return _type;
|
|
}
|
|
|
|
const Common::Array<int32> &DynamicList::getInt() const {
|
|
assert(_type == DynamicValueTypes::kInteger);
|
|
return *static_cast<const Common::Array<int32> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<double> &DynamicList::getFloat() const {
|
|
assert(_type == DynamicValueTypes::kFloat);
|
|
return *static_cast<const Common::Array<double> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<Common::Point> &DynamicList::getPoint() const {
|
|
assert(_type == DynamicValueTypes::kPoint);
|
|
return *static_cast<const Common::Array<Common::Point> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<IntRange> &DynamicList::getIntRange() const {
|
|
assert(_type == DynamicValueTypes::kIntegerRange);
|
|
return *static_cast<const Common::Array<IntRange> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<AngleMagVector> &DynamicList::getVector() const {
|
|
assert(_type == DynamicValueTypes::kVector);
|
|
return *static_cast<const Common::Array<AngleMagVector> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<Label> &DynamicList::getLabel() const {
|
|
assert(_type == DynamicValueTypes::kLabel);
|
|
return *static_cast<const Common::Array<Label> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<Event> &DynamicList::getEvent() const {
|
|
assert(_type == DynamicValueTypes::kEvent);
|
|
return *static_cast<const Common::Array<Event> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<Common::String> &DynamicList::getString() const {
|
|
assert(_type == DynamicValueTypes::kString);
|
|
return *static_cast<const Common::Array<Common::String> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<bool> &DynamicList::getBool() const {
|
|
assert(_type == DynamicValueTypes::kBoolean);
|
|
return *static_cast<const Common::Array<bool> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<Common::SharedPtr<DynamicList> > &DynamicList::getList() const {
|
|
assert(_type == DynamicValueTypes::kList);
|
|
return *static_cast<const Common::Array<Common::SharedPtr<DynamicList> > *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
const Common::Array<ObjectReference> &DynamicList::getObjectReference() const {
|
|
assert(_type == DynamicValueTypes::kObject);
|
|
return *static_cast<const Common::Array<ObjectReference> *>(_container->getConstArrayPtr());
|
|
}
|
|
|
|
Common::Array<int32> &DynamicList::getInt() {
|
|
assert(_type == DynamicValueTypes::kInteger);
|
|
return *static_cast<Common::Array<int32> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<double> &DynamicList::getFloat() {
|
|
assert(_type == DynamicValueTypes::kFloat);
|
|
return *static_cast<Common::Array<double> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<Common::Point> &DynamicList::getPoint() {
|
|
assert(_type == DynamicValueTypes::kPoint);
|
|
return *static_cast<Common::Array<Common::Point> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<IntRange> &DynamicList::getIntRange() {
|
|
assert(_type == DynamicValueTypes::kIntegerRange);
|
|
return *static_cast<Common::Array<IntRange> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<AngleMagVector> &DynamicList::getVector() {
|
|
assert(_type == DynamicValueTypes::kVector);
|
|
return *static_cast<Common::Array<AngleMagVector> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<Label> &DynamicList::getLabel() {
|
|
assert(_type == DynamicValueTypes::kLabel);
|
|
return *static_cast<Common::Array<Label> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<Event> &DynamicList::getEvent() {
|
|
assert(_type == DynamicValueTypes::kEvent);
|
|
return *static_cast<Common::Array<Event> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<Common::String> &DynamicList::getString() {
|
|
assert(_type == DynamicValueTypes::kString);
|
|
return *static_cast<Common::Array<Common::String> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<bool> &DynamicList::getBool() {
|
|
assert(_type == DynamicValueTypes::kBoolean);
|
|
return *static_cast<Common::Array<bool> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<Common::SharedPtr<DynamicList> > &DynamicList::getList() {
|
|
assert(_type == DynamicValueTypes::kList);
|
|
return *static_cast<Common::Array<Common::SharedPtr<DynamicList> > *>(_container->getArrayPtr());
|
|
}
|
|
|
|
Common::Array<ObjectReference> &DynamicList::getObjectReference() {
|
|
assert(_type == DynamicValueTypes::kObject);
|
|
return *static_cast<Common::Array<ObjectReference> *>(_container->getArrayPtr());
|
|
}
|
|
|
|
bool DynamicList::setAtIndex(size_t index, const DynamicValue &value) {
|
|
if (_type != value.getType()) {
|
|
if (_container) {
|
|
DynamicValue converted;
|
|
if (!value.convertToType(_type, converted))
|
|
return false;
|
|
return setAtIndex(index, converted);
|
|
} else {
|
|
createContainerAndSetType(value.getType());
|
|
return _container->setAtIndex(index, value);
|
|
}
|
|
} else {
|
|
return _container->setAtIndex(index, value);
|
|
}
|
|
}
|
|
|
|
void DynamicList::deleteAtIndex(size_t index) {
|
|
if (_container != nullptr) {
|
|
size_t size = _container->getSize();
|
|
if (index < _container->getSize()) {
|
|
for (size_t i = index + 1; i < size; i++) {
|
|
DynamicValue valueToMove;
|
|
_container->getAtIndex(i, valueToMove);
|
|
_container->setAtIndex(i - 1, valueToMove);
|
|
}
|
|
|
|
_container->truncateToSize(size - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DynamicList::truncateToSize(size_t sz) {
|
|
if (_container)
|
|
_container->truncateToSize(sz);
|
|
}
|
|
|
|
void DynamicList::expandToMinimumSize(size_t sz) {
|
|
if (_container)
|
|
_container->expandToMinimumSize(sz);
|
|
}
|
|
|
|
bool DynamicList::getAtIndex(size_t index, DynamicValue &value) const {
|
|
if (_container == nullptr || index >= _container->getSize())
|
|
return false;
|
|
|
|
return _container->getAtIndex(index, value);
|
|
}
|
|
|
|
size_t DynamicList::getSize() const {
|
|
if (!_container)
|
|
return 0;
|
|
else
|
|
return _container->getSize();
|
|
}
|
|
|
|
void DynamicList::forceType(DynamicValueTypes::DynamicValueType type) {
|
|
if (_type != type) {
|
|
destroyContainer();
|
|
createContainerAndSetType(type);
|
|
}
|
|
}
|
|
|
|
bool DynamicList::dynamicValueToIndex(size_t &outIndex, const DynamicValue &value) {
|
|
if (value.getType() == DynamicValueTypes::kFloat) {
|
|
double rounded = floor(value.getFloat() + 0.5);
|
|
if (!isfinite(rounded) || rounded < 1.0 || rounded > 0xffffffffu)
|
|
return false;
|
|
|
|
outIndex = static_cast<size_t>(rounded - 1.0);
|
|
} else if (value.getType() == DynamicValueTypes::kInteger) {
|
|
int32 i = value.getInt();
|
|
if (i < 1)
|
|
return false;
|
|
outIndex = static_cast<size_t>(i - 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DynamicList &DynamicList::operator=(const DynamicList &other) {
|
|
if (this != &other) {
|
|
destroyContainer();
|
|
initFromOther(other);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool DynamicList::operator==(const DynamicList &other) const {
|
|
if (this == &other)
|
|
return true;
|
|
|
|
if (_type != other._type)
|
|
return false;
|
|
|
|
if (_container == nullptr)
|
|
return other._container == nullptr;
|
|
|
|
if (other._container == nullptr)
|
|
return false; // (_container == nullptr)
|
|
|
|
return _container->compareEqual(*other._container);
|
|
}
|
|
|
|
void DynamicList::swap(DynamicList &other) {
|
|
if (this == &other)
|
|
return;
|
|
|
|
DynamicValueTypes::DynamicValueType tempType = _type;
|
|
_type = other._type;
|
|
other._type = tempType;
|
|
|
|
DynamicListContainerBase *tempContainer = _container;
|
|
_container = other._container;
|
|
other._container = tempContainer;
|
|
}
|
|
|
|
Common::SharedPtr<DynamicList> DynamicList::clone() const {
|
|
Common::SharedPtr<DynamicList> clonedList(new DynamicList());
|
|
|
|
if (_container)
|
|
clonedList->_container = _container->clone();
|
|
clonedList->_type = _type;
|
|
|
|
return clonedList;
|
|
}
|
|
|
|
void DynamicList::createWriteProxyForIndex(size_t index, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<WriteProxyInterface>::getInstance();
|
|
proxy.pod.objectRef = this;
|
|
proxy.pod.ptrOrOffset = index;
|
|
}
|
|
|
|
bool DynamicList::createContainerAndSetType(DynamicValueTypes::DynamicValueType type) {
|
|
switch (type) {
|
|
case DynamicValueTypes::kInvalid:
|
|
// FIXME: Set _container as per kNull case?
|
|
break;
|
|
case DynamicValueTypes::kNull:
|
|
_container = new DynamicListContainer<void>();
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
_container = new DynamicListContainer<int32>();
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
_container = new DynamicListContainer<double>();
|
|
break;
|
|
case DynamicValueTypes::kPoint:
|
|
_container = new DynamicListContainer<Common::Point>();
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
_container = new DynamicListContainer<IntRange>();
|
|
break;
|
|
case DynamicValueTypes::kBoolean:
|
|
_container = new DynamicListContainer<bool>();
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
_container = new DynamicListContainer<AngleMagVector>();
|
|
break;
|
|
case DynamicValueTypes::kLabel:
|
|
_container = new DynamicListContainer<Label>();
|
|
break;
|
|
case DynamicValueTypes::kEvent:
|
|
_container = new DynamicListContainer<Event>();
|
|
break;
|
|
case DynamicValueTypes::kString:
|
|
_container = new DynamicListContainer<Common::String>();
|
|
break;
|
|
case DynamicValueTypes::kList:
|
|
_container = new DynamicListContainer<Common::SharedPtr<DynamicList> >();
|
|
break;
|
|
case DynamicValueTypes::kObject:
|
|
_container = new DynamicListContainer<ObjectReference>();
|
|
break;
|
|
case DynamicValueTypes::kWriteProxy:
|
|
// FIXME
|
|
break;
|
|
case DynamicValueTypes::kUnspecified:
|
|
// FIXME: Set _container as per kNull case?
|
|
break;
|
|
default:
|
|
error("List was set to an invalid type");
|
|
}
|
|
|
|
_type = type;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DynamicList::destroyContainer() {
|
|
if (_container)
|
|
delete _container;
|
|
_container = nullptr;
|
|
_type = DynamicValueTypes::kUnspecified;
|
|
}
|
|
|
|
void DynamicList::initFromOther(const DynamicList &other) {
|
|
assert(_container == nullptr);
|
|
assert(_type == DynamicValueTypes::kUnspecified);
|
|
|
|
if (other._type != DynamicValueTypes::kUnspecified) {
|
|
createContainerAndSetType(other._type);
|
|
_container->setFrom(*other._container);
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
if (!static_cast<DynamicList *>(objectRef)->setAtIndex(ptrOrOffset, value))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
DynamicList *list = static_cast<DynamicList *>(objectRef);
|
|
|
|
if (ptrOrOffset >= list->getSize()) {
|
|
// Only direct sets of elements may change the size of a list. Accessing attributes of elements that aren't set is an error.
|
|
thread->error("List attrib write dereference was out of bounds");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
switch (list->getType()) {
|
|
case DynamicValueTypes::kPoint:
|
|
return pointWriteRefAttrib(list->getPoint()[ptrOrOffset], thread, proxy, attrib);
|
|
case DynamicValueTypes::kIntegerRange:
|
|
return list->getIntRange()[ptrOrOffset].refAttrib(thread, proxy, attrib);
|
|
case DynamicValueTypes::kVector:
|
|
return list->getVector()[ptrOrOffset].refAttrib(thread, proxy, attrib);
|
|
case DynamicValueTypes::kObject: {
|
|
Common::SharedPtr<RuntimeObject> obj = list->getObjectReference()[ptrOrOffset].object.lock();
|
|
proxy.containerList.reset();
|
|
if (!obj) {
|
|
thread->error("Attempted to reference an attribute of an invalid object reference");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
return obj->writeRefAttribute(thread, proxy, attrib);
|
|
} break;
|
|
default:
|
|
thread->error("Couldn't reference an attribute of a list element");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
DynamicList *list = static_cast<DynamicList *>(objectRef);
|
|
switch (list->getType()) {
|
|
case DynamicValueTypes::kList: {
|
|
if (list->getSize() <= ptrOrOffset)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::SharedPtr<DynamicList> subList = list->getList()[ptrOrOffset];
|
|
|
|
size_t subIndex = 0;
|
|
if (!subList->dynamicValueToIndex(subIndex, index))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
subList->createWriteProxyForIndex(subIndex, proxy);
|
|
proxy.containerList = subList;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} break;
|
|
case DynamicValueTypes::kObject: {
|
|
if (list->getSize() <= ptrOrOffset)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::SharedPtr<RuntimeObject> obj = list->getObjectReference()[ptrOrOffset].object.lock();
|
|
proxy.containerList.reset();
|
|
if (!obj && !obj->writeRefAttributeIndexed(thread, proxy, attrib, index))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} break;
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
DynamicValue::ValueUnion::ValueUnion() : asUnset(0) {
|
|
}
|
|
|
|
DynamicValue::ValueUnion::~ValueUnion() {
|
|
}
|
|
|
|
template<class T, T(DynamicValue::ValueUnion::*TMember)>
|
|
void DynamicValue::ValueUnion::construct(const T &value) {
|
|
T *field = &(this->*TMember);
|
|
new (field) T(value);
|
|
}
|
|
|
|
template<class T, T(DynamicValue::ValueUnion::*TMember)>
|
|
void DynamicValue::ValueUnion::construct(T &&value) {
|
|
T *field = &(this->*TMember);
|
|
new (field) T(static_cast<T&&>(value));
|
|
}
|
|
|
|
template<class T, T(DynamicValue::ValueUnion::*TMember)>
|
|
void DynamicValue::ValueUnion::assign(const T &value) {
|
|
T *field = &(this->*TMember);
|
|
*field = value;
|
|
}
|
|
|
|
template<class T, T(DynamicValue::ValueUnion::*TMember)>
|
|
void DynamicValue::ValueUnion::assign(T &&value) {
|
|
T *field = &(this->*TMember);
|
|
*field = static_cast<T &&>(value);
|
|
}
|
|
|
|
template<class T, T(DynamicValue::ValueUnion::*TMember)>
|
|
void DynamicValue::ValueUnion::destruct() {
|
|
T *field = &(this->*TMember);
|
|
field->~T();
|
|
}
|
|
|
|
DynamicValue::DynamicValue() : _type(DynamicValueTypes::kNull) {
|
|
}
|
|
|
|
DynamicValue::DynamicValue(const DynamicValue &other) : _type(DynamicValueTypes::kNull) {
|
|
setFromOther(other);
|
|
}
|
|
|
|
DynamicValue::~DynamicValue() {
|
|
clear();
|
|
}
|
|
|
|
bool DynamicValue::loadConstant(const Data::InternalTypeTaggedValue &data, const Common::String &varString) {
|
|
clear();
|
|
|
|
switch (data.type) {
|
|
case Data::InternalTypeTaggedValue::kNull:
|
|
_type = DynamicValueTypes::kNull;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kInteger:
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.construct<int32, &ValueUnion::asInt>(data.value.asInteger);
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kString:
|
|
_type = DynamicValueTypes::kString;
|
|
_value.construct<Common::String, &ValueUnion::asString>(varString);
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kPoint:
|
|
_type = DynamicValueTypes::kPoint;
|
|
_value.construct<Common::Point, &ValueUnion::asPoint>(Common::Point(data.value.asPoint.x, data.value.asPoint.y));
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kIntegerRange:
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
_value.construct<IntRange, &ValueUnion::asIntRange>(IntRange(0, 0));
|
|
if (!_value.asIntRange.load(data.value.asIntegerRange))
|
|
return false;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kFloat:
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.construct<double, &ValueUnion::asFloat>(data.value.asFloat.toXPFloat().toDouble());
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kBool:
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.construct<bool, &ValueUnion::asBool>(data.value.asBool != 0);
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kLabel:
|
|
_type = DynamicValueTypes::kLabel;
|
|
_value.construct<Label, &ValueUnion::asLabel>(Label());
|
|
if (!_value.asLabel.load(data.value.asLabel))
|
|
return false;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DynamicValue::loadConstant(const Data::PlugInTypeTaggedValue &data) {
|
|
clear();
|
|
|
|
switch (data.type) {
|
|
case Data::PlugInTypeTaggedValue::kNull:
|
|
_type = DynamicValueTypes::kNull;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kInteger:
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.construct<int32, &ValueUnion::asInt>(data.value.asInt);
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kIntegerRange:
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
_value.construct<IntRange, &ValueUnion::asIntRange>(IntRange());
|
|
if (!_value.asIntRange.load(data.value.asIntRange))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kFloat:
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.construct<double, &ValueUnion::asFloat>(data.value.asFloat.toXPFloat().toDouble());
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kBoolean:
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.construct<bool, &ValueUnion::asBool>(data.value.asBoolean != 0);
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kEvent:
|
|
_type = DynamicValueTypes::kEvent;
|
|
_value.construct<Event, &ValueUnion::asEvent>(Event());
|
|
if (!_value.asEvent.load(data.value.asEvent))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kLabel:
|
|
_type = DynamicValueTypes::kLabel;
|
|
_value.construct<Label, &ValueUnion::asLabel>(Label());
|
|
if (!_value.asLabel.load(data.value.asLabel))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kString:
|
|
_type = DynamicValueTypes::kString;
|
|
_value.construct<Common::String, &ValueUnion::asString>(data.value.asString);
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kPoint:
|
|
_type = DynamicValueTypes::kPoint;
|
|
_value.construct<Common::Point, &ValueUnion::asPoint>(Common::Point());
|
|
if (!data.value.asPoint.toScummVMPoint(_value.asPoint))
|
|
return false;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DynamicValueTypes::DynamicValueType DynamicValue::getType() const {
|
|
return _type;
|
|
}
|
|
|
|
const int32 &DynamicValue::getInt() const {
|
|
assert(_type == DynamicValueTypes::kInteger);
|
|
return _value.asInt;
|
|
}
|
|
|
|
const double &DynamicValue::getFloat() const {
|
|
assert(_type == DynamicValueTypes::kFloat);
|
|
return _value.asFloat;
|
|
}
|
|
|
|
const Common::Point &DynamicValue::getPoint() const {
|
|
assert(_type == DynamicValueTypes::kPoint);
|
|
return _value.asPoint;
|
|
}
|
|
|
|
const IntRange &DynamicValue::getIntRange() const {
|
|
assert(_type == DynamicValueTypes::kIntegerRange);
|
|
return _value.asIntRange;
|
|
}
|
|
|
|
const AngleMagVector &DynamicValue::getVector() const {
|
|
assert(_type == DynamicValueTypes::kVector);
|
|
return _value.asVector;
|
|
}
|
|
|
|
const Label &DynamicValue::getLabel() const {
|
|
assert(_type == DynamicValueTypes::kLabel);
|
|
return _value.asLabel;
|
|
}
|
|
|
|
const Event &DynamicValue::getEvent() const {
|
|
assert(_type == DynamicValueTypes::kEvent);
|
|
return _value.asEvent;
|
|
}
|
|
|
|
const Common::String &DynamicValue::getString() const {
|
|
assert(_type == DynamicValueTypes::kString);
|
|
return _value.asString;
|
|
}
|
|
|
|
const bool &DynamicValue::getBool() const {
|
|
assert(_type == DynamicValueTypes::kBoolean);
|
|
return _value.asBool;
|
|
}
|
|
|
|
const Common::SharedPtr<DynamicList> &DynamicValue::getList() const {
|
|
assert(_type == DynamicValueTypes::kList);
|
|
return _value.asList;
|
|
}
|
|
|
|
const ObjectReference &DynamicValue::getObject() const {
|
|
assert(_type == DynamicValueTypes::kObject);
|
|
return _value.asObj;
|
|
}
|
|
|
|
const DynamicValueWriteProxy &DynamicValue::getWriteProxy() const {
|
|
assert(_type == DynamicValueTypes::kWriteProxy);
|
|
return _value.asWriteProxy;
|
|
}
|
|
|
|
void DynamicValue::setInt(int32 value) {
|
|
if (_type != DynamicValueTypes::kInteger) {
|
|
clear();
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.construct<int32, &ValueUnion::asInt>(value);
|
|
} else {
|
|
_value.assign<int32, &ValueUnion::asInt>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setFloat(double value) {
|
|
if (_type != DynamicValueTypes::kFloat) {
|
|
clear();
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.construct<double, &ValueUnion::asFloat>(value);
|
|
} else {
|
|
_value.assign<double, &ValueUnion::asFloat>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setPoint(const Common::Point &value) {
|
|
if (_type != DynamicValueTypes::kPoint) {
|
|
clear();
|
|
_type = DynamicValueTypes::kPoint;
|
|
_value.construct<Common::Point, &ValueUnion::asPoint>(value);
|
|
} else {
|
|
_value.assign<Common::Point, &ValueUnion::asPoint>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setIntRange(const IntRange &value) {
|
|
if (_type != DynamicValueTypes::kIntegerRange) {
|
|
clear();
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
_value.construct<IntRange, &ValueUnion::asIntRange>(value);
|
|
} else {
|
|
_value.assign<IntRange, &ValueUnion::asIntRange>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setVector(const AngleMagVector &value) {
|
|
if (_type != DynamicValueTypes::kVector) {
|
|
clear();
|
|
_type = DynamicValueTypes::kVector;
|
|
_value.construct<AngleMagVector, &ValueUnion::asVector>(value);
|
|
} else {
|
|
_value.assign<AngleMagVector, &ValueUnion::asVector>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setLabel(const Label &value) {
|
|
if (_type != DynamicValueTypes::kLabel) {
|
|
clear();
|
|
_type = DynamicValueTypes::kLabel;
|
|
_value.construct<Label, &ValueUnion::asLabel>(value);
|
|
} else {
|
|
_value.assign<Label, &ValueUnion::asLabel>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setEvent(const Event &value) {
|
|
if (_type != DynamicValueTypes::kEvent) {
|
|
clear();
|
|
_type = DynamicValueTypes::kEvent;
|
|
_value.construct<Event, &ValueUnion::asEvent>(value);
|
|
} else {
|
|
_value.assign<Event, &ValueUnion::asEvent>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setString(const Common::String &value) {
|
|
if (_type != DynamicValueTypes::kString) {
|
|
clear();
|
|
_type = DynamicValueTypes::kString;
|
|
_value.construct<Common::String, &ValueUnion::asString>(value);
|
|
} else {
|
|
_value.assign<Common::String, &ValueUnion::asString>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setBool(bool value) {
|
|
if (_type != DynamicValueTypes::kBoolean) {
|
|
clear();
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.construct<bool, &ValueUnion::asBool>(value);
|
|
} else {
|
|
_value.assign<bool, &ValueUnion::asBool>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setList(const Common::SharedPtr<DynamicList> &value) {
|
|
if (_type != DynamicValueTypes::kList) {
|
|
clear();
|
|
_type = DynamicValueTypes::kList;
|
|
_value.construct<Common::SharedPtr<DynamicList>, &ValueUnion::asList>(value);
|
|
} else {
|
|
_value.assign<Common::SharedPtr<DynamicList>, &ValueUnion::asList>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setWriteProxy(const DynamicValueWriteProxy &writeProxy) {
|
|
Common::SharedPtr<DynamicList> listRef = writeProxy.containerList; // Back up list ref in case this is a self-assign
|
|
if (_type != DynamicValueTypes::kWriteProxy) {
|
|
clear();
|
|
_type = DynamicValueTypes::kWriteProxy;
|
|
_value.construct<DynamicValueWriteProxy, &ValueUnion::asWriteProxy>(writeProxy);
|
|
} else {
|
|
_value.assign<DynamicValueWriteProxy, &ValueUnion::asWriteProxy>(writeProxy);
|
|
}
|
|
}
|
|
|
|
bool DynamicValue::roundToInt(int32 &outInt) const {
|
|
if (_type == DynamicValueTypes::kInteger) {
|
|
outInt = _value.asInt;
|
|
return true;
|
|
} else if (_type == DynamicValueTypes::kFloat) {
|
|
outInt = static_cast<int32>(floor(_value.asFloat + 0.5));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DynamicValue::convertToType(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
if (_type == DynamicValueTypes::kObject && targetType != DynamicValueTypes::kObject) {
|
|
RuntimeObject *obj = this->_value.asObj.object.lock().get();
|
|
if (obj && obj->isModifier() && static_cast<Modifier *>(obj)->isVariable()) {
|
|
DynamicValue varContents;
|
|
static_cast<VariableModifier *>(obj)->varGetValue(varContents);
|
|
return varContents.convertToTypeNoDereference(targetType, result);
|
|
}
|
|
}
|
|
|
|
if (_type == DynamicValueTypes::kList && targetType != DynamicValueTypes::kList) {
|
|
if (_value.asList.get() && _value.asList->getSize() == 1) {
|
|
// Single-value lists are convertible
|
|
DynamicValue firstListElement;
|
|
(void)_value.asList->getAtIndex(0, firstListElement);
|
|
|
|
return firstListElement.convertToType(targetType, result);
|
|
}
|
|
}
|
|
|
|
return convertToTypeNoDereference(targetType, result);
|
|
}
|
|
|
|
DynamicValue DynamicValue::dereference() const {
|
|
if (_type == DynamicValueTypes::kObject) {
|
|
RuntimeObject *obj = this->_value.asObj.object.lock().get();
|
|
if (obj && obj->isModifier() && static_cast<Modifier *>(obj)->isVariable()) {
|
|
DynamicValue varContents;
|
|
static_cast<VariableModifier *>(obj)->varGetValue(varContents);
|
|
return varContents;
|
|
}
|
|
}
|
|
|
|
if (_type == DynamicValueTypes::kList) {
|
|
if (_value.asList.get() && _value.asList->getSize() == 1) {
|
|
// Single-value lists are convertible
|
|
DynamicValue firstListElement;
|
|
(void)_value.asList->getAtIndex(0, firstListElement);
|
|
|
|
return firstListElement;
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool DynamicValue::convertToTypeNoDereference(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
if (_type == targetType) {
|
|
result = *this;
|
|
return true;
|
|
}
|
|
|
|
switch (_type) {
|
|
case DynamicValueTypes::kNull:
|
|
if (targetType == DynamicValueTypes::kObject) {
|
|
result.setObject(Common::WeakPtr<RuntimeObject>());
|
|
return true;
|
|
}
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
return convertIntToType(targetType, result);
|
|
case DynamicValueTypes::kFloat:
|
|
return convertFloatToType(targetType, result);
|
|
case DynamicValueTypes::kBoolean:
|
|
return convertBoolToType(targetType, result);
|
|
case DynamicValueTypes::kString:
|
|
return convertStringToType(targetType, result);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
warning("Couldn't convert dynamic value from source type");
|
|
return false;
|
|
}
|
|
|
|
void DynamicValue::setObject(const ObjectReference &value) {
|
|
if (_type != DynamicValueTypes::kObject) {
|
|
clear();
|
|
_type = DynamicValueTypes::kObject;
|
|
_value.construct<ObjectReference, &ValueUnion::asObj>(value);
|
|
} else {
|
|
_value.assign<ObjectReference, &ValueUnion::asObj>(value);
|
|
}
|
|
}
|
|
|
|
void DynamicValue::setObject(const Common::WeakPtr<RuntimeObject> &value) {
|
|
setObject(ObjectReference(value));
|
|
}
|
|
|
|
DynamicValue &DynamicValue::operator=(const DynamicValue &other) {
|
|
setFromOther(other);
|
|
return *this;
|
|
}
|
|
|
|
bool DynamicValue::operator==(const DynamicValue &other) const {
|
|
if (this == &other)
|
|
return true;
|
|
|
|
if (_type != other._type)
|
|
return false;
|
|
|
|
switch (_type) {
|
|
case DynamicValueTypes::kNull:
|
|
return true;
|
|
case DynamicValueTypes::kInteger:
|
|
return _value.asInt == other._value.asInt;
|
|
case DynamicValueTypes::kFloat:
|
|
return _value.asFloat == other._value.asFloat;
|
|
case DynamicValueTypes::kPoint:
|
|
return _value.asPoint.x == other._value.asPoint.x && _value.asPoint.y == other._value.asPoint.y;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
return _value.asIntRange == other._value.asIntRange;
|
|
case DynamicValueTypes::kVector:
|
|
return _value.asVector == other._value.asVector;
|
|
case DynamicValueTypes::kLabel:
|
|
return _value.asLabel == other._value.asLabel;
|
|
case DynamicValueTypes::kEvent:
|
|
return _value.asEvent == other._value.asEvent;
|
|
case DynamicValueTypes::kString:
|
|
return _value.asString == other._value.asString;
|
|
case DynamicValueTypes::kBoolean:
|
|
return _value.asBool == other._value.asBool;
|
|
case DynamicValueTypes::kList:
|
|
return (*_value.asList.get()) == (*other._value.asList.get());
|
|
case DynamicValueTypes::kObject:
|
|
return _value.asObj == other._value.asObj;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
void DynamicValue::clear() {
|
|
switch (_type) {
|
|
case DynamicValueTypes::kNull:
|
|
case DynamicValueTypes::kUnspecified:
|
|
_value.destruct<uint64, &ValueUnion::asUnset>();
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
_value.destruct<int32, &ValueUnion::asInt>();
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
_value.destruct<double, &ValueUnion::asFloat>();
|
|
break;
|
|
case DynamicValueTypes::kPoint:
|
|
_value.destruct<Common::Point, &ValueUnion::asPoint>();
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
_value.destruct<IntRange, &ValueUnion::asIntRange>();
|
|
break;
|
|
case DynamicValueTypes::kBoolean:
|
|
_value.destruct<bool, &ValueUnion::asBool>();
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
_value.destruct<AngleMagVector, &ValueUnion::asVector>();
|
|
break;
|
|
case DynamicValueTypes::kLabel:
|
|
_value.destruct<Label, &ValueUnion::asLabel>();
|
|
break;
|
|
case DynamicValueTypes::kEvent:
|
|
_value.destruct<Event, &ValueUnion::asEvent>();
|
|
break;
|
|
case DynamicValueTypes::kString:
|
|
_value.destruct<Common::String, &ValueUnion::asString>();
|
|
break;
|
|
case DynamicValueTypes::kList:
|
|
_value.destruct<Common::SharedPtr<DynamicList>, &ValueUnion::asList>();
|
|
break;
|
|
case DynamicValueTypes::kObject:
|
|
_value.destruct<ObjectReference, &ValueUnion::asObj>();
|
|
break;
|
|
case DynamicValueTypes::kWriteProxy:
|
|
_value.destruct<DynamicValueWriteProxy, &ValueUnion::asWriteProxy>();
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
};
|
|
|
|
_type = DynamicValueTypes::kNull;
|
|
}
|
|
|
|
bool DynamicValue::convertIntToType(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
int32 value = this->getInt();
|
|
|
|
switch (targetType) {
|
|
case DynamicValueTypes::kInteger:
|
|
result.setInt(value);
|
|
return true;
|
|
case DynamicValueTypes::kBoolean:
|
|
result.setBool(value != 0);
|
|
return true;
|
|
case DynamicValueTypes::kFloat:
|
|
result.setFloat(value);
|
|
return true;
|
|
default:
|
|
warning("Unable to implicitly convert dynamic value");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool DynamicValue::convertFloatToType(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
double value = this->getFloat();
|
|
|
|
switch (targetType) {
|
|
case DynamicValueTypes::kInteger:
|
|
result.setInt(static_cast<int32>(round(value)));
|
|
return true;
|
|
case DynamicValueTypes::kBoolean:
|
|
result.setBool(value != 0.0);
|
|
return true;
|
|
case DynamicValueTypes::kFloat:
|
|
result.setFloat(value);
|
|
return true;
|
|
default:
|
|
warning("Unable to implicitly convert dynamic value");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool DynamicValue::convertBoolToType(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
bool value = this->getBool();
|
|
|
|
switch (targetType) {
|
|
case DynamicValueTypes::kInteger:
|
|
result.setInt(value ? 1 : 0);
|
|
return true;
|
|
case DynamicValueTypes::kBoolean:
|
|
result.setBool(value);
|
|
return true;
|
|
case DynamicValueTypes::kFloat:
|
|
result.setFloat(value ? 1.0 : 0.0);
|
|
return true;
|
|
default:
|
|
warning("Unable to implicitly convert dynamic value");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool DynamicValue::convertStringToType(DynamicValueTypes::DynamicValueType targetType, DynamicValue &result) const {
|
|
const Common::String &value = this->getString();
|
|
|
|
// Docs say that strings are always false but they are not convertible
|
|
switch (targetType) {
|
|
case DynamicValueTypes::kInteger: {
|
|
// Passing float values is allowed, but they are truncated toward zero instead of rounded
|
|
double floatValue = 0;
|
|
if (sscanf(value.c_str(), "%lf", &floatValue))
|
|
result.setInt(static_cast<int>(floatValue));
|
|
else
|
|
result.setInt(0);
|
|
return true;
|
|
} break;
|
|
case DynamicValueTypes::kFloat: {
|
|
double floatValue = 0;
|
|
if (sscanf(value.c_str(), "%lf", &floatValue))
|
|
result.setFloat(floatValue);
|
|
else
|
|
result.setFloat(0.0);
|
|
return true;
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
warning("Unable to implicitly convert dynamic value");
|
|
return false;
|
|
}
|
|
|
|
void DynamicValue::setFromOther(const DynamicValue &other) {
|
|
if (this == &other)
|
|
return;
|
|
|
|
// Keep the list alive until the end of this in case the other value is contained inside of this one
|
|
Common::SharedPtr<DynamicList> listHolder;
|
|
if (_type == DynamicValueTypes::kList)
|
|
listHolder = _value.asList;
|
|
|
|
switch (other._type) {
|
|
case DynamicValueTypes::kNull:
|
|
case DynamicValueTypes::kUnspecified:
|
|
clear();
|
|
_type = other._type;
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
setInt(other._value.asInt);
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
setFloat(other._value.asFloat);
|
|
break;
|
|
case DynamicValueTypes::kPoint:
|
|
setPoint(other._value.asPoint);
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
setIntRange(other._value.asIntRange);
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
setVector(other._value.asVector);
|
|
break;
|
|
case DynamicValueTypes::kLabel:
|
|
setLabel(other._value.asLabel);
|
|
break;
|
|
case DynamicValueTypes::kEvent:
|
|
setEvent(other._value.asEvent);
|
|
break;
|
|
case DynamicValueTypes::kString:
|
|
setString(other._value.asString);
|
|
break;
|
|
case DynamicValueTypes::kBoolean:
|
|
setBool(other._value.asBool);
|
|
break;
|
|
case DynamicValueTypes::kList:
|
|
setList(other._value.asList);
|
|
break;
|
|
case DynamicValueTypes::kObject:
|
|
setObject(other._value.asObj);
|
|
break;
|
|
case DynamicValueTypes::kWriteProxy:
|
|
setWriteProxy(other._value.asWriteProxy);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
assert(_type == other._type);
|
|
}
|
|
|
|
|
|
DynamicValueSource::DynamicValueSource() : _sourceType(DynamicValueSourceTypes::kInvalid) {
|
|
}
|
|
|
|
DynamicValueSource::DynamicValueSource(const DynamicValueSource &other) : _sourceType(DynamicValueSourceTypes::kInvalid) {
|
|
initFromOther(other);
|
|
}
|
|
|
|
DynamicValueSource::DynamicValueSource(DynamicValueSource &&other) : _sourceType(DynamicValueSourceTypes::kInvalid) {
|
|
initFromOther(static_cast<DynamicValueSource &&>(other));
|
|
}
|
|
|
|
DynamicValueSource::~DynamicValueSource() {
|
|
destructValue();
|
|
}
|
|
|
|
DynamicValueSource &DynamicValueSource::operator=(const DynamicValueSource &other) {
|
|
if (this == &other)
|
|
return *this;
|
|
|
|
destructValue();
|
|
initFromOther(other);
|
|
return *this;
|
|
}
|
|
|
|
DynamicValueSource &DynamicValueSource::operator=(DynamicValueSource &&other) {
|
|
if (this == &other)
|
|
return *this;
|
|
|
|
destructValue();
|
|
initFromOther(static_cast<DynamicValueSource &&>(other));
|
|
return *this;
|
|
}
|
|
|
|
DynamicValueSourceTypes::DynamicValueSourceType DynamicValueSource::getSourceType() const {
|
|
return _sourceType;
|
|
}
|
|
|
|
const DynamicValue &DynamicValueSource::getConstant() const {
|
|
assert(_sourceType == DynamicValueSourceTypes::kConstant);
|
|
return _valueUnion._constValue;
|
|
}
|
|
const VarReference &DynamicValueSource::getVarReference() const {
|
|
assert(_sourceType == DynamicValueSourceTypes::kVariableReference);
|
|
return _valueUnion._varReference;
|
|
}
|
|
|
|
bool DynamicValueSource::load(const Data::InternalTypeTaggedValue &data, const Common::String &varSource, const Common::String &varString) {
|
|
destructValue();
|
|
|
|
switch (data.type)
|
|
{
|
|
case Data::InternalTypeTaggedValue::kIncomingData:
|
|
_sourceType = DynamicValueSourceTypes::kIncomingData;
|
|
return true;
|
|
case Data::InternalTypeTaggedValue::kVariableReference:
|
|
_sourceType = DynamicValueSourceTypes::kVariableReference;
|
|
new (&_valueUnion._varReference) VarReference(data.value.asVariableReference.guid, varSource);
|
|
return true;
|
|
default:
|
|
_sourceType = DynamicValueSourceTypes::kConstant;
|
|
new (&_valueUnion._constValue) DynamicValue();
|
|
return _valueUnion._constValue.loadConstant(data, varString);
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
bool DynamicValueSource::load(const Data::PlugInTypeTaggedValue &data) {
|
|
destructValue();
|
|
|
|
switch (data.type) {
|
|
case Data::PlugInTypeTaggedValue::kIncomingData:
|
|
_sourceType = DynamicValueSourceTypes::kIncomingData;
|
|
return true;
|
|
case Data::PlugInTypeTaggedValue::kVariableReference:
|
|
_sourceType = DynamicValueSourceTypes::kVariableReference;
|
|
new (&_valueUnion._varReference) VarReference(data.value.asVarRefGUID, "");
|
|
return true;
|
|
default:
|
|
_sourceType = DynamicValueSourceTypes::kConstant;
|
|
new (&_valueUnion._constValue) DynamicValue();
|
|
return _valueUnion._constValue.loadConstant(data);
|
|
}
|
|
}
|
|
|
|
|
|
void DynamicValueSource::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
if (_sourceType == DynamicValueSourceTypes::kVariableReference) {
|
|
_valueUnion._varReference.linkInternalReferences(scope);
|
|
}
|
|
}
|
|
|
|
void DynamicValueSource::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
if (_sourceType == DynamicValueSourceTypes::kVariableReference) {
|
|
_valueUnion._varReference.visitInternalReferences(visitor);
|
|
}
|
|
}
|
|
|
|
DynamicValue DynamicValueSource::produceValue(const DynamicValue &incomingData) const {
|
|
switch (_sourceType) {
|
|
case DynamicValueSourceTypes::kConstant:
|
|
return _valueUnion._constValue;
|
|
case DynamicValueSourceTypes::kIncomingData:
|
|
return incomingData;
|
|
case DynamicValueSourceTypes::kVariableReference: {
|
|
DynamicValue result;
|
|
result.setObject(_valueUnion._varReference.resolution);
|
|
return result;
|
|
} break;
|
|
default:
|
|
warning("Dynamic value couldn't be resolved");
|
|
return DynamicValue();
|
|
}
|
|
}
|
|
|
|
|
|
DynamicValueSource::ValueUnion::ValueUnion() {
|
|
}
|
|
|
|
DynamicValueSource::ValueUnion::~ValueUnion() {
|
|
}
|
|
|
|
void DynamicValueSource::destructValue() {
|
|
switch (_sourceType) {
|
|
case DynamicValueSourceTypes::kConstant:
|
|
_valueUnion._constValue.~DynamicValue();
|
|
break;
|
|
case DynamicValueSourceTypes::kVariableReference:
|
|
_valueUnion._varReference.~VarReference();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_sourceType = DynamicValueSourceTypes::kInvalid;
|
|
}
|
|
|
|
void DynamicValueSource::initFromOther(const DynamicValueSource &other) {
|
|
assert(_sourceType == DynamicValueSourceTypes::kInvalid);
|
|
|
|
switch (other._sourceType) {
|
|
case DynamicValueSourceTypes::kConstant:
|
|
new (&_valueUnion._constValue) DynamicValue(other._valueUnion._constValue);
|
|
break;
|
|
case DynamicValueSourceTypes::kVariableReference:
|
|
new (&_valueUnion._varReference) VarReference(other._valueUnion._varReference);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_sourceType = other._sourceType;
|
|
}
|
|
|
|
void DynamicValueSource::initFromOther(DynamicValueSource &&other) {
|
|
assert(_sourceType == DynamicValueSourceTypes::kInvalid);
|
|
|
|
switch (other._sourceType) {
|
|
case DynamicValueSourceTypes::kConstant:
|
|
new (&_valueUnion._constValue) DynamicValue(static_cast<DynamicValue &&>(other._valueUnion._constValue));
|
|
break;
|
|
case DynamicValueSourceTypes::kVariableReference:
|
|
new (&_valueUnion._varReference) VarReference(static_cast<VarReference &&>(other._valueUnion._varReference));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
_sourceType = other._sourceType;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
DynamicValue derefValue = value.dereference();
|
|
|
|
Common::String &dest = *static_cast<Common::String *>(objectRef);
|
|
switch (derefValue.getType()) {
|
|
case DynamicValueTypes::kString:
|
|
dest = derefValue.getString();
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void DynamicValueWriteStringHelper::create(Common::String *strValue, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.objectRef = strValue;
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<DynamicValueWriteStringHelper>::getInstance();
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteDiscardHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteDiscardHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteDiscardHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void DynamicValueWriteDiscardHelper::create(DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.objectRef = nullptr;
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<DynamicValueWriteDiscardHelper>::getInstance();
|
|
}
|
|
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWritePointHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
DynamicValue derefValue = value.dereference();
|
|
|
|
if (value.getType() != DynamicValueTypes::kPoint) {
|
|
thread->error("Can't set point to invalid type");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
*static_cast<Common::Point *>(objectRef) = value.getPoint();
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWritePointHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&static_cast<Common::Point *>(objectRef)->x, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "y") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&static_cast<Common::Point *>(objectRef)->y, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
thread->error("Invalid attribute for point");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWritePointHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void DynamicValueWritePointHelper::create(Common::Point *pointValue, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.objectRef = pointValue;
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<DynamicValueWritePointHelper>::getInstance();
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteBoolHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
DynamicValue derefValue = value.dereference();
|
|
|
|
bool &dest = *static_cast<bool *>(objectRef);
|
|
switch (derefValue.getType()) {
|
|
case DynamicValueTypes::kBoolean:
|
|
dest = derefValue.getBool();
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteBoolHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteBoolHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void DynamicValueWriteBoolHelper::create(bool *boolValue, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.objectRef = boolValue;
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<DynamicValueWriteBoolHelper>::getInstance();
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
|
|
RuntimeObject *obj = static_cast<RuntimeObject *>(objectRef);
|
|
if (obj->isModifier() && static_cast<Modifier *>(obj)->isVariable()) {
|
|
VariableModifier *var = static_cast<VariableModifier *>(obj);
|
|
if (var->varSetValue(thread, value))
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
else {
|
|
thread->error("Failed to assign value to variable");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
thread->error("Can't write to read-only object value");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
return static_cast<RuntimeObject *>(objectRef)->writeRefAttribute(thread, proxy, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return static_cast<RuntimeObject *>(objectRef)->writeRefAttributeIndexed(thread, proxy, attrib, index);
|
|
}
|
|
|
|
void DynamicValueWriteObjectHelper::create(RuntimeObject *obj, DynamicValueWriteProxy &proxy) {
|
|
proxy.containerList.reset(); // Object references are always anchored while threads are running, so don't need to preserve the container
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<DynamicValueWriteObjectHelper>::getInstance();
|
|
proxy.pod.objectRef = obj;
|
|
proxy.pod.ptrOrOffset = 0;
|
|
}
|
|
|
|
MessengerSendSpec::MessengerSendSpec() : destination(kMessageDestNone), _linkType(kLinkTypeNotYetLinked) {
|
|
}
|
|
|
|
bool MessengerSendSpec::load(const Data::Event &dataEvent, uint32 dataMessageFlags, const Data::InternalTypeTaggedValue &dataLocator, const Common::String &dataWithSource, const Common::String &dataWithString, uint32 dataDestination) {
|
|
messageFlags.relay = ((dataMessageFlags & 0x20000000) == 0);
|
|
messageFlags.cascade = ((dataMessageFlags & 0x40000000) == 0);
|
|
messageFlags.immediate = ((dataMessageFlags & 0x80000000) == 0);
|
|
|
|
if (!this->send.load(dataEvent))
|
|
return false;
|
|
|
|
if (!this->with.load(dataLocator, dataWithSource, dataWithString))
|
|
return false;
|
|
|
|
this->destination = dataDestination;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MessengerSendSpec::load(const Data::PlugInTypeTaggedValue &dataEvent, const MessageFlags &dataMessageFlags, const Data::PlugInTypeTaggedValue &dataWith, uint32 dataDestination) {
|
|
if (dataEvent.type != Data::PlugInTypeTaggedValue::kEvent)
|
|
return false;
|
|
|
|
if (!this->send.load(dataEvent.value.asEvent))
|
|
return false;
|
|
|
|
if (!this->with.load(dataWith))
|
|
return false;
|
|
|
|
this->destination = dataDestination;
|
|
|
|
return true;
|
|
}
|
|
|
|
void MessengerSendSpec::linkInternalReferences(ObjectLinkingScope *outerScope) {
|
|
switch (destination) {
|
|
case kMessageDestNone:
|
|
case kMessageDestSharedScene:
|
|
case kMessageDestScene:
|
|
case kMessageDestSection:
|
|
case kMessageDestProject:
|
|
case kMessageDestActiveScene:
|
|
case kMessageDestElementsParent:
|
|
case kMessageDestChildren:
|
|
case kMessageDestModifiersParent:
|
|
case kMessageDestSubsection:
|
|
case kMessageDestElement:
|
|
case kMessageDestSourcesParent:
|
|
case kMessageDestBehavior:
|
|
case kMessageDestNextElement:
|
|
case kMessageDestPrevElement:
|
|
case kMessageDestBehaviorsParent:
|
|
_linkType = kLinkTypeCoded;
|
|
break;
|
|
default: {
|
|
Common::SharedPtr<RuntimeObject> resolution = outerScope->resolve(destination).lock();
|
|
if (resolution) {
|
|
if (resolution->isModifier()) {
|
|
_resolvedModifierDest = resolution.staticCast<Modifier>();
|
|
_linkType = kLinkTypeModifier;
|
|
} else if (resolution->isStructural()) {
|
|
_resolvedStructuralDest = resolution.staticCast<Structural>();
|
|
_linkType = kLinkTypeStructural;
|
|
} else {
|
|
_linkType = kLinkTypeUnresolved;
|
|
}
|
|
} else {
|
|
_linkType = kLinkTypeUnresolved;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
with.linkInternalReferences(outerScope);
|
|
}
|
|
|
|
void MessengerSendSpec::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
visitor->visitWeakModifierRef(_resolvedModifierDest);
|
|
visitor->visitWeakStructuralRef(_resolvedStructuralDest);
|
|
visitor->visitWeakModifierRef(_resolvedVarSource);
|
|
}
|
|
|
|
void MessengerSendSpec::resolveDestination(Runtime *runtime, Modifier *sender, RuntimeObject *triggerSource, Common::WeakPtr<Structural> &outStructuralDest, Common::WeakPtr<Modifier> &outModifierDest, RuntimeObject *customDestination) const {
|
|
outStructuralDest.reset();
|
|
outModifierDest.reset();
|
|
|
|
if (customDestination) {
|
|
if (customDestination->isStructural())
|
|
outStructuralDest = customDestination->getSelfReference().staticCast<Structural>();
|
|
else if (customDestination->isModifier())
|
|
outModifierDest = customDestination->getSelfReference().staticCast<Modifier>();
|
|
else
|
|
error("Custom destination was invalid");
|
|
|
|
return;
|
|
}
|
|
|
|
if (_linkType == kLinkTypeModifier) {
|
|
outModifierDest = _resolvedModifierDest;
|
|
} else if (_linkType == kLinkTypeStructural) {
|
|
outStructuralDest = _resolvedStructuralDest;
|
|
} else if (_linkType == kLinkTypeCoded) {
|
|
switch (destination) {
|
|
case kMessageDestNone:
|
|
break;
|
|
case kMessageDestSharedScene:
|
|
outStructuralDest = runtime->getActiveSharedScene();
|
|
break;
|
|
case kMessageDestScene:
|
|
resolveHierarchyStructuralDestination(runtime, sender, outStructuralDest, outModifierDest, isSceneFilter);
|
|
break;
|
|
case kMessageDestSection:
|
|
resolveHierarchyStructuralDestination(runtime, sender, outStructuralDest, outModifierDest, isSectionFilter);
|
|
break;
|
|
case kMessageDestProject:
|
|
outStructuralDest = runtime->getProject()->getSelfReference().staticCast<Project>();
|
|
break;
|
|
case kMessageDestActiveScene:
|
|
outStructuralDest = runtime->getActiveMainScene();
|
|
break;
|
|
case kMessageDestElement:
|
|
resolveHierarchyStructuralDestination(runtime, sender, outStructuralDest, outModifierDest, isElementFilter);
|
|
break;
|
|
case kMessageDestModifiersParent:
|
|
resolveVariableObjectType(sender->getParent().lock().get(), outStructuralDest, outModifierDest);
|
|
break;
|
|
case kMessageDestElementsParent:
|
|
resolveHierarchyStructuralDestination(runtime, sender, outStructuralDest, outModifierDest, isElementFilter);
|
|
if (!outStructuralDest.expired())
|
|
outStructuralDest = outStructuralDest.lock()->getParent()->getSelfReference().staticCast<Structural>();
|
|
break;
|
|
case kMessageDestNextElement:
|
|
case kMessageDestPrevElement: {
|
|
Common::WeakPtr<Structural> elementWeak;
|
|
Common::WeakPtr<Modifier> modifier;
|
|
resolveHierarchyStructuralDestination(runtime, sender, elementWeak, modifier, isElementFilter);
|
|
|
|
Common::SharedPtr<Structural> sibling;
|
|
Common::SharedPtr<Structural> element = elementWeak.lock();
|
|
if (element) {
|
|
Structural *parent = element->getParent();
|
|
if (parent) {
|
|
const Common::Array<Common::SharedPtr<Structural> > &siblings = parent->getChildren();
|
|
for (size_t i = 0; i < siblings.size(); i++) {
|
|
if (siblings[i] == element) {
|
|
if (destination == kMessageDestPrevElement) {
|
|
if (i != 0)
|
|
sibling = siblings[i - 1];
|
|
} else if (destination == kMessageDestNextElement) {
|
|
if (i != siblings.size() - 1)
|
|
sibling = siblings[i + 1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sibling)
|
|
outStructuralDest = sibling;
|
|
} break;
|
|
case kMessageDestSourcesParent: {
|
|
// This sends to the exact parent, e.g. if the source is inside of a behavior, then it sends it to the behavior.
|
|
if (triggerSource) {
|
|
RuntimeObject *parentObj = nullptr;
|
|
if (triggerSource->isModifier())
|
|
parentObj = static_cast<Modifier *>(triggerSource)->getParent().lock().get();
|
|
else if (triggerSource->isStructural())
|
|
parentObj = static_cast<Structural *>(triggerSource)->getParent();
|
|
|
|
if (parentObj) {
|
|
if (parentObj->isModifier())
|
|
outModifierDest = parentObj->getSelfReference().staticCast<Modifier>();
|
|
else if (parentObj->isStructural())
|
|
outStructuralDest = parentObj->getSelfReference().staticCast<Structural>();
|
|
}
|
|
}
|
|
} break;
|
|
case kMessageDestChildren:
|
|
case kMessageDestSubsection:
|
|
case kMessageDestBehavior:
|
|
case kMessageDestBehaviorsParent:
|
|
warning("Not-yet-implemented message destination type");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (_linkType == kLinkTypeNotYetLinked) {
|
|
error("Messenger wasn't linked, programmer probably forgot to call linkInternalReferences on the send spec");
|
|
}
|
|
}
|
|
|
|
void MessengerSendSpec::resolveVariableObjectType(RuntimeObject *obj, Common::WeakPtr<Structural> &outStructuralDest, Common::WeakPtr<Modifier> &outModifierDest) {
|
|
if (!obj) {
|
|
warning("Couldn't resolve messenger destination");
|
|
return;
|
|
}
|
|
|
|
if (obj->isStructural())
|
|
outStructuralDest = obj->getSelfReference().staticCast<Structural>();
|
|
else if (obj->isModifier())
|
|
outModifierDest = obj->getSelfReference().staticCast<Modifier>();
|
|
else {
|
|
warning("Messenger destination was not a valid recipient type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void MessengerSendSpec::sendFromMessenger(Runtime *runtime, Modifier *sender, RuntimeObject *triggerSource, const DynamicValue &incomingData, RuntimeObject *customDestination) const {
|
|
sendFromMessengerWithCustomData(runtime, sender, triggerSource, this->with.produceValue(incomingData), customDestination);
|
|
}
|
|
|
|
void MessengerSendSpec::sendFromMessengerWithCustomData(Runtime *runtime, Modifier *sender, RuntimeObject *triggerSource, const DynamicValue &data, RuntimeObject *customDestination) const {
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(this->send, data, sender->getSelfReference()));
|
|
|
|
Common::WeakPtr<Modifier> modifierDestRef;
|
|
Common::WeakPtr<Structural> structuralDestRef;
|
|
|
|
resolveDestination(runtime, sender, triggerSource, structuralDestRef, modifierDestRef, customDestination);
|
|
|
|
Common::SharedPtr<Modifier> modifierDest = modifierDestRef.lock();
|
|
Common::SharedPtr<Structural> structuralDest = structuralDestRef.lock();
|
|
|
|
Common::SharedPtr<MessageDispatch> dispatch;
|
|
if (structuralDest)
|
|
dispatch.reset(new MessageDispatch(props, structuralDest.get(), messageFlags.cascade, messageFlags.relay, true));
|
|
else if (modifierDest)
|
|
dispatch.reset(new MessageDispatch(props, modifierDest.get(), messageFlags.cascade, messageFlags.relay, true));
|
|
|
|
if (dispatch) {
|
|
if (messageFlags.immediate)
|
|
runtime->sendMessageOnVThread(dispatch);
|
|
else
|
|
runtime->queueMessage(dispatch);
|
|
}
|
|
}
|
|
|
|
void MessengerSendSpec::resolveHierarchyStructuralDestination(Runtime *runtime, Modifier *sender, Common::WeakPtr<Structural> &outStructuralDest, Common::WeakPtr<Modifier> &outModifierDest, bool (*compareFunc)(Structural *structural)) const {
|
|
RuntimeObject *obj = sender->getParent().lock().get();
|
|
while (obj) {
|
|
if (obj->isStructural()) {
|
|
Structural *structural = static_cast<Structural *>(obj);
|
|
if (compareFunc(structural)) {
|
|
outStructuralDest = structural->getSelfReference().staticCast<Structural>();
|
|
return;
|
|
}
|
|
|
|
obj = structural->getParent();
|
|
} else if (obj->isModifier()) {
|
|
Modifier *modifier = static_cast<Modifier *>(obj);
|
|
obj = modifier->getParent().lock().get();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MessengerSendSpec::isSceneFilter(Structural *structural) {
|
|
Structural *parent = structural->getParent();
|
|
return parent != nullptr && parent->isSubsection();
|
|
}
|
|
|
|
bool MessengerSendSpec::isSectionFilter(Structural *structural) {
|
|
return structural->isSection();
|
|
}
|
|
|
|
bool MessengerSendSpec::isSubsectionFilter(Structural *structural) {
|
|
return structural->isSubsection();
|
|
}
|
|
|
|
bool MessengerSendSpec::isElementFilter(Structural *structural) {
|
|
return structural->isElement();
|
|
}
|
|
|
|
Event::Event() : eventType(EventIDs::kNothing), eventInfo(0) {
|
|
}
|
|
|
|
Event::Event(EventIDs::EventID peventType, uint32 peventInfo) : eventType(peventType), eventInfo(peventInfo) {
|
|
}
|
|
|
|
bool Event::respondsTo(const Event &otherEvent) const {
|
|
return (*this) == otherEvent;
|
|
}
|
|
|
|
bool Event::load(const Data::Event &data) {
|
|
eventType = static_cast<EventIDs::EventID>(data.eventID);
|
|
eventInfo = data.eventInfo;
|
|
|
|
return true;
|
|
}
|
|
|
|
VarReference::VarReference() : guid(0) {
|
|
}
|
|
|
|
VarReference::VarReference(uint32 pguid, const Common::String &psource) : guid(pguid), source(psource) {
|
|
}
|
|
|
|
bool VarReference::resolve(Structural *structuralScope, Common::WeakPtr<RuntimeObject> &outObject) const {
|
|
if (resolveContainer(structuralScope, outObject))
|
|
return true;
|
|
|
|
Structural *parent = structuralScope->getParent();
|
|
if (!parent)
|
|
return false;
|
|
|
|
return resolve(parent, outObject);
|
|
}
|
|
|
|
bool VarReference::resolve(Modifier *modifierScope, Common::WeakPtr<RuntimeObject> &outObject) const {
|
|
if (resolveSingleModifier(modifierScope, outObject))
|
|
return true;
|
|
|
|
RuntimeObject *parent = modifierScope->getParent().lock().get();
|
|
if (parent->isStructural())
|
|
return resolve(static_cast<Structural *>(parent), outObject);
|
|
|
|
if (parent->isModifier()) {
|
|
Modifier *parentModifier = static_cast<Modifier *>(parent);
|
|
IModifierContainer *parentContainer = parentModifier->getChildContainer();
|
|
if (parentContainer && resolveContainer(parentContainer, outObject))
|
|
return true;
|
|
|
|
return resolve(parentModifier, outObject);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void VarReference::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
if (guid) {
|
|
Common::WeakPtr<RuntimeObject> obj = scope->resolve(guid, source, false);
|
|
if (obj.expired()) {
|
|
warning("VarReference to '%s' failed to resolve a valid object", source.c_str());
|
|
} else {
|
|
Common::SharedPtr<RuntimeObject> objShr = obj.lock();
|
|
if (objShr->isModifier())
|
|
this->resolution = obj.staticCast<Modifier>();
|
|
else
|
|
warning("VarReference to '%s' wasn't a modifier", source.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void VarReference::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
visitor->visitWeakModifierRef(this->resolution);
|
|
}
|
|
|
|
bool VarReference::resolveContainer(IModifierContainer *modifierContainer, Common::WeakPtr<RuntimeObject> &outObject) const {
|
|
for (const Common::SharedPtr<Modifier> &modifier : modifierContainer->getModifiers())
|
|
if (resolveSingleModifier(modifier.get(), outObject))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool VarReference::resolveSingleModifier(Modifier *modifier, Common::WeakPtr<RuntimeObject> &outObject) const {
|
|
if (modifier->getStaticGUID() == guid || (source.size() > 0 && caseInsensitiveEqual(modifier->getName(), source))) {
|
|
outObject = modifier->getSelfReference();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AngleMagVector::AngleMagVector() : angleDegrees(0), magnitude(0) {
|
|
}
|
|
|
|
AngleMagVector::AngleMagVector(double pangleDegrees, double pmagnitude) : angleDegrees(pangleDegrees), magnitude(pmagnitude) {
|
|
}
|
|
|
|
MiniscriptInstructionOutcome AngleMagVector::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "angle") {
|
|
DynamicValueWriteFloatHelper<double>::create(&angleDegrees, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "magnitude") {
|
|
DynamicValueWriteFloatHelper<double>::create(&magnitude, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
Common::String AngleMagVector::toString() const {
|
|
return Common::String::format("(%g deg %g mag)", angleDegrees, magnitude);
|
|
}
|
|
|
|
|
|
|
|
SegmentDescription::SegmentDescription() : volumeID(0), stream(nullptr) {
|
|
}
|
|
|
|
void IPlugInModifierRegistrar::registerPlugInModifier(const char *name, const IPlugInModifierFactoryAndDataFactory *loaderFactory) {
|
|
return this->registerPlugInModifier(name, loaderFactory, loaderFactory);
|
|
}
|
|
|
|
PlugIn::~PlugIn() {
|
|
}
|
|
|
|
ProjectPersistentResource::~ProjectPersistentResource() {
|
|
}
|
|
|
|
ProjectResources::~ProjectResources() {
|
|
// We need these destroyed in reverse order exactly, and unfortunately the ScummVM Common::Array destructor
|
|
// destroys forward
|
|
while (persistentResources.size() > 0)
|
|
persistentResources.pop_back();
|
|
}
|
|
|
|
CursorGraphic::~CursorGraphic() {
|
|
}
|
|
|
|
MacCursorGraphic::MacCursorGraphic(const Common::SharedPtr<Graphics::MacCursor>& macCursor) : _macCursor(macCursor) {
|
|
}
|
|
|
|
Graphics::Cursor *MacCursorGraphic::getCursor() const {
|
|
return _macCursor.get();
|
|
}
|
|
|
|
WinCursorGraphic::WinCursorGraphic(const Common::SharedPtr<Graphics::WinCursorGroup> &winCursorGroup, Graphics::Cursor *cursor) : _winCursorGroup(winCursorGroup), _cursor(cursor) {
|
|
}
|
|
|
|
Graphics::Cursor *WinCursorGraphic::getCursor() const {
|
|
return _cursor;
|
|
}
|
|
|
|
CursorGraphicCollection::CursorGraphicCollection() {
|
|
}
|
|
|
|
CursorGraphicCollection::~CursorGraphicCollection() {
|
|
}
|
|
|
|
void CursorGraphicCollection::addWinCursorGroup(uint32 cursorGroupID, const Common::SharedPtr<Graphics::WinCursorGroup> &cursorGroup) {
|
|
Graphics::Cursor *selectedCursor = nullptr;
|
|
if (cursorGroup->cursors.size() > 0) {
|
|
// Not sure what the proper logic should be here, but the second one seems to be the one we usually want
|
|
if (cursorGroup->cursors.size() > 1)
|
|
selectedCursor = cursorGroup->cursors[1].cursor;
|
|
else
|
|
selectedCursor = cursorGroup->cursors.back().cursor;
|
|
_cursorGraphics[cursorGroupID].reset(new WinCursorGraphic(cursorGroup, selectedCursor));
|
|
}
|
|
}
|
|
|
|
void CursorGraphicCollection::addMacCursor(uint32 cursorID, const Common::SharedPtr<Graphics::MacCursor> &cursor) {
|
|
_cursorGraphics[cursorID].reset(new MacCursorGraphic(cursor));
|
|
}
|
|
|
|
Common::SharedPtr<CursorGraphic> CursorGraphicCollection::getGraphicByID(uint32 id) const {
|
|
Common::HashMap<uint32, Common::SharedPtr<CursorGraphic> >::const_iterator it = _cursorGraphics.find(id);
|
|
if (it != _cursorGraphics.end())
|
|
return it->_value;
|
|
return nullptr;
|
|
}
|
|
|
|
ProjectDescription::ProjectDescription(ProjectPlatform platform, RuntimeVersion runtimeVersion, bool autoDetectVersion, Common::Archive *rootArchive, const Common::Path &projectRootDir)
|
|
: _language(Common::EN_ANY), _platform(platform), _rootArchive(rootArchive), _projectRootDir(projectRootDir), _runtimeVersion(runtimeVersion), _isRuntimeVersionAuto(autoDetectVersion) {
|
|
}
|
|
|
|
ProjectDescription::~ProjectDescription() {
|
|
}
|
|
|
|
void ProjectDescription::addSegment(int volumeID, const char *filePath) {
|
|
SegmentDescription desc;
|
|
desc.volumeID = volumeID;
|
|
desc.filePath = filePath;
|
|
desc.stream = nullptr;
|
|
|
|
_segments.push_back(desc);
|
|
}
|
|
|
|
void ProjectDescription::addSegment(int volumeID, Common::SeekableReadStream *stream) {
|
|
SegmentDescription desc;
|
|
desc.volumeID = volumeID;
|
|
desc.stream = stream;
|
|
|
|
_segments.push_back(desc);
|
|
}
|
|
|
|
const Common::Array<SegmentDescription> &ProjectDescription::getSegments() const {
|
|
return _segments;
|
|
}
|
|
|
|
void ProjectDescription::addPlugIn(const Common::SharedPtr<PlugIn>& plugIn) {
|
|
_plugIns.push_back(plugIn);
|
|
}
|
|
|
|
const Common::Array<Common::SharedPtr<PlugIn> >& ProjectDescription::getPlugIns() const {
|
|
return _plugIns;
|
|
}
|
|
|
|
void ProjectDescription::setResources(const Common::SharedPtr<ProjectResources> &resources) {
|
|
_resources = resources;
|
|
}
|
|
|
|
const Common::SharedPtr<ProjectResources> &ProjectDescription::getResources() const {
|
|
return _resources;
|
|
}
|
|
|
|
void ProjectDescription::setCursorGraphics(const Common::SharedPtr<CursorGraphicCollection>& cursorGraphics) {
|
|
_cursorGraphics = cursorGraphics;
|
|
}
|
|
|
|
const Common::SharedPtr<CursorGraphicCollection> &ProjectDescription::getCursorGraphics() const {
|
|
return _cursorGraphics;
|
|
}
|
|
|
|
void ProjectDescription::setLanguage(const Common::Language &language) {
|
|
_language = language;
|
|
}
|
|
|
|
const Common::Language &ProjectDescription::getLanguage() const {
|
|
return _language;
|
|
}
|
|
|
|
ProjectPlatform ProjectDescription::getPlatform() const {
|
|
return _platform;
|
|
}
|
|
|
|
RuntimeVersion ProjectDescription::getRuntimeVersion() const {
|
|
return _runtimeVersion;
|
|
}
|
|
|
|
bool ProjectDescription::isRuntimeVersionAuto() const {
|
|
return _isRuntimeVersionAuto;
|
|
}
|
|
|
|
Common::Archive *ProjectDescription::getRootArchive() const {
|
|
return _rootArchive;
|
|
}
|
|
|
|
const Common::Path &ProjectDescription::getProjectRootDir() const {
|
|
return _projectRootDir;
|
|
}
|
|
|
|
const SubtitleTables &ProjectDescription::getSubtitles() const {
|
|
return _subtitles;
|
|
}
|
|
|
|
void ProjectDescription::setSubtitles(const SubtitleTables &subs) {
|
|
_subtitles = subs;
|
|
}
|
|
|
|
const Common::Array<Common::SharedPtr<Modifier> >& SimpleModifierContainer::getModifiers() const {
|
|
return _modifiers;
|
|
}
|
|
|
|
void SimpleModifierContainer::appendModifier(const Common::SharedPtr<Modifier> &modifier) {
|
|
_modifiers.push_back(modifier);
|
|
if (modifier)
|
|
modifier->setParent(nullptr);
|
|
}
|
|
|
|
void SimpleModifierContainer::removeModifier(const Modifier *modifier) {
|
|
for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
|
|
if (it->get() == modifier) {
|
|
_modifiers.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SimpleModifierContainer::clear() {
|
|
_modifiers.clear();
|
|
}
|
|
|
|
RuntimeObject::RuntimeObject() : _guid(0), _runtimeGUID(0) {
|
|
}
|
|
|
|
RuntimeObject::~RuntimeObject() {
|
|
}
|
|
|
|
uint32 RuntimeObject::getStaticGUID() const {
|
|
return _guid;
|
|
}
|
|
|
|
uint32 RuntimeObject::getRuntimeGUID() const {
|
|
return _runtimeGUID;
|
|
}
|
|
|
|
void RuntimeObject::setRuntimeGUID(uint32 runtimeGUID) {
|
|
_runtimeGUID = runtimeGUID;
|
|
}
|
|
|
|
void RuntimeObject::setSelfReference(const Common::WeakPtr<RuntimeObject> &selfReference) {
|
|
_selfReference = selfReference;
|
|
}
|
|
|
|
const Common::WeakPtr<RuntimeObject>& RuntimeObject::getSelfReference() const {
|
|
return _selfReference;
|
|
}
|
|
|
|
bool RuntimeObject::isStructural() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::isProject() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::isSection() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::isSubsection() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::isModifier() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::isElement() const {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
return false;
|
|
}
|
|
|
|
bool RuntimeObject::readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) {
|
|
return false;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (thread->getRuntime()->getProject()->getRuntimeVersion() < kRuntimeVersion200) {
|
|
// Per 2.0 release notes, the following attrib writes succeed without error
|
|
// on all objects
|
|
const char *sunkAttribs[] = {
|
|
"position", "width", "height", "rate", "range", "cel", "text", "volume", "timevalue",
|
|
"mastervolume", "usertimeout", "layer", "paused", "trackenable", "trackdisable",
|
|
"cache", "direct", "loop", "visible", "loopbackforth", "playeveryframe"
|
|
};
|
|
|
|
for (const char *sunkAttrib : sunkAttribs) {
|
|
if (attrib == sunkAttrib) {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
|
|
debugger->notify(kDebugSeverityWarning, Common::String::format("'%s' attribute write was discarded", sunkAttrib));
|
|
#endif
|
|
DynamicValueWriteDiscardHelper::create(writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (attrib == "clone") {
|
|
DynamicValueWriteFuncHelper<RuntimeObject, &RuntimeObject::scriptSetClone, false>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
if (attrib == "kill") {
|
|
DynamicValueWriteFuncHelper<RuntimeObject, &RuntimeObject::scriptSetKill, false>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
if (attrib == "parent") {
|
|
writeProxy.pod.ifc = DynamicValueWriteInterfaceGlue<ParentWriteProxyInterface>::getInstance();
|
|
writeProxy.pod.objectRef = this;
|
|
writeProxy.pod.ptrOrOffset = 0;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::scriptSetClone(MiniscriptThread *thread, const DynamicValue &value) {
|
|
thread->getRuntime()->queueCloneObject(this->getSelfReference());
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::scriptSetKill(MiniscriptThread *thread, const DynamicValue &value) {
|
|
thread->getRuntime()->queueKillObject(this->getSelfReference());
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::scriptSetParent(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kObject) {
|
|
thread->error("Object couldn't be re-parented to a non-object");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
thread->getRuntime()->queueChangeObjectParent(this->getSelfReference(), value.getObject().object);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
// Need special handling of "parent" property, assigns indirect the value but writes re-parent the object
|
|
MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) {
|
|
return static_cast<RuntimeObject *>(objectRef)->scriptSetParent(thread, dest);
|
|
}
|
|
|
|
RuntimeObject *RuntimeObject::ParentWriteProxyInterface::resolveObjectParent(RuntimeObject *obj) {
|
|
if (obj->isStructural())
|
|
return static_cast<Structural *>(obj)->getParent();
|
|
else if (obj->isModifier())
|
|
return static_cast<Modifier *>(obj)->getParent().lock().get();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
RuntimeObject *parent = resolveObjectParent(static_cast<RuntimeObject *>(objectRef));
|
|
|
|
if (!parent)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
DynamicValueWriteProxy tempProxy;
|
|
DynamicValueWriteObjectHelper::create(parent, tempProxy);
|
|
|
|
return tempProxy.pod.ifc->refAttrib(thread, proxy, tempProxy.pod.objectRef, tempProxy.pod.ptrOrOffset, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::ParentWriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
RuntimeObject *parent = resolveObjectParent(static_cast<RuntimeObject *>(objectRef));
|
|
|
|
if (!parent)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
DynamicValueWriteProxy tempProxy;
|
|
DynamicValueWriteObjectHelper::create(parent, tempProxy);
|
|
|
|
return tempProxy.pod.ifc->refAttribIndexed(thread, proxy, tempProxy.pod.objectRef, tempProxy.pod.ptrOrOffset, attrib, index);
|
|
}
|
|
|
|
MessageProperties::MessageProperties(const Event &evt, const DynamicValue &value, const Common::WeakPtr<RuntimeObject> &source)
|
|
: _evt(evt), _value(value), _source(source) {
|
|
}
|
|
|
|
const Event &MessageProperties::getEvent() const {
|
|
return _evt;
|
|
}
|
|
|
|
const DynamicValue& MessageProperties::getValue() const {
|
|
return _value;
|
|
}
|
|
|
|
const Common::WeakPtr<RuntimeObject>& MessageProperties::getSource() const {
|
|
return _source;
|
|
}
|
|
|
|
void MessageProperties::setValue(const DynamicValue &value) {
|
|
if (value.getType() == DynamicValueTypes::kList)
|
|
_value.setList(value.getList()->clone());
|
|
else
|
|
_value = value;
|
|
}
|
|
|
|
WorldManagerInterface::WorldManagerInterface() : _gameMode(false), _combineRedraws(true), _postponeRedraws(false), _opInt(0) {
|
|
}
|
|
|
|
bool WorldManagerInterface::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "currentscene") {
|
|
Common::SharedPtr<RuntimeObject> mainScene = thread->getRuntime()->getActiveMainScene();
|
|
if (mainScene)
|
|
result.setObject(mainScene->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "monitordepth") {
|
|
int bitDepth = displayModeToBitDepth(thread->getRuntime()->getFakeColorDepth());
|
|
if (bitDepth <= 0)
|
|
return false;
|
|
|
|
result.setInt(bitDepth);
|
|
return true;
|
|
} else if (attrib == "gamemode") {
|
|
result.setBool(_gameMode);
|
|
return true;
|
|
} else if (attrib == "combineredraws") {
|
|
result.setBool(_combineRedraws);
|
|
return true;
|
|
} else if (attrib == "postponeredraws") {
|
|
result.setBool(_postponeRedraws);
|
|
return true;
|
|
} else if (attrib == "clickcount") {
|
|
result.setInt(thread->getRuntime()->getMultiClickCount());
|
|
return true;
|
|
}
|
|
|
|
return RuntimeObject::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "currentscene") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setCurrentScene, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "refreshcursor") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setRefreshCursor, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "autoresetcursor") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setAutoResetCursor, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "winsndbuffersize") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setWinSndBufferSize, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "gamemode") {
|
|
DynamicValueWriteBoolHelper::create(&_gameMode, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "combineredraws") {
|
|
DynamicValueWriteBoolHelper::create(&_combineRedraws, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "postponeredraws") {
|
|
DynamicValueWriteBoolHelper::create(&_postponeRedraws, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "qtpalettehack") {
|
|
DynamicValueWriteDiscardHelper::create(result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "opint") {
|
|
// This is used by SPQR before changing scenes in many instances. It's not clear what it does.
|
|
// It's usually set to 2 or 4, sometimes 7
|
|
DynamicValueWriteIntegerHelper<int32>::create(&_opInt, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "scenefades") {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
|
|
debugger->notify(kDebugSeverityWarning, "'scenefades' attribute was set on WorldManager, which is implemented yet");
|
|
#endif
|
|
DynamicValueWriteDiscardHelper::create(result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "cursor") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setCursor, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
return RuntimeObject::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::setCurrentScene(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kObject)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::SharedPtr<RuntimeObject> sceneObj = value.getObject().object.lock();
|
|
if (!sceneObj) {
|
|
thread->error("Failed to get scene reference");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
if (!sceneObj->isStructural()) {
|
|
thread->error("Tried to change to a non-structural object as a scene");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
Structural *scene = static_cast<Structural *>(sceneObj.get());
|
|
Structural *subsection = scene->getParent();
|
|
if (!subsection->isSubsection()) {
|
|
thread->error("Tried to change to a non-scene as a scene");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
// Note that this does NOT prevent transitioning to the same scene, which is intentional.
|
|
// Transitioning to the current scene is allowed (and will fire Scene Ended+Scene Started events)
|
|
bool addToReturnList = (_opInt & 0x02) != 0;
|
|
bool addToDest = (_opInt & 0x01) != 0;
|
|
|
|
_opInt = 0; // Possibly inaccurate
|
|
|
|
thread->getRuntime()->addSceneStateTransition(HighLevelSceneTransition(scene->getSelfReference().lock().staticCast<Structural>(), HighLevelSceneTransition::kTypeChangeToScene, addToDest, addToReturnList));
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::setRefreshCursor(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
if (value.getBool())
|
|
thread->getRuntime()->forceCursorRefreshOnce();
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::setAutoResetCursor(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
thread->getRuntime()->setAutoResetCursor(value.getBool());
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::setWinSndBufferSize(MiniscriptThread *thread, const DynamicValue &value) {
|
|
// Ignore
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::setCursor(MiniscriptThread *thread, const DynamicValue &value) {
|
|
switch (value.getType())
|
|
{
|
|
case DynamicValueTypes::kNull:
|
|
thread->getRuntime()->setCursorElement(Common::WeakPtr<VisualElement>());
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
case DynamicValueTypes::kObject: {
|
|
Common::SharedPtr<RuntimeObject> obj = value.getObject().object.lock();
|
|
if (obj && obj->isElement() && static_cast<Element *>(obj.get())->isVisual()) {
|
|
thread->getRuntime()->setCursorElement(obj.staticCast<VisualElement>());
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else {
|
|
thread->error("Object assigned as cursor wasn't a visual element");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
} break;
|
|
default:
|
|
thread->error("Value assigned as cursor wasn't an object");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
SystemInterface::SystemInterface() : _masterVolume(kFullVolume) {
|
|
}
|
|
|
|
bool SystemInterface::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "mastervolume") {
|
|
result.setInt(_masterVolume);
|
|
return true;
|
|
} else if (attrib == "monitorbitdepth") {
|
|
int bitDepth = displayModeToBitDepth(thread->getRuntime()->getFakeColorDepth());
|
|
if (bitDepth <= 0)
|
|
return false;
|
|
|
|
result.setInt(bitDepth);
|
|
return true;
|
|
} else if (attrib == "volumeismounted") {
|
|
int volID = 0;
|
|
bool isMounted = false;
|
|
bool hasVolume = thread->getRuntime()->getVolumeState(_volumeName.c_str(), volID, isMounted);
|
|
|
|
result.setBool(hasVolume && isMounted);
|
|
return true;
|
|
}
|
|
|
|
return RuntimeObject::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
bool SystemInterface::readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) {
|
|
if (attrib == "supportsbitdepth") {
|
|
int32 asInteger = 0;
|
|
if (!index.roundToInt(asInteger))
|
|
return false;
|
|
|
|
bool supported = false;
|
|
ColorDepthMode mode = bitDepthToDisplayMode(asInteger);
|
|
|
|
if (mode != kColorDepthModeInvalid)
|
|
supported = thread->getRuntime()->isDisplayModeSupported(mode);
|
|
|
|
result.setBool(supported);
|
|
return true;
|
|
}
|
|
|
|
return RuntimeObject::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "ejectcd") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setEjectCD, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "gamemode") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setGameMode, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "mastervolume") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setMasterVolume, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "monitorbitdepth") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setMonitorBitDepth, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "volumename") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setVolumeName, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return RuntimeObject::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::setEjectCD(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
// If set to true, supposed to eject the CD.
|
|
// Maybe we could dismount one of the runtime volumes here if we really wanted to, but ironically,
|
|
// while volumeIsMounted supports multiple CD drives at once, ejectCD doesn't, so... do nothing?
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::setGameMode(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
// Nothing to do here
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::setMasterVolume(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (!value.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
if (asInteger < 0)
|
|
asInteger = 0;
|
|
else if (asInteger > kFullVolume)
|
|
asInteger = kFullVolume;
|
|
|
|
thread->getRuntime()->setVolume(static_cast<double>(asInteger) / kFullVolume);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::setMonitorBitDepth(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (!value.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
const ColorDepthMode depthMode = bitDepthToDisplayMode(asInteger);
|
|
if (depthMode != kColorDepthModeInvalid) {
|
|
thread->getRuntime()->switchDisplayMode(thread->getRuntime()->getRealColorDepth(), depthMode);
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome SystemInterface::setVolumeName(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kString)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
_volumeName = value.getString();
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
StructuralHooks::~StructuralHooks() {
|
|
}
|
|
|
|
AssetManagerInterface::AssetManagerInterface() {
|
|
}
|
|
|
|
bool AssetManagerInterface::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "volumeismounted") {
|
|
int volID = 0;
|
|
bool isMounted = false;
|
|
bool hasVolume = thread->getRuntime()->getVolumeState(_opString.c_str(), volID, isMounted);
|
|
|
|
result.setBool(hasVolume && isMounted);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome AssetManagerInterface::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "opstring") {
|
|
DynamicValueWriteStringHelper::create(&_opString, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "cdeject") {
|
|
DynamicValueWriteDiscardHelper::create(result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void StructuralHooks::onCreate(Structural *structural) {
|
|
}
|
|
|
|
void StructuralHooks::onPostActivate(Structural *structural) {
|
|
}
|
|
|
|
void StructuralHooks::onSetPosition(Runtime *runtime, Structural *structural, const Common::Point &oldPt, Common::Point &pt) {
|
|
}
|
|
|
|
void StructuralHooks::onStopPlayingMToon(Structural *structural, bool &visible, bool &stopped, Graphics::ManagedSurface *lastSurf) {
|
|
}
|
|
|
|
void StructuralHooks::onHidden(Structural *structural, bool &visible) {
|
|
}
|
|
|
|
ProjectPresentationSettings::ProjectPresentationSettings() : width(640), height(480), bitsPerPixel(8) {
|
|
}
|
|
|
|
Structural::Structural() : Structural(nullptr) {
|
|
}
|
|
|
|
Structural::Structural(Runtime *runtime)
|
|
: _parent(nullptr)
|
|
, _paused(false)
|
|
, _loop(false)
|
|
, _flushPriority(0)
|
|
, _runtime(runtime)
|
|
, _sceneLoadState(SceneLoadState::kNotAScene) {
|
|
}
|
|
|
|
Structural::Structural(const Structural &other)
|
|
: RuntimeObject(other), Debuggable(other), _parent(other._parent), _children(other._children), _modifiers(other._modifiers), _name(other._name), _assets(other._assets)
|
|
, _paused(other._paused), _loop(other._loop), _flushPriority(other._flushPriority)/*, _hooks(other._hooks)*/, _runtime(other._runtime)
|
|
, _sceneLoadState(SceneLoadState::kNotAScene) {
|
|
}
|
|
|
|
Structural::~Structural() {
|
|
}
|
|
|
|
void Structural::setHooks(const Common::SharedPtr<StructuralHooks> &hooks) {
|
|
_hooks = hooks;
|
|
}
|
|
|
|
const Common::SharedPtr<StructuralHooks> &Structural::getHooks() const {
|
|
return _hooks;
|
|
}
|
|
|
|
void Structural::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
for (Common::SharedPtr<Structural> &child : _children)
|
|
visitor->visitChildStructuralRef(child);
|
|
|
|
for (Common::SharedPtr<Modifier> &child : _modifiers)
|
|
visitor->visitChildModifierRef(child);
|
|
}
|
|
|
|
bool Structural::isStructural() const {
|
|
return true;
|
|
}
|
|
|
|
Structural::SceneLoadState Structural::getSceneLoadState() const {
|
|
return _sceneLoadState;
|
|
}
|
|
|
|
void Structural::setSceneLoadState(SceneLoadState sceneLoadState) {
|
|
_sceneLoadState = sceneLoadState;
|
|
}
|
|
|
|
|
|
bool Structural::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "name") {
|
|
result.setString(_name);
|
|
return true;
|
|
} else if (attrib == "paused") {
|
|
result.setBool(_paused);
|
|
return true;
|
|
} else if (attrib == "loop") {
|
|
result.setBool(_paused);
|
|
return true;
|
|
} else if (attrib == "this") {
|
|
// Yes, "this" is an attribute
|
|
result.setObject(thread->getModifier()->getSelfReference());
|
|
return true;
|
|
} else if (attrib == "wm" || attrib == "worldmanager") {
|
|
result.setObject(thread->getRuntime()->getWorldManagerInterface()->getSelfReference());
|
|
return true;
|
|
} else if (attrib == "assetmanager") {
|
|
result.setObject(thread->getRuntime()->getAssetManagerInterface()->getSelfReference());
|
|
return true;
|
|
} else if (attrib == "system") {
|
|
result.setObject(thread->getRuntime()->getSystemInterface()->getSelfReference());
|
|
return true;
|
|
} else if (attrib == "parent") {
|
|
Structural *parent = getParent();
|
|
if (parent)
|
|
result.setObject(parent->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "previous") {
|
|
Structural *sibling = findPrevSibling();
|
|
if (sibling)
|
|
result.setObject(sibling->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "next") {
|
|
Structural *sibling = findNextSibling();
|
|
if (sibling)
|
|
result.setObject(sibling->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "scene") {
|
|
result.clear();
|
|
|
|
RuntimeObject *possibleScene = this;
|
|
while (possibleScene) {
|
|
if (possibleScene->isModifier()) {
|
|
possibleScene = static_cast<Modifier *>(possibleScene)->getParent().lock().get();
|
|
continue;
|
|
}
|
|
|
|
if (possibleScene->isStructural()) {
|
|
Structural *parent = static_cast<Structural *>(possibleScene)->getParent();
|
|
if (parent->isSubsection())
|
|
break;
|
|
else {
|
|
possibleScene = parent;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
if (possibleScene)
|
|
result.setObject(possibleScene->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "section") {
|
|
result.clear();
|
|
|
|
RuntimeObject *possibleSection = this;
|
|
while (possibleSection) {
|
|
if (possibleSection->isSection())
|
|
break;
|
|
|
|
if (possibleSection->isModifier()) {
|
|
possibleSection = static_cast<Modifier *>(possibleSection)->getParent().lock().get();
|
|
continue;
|
|
}
|
|
|
|
if (possibleSection->isStructural()) {
|
|
possibleSection = static_cast<Structural *>(possibleSection)->getParent();
|
|
continue;
|
|
}
|
|
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
if (possibleSection)
|
|
result.setObject(possibleSection->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "subsection") {
|
|
result.clear();
|
|
|
|
RuntimeObject *possibleSubsection = this;
|
|
while (possibleSubsection) {
|
|
if (possibleSubsection->isSubsection())
|
|
break;
|
|
|
|
if (possibleSubsection->isModifier()) {
|
|
possibleSubsection = static_cast<Modifier *>(possibleSubsection)->getParent().lock().get();
|
|
continue;
|
|
}
|
|
|
|
if (possibleSubsection->isStructural()) {
|
|
possibleSubsection = static_cast<Structural *>(possibleSubsection)->getParent();
|
|
continue;
|
|
}
|
|
|
|
assert(false);
|
|
break;
|
|
}
|
|
if (possibleSubsection)
|
|
result.setObject(possibleSubsection->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "flushpriority") {
|
|
result.setInt(_flushPriority);
|
|
return true;
|
|
} else if (attrib == "firstchild") {
|
|
// Despite documentation describing modifiers as children, "firstchild" on a structural element always returns
|
|
// the first structural child.
|
|
if (_children.size() == 0)
|
|
result.clear();
|
|
else
|
|
result.setObject(_children[0]->getSelfReference());
|
|
return true;
|
|
} else if (attrib == "element") {
|
|
result.setObject(getSelfReference());
|
|
return true;
|
|
} else if (attrib == "elementindex") {
|
|
int32 elementIndex = 0;
|
|
|
|
for (const Common::SharedPtr<Structural> &parentChild : _parent->getChildren()) {
|
|
if (parentChild.get() == this)
|
|
break;
|
|
|
|
elementIndex++;
|
|
}
|
|
|
|
assert(static_cast<uint>(elementIndex) < _parent->getChildren().size());
|
|
|
|
result.setInt(elementIndex + 1);
|
|
return true;
|
|
}
|
|
|
|
if (RuntimeObject::readAttribute(thread, result, attrib))
|
|
return true;
|
|
|
|
if (_sceneLoadState == Structural::SceneLoadState::kSceneNotLoaded) {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
|
|
debugger->notify(kDebugSeverityError, "Hot-loading scenes is not yet implemented (readAttribute)");
|
|
#endif
|
|
}
|
|
|
|
// Traverse children (modifiers must be first)
|
|
for (const Common::SharedPtr<Modifier> &modifier : _modifiers) {
|
|
if (caseInsensitiveEqual(modifier->getName(), attrib)) {
|
|
result.setObject(modifier);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const Common::SharedPtr<Structural> &child : _children) {
|
|
if (caseInsensitiveEqual(child->getName(), attrib)) {
|
|
result.setObject(child);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Structural::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "name") {
|
|
DynamicValueWriteStringHelper::create(&_name, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "paused") {
|
|
DynamicValueWriteFuncHelper<Structural, &Structural::scriptSetPaused, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "this") {
|
|
// Yes, "this" is an attribute
|
|
DynamicValueWriteObjectHelper::create(thread->getModifier(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "wm" || attrib == "worldmanager") {
|
|
DynamicValueWriteObjectHelper::create(thread->getRuntime()->getWorldManagerInterface(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "assetmanager") {
|
|
DynamicValueWriteObjectHelper::create(thread->getRuntime()->getAssetManagerInterface(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "system") {
|
|
DynamicValueWriteObjectHelper::create(thread->getRuntime()->getSystemInterface(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} 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, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "debug") {
|
|
DynamicValueWriteFuncHelper<Structural, &Structural::scriptSetDebug, true>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "flushpriority") {
|
|
DynamicValueWriteIntegerHelper<int32>::create(&_flushPriority, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "unload") {
|
|
DynamicValueWriteFuncHelper<Structural, &Structural::scriptSetUnload, false>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
// Attempt to resolve as a child object
|
|
// Modifiers are first, then structural
|
|
for (const Common::SharedPtr<Modifier> &modifier : _modifiers) {
|
|
if (caseInsensitiveEqual(modifier->getName(), attrib)) {
|
|
DynamicValueWriteObjectHelper::create(modifier.get(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
}
|
|
|
|
for (const Common::SharedPtr<Structural> &child : _children) {
|
|
if (caseInsensitiveEqual(child->getName(), attrib)) {
|
|
DynamicValueWriteObjectHelper::create(child.get(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
}
|
|
|
|
return RuntimeObject::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
bool Structural::readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) {
|
|
if (attrib == "nthelement") {
|
|
if (_sceneLoadState == Structural::SceneLoadState::kSceneNotLoaded) {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
|
|
debugger->notify(kDebugSeverityError, "Hot-loading scenes is not yet implemented (readAttributeIndexed)");
|
|
#endif
|
|
}
|
|
|
|
DynamicValue indexConverted;
|
|
if (!index.convertToType(DynamicValueTypes::kInteger, indexConverted)) {
|
|
thread->error("Invalid index for 'nthelement'");
|
|
return false;
|
|
}
|
|
|
|
int32 indexInt = indexConverted.getInt();
|
|
|
|
if (indexInt < 1 || static_cast<uint32>(indexInt) > _children.size()) {
|
|
thread->error("Index out of range for 'nthelement'");
|
|
return false;
|
|
}
|
|
|
|
result.setObject(_children[indexInt - 1]->getSelfReference());
|
|
return true;
|
|
}
|
|
|
|
return RuntimeObject::readAttributeIndexed(thread, result, attrib, index);
|
|
}
|
|
|
|
|
|
const Common::Array<Common::SharedPtr<Structural> > &Structural::getChildren() const {
|
|
return _children;
|
|
}
|
|
|
|
void Structural::addChild(const Common::SharedPtr<Structural>& child) {
|
|
_children.push_back(child);
|
|
child->setParent(this);
|
|
}
|
|
|
|
void Structural::removeAllChildren() {
|
|
_children.clear();
|
|
}
|
|
|
|
void Structural::removeAllModifiers() {
|
|
_modifiers.clear();
|
|
}
|
|
|
|
void Structural::removeChild(Structural* child) {
|
|
for (size_t i = 0; i < _children.size(); i++) {
|
|
if (_children[i].get() == child) {
|
|
_children.remove_at(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Structural::removeAllAssets() {
|
|
_assets.clear();
|
|
}
|
|
|
|
void Structural::holdAssets(const Common::Array<Common::SharedPtr<Asset> >& assets) {
|
|
_assets = assets;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Runtime *Structural::getRuntime() const {
|
|
return _runtime;
|
|
}
|
|
|
|
void Structural::setParent(Structural *parent) {
|
|
_parent = parent;
|
|
}
|
|
|
|
VisualElement *Structural::findScene() {
|
|
Structural *parent = _parent;
|
|
if (!parent)
|
|
return nullptr;
|
|
|
|
if (parent->isSubsection())
|
|
return static_cast<VisualElement *>(this);
|
|
else
|
|
return parent->findScene();
|
|
}
|
|
|
|
const Common::String &Structural::getName() const {
|
|
return _name;
|
|
}
|
|
|
|
const Common::Array<Common::SharedPtr<Modifier> > &Structural::getModifiers() const {
|
|
return _modifiers;
|
|
}
|
|
|
|
void Structural::appendModifier(const Common::SharedPtr<Modifier> &modifier) {
|
|
_modifiers.push_back(modifier);
|
|
modifier->setParent(getSelfReference());
|
|
}
|
|
|
|
void Structural::removeModifier(const Modifier *modifier) {
|
|
for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
|
|
if (it->get() == modifier) {
|
|
_modifiers.erase(it);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Structural::respondsToEvent(const Event &evt) const {
|
|
return false;
|
|
}
|
|
|
|
VThreadState Structural::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
assert(false);
|
|
return kVThreadError;
|
|
}
|
|
|
|
void Structural::materializeSelfAndDescendents(Runtime *runtime, ObjectLinkingScope *outerScope) {
|
|
linkInternalReferences(outerScope);
|
|
setRuntimeGUID(runtime->allocateRuntimeGUID());
|
|
|
|
materializeDescendents(runtime, outerScope);
|
|
|
|
_runtime = runtime;
|
|
}
|
|
|
|
void Structural::materializeDescendents(Runtime *runtime, ObjectLinkingScope *outerScope) {
|
|
// Materialization is the step after objects are fully constructed and filled with data.
|
|
// It does three things, recursively:
|
|
// - Assigns all objects a new runtime GUID
|
|
// - Clones any non-variable aliased modifiers
|
|
// - Links any static GUIDs to in-scope visible objects
|
|
// Objects are only ever materialized once
|
|
//
|
|
// Modifiers can see other modifiers but can't see sibling elements...
|
|
// so the structural scope is effectively nested inside of the modifier scope.
|
|
ObjectLinkingScope tempModifierScope;
|
|
ObjectLinkingScope tempStructuralScope;
|
|
ObjectLinkingScope *modifierScope = this->getPersistentModifierScope();
|
|
ObjectLinkingScope *structuralScope = this->getPersistentStructuralScope();
|
|
|
|
if (!modifierScope)
|
|
modifierScope = &tempModifierScope;
|
|
if (!structuralScope)
|
|
structuralScope = &tempStructuralScope;
|
|
|
|
modifierScope->setParent(outerScope);
|
|
|
|
for (Common::Array<Common::SharedPtr<Modifier> >::iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
|
|
Common::SharedPtr<Modifier> &modifierRef = *it;
|
|
|
|
runtime->instantiateIfAlias(modifierRef, getSelfReference());
|
|
modifierScope->addObject(modifierRef->getStaticGUID(), modifierRef->getName(), modifierRef);
|
|
}
|
|
|
|
for (Common::Array<Common::SharedPtr<Modifier> >::const_iterator it = _modifiers.begin(), itEnd = _modifiers.end(); it != itEnd; ++it) {
|
|
Modifier *modifier = it->get();
|
|
modifier->materialize(runtime, modifierScope);
|
|
}
|
|
|
|
structuralScope->setParent(modifierScope);
|
|
|
|
const Common::Array<Common::SharedPtr<Structural> > &children = this->getChildren();
|
|
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = children.begin(), itEnd = children.end(); it != itEnd; ++it) {
|
|
Structural *child = it->get();
|
|
structuralScope->addObject(child->getStaticGUID(), child->getName(), *it);
|
|
}
|
|
|
|
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = children.begin(), itEnd = children.end(); it != itEnd; ++it) {
|
|
Structural *child = it->get();
|
|
child->materializeSelfAndDescendents(runtime, structuralScope);
|
|
}
|
|
}
|
|
|
|
CORO_BEGIN_DEFINITION(Structural::StructuralConsumeCommandCoroutine)
|
|
struct Locals {
|
|
Common::ScopedPtr<MiniscriptThread> miniscriptThread;
|
|
uint32 attribID;
|
|
const Common::String *attribName;
|
|
DynamicValueWriteProxy writeProxy;
|
|
DynamicValue attribGetResult;
|
|
bool quietlyDiscard;
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
CORO_IF(Event(EventIDs::kUnpause, 0).respondsTo(params->msg->getEvent()))
|
|
if (params->self->_paused) {
|
|
params->self->_paused = false;
|
|
params->self->onPauseStateChanged();
|
|
}
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kUnpause, 0), DynamicValue(), params->self->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
|
|
|
|
CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
|
|
|
|
CORO_RETURN;
|
|
CORO_ELSE_IF(Event(EventIDs::kPause, 0).respondsTo(params->msg->getEvent()))
|
|
if (!params->self->_paused) {
|
|
params->self->_paused = true;
|
|
params->self->onPauseStateChanged();
|
|
}
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPause, 0), DynamicValue(), params->self->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
|
|
|
|
CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
|
|
|
|
CORO_RETURN;
|
|
CORO_ELSE_IF(params->msg->getEvent().eventType == EventIDs::kAttribSet)
|
|
locals->attribID = params->msg->getEvent().eventInfo;
|
|
|
|
locals->attribName = params->runtime->resolveAttributeIDName(locals->attribID);
|
|
|
|
CORO_IF(locals->attribName == nullptr)
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = params->runtime->debugGetDebugger())
|
|
debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Set Attribute message", static_cast<int>(locals->attribID));
|
|
#endif
|
|
CORO_ERROR;
|
|
CORO_END_IF
|
|
|
|
locals->miniscriptThread.reset(new MiniscriptThread(params->runtime, params->msg, nullptr, nullptr, nullptr));
|
|
|
|
CORO_AWAIT_MINISCRIPT(params->self->writeRefAttribute(locals->miniscriptThread.get(), locals->writeProxy, *locals->attribName));
|
|
|
|
CORO_AWAIT_MINISCRIPT(locals->writeProxy.pod.ifc->write(locals->miniscriptThread.get(), params->msg->getValue(), locals->writeProxy.pod.objectRef, locals->writeProxy.pod.ptrOrOffset));
|
|
|
|
CORO_RETURN;
|
|
CORO_ELSE_IF(params->msg->getEvent().eventType == EventIDs::kAttribGet)
|
|
locals->attribID = params->msg->getEvent().eventInfo;
|
|
|
|
locals->attribName = params->runtime->resolveAttributeIDName(locals->attribID);
|
|
CORO_IF(locals->attribName == nullptr)
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = params->runtime->debugGetDebugger())
|
|
debugger->notifyFmt(kDebugSeverityError, "Attribute ID '%i' couldn't be resolved for Set Attribute message", static_cast<int>(locals->attribID));
|
|
#endif
|
|
CORO_ERROR;
|
|
CORO_END_IF
|
|
|
|
locals->miniscriptThread.reset(new MiniscriptThread(params->runtime, params->msg, nullptr, nullptr, nullptr));
|
|
|
|
CORO_IF(!params->self->readAttribute(locals->miniscriptThread.get(), locals->attribGetResult, *locals->attribName))
|
|
CORO_ERROR;
|
|
CORO_END_IF
|
|
|
|
params->msg->setValue(locals->attribGetResult);
|
|
|
|
CORO_RETURN;
|
|
CORO_END_IF
|
|
|
|
locals->quietlyDiscard = false;
|
|
|
|
// Just ignore these
|
|
const EventIDs::EventID ignoredIDs[] = {
|
|
EventIDs::kPreloadMedia,
|
|
EventIDs::kFlushMedia,
|
|
EventIDs::kFlushAllMedia,
|
|
EventIDs::kPrerollMedia
|
|
};
|
|
|
|
for (EventIDs::EventID evtID : ignoredIDs) {
|
|
if (Event(evtID, 0).respondsTo(params->msg->getEvent())) {
|
|
locals->quietlyDiscard = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!locals->quietlyDiscard)
|
|
warning("Command type %i was ignored", params->msg->getEvent().eventType);
|
|
CORO_END_FUNCTION
|
|
|
|
CORO_END_DEFINITION
|
|
|
|
VThreadState Structural::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
runtime->getVThread().pushCoroutine<StructuralConsumeCommandCoroutine>(this, runtime, msg);
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void Structural::activate() {
|
|
}
|
|
|
|
void Structural::deactivate() {
|
|
}
|
|
|
|
void Structural::recursiveCollectObjectsMatchingCriteria(Common::Array<Common::WeakPtr<RuntimeObject> > &results, bool (*evalFunc)(void *userData, RuntimeObject *object), void *userData, bool onlyEnabled) {
|
|
if (evalFunc(userData, this))
|
|
results.push_back(this->getSelfReference().lock());
|
|
|
|
for (const Common::SharedPtr<Structural> &child : _children)
|
|
child->recursiveCollectObjectsMatchingCriteria(results, evalFunc, userData, onlyEnabled);
|
|
|
|
for (const Common::SharedPtr<Modifier> &modifier : _modifiers)
|
|
modifier->recursiveCollectObjectsMatchingCriteria(results, evalFunc, userData, onlyEnabled);
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
SupportStatus Structural::debugGetSupportStatus() const {
|
|
return kSupportStatusNone;
|
|
}
|
|
|
|
const Common::String &Structural::debugGetName() const {
|
|
return _name;
|
|
}
|
|
|
|
void Structural::debugInspect(IDebugInspectionReport *report) const {
|
|
if (report->declareStatic("type"))
|
|
report->declareStaticContents(debugGetTypeName());
|
|
if (report->declareStatic("guid"))
|
|
report->declareStaticContents(Common::String::format("%x", getStaticGUID()));
|
|
if (report->declareStatic("runtimeID"))
|
|
report->declareStaticContents(Common::String::format("%x", getRuntimeGUID()));
|
|
}
|
|
|
|
void Structural::debugSkipMovies() {
|
|
for (Common::SharedPtr<Structural> &child : _children)
|
|
child->debugSkipMovies();
|
|
}
|
|
|
|
#endif /* MTROPOLIS_DEBUG_ENABLE */
|
|
|
|
void Structural::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
}
|
|
|
|
void Structural::onPauseStateChanged() {
|
|
}
|
|
|
|
ObjectLinkingScope *Structural::getPersistentStructuralScope() {
|
|
return nullptr;
|
|
}
|
|
|
|
ObjectLinkingScope* Structural::getPersistentModifierScope() {
|
|
return nullptr;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Structural::scriptSetPaused(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
const bool targetValue = value.getBool();
|
|
|
|
if (targetValue == _paused)
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
|
|
_paused = targetValue;
|
|
onPauseStateChanged();
|
|
|
|
// Quirk: "pause" state changes during scene transitions don't fire "Paused" events.
|
|
// This is necessary in Obsidian to prevent the rotator lever from triggering when leaving the menu
|
|
// while at the Bureau light carousel, since the lever isn't flagged as paused but is set paused
|
|
// via an init script, and the lever trigger is detected via the pause event.
|
|
//
|
|
// The event does, however, need to be sent immediately.
|
|
if (!thread->getRuntime()->isAwaitingSceneTransition()) {
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(targetValue ? EventIDs::kPause : EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
|
|
thread->getRuntime()->sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeYieldToVThread;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Structural::scriptSetLoop(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() != DynamicValueTypes::kBoolean)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
_loop = value.getBool();
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Structural::scriptSetDebug(MiniscriptThread *thread, const DynamicValue &value) {
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Structural::scriptSetUnload(MiniscriptThread *thread, const DynamicValue &value) {
|
|
// Doesn't matter what value this is set to, it always tries to unload.
|
|
if (_sceneLoadState != SceneLoadState::kNotAScene)
|
|
thread->getRuntime()->addSceneStateTransition(HighLevelSceneTransition(getSelfReference().lock().staticCast<Structural>(), HighLevelSceneTransition::kTypeRequestUnloadScene, false, false));
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
VolumeState::VolumeState() : volumeID(0), isMounted(false) {
|
|
}
|
|
|
|
ObjectLinkingScope::ObjectLinkingScope() : _parent(nullptr) {
|
|
}
|
|
|
|
ObjectLinkingScope::~ObjectLinkingScope() {
|
|
}
|
|
|
|
void ObjectLinkingScope::setParent(ObjectLinkingScope *parent) {
|
|
_parent = parent;
|
|
}
|
|
|
|
void ObjectLinkingScope::addObject(uint32 guid, const Common::String &name, const Common::WeakPtr<RuntimeObject> &object) {
|
|
_guidToObject[guid] = object;
|
|
|
|
if (name.size() > 0) {
|
|
// Have to keep the first instance we find and ignore later instances.
|
|
// Obsidian depends on this to properly resolve the scene destination when returning to the plane from the Statue
|
|
// because there are two "sDest" variables but the first one is the correct one (and the one matching the GUID
|
|
// link from the script that assigns to it)
|
|
Common::WeakPtr<RuntimeObject> &objRef = _nameToObject[toCaseInsensitive(name)];
|
|
|
|
if (objRef.expired())
|
|
objRef = object;
|
|
}
|
|
}
|
|
|
|
Common::WeakPtr<RuntimeObject> ObjectLinkingScope::resolve(uint32 staticGUID) const {
|
|
if (staticGUID == 0)
|
|
return Common::WeakPtr<RuntimeObject>();
|
|
|
|
Common::HashMap<uint32, Common::WeakPtr<RuntimeObject> >::const_iterator it = _guidToObject.find(staticGUID);
|
|
if (it != _guidToObject.end()) {
|
|
return it->_value;
|
|
} else {
|
|
if (_parent)
|
|
return _parent->resolve(staticGUID);
|
|
|
|
return Common::WeakPtr<RuntimeObject>();
|
|
}
|
|
}
|
|
|
|
Common::WeakPtr<RuntimeObject> ObjectLinkingScope::resolve(const Common::String &name, bool isNameAlreadyInsensitive) const {
|
|
const Common::String *namePtr = &name;
|
|
Common::String madeInsensitive;
|
|
|
|
if (!isNameAlreadyInsensitive) {
|
|
madeInsensitive = toCaseInsensitive(name);
|
|
namePtr = &madeInsensitive;
|
|
}
|
|
|
|
Common::HashMap<Common::String, Common::WeakPtr<RuntimeObject> >::const_iterator it = _nameToObject.find(*namePtr);
|
|
if (it != _nameToObject.end()) {
|
|
return it->_value;
|
|
} else {
|
|
if (_parent)
|
|
return _parent->resolve(*namePtr, true);
|
|
|
|
return Common::WeakPtr<RuntimeObject>();
|
|
}
|
|
}
|
|
|
|
Common::WeakPtr<RuntimeObject> ObjectLinkingScope::resolve(uint32 staticGUID, const Common::String &name, bool isNameAlreadyInsensitive) const {
|
|
Common::WeakPtr<RuntimeObject> byGUIDResult = resolve(staticGUID);
|
|
if (!byGUIDResult.expired())
|
|
return byGUIDResult;
|
|
else {
|
|
Common::WeakPtr<RuntimeObject> fallback = resolve(name, isNameAlreadyInsensitive);
|
|
if (fallback.expired()) {
|
|
warning("Couldn't resolve static guid '%x' with name '%s'", staticGUID, name.c_str());
|
|
}
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
void ObjectLinkingScope::reset() {
|
|
_parent = nullptr;
|
|
_guidToObject.clear(true);
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction::LowLevelSceneStateTransitionAction(const Common::SharedPtr<MessageDispatch> &msg)
|
|
: _actionType(LowLevelSceneStateTransitionAction::kSendMessage), _msg(msg) {
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction::LowLevelSceneStateTransitionAction(ActionType actionType)
|
|
: _actionType(actionType) {
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction::LowLevelSceneStateTransitionAction(const LowLevelSceneStateTransitionAction &other)
|
|
: _actionType(other._actionType), _msg(other._msg), _scene(other._scene) {
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction::LowLevelSceneStateTransitionAction(const Common::SharedPtr<Structural> &scene, ActionType actionType)
|
|
: _scene(scene), _actionType(actionType) {
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction::ActionType LowLevelSceneStateTransitionAction::getActionType() const {
|
|
return _actionType;
|
|
}
|
|
|
|
const Common::SharedPtr<Structural>& LowLevelSceneStateTransitionAction::getScene() const {
|
|
return _scene;
|
|
}
|
|
|
|
const Common::SharedPtr<MessageDispatch>& LowLevelSceneStateTransitionAction::getMessage() const {
|
|
return _msg;
|
|
}
|
|
|
|
LowLevelSceneStateTransitionAction &LowLevelSceneStateTransitionAction::operator=(const LowLevelSceneStateTransitionAction &other) {
|
|
_scene = other._scene;
|
|
_msg = other._msg;
|
|
_actionType = other._actionType;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
HighLevelSceneTransition::HighLevelSceneTransition(const Common::SharedPtr<Structural> &hlst_scene, Type hlst_type, bool hlst_addToDestinationScene, bool hlst_addToReturnList)
|
|
: scene(hlst_scene), type(hlst_type), addToDestinationScene(hlst_addToDestinationScene), addToReturnList(hlst_addToReturnList) {
|
|
}
|
|
|
|
ObjectParentChange::ObjectParentChange(const Common::WeakPtr<RuntimeObject> &object, const Common::WeakPtr<RuntimeObject> &newParent)
|
|
: _object(object), _newParent(newParent) {
|
|
}
|
|
|
|
SceneTransitionEffect::SceneTransitionEffect()
|
|
: _duration(100000), _steps(64), _transitionType(SceneTransitionTypes::kNone), _transitionDirection(SceneTransitionDirections::kUp) {
|
|
}
|
|
|
|
MessageDispatch::MessageDispatch(const Common::SharedPtr<MessageProperties> &msgProps, Structural *root, bool cascade, bool relay, bool couldBeCommand)
|
|
: _cascade(cascade), _relay(relay), _msg(msgProps), _isCommand(false), _rootType(RootType::Structural) {
|
|
if (couldBeCommand && EventIDs::isCommand(msgProps->getEvent().eventType)) {
|
|
_isCommand = true;
|
|
_rootType = RootType::Command;
|
|
}
|
|
|
|
_root = root->getSelfReference();
|
|
}
|
|
|
|
MessageDispatch::MessageDispatch(const Common::SharedPtr<MessageProperties> &msgProps, Modifier *root, bool cascade, bool relay, bool couldBeCommand)
|
|
: _cascade(cascade), _relay(relay), _msg(msgProps), _isCommand(false), _rootType(RootType::Modifier) {
|
|
|
|
// Apparently if a command message is sent to a modifier, it's handled as a message.
|
|
// SPQR depends on this to send "Element Select" messages to pick the palette.
|
|
_root = root->getSelfReference();
|
|
}
|
|
|
|
const Common::SharedPtr<MessageProperties> &MessageDispatch::getMsg() const {
|
|
return _msg;
|
|
}
|
|
|
|
bool MessageDispatch::isCascade() const {
|
|
return _cascade;
|
|
}
|
|
|
|
bool MessageDispatch::isRelay() const {
|
|
return _relay;
|
|
}
|
|
|
|
MessageDispatch::RootType MessageDispatch::getRootType() const {
|
|
return _rootType;
|
|
}
|
|
|
|
const Common::WeakPtr<RuntimeObject> &MessageDispatch::getRootWeakPtr() const {
|
|
return _root;
|
|
}
|
|
|
|
|
|
RuntimeObject *MessageDispatch::getRootPropagator() const {
|
|
return _root.lock().get();
|
|
}
|
|
|
|
KeyEventDispatch::KeyEventDispatch(const Common::SharedPtr<KeyboardInputEvent> &evt) : _dispatchIndex(0), _evt(evt) {
|
|
}
|
|
|
|
Common::Array<Common::WeakPtr<RuntimeObject> >& KeyEventDispatch::getKeyboardMessengerArray() {
|
|
return _keyboardMessengers;
|
|
}
|
|
|
|
bool KeyEventDispatch::keyboardMessengerFilterFunc(void *userData, RuntimeObject *object) {
|
|
if (!object->isModifier())
|
|
return false;
|
|
|
|
return static_cast<Modifier *>(object)->isKeyboardMessenger();
|
|
}
|
|
|
|
bool KeyEventDispatch::isTerminated() const {
|
|
return _dispatchIndex == _keyboardMessengers.size();
|
|
}
|
|
|
|
VThreadState KeyEventDispatch::continuePropagating(Runtime *runtime) {
|
|
KeyboardInputEvent *evt = _evt.get();
|
|
|
|
// This is kind of messy but we have to guard against situations where a key event triggers a clone
|
|
// which may itself contain a keyboard messenger. (Do multiple messengers respond to keystrokes?)
|
|
while (_dispatchIndex < _keyboardMessengers.size()) {
|
|
Common::SharedPtr<RuntimeObject> obj = _keyboardMessengers[_dispatchIndex++].lock();
|
|
assert(obj->isModifier());
|
|
|
|
Modifier *modifier = static_cast<Modifier *>(obj.get());
|
|
assert(modifier->isKeyboardMessenger());
|
|
|
|
KeyboardMessengerModifier *msgr = static_cast<KeyboardMessengerModifier *>(modifier);
|
|
Common::String charStr;
|
|
if (msgr->checkKeyEventTrigger(runtime, evt->getKeyEventType(), evt->isRepeat(), evt->getKeyState(), charStr)) {
|
|
msgr->dispatchMessage(runtime, charStr);
|
|
return kVThreadReturn;
|
|
}
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void ScheduledEvent::cancel() {
|
|
if (_scheduler)
|
|
_scheduler->removeEvent(this);
|
|
|
|
_scheduler = nullptr;
|
|
}
|
|
|
|
uint64 ScheduledEvent::getScheduledTime() const {
|
|
return _scheduledTime;
|
|
}
|
|
|
|
void ScheduledEvent::activate(Runtime *runtime) const {
|
|
_activateFunc(this->_obj, runtime);
|
|
}
|
|
|
|
|
|
ScheduledEvent::ScheduledEvent(void *obj, void (*activateFunc)(void *, Runtime *runtime), uint64 scheduledTime, Scheduler *scheduler)
|
|
: _obj(obj), _activateFunc(activateFunc), _scheduledTime(scheduledTime), _scheduler(scheduler) {
|
|
}
|
|
|
|
Scheduler::Scheduler() {
|
|
}
|
|
|
|
Scheduler::~Scheduler() {
|
|
for (const Common::SharedPtr<ScheduledEvent> &evt : _events)
|
|
evt->_scheduler = nullptr;
|
|
|
|
_events.clear();
|
|
}
|
|
|
|
Common::SharedPtr<ScheduledEvent> Scheduler::getFirstEvent() const {
|
|
if (_events.size() > 0)
|
|
return _events[0];
|
|
return nullptr;
|
|
}
|
|
|
|
void Scheduler::descheduleFirstEvent() {
|
|
_events.remove_at(0);
|
|
}
|
|
|
|
void Scheduler::insertEvent(const Common::SharedPtr<ScheduledEvent> &evt) {
|
|
uint32 t = evt->getScheduledTime();
|
|
size_t insertionIndex = 0;
|
|
while (insertionIndex < _events.size()) {
|
|
if (_events[insertionIndex]->getScheduledTime() > t)
|
|
break;
|
|
insertionIndex++;
|
|
}
|
|
|
|
_events.insert_at(insertionIndex, evt);
|
|
}
|
|
|
|
void Scheduler::removeEvent(const ScheduledEvent *evt) {
|
|
for (size_t i = 0; i < _events.size(); i++) {
|
|
if (_events[i].get() == evt) {
|
|
_events[i].get()->_scheduler = nullptr;
|
|
_events.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
class DefaultCursor : public Graphics::Cursor {
|
|
public:
|
|
uint16 getWidth() const override { return 16; }
|
|
uint16 getHeight() const override { return 16; }
|
|
uint16 getHotspotX() const override { return 1; }
|
|
uint16 getHotspotY() const override { return 2; }
|
|
byte getKeyColor() const override { return 0; }
|
|
|
|
const byte *getSurface() const override { return _cursorGraphic; }
|
|
|
|
const byte *getPalette() const override { return _cursorPalette; }
|
|
byte getPaletteStartIndex() const override { return 0; }
|
|
uint16 getPaletteCount() const override { return 3; }
|
|
|
|
private:
|
|
static const byte _cursorGraphic[256];
|
|
static const byte _cursorPalette[9];
|
|
};
|
|
|
|
const byte DefaultCursor::_cursorGraphic[256] = {
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 2, 1, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 2, 1, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 1, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
};
|
|
|
|
const byte DefaultCursor::_cursorPalette[9] = {
|
|
255, 0, 255,
|
|
0, 0, 0,
|
|
255, 255, 255
|
|
};
|
|
|
|
|
|
class DefaultCursorGraphic : public CursorGraphic {
|
|
public:
|
|
Graphics::Cursor *getCursor() const override;
|
|
|
|
private:
|
|
mutable DefaultCursor _cursor;
|
|
};
|
|
|
|
Graphics::Cursor *DefaultCursorGraphic::getCursor() const {
|
|
return &_cursor;
|
|
}
|
|
|
|
|
|
OSEvent::OSEvent(OSEventType eventType) : _eventType(eventType) {
|
|
}
|
|
|
|
OSEvent::~OSEvent() {
|
|
}
|
|
|
|
OSEventType OSEvent::getEventType() const {
|
|
return _eventType;
|
|
}
|
|
|
|
MouseInputEvent::MouseInputEvent(OSEventType eventType, int32 x, int32 y, Actions::MouseButton button) : OSEvent(eventType), _x(x), _y(y), _button(button) {
|
|
}
|
|
|
|
int32 MouseInputEvent::getX() const {
|
|
return _x;
|
|
}
|
|
|
|
int32 MouseInputEvent::getY() const {
|
|
return _y;
|
|
}
|
|
|
|
Actions::MouseButton MouseInputEvent::getButton() const {
|
|
return _button;
|
|
}
|
|
|
|
|
|
KeyboardInputEvent::KeyboardInputEvent(OSEventType osEventType, Common::EventType keyEventType, bool repeat, const Common::KeyState &keyEvt)
|
|
: OSEvent(osEventType), _keyEventType(keyEventType), _repeat(repeat), _keyEvt(keyEvt) {
|
|
}
|
|
|
|
Common::EventType KeyboardInputEvent::getKeyEventType() const {
|
|
return _keyEventType;
|
|
}
|
|
|
|
bool KeyboardInputEvent::isRepeat() const {
|
|
return _repeat;
|
|
}
|
|
|
|
const Common::KeyState &KeyboardInputEvent::getKeyState() const {
|
|
return _keyEvt;
|
|
}
|
|
|
|
ActionEvent::ActionEvent(OSEventType osEventType, Actions::Action action) : OSEvent(osEventType), _action(action) {
|
|
}
|
|
|
|
Actions::Action ActionEvent::getAction() const {
|
|
return _action;
|
|
}
|
|
|
|
Runtime::SceneStackEntry::SceneStackEntry() {
|
|
}
|
|
|
|
Runtime::Teardown::Teardown() : onlyRemoveChildren(false) {
|
|
}
|
|
|
|
Runtime::SceneReturnListEntry::SceneReturnListEntry() : isAddToDestinationSceneTransition(false) {
|
|
}
|
|
|
|
Runtime::DispatchActionTaskData::DispatchActionTaskData() : action(Actions::kNone) {
|
|
}
|
|
|
|
Runtime::ConsumeMessageTaskData::ConsumeMessageTaskData() : consumer(nullptr) {
|
|
}
|
|
|
|
Runtime::ConsumeCommandTaskData::ConsumeCommandTaskData() : structural(nullptr) {
|
|
}
|
|
|
|
Runtime::ApplyDefaultVisibilityTaskData::ApplyDefaultVisibilityTaskData() : element(nullptr), targetVisibility(false) {
|
|
}
|
|
|
|
|
|
Runtime::UpdateMouseStateTaskData::UpdateMouseStateTaskData() : mouseDown(false) {
|
|
}
|
|
|
|
Runtime::UpdateMousePositionTaskData::UpdateMousePositionTaskData() : x(0), y(0) {
|
|
}
|
|
|
|
Runtime::CollisionCheckState::CollisionCheckState() : collider(nullptr) {
|
|
}
|
|
|
|
Runtime::BoundaryCheckState::BoundaryCheckState() : detector(nullptr), currentContacts(0), positionResolved(false) {
|
|
}
|
|
|
|
Runtime::ColliderInfo::ColliderInfo() : sceneStackDepth(0), layer(0), element(nullptr) {
|
|
}
|
|
|
|
DragMotionProperties::DragMotionProperties() : constraintDirection(kConstraintDirectionNone), constrainToParent(false) {
|
|
}
|
|
|
|
SceneTransitionHooks::~SceneTransitionHooks() {
|
|
}
|
|
|
|
void SceneTransitionHooks::onSceneTransitionSetup(Runtime *runtime, const Common::WeakPtr<Structural> &oldScene, const Common::WeakPtr<Structural> &newScene) {
|
|
}
|
|
|
|
void SceneTransitionHooks::onSceneTransitionEnded(Runtime *runtime, const Common::WeakPtr<Structural> &newScene) {
|
|
}
|
|
|
|
void SceneTransitionHooks::onProjectStarted(Runtime *runtime) {
|
|
}
|
|
|
|
|
|
Palette::Palette() {
|
|
initDefaultPalette(1);
|
|
}
|
|
|
|
void Palette::initDefaultPalette(int version) {
|
|
// NOTE: The "V2" palette is correct for Unit: Rebooted.
|
|
// Is it correct for all V2 apps?
|
|
assert(version == 1 || version == 2);
|
|
int outColorIndex = 0;
|
|
for (int rb = 0; rb < 6; rb++) {
|
|
for (int rg = 0; rg < 6; rg++) {
|
|
for (int rr = 0; rr < 6; rr++) {
|
|
byte *color = _colors + outColorIndex * 3;
|
|
outColorIndex++;
|
|
|
|
if (version == 1) {
|
|
color[0] = 255 - rr * 51;
|
|
color[1] = 255 - rg * 51;
|
|
color[2] = 255 - rb * 51;
|
|
} else {
|
|
color[2] = 255 - rr * 51;
|
|
color[1] = 255 - rg * 51;
|
|
color[0] = 255 - rb * 51;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
outColorIndex--;
|
|
|
|
for (int ch = 0; ch < 4; ch++) {
|
|
for (int ri = 0; ri < 16; ri++) {
|
|
if (ri % 3 == 0)
|
|
continue;
|
|
|
|
byte *color = _colors + outColorIndex * 3;
|
|
outColorIndex++;
|
|
|
|
byte intensity = 255 - ri * 17;
|
|
|
|
if (ch == 3) {
|
|
color[0] = color[1] = color[2] = intensity;
|
|
} else {
|
|
color[0] = color[1] = color[2] = 0;
|
|
color[ch] = intensity;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(outColorIndex == 255);
|
|
|
|
if (version == 1) {
|
|
_colors[255 * 3 + 0] = 0;
|
|
_colors[255 * 3 + 1] = 0;
|
|
_colors[255 * 3 + 2] = 0;
|
|
} else {
|
|
_colors[0 * 3 + 0] = 0;
|
|
_colors[0 * 3 + 1] = 0;
|
|
_colors[0 * 3 + 2] = 0;
|
|
_colors[255 * 3 + 0] = 255;
|
|
_colors[255 * 3 + 1] = 255;
|
|
_colors[255 * 3 + 2] = 255;
|
|
}
|
|
}
|
|
|
|
Palette::Palette(const ColorRGB8 *colors) {
|
|
for (int i = 0; i < 256; i++) {
|
|
_colors[i * 3 + 0] = colors[i].r;
|
|
_colors[i * 3 + 1] = colors[i].g;
|
|
_colors[i * 3 + 2] = colors[i].b;
|
|
}
|
|
}
|
|
|
|
const byte *Palette::getPalette() const {
|
|
return _colors;
|
|
}
|
|
|
|
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, ISaveUIProvider *saveProvider, ILoadUIProvider *loadProvider, const Common::SharedPtr<SubtitleRenderer> &subRenderer)
|
|
: _system(system), _mixer(mixer), _saveProvider(saveProvider), _loadProvider(loadProvider),
|
|
_nextRuntimeGUID(1), _realDisplayMode(kColorDepthModeInvalid), _fakeDisplayMode(kColorDepthModeInvalid),
|
|
_displayWidth(1024), _displayHeight(768), _realTime(0), _realTimeBase(0), _playTime(0), _playTimeBase(0), _sceneTransitionState(kSceneTransitionStateNotTransitioning),
|
|
_lastFrameCursor(nullptr), _lastFrameMouseVisible(false), _defaultCursor(new DefaultCursorGraphic()),
|
|
_cachedMousePosition(Common::Point(0, 0)), _realMousePosition(Common::Point(0, 0)), _trackedMouseOutside(false),
|
|
_forceCursorRefreshOnce(true), _autoResetCursor(false), _haveModifierOverrideCursor(false), _haveCursorElement(false), _sceneGraphChanged(false), _isQuitting(false),
|
|
_collisionCheckTime(0), /*_elementCursorUpdateTime(0), */_defaultVolumeState(true), _activeSceneTransitionEffect(nullptr), _sceneTransitionStartTime(0), _sceneTransitionEndTime(0),
|
|
_sharedSceneWasSetExplicitly(false), _modifierOverrideCursorID(0), _subtitleRenderer(subRenderer), _multiClickStartTime(0), _multiClickInterval(500), _multiClickCount(0), _numMouseBlockers(0),
|
|
_pendingSceneReturnCount(0) {
|
|
_random.reset(new Common::RandomSource("mtropolis"));
|
|
|
|
_coroManager.reset(ICoroutineManager::create());
|
|
_vthread.reset(new VThread(_coroManager.get()));
|
|
|
|
for (int i = 0; i < kColorDepthModeCount; i++) {
|
|
_displayModeSupported[i] = false;
|
|
_realDisplayMode = kColorDepthModeInvalid;
|
|
_fakeDisplayMode = kColorDepthModeInvalid;
|
|
}
|
|
|
|
_realTimeBase = system->getMillis();
|
|
_playTimeBase = system->getMillis();
|
|
|
|
for (int i = 0; i < Actions::kMouseButtonCount; i++)
|
|
_mouseFocusFlags[i] = false;
|
|
|
|
_worldManagerInterface.reset(new WorldManagerInterface());
|
|
_worldManagerInterface->setSelfReference(_worldManagerInterface);
|
|
|
|
_assetManagerInterface.reset(new AssetManagerInterface());
|
|
_assetManagerInterface->setSelfReference(_assetManagerInterface);
|
|
|
|
_systemInterface.reset(new SystemInterface());
|
|
_systemInterface->setSelfReference(_systemInterface);
|
|
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribCache] = "cache";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribDirect] = "direct";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribVisible] = "visible";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribLayer] = "layer";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribPaused] = "paused";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribVisible] = "loop";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribPosition] = "position";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribWidth] = "width";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribHeight] = "height";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribRate] = "rate";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribRange] = "range";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribCel] = "cel";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribLoopBackForth] = "loopbackforth";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribPlayEveryFrame] = "playeveryframe";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribTimeValue] = "timevalue";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribTrackDisable] = "trackdisable";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribTrackEnable] = "trackenable";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribVolume] = "volume";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribBalance] = "balance";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribText] = "text";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribMasterVolume] = "mastervolume";
|
|
_getSetAttribIDsToAttribName[AttributeIDs::kAttribUserTimeout] = "usertimeout";
|
|
}
|
|
|
|
Runtime::~Runtime() {
|
|
// Clear the project first, which should detach any references to other things
|
|
unloadProject();
|
|
|
|
_subtitleRenderer.reset();
|
|
}
|
|
|
|
bool Runtime::runFrame() {
|
|
uint32 timeMillis = _system->getMillis();
|
|
|
|
uint32 realMSec = timeMillis - _realTimeBase - _realTime;
|
|
|
|
_realTime = timeMillis - _realTimeBase;
|
|
_playTime = timeMillis - _playTimeBase;
|
|
|
|
for (;;) {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugger && _debugger->isPaused())
|
|
break;
|
|
#endif
|
|
|
|
VThreadState state = _vthread->step();
|
|
if (state != kVThreadReturn) {
|
|
// Still doing blocking tasks
|
|
break;
|
|
}
|
|
|
|
if (_sceneTransitionState != kSceneTransitionStateTransitioning && _osEventQueue.size() > 0) {
|
|
Common::SharedPtr<OSEvent> evt = _osEventQueue[0];
|
|
_osEventQueue.remove_at(0);
|
|
|
|
OSEventType evtType = evt->getEventType();
|
|
switch (evtType) {
|
|
case kOSEventTypeKeyboard:
|
|
if (_project) {
|
|
Common::SharedPtr<KeyEventDispatch> dispatch(new KeyEventDispatch(evt.staticCast<KeyboardInputEvent>()));
|
|
|
|
// We don't want to filter by enabled because of the edge case where a keyboard
|
|
// messenger fires and disables or enables other keyboard messengers.
|
|
// Not sure this is actually possible though... can multiple messengers respond to
|
|
// the same keystroke? Not sure.
|
|
_project->recursiveCollectObjectsMatchingCriteria(dispatch->getKeyboardMessengerArray(), KeyEventDispatch::keyboardMessengerFilterFunc, dispatch.get(), false);
|
|
|
|
if (dispatch->getKeyboardMessengerArray().size() > 0) {
|
|
DispatchKeyTaskData *taskData = _vthread->pushTask("Runtime::dispatchKeyTask", this, &Runtime::dispatchKeyTask);
|
|
taskData->dispatch = dispatch;
|
|
}
|
|
|
|
_project->onKeyboardEvent(this, *static_cast<KeyboardInputEvent *>(evt.get()));
|
|
}
|
|
break;
|
|
case kOSEventTypeMouseDown:
|
|
case kOSEventTypeMouseUp:
|
|
case kOSEventTypeMouseMove: {
|
|
MouseInputEvent *mouseEvt = static_cast<MouseInputEvent *>(evt.get());
|
|
|
|
if (evtType == kOSEventTypeMouseDown) {
|
|
if (_multiClickStartTime + _multiClickInterval <= _playTime) {
|
|
_multiClickStartTime = _playTime;
|
|
_multiClickCount = 1;
|
|
} else
|
|
_multiClickCount++;
|
|
}
|
|
|
|
// Maybe shouldn't post the update mouse button task if non-left buttons are pressed?
|
|
if ((evtType == kOSEventTypeMouseDown || evtType == kOSEventTypeMouseUp) && mouseEvt->getButton() == Actions::kMouseButtonLeft) {
|
|
UpdateMouseStateTaskData *taskData = _vthread->pushTask("Runtime::updateMouseStateTask", this, &Runtime::updateMouseStateTask);
|
|
taskData->mouseDown = (evtType == kOSEventTypeMouseDown);
|
|
}
|
|
|
|
// Pushed second, so this happens first
|
|
if (mouseEvt->getX() != _cachedMousePosition.x || mouseEvt->getY() != _cachedMousePosition.y) {
|
|
UpdateMousePositionTaskData *taskData = _vthread->pushTask("Runtime::updateMousePositionTask", this, &Runtime::updateMousePositionTask);
|
|
taskData->x = mouseEvt->getX();
|
|
taskData->y = mouseEvt->getY();
|
|
}
|
|
} break;
|
|
case kOSEventTypeAction: {
|
|
Actions::Action action = static_cast<ActionEvent *>(evt.get())->getAction();
|
|
|
|
DispatchActionTaskData *taskData = _vthread->pushTask("Runtime::dispatchAction", this, &Runtime::dispatchActionTask);
|
|
taskData->action = action;
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (_forceCursorRefreshOnce) {
|
|
_forceCursorRefreshOnce = false;
|
|
|
|
UpdateMousePositionTaskData *taskData = _vthread->pushTask("Runtime::updateMousePositionTask", this, &Runtime::updateMousePositionTask);
|
|
taskData->x = _cachedMousePosition.x;
|
|
taskData->y = _cachedMousePosition.y;
|
|
continue;
|
|
}
|
|
|
|
if (_queuedProjectDesc) {
|
|
Common::SharedPtr<ProjectDescription> desc = _queuedProjectDesc;
|
|
_queuedProjectDesc.reset();
|
|
|
|
unloadProject();
|
|
|
|
_macFontMan.reset(new Graphics::MacFontManager(0, desc->getLanguage()));
|
|
|
|
_project.reset(new Project(this));
|
|
_project->setSelfReference(_project);
|
|
|
|
_project->loadFromDescription(*desc, getHacks());
|
|
|
|
ensureMainWindowExists();
|
|
|
|
_rootLinkingScope.addObject(_project->getStaticGUID(), _project->getName(), _project);
|
|
|
|
// We have to materialize global variables because they are not cloned from aliases.
|
|
debug(1, "Materializing global variables...");
|
|
_project->materializeGlobalVariables(this, &_rootLinkingScope);
|
|
|
|
debug(1, "Materializing project...");
|
|
_project->materializeSelfAndDescendents(this, &_rootLinkingScope);
|
|
|
|
for (const Common::SharedPtr<Structural> §ion : _project->getChildren()) {
|
|
for (const Common::SharedPtr<Structural> &subsection : section->getChildren()) {
|
|
for (const Common::SharedPtr<Structural> &scene : subsection->getChildren())
|
|
scene->setSceneLoadState(Structural::SceneLoadState::kSceneNotLoaded);
|
|
}
|
|
}
|
|
|
|
debug(1, "Project is fully loaded! Starting up...");
|
|
|
|
if (_project->getChildren().size() == 0) {
|
|
error("Project has no sections");
|
|
}
|
|
|
|
Structural *firstSection = _project->getChildren()[0].get();
|
|
if (firstSection->getChildren().size() == 0) {
|
|
error("Project has no subsections");
|
|
}
|
|
|
|
Structural *firstSubsection = firstSection->getChildren()[0].get();
|
|
if (firstSubsection->getChildren().size() < 2) {
|
|
error("Project has no subsections");
|
|
}
|
|
|
|
Common::SharedPtr<MessageProperties> psProps(new MessageProperties(Event(EventIDs::kProjectStarted, 0), DynamicValue(), _project->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> psDispatch(new MessageDispatch(psProps, _project.get(), false, true, false));
|
|
queueMessage(psDispatch);
|
|
|
|
for (const Common::SharedPtr<SceneTransitionHooks> &hook : _hacks.sceneTransitionHooks)
|
|
hook->onProjectStarted(this);
|
|
|
|
_pendingSceneTransitions.push_back(HighLevelSceneTransition(firstSubsection->getChildren()[1], HighLevelSceneTransition::kTypeChangeToScene, false, false));
|
|
continue;
|
|
}
|
|
|
|
// The order of operations for dynamic object behaviors is:
|
|
// - Parent changes
|
|
// - Parent Enabled -> Clone for each cloned object
|
|
// - Show for each cloned object that is visible
|
|
// - Hide -> Kill -> Parent Disabled for each killed object
|
|
if (_pendingParentChanges.size() > 0) {
|
|
ObjectParentChange parentChange = _pendingParentChanges.remove_at(0);
|
|
|
|
RuntimeObject *obj = parentChange._object.lock().get();
|
|
RuntimeObject *newParent = parentChange._newParent.lock().get();
|
|
|
|
if (obj) {
|
|
if (newParent)
|
|
executeChangeObjectParent(obj, newParent);
|
|
else
|
|
warning("Object re-parenting failed, the new parent was invalid!");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (_pendingClones.size() > 0) {
|
|
RuntimeObject *objectToClone = _pendingClones.remove_at(0).lock().get();
|
|
|
|
if (objectToClone)
|
|
executeCloneObject(objectToClone);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (_pendingShowClonedObject.size() > 0) {
|
|
Structural *objectToShow = _pendingShowClonedObject.remove_at(0).lock().get();
|
|
|
|
if (objectToShow && objectToShow->isElement() && static_cast<Element *>(objectToShow)->isVisual() && static_cast<VisualElement *>(objectToShow)->isVisible()) {
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementShow, 0), DynamicValue(), objectToShow->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, objectToShow, false, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (_pendingKills.size() > 0) {
|
|
RuntimeObject *objectToKill = _pendingKills.remove_at(0).lock().get();
|
|
|
|
if (objectToKill)
|
|
executeKillObject(objectToKill);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Teardowns must only occur during idle conditions where there are no VThread tasks
|
|
if (_pendingTeardowns.size() > 0) {
|
|
for (Common::Array<Teardown>::const_iterator it = _pendingTeardowns.begin(), itEnd = _pendingTeardowns.end(); it != itEnd; ++it) {
|
|
executeTeardown(*it);
|
|
}
|
|
_pendingTeardowns.clear();
|
|
_sceneGraphChanged = true;
|
|
continue;
|
|
}
|
|
|
|
if (_pendingLowLevelTransitions.size() > 0) {
|
|
LowLevelSceneStateTransitionAction transition = _pendingLowLevelTransitions[0];
|
|
_pendingLowLevelTransitions.remove_at(0);
|
|
|
|
executeLowLevelSceneStateTransition(transition);
|
|
continue;
|
|
}
|
|
|
|
// This has to be in this specific spot: Queued messages that occur from scene transitions are normally discharged
|
|
// after Scene Started, Scene Changed, and element Show events, but before scene transition.
|
|
//
|
|
// Obsidian depends on this behavior in several scripts, most notably setting up conditional ambience correctly.
|
|
// For example, in Inspiration chapter, on exiting the plane into the statue:
|
|
// Shared scene fires Parent Enabled which triggers "GEN_SND_Start_Ambience on PE" which sends GEN_SND_Start_Ambience
|
|
// but not immediately, so it goes into the queue.
|
|
// After the main scene loads, it fires Scene Started and which in turn triggers "NAV_setup_navigation on SS" which
|
|
// sends NAV_setup_navigation immediately, which sets the current nav node variable.
|
|
// Then, the queued GEN_SND_Start_Ambience can read the nav node variable to set up ambience correctly.
|
|
//
|
|
// If messages are discharged before low-level scene transitions, then the music plays in the lower level of the
|
|
// statue on disembarking because the nav node variable is set to the wrong value.
|
|
if (_messageQueue.size() > 0) {
|
|
Common::SharedPtr<MessageDispatch> msg = _messageQueue[0];
|
|
_messageQueue.remove_at(0);
|
|
|
|
sendMessageOnVThread(msg);
|
|
continue;
|
|
}
|
|
|
|
// Scene returns are processed before transitions even if the transition was
|
|
// executed first. SPQR requires this behavior to properly return from the
|
|
// menu, since it sets a scene change via WorldManager to the restored scene
|
|
// and then triggers a Return modifier which would return to the last scene
|
|
// that added to the return list, which in this case is the scene that was
|
|
// active before opening the menu.
|
|
if (_pendingSceneReturnCount > 0) {
|
|
_pendingSceneReturnCount--;
|
|
|
|
executeHighLevelSceneReturn();
|
|
_sceneGraphChanged = true;
|
|
continue;
|
|
}
|
|
|
|
if (_pendingSceneTransitions.size() > 0) {
|
|
HighLevelSceneTransition transition = _pendingSceneTransitions[0];
|
|
_pendingSceneTransitions.remove_at(0);
|
|
|
|
executeHighLevelSceneTransition(transition);
|
|
_sceneGraphChanged = true;
|
|
continue;
|
|
}
|
|
|
|
if (_sceneTransitionState == kSceneTransitionStateWaitingForDraw) {
|
|
if (_sourceSceneTransitionEffect._transitionType != SceneTransitionTypes::kNone)
|
|
_activeSceneTransitionEffect = &_sourceSceneTransitionEffect;
|
|
else if (_destinationSceneTransitionEffect._transitionType != SceneTransitionTypes::kNone)
|
|
_activeSceneTransitionEffect = &_destinationSceneTransitionEffect;
|
|
else
|
|
_activeSceneTransitionEffect = nullptr;
|
|
|
|
_sceneTransitionState = kSceneTransitionStateTransitioning;
|
|
_sceneTransitionStartTime = _playTime;
|
|
|
|
uint32 transitionDuration = 0;
|
|
|
|
if (_activeSceneTransitionEffect) {
|
|
transitionDuration = _activeSceneTransitionEffect->_duration;
|
|
|
|
if (transitionDuration < _hacks.minTransitionDuration)
|
|
transitionDuration = _hacks.minTransitionDuration;
|
|
}
|
|
|
|
if (transitionDuration == 0) {
|
|
// No transition at all. This needs to skip past the transition phase and hit the next condition
|
|
_sceneTransitionEndTime = _playTime;
|
|
} else {
|
|
_sceneTransitionEndTime = _playTime + transitionDuration;
|
|
|
|
if (!_mainWindow.expired()) {
|
|
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
|
|
_sceneTransitionOldFrame.reset(new Graphics::ManagedSurface());
|
|
_sceneTransitionNewFrame.reset(new Graphics::ManagedSurface());
|
|
|
|
_sceneTransitionOldFrame->copyFrom(*mainWindow->getSurface());
|
|
|
|
Render::renderProject(this, mainWindow.get(), nullptr);
|
|
|
|
_sceneTransitionNewFrame->copyFrom(*mainWindow->getSurface());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_sceneTransitionState == kSceneTransitionStateTransitioning && _playTime >= _sceneTransitionEndTime) {
|
|
_sceneTransitionState = kSceneTransitionStateNotTransitioning;
|
|
|
|
if (_sceneTransitionNewFrame && !_mainWindow.expired())
|
|
_mainWindow.lock()->getSurface()->copyFrom(*_sceneTransitionNewFrame);
|
|
|
|
_sceneTransitionOldFrame.reset();
|
|
_sceneTransitionNewFrame.reset();
|
|
|
|
_sourceSceneTransitionEffect = SceneTransitionEffect();
|
|
_destinationSceneTransitionEffect = SceneTransitionEffect();
|
|
|
|
for (const SceneStackEntry &sceneStackEntry : _sceneStack)
|
|
recursiveAutoPlayMedia(sceneStackEntry.scene.get());
|
|
|
|
for (const Common::SharedPtr<SceneTransitionHooks> &hooks : _hacks.sceneTransitionHooks)
|
|
hooks->onSceneTransitionEnded(this, _activeMainScene);
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneTransitionEnded, 0), _activeMainScene.get(), true, true);
|
|
continue;
|
|
}
|
|
|
|
if (_sceneTransitionState == kSceneTransitionStateTransitioning) {
|
|
// Keep looping transition and don't do anything else until it's done
|
|
break;
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<ScheduledEvent> firstScheduledEvent = _scheduler.getFirstEvent();
|
|
if (firstScheduledEvent && firstScheduledEvent->getScheduledTime() <= _playTime) {
|
|
_scheduler.descheduleFirstEvent();
|
|
|
|
firstScheduledEvent->activate(this);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (_collisionCheckTime < _playTime) {
|
|
_collisionCheckTime = _playTime;
|
|
|
|
checkBoundaries();
|
|
checkCollisions(nullptr);
|
|
continue;
|
|
}
|
|
|
|
// Reset cursor if the cursor element is gone
|
|
if (_haveCursorElement && _elementTrackedToCursor.expired())
|
|
updateMainWindowCursor();
|
|
|
|
if (_isQuitting)
|
|
return false;
|
|
|
|
// Ran out of actions
|
|
break;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugger)
|
|
_debugger->runFrame(realMSec);
|
|
#else
|
|
(void)realMSec;
|
|
#endif
|
|
|
|
// Frame completed
|
|
return true;
|
|
}
|
|
|
|
struct WindowSortingBucket {
|
|
size_t originalIndex;
|
|
Window *window;
|
|
|
|
static bool sortPredicate(const WindowSortingBucket &a, const WindowSortingBucket &b) {
|
|
int aStrata = a.window->getStrata();
|
|
int bStrata = b.window->getStrata();
|
|
|
|
if (aStrata != bStrata)
|
|
return aStrata < bStrata;
|
|
|
|
return a.originalIndex < b.originalIndex;
|
|
}
|
|
};
|
|
|
|
void Runtime::drawFrame() {
|
|
int width = _system->getWidth();
|
|
int height = _system->getHeight();
|
|
|
|
_system->fillScreen(Render::resolveRGB(0, 0, 0, getRenderPixelFormat()));
|
|
|
|
bool needToRenderSubtitles = false;
|
|
if (_subtitleRenderer && _subtitleRenderer->update(_playTime))
|
|
setSceneGraphDirty();
|
|
|
|
{
|
|
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
|
|
if (mainWindow) {
|
|
if (_sceneTransitionState == kSceneTransitionStateTransitioning) {
|
|
assert(_activeSceneTransitionEffect != nullptr);
|
|
Render::renderSceneTransition(this, mainWindow.get(), *_activeSceneTransitionEffect, _sceneTransitionStartTime, _sceneTransitionEndTime, _playTime, *_sceneTransitionOldFrame, *_sceneTransitionNewFrame);
|
|
needToRenderSubtitles = true;
|
|
} else {
|
|
bool skipped = false;
|
|
Render::renderProject(this, mainWindow.get(), &skipped);
|
|
|
|
needToRenderSubtitles = !skipped;
|
|
}
|
|
|
|
if (needToRenderSubtitles && _subtitleRenderer)
|
|
_subtitleRenderer->composite(*mainWindow);
|
|
}
|
|
}
|
|
|
|
const size_t numWindows = _windows.size();
|
|
WindowSortingBucket singleBucket[1];
|
|
Common::Array<WindowSortingBucket> multipleBuckets;
|
|
WindowSortingBucket *sortedBuckets = singleBucket;
|
|
|
|
if (numWindows < 2) {
|
|
sortedBuckets = singleBucket;
|
|
|
|
singleBucket[0].originalIndex = 0;
|
|
singleBucket[0].window = _windows[0].get();
|
|
} else {
|
|
multipleBuckets.resize(numWindows);
|
|
sortedBuckets = &multipleBuckets[0];
|
|
|
|
for (size_t i = 0; i < numWindows; i++) {
|
|
sortedBuckets[i].originalIndex = i;
|
|
sortedBuckets[i].window = _windows[i].get();
|
|
}
|
|
|
|
Common::sort(sortedBuckets, sortedBuckets + numWindows, WindowSortingBucket::sortPredicate);
|
|
}
|
|
|
|
for (size_t i = 0; i < numWindows; i++) {
|
|
const Window &window = *sortedBuckets[i].window;
|
|
const Graphics::ManagedSurface &surface = *window.getSurface();
|
|
|
|
int32 destLeft = window.getX();
|
|
int32 destRight = destLeft + surface.w;
|
|
int32 destTop = window.getY();
|
|
int32 destBottom = destTop + surface.h;
|
|
|
|
int32 srcLeft = 0;
|
|
//int32 srcRight = surface.w;
|
|
int32 srcTop = 0;
|
|
//int32 srcBottom = surface.h;
|
|
|
|
// Clip to drawable area
|
|
if (destLeft < 0) {
|
|
int leftAdjust = -destLeft;
|
|
destLeft += leftAdjust;
|
|
srcLeft += leftAdjust;
|
|
}
|
|
|
|
if (destTop < 0) {
|
|
int topAdjust = -destTop;
|
|
destTop += topAdjust;
|
|
srcTop += topAdjust;
|
|
}
|
|
|
|
if (destRight > width) {
|
|
int rightAdjust = width - destRight;
|
|
destRight += rightAdjust;
|
|
//srcRight += rightAdjust;
|
|
}
|
|
|
|
if (destBottom > height) {
|
|
int bottomAdjust = height - destBottom;
|
|
destBottom += bottomAdjust;
|
|
//srcBottom += bottomAdjust;
|
|
}
|
|
|
|
if (destLeft >= destRight || destTop >= destBottom || destLeft >= width || destTop >= height || destRight <= 0 || destBottom <= 0)
|
|
continue;
|
|
|
|
_system->copyRectToScreen(surface.getBasePtr(srcLeft, srcTop), surface.pitch, destLeft, destTop, destRight - destLeft, destBottom - destTop);
|
|
}
|
|
|
|
_system->updateScreen();
|
|
|
|
Common::SharedPtr<CursorGraphic> cursor;
|
|
bool mouseVisible = true;
|
|
|
|
Common::SharedPtr<Window> focusWindow = _mouseFocusWindow.lock();
|
|
|
|
if (!focusWindow)
|
|
focusWindow = findTopWindow(_realMousePosition.x, _realMousePosition.y);
|
|
|
|
if (focusWindow) {
|
|
cursor = focusWindow->getCursorGraphic();
|
|
mouseVisible = focusWindow->getMouseVisible();
|
|
}
|
|
|
|
if (!cursor)
|
|
cursor = _defaultCursor;
|
|
|
|
if ((cursor != _lastFrameCursor || !_lastFrameMouseVisible) && mouseVisible) {
|
|
CursorMan.showMouse(true);
|
|
CursorMan.replaceCursor(cursor->getCursor());
|
|
|
|
_lastFrameCursor = cursor;
|
|
_lastFrameMouseVisible = true;
|
|
} else if (_lastFrameMouseVisible && !mouseVisible) {
|
|
CursorMan.showMouse(false);
|
|
_lastFrameMouseVisible = false;
|
|
}
|
|
|
|
if (_project)
|
|
_project->onPostRender();
|
|
}
|
|
|
|
Common::SharedPtr<Structural> Runtime::findDefaultSharedSceneForScene(Structural *scene) {
|
|
Structural *subsection = scene->getParent();
|
|
|
|
const Common::Array<Common::SharedPtr<Structural> > &children = subsection->getChildren();
|
|
if (children.size() == 0 || children[0].get() == scene)
|
|
return Common::SharedPtr<Structural>();
|
|
|
|
return children[0];
|
|
}
|
|
|
|
void Runtime::executeTeardown(const Teardown &teardown) {
|
|
if (Common::SharedPtr<Structural> structural = teardown.structural.lock()) {
|
|
recursiveDeactivateStructural(structural.get());
|
|
|
|
if (teardown.onlyRemoveChildren) {
|
|
structural->removeAllChildren();
|
|
structural->removeAllModifiers();
|
|
structural->removeAllAssets();
|
|
|
|
assert(structural->getSceneLoadState() == Structural::SceneLoadState::kSceneLoaded);
|
|
structural->setSceneLoadState(Structural::SceneLoadState::kSceneNotLoaded);
|
|
} else {
|
|
Structural *parent = structural->getParent();
|
|
|
|
// Nothing should be holding strong references to structural objects after they're removed from the project
|
|
assert(parent != nullptr);
|
|
|
|
if (!parent) {
|
|
return; // Already unlinked but still alive somehow
|
|
}
|
|
|
|
parent->removeChild(structural.get());
|
|
|
|
structural->setParent(nullptr);
|
|
}
|
|
}
|
|
|
|
if (Common::SharedPtr<Modifier> modifier = teardown.modifier.lock()) {
|
|
IModifierContainer *container = nullptr;
|
|
RuntimeObject *parent = modifier->getParent().lock().get();
|
|
|
|
if (parent) {
|
|
if (parent->isStructural())
|
|
container = static_cast<Structural *>(parent);
|
|
else if (parent->isModifier())
|
|
container = static_cast<Modifier *>(parent)->getChildContainer();
|
|
}
|
|
|
|
if (container)
|
|
container->removeModifier(modifier.get());
|
|
}
|
|
}
|
|
|
|
void Runtime::executeLowLevelSceneStateTransition(const LowLevelSceneStateTransitionAction &action) {
|
|
switch (action.getActionType()) {
|
|
case LowLevelSceneStateTransitionAction::kSendMessage:
|
|
sendMessageOnVThread(action.getMessage());
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kLoad:
|
|
loadScene(action.getScene());
|
|
|
|
// Refresh play time so anything time-based doesn't skip ahead
|
|
refreshPlayTime();
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kUnload: {
|
|
Teardown teardown;
|
|
teardown.onlyRemoveChildren = true;
|
|
teardown.structural = action.getScene();
|
|
|
|
_pendingTeardowns.push_back(teardown);
|
|
} break;
|
|
case LowLevelSceneStateTransitionAction::kAutoResetCursor:
|
|
if (_autoResetCursor) {
|
|
clearModifierCursorOverride();
|
|
forceCursorRefreshOnce();
|
|
}
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kShowDefaultVisibleElements:
|
|
executeSceneChangeRecursiveVisibilityChange(action.getScene().get(), true);
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kHideAllElements:
|
|
executeSceneChangeRecursiveVisibilityChange(action.getScene().get(), false);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Runtime::executeSceneChangeRecursiveVisibilityChange(Structural *structural, bool showing) {
|
|
const Common::Array<Common::SharedPtr<Structural> > &children = structural->getChildren();
|
|
|
|
// Queue in reverse order since VThread sends are LIFO
|
|
for (size_t i = 0; i < children.size(); i++)
|
|
executeSceneChangeRecursiveVisibilityChange(children[children.size() - 1 - i].get(), showing);
|
|
|
|
if (structural->isElement() && static_cast<Element *>(structural)->isVisual()) {
|
|
VisualElement *visual = static_cast<VisualElement *>(structural);
|
|
|
|
ApplyDefaultVisibilityTaskData *taskData = getVThread().pushTask("Runtime::applyDefaultVisibility", this, &Runtime::applyDefaultVisibility);
|
|
taskData->element = visual;
|
|
taskData->targetVisibility = showing;
|
|
}
|
|
}
|
|
|
|
void Runtime::executeChangeObjectParent(RuntimeObject *object, RuntimeObject *newParent) {
|
|
// TODO: Should do circularity checks
|
|
|
|
if (object->isModifier()) {
|
|
Common::SharedPtr<Modifier> modifier = object->getSelfReference().lock().staticCast<Modifier>();
|
|
|
|
IModifierContainer *oldParentContainer = nullptr;
|
|
|
|
RuntimeObject *oldParent = modifier->getParent().lock().get();
|
|
|
|
if (oldParent == newParent)
|
|
return;
|
|
|
|
if (oldParent->isStructural())
|
|
oldParentContainer = static_cast<Structural *>(oldParent);
|
|
else if (oldParent->isModifier())
|
|
oldParentContainer = static_cast<Modifier *>(oldParent)->getChildContainer();
|
|
|
|
IModifierContainer *newParentContainer = nullptr;
|
|
if (newParent->isStructural())
|
|
newParentContainer = static_cast<Structural *>(newParent);
|
|
else if (newParent->isModifier())
|
|
newParentContainer = static_cast<Modifier *>(newParent)->getChildContainer();
|
|
|
|
if (!newParentContainer) {
|
|
warning("Object re-parent failed, the new parent isn't a modifier container");
|
|
return;
|
|
}
|
|
|
|
oldParentContainer->removeModifier(modifier.get());
|
|
newParentContainer->appendModifier(modifier);
|
|
|
|
modifier->setParent(newParent->getSelfReference());
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentChanged, 0), DynamicValue(), modifier->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier.get(), false, false, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
}
|
|
|
|
if (object->isStructural()) {
|
|
Common::SharedPtr<Structural> structural = object->getSelfReference().lock().staticCast<Structural>();
|
|
|
|
Structural *oldParent = structural->getParent();
|
|
|
|
if (oldParent == newParent)
|
|
return;
|
|
|
|
if (!newParent->isStructural()) {
|
|
warning("Object re-parent failed, the new parent isn't structural");
|
|
return;
|
|
}
|
|
|
|
Structural *newParentStructural = static_cast<Structural *>(newParent);
|
|
|
|
oldParent->removeChild(structural.get());
|
|
newParentStructural->addChild(structural);
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentChanged, 0), DynamicValue(), structural->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural.get(), false, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::executeCloneObject(RuntimeObject *object) {
|
|
Common::HashMap<RuntimeObject *, RuntimeObject *> objectRemaps;
|
|
|
|
if (object->isModifier()) {
|
|
Common::SharedPtr<Modifier> modifierRef = object->getSelfReference().lock().staticCast<Modifier>();
|
|
Common::WeakPtr<RuntimeObject> relinkParent = modifierRef->getParent();
|
|
|
|
ObjectCloner cloner(this, relinkParent, &objectRemaps);
|
|
cloner.visitChildModifierRef(modifierRef);
|
|
|
|
ObjectRefRemapper remapper(objectRemaps);
|
|
remapper.visitChildModifierRef(modifierRef);
|
|
|
|
IModifierContainer *container = nullptr;
|
|
Common::SharedPtr<RuntimeObject> parent = relinkParent.lock();
|
|
if (parent) {
|
|
if (parent->isStructural())
|
|
container = static_cast<Structural *>(parent.get());
|
|
else if (parent->isModifier())
|
|
container = static_cast<Modifier *>(parent.get())->getChildContainer();
|
|
}
|
|
|
|
if (container)
|
|
container->appendModifier(modifierRef);
|
|
else
|
|
error("Internal error: Cloned a modifier, but the parent isn't a modifier container");
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kClone, 0), DynamicValue(), modifierRef));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifierRef.get(), true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentEnabled, 0), DynamicValue(), modifierRef));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifierRef.get(), true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
} else if (object->isStructural()) {
|
|
Common::SharedPtr<Structural> structuralRef = object->getSelfReference().lock().staticCast<Structural>();
|
|
Common::WeakPtr<RuntimeObject> relinkParent = structuralRef->getParent()->getSelfReference();
|
|
|
|
ObjectCloner cloner(this, relinkParent, &objectRemaps);
|
|
cloner.visitChildStructuralRef(structuralRef);
|
|
|
|
ObjectRefRemapper remapper(objectRemaps);
|
|
remapper.visitChildStructuralRef(structuralRef);
|
|
|
|
structuralRef->getParent()->addChild(structuralRef);
|
|
|
|
_pendingPostCloneShowChecks.push_back(structuralRef);
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kClone, 0), DynamicValue(), structuralRef));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structuralRef.get(), true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentEnabled, 0), DynamicValue(), structuralRef));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structuralRef.get(), true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
} else
|
|
error("Internal error: Cloned something unusual");
|
|
}
|
|
|
|
void Runtime::executeKillObject(RuntimeObject *object) {
|
|
// TODO: Should do circularity checks
|
|
|
|
if (object->isModifier()) {
|
|
Modifier *modifier = static_cast<Modifier *>(object);
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentDisabled, 0), DynamicValue(), modifier->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier, true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kKill, 0), DynamicValue(), modifier->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, modifier, true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
Teardown teardown;
|
|
teardown.modifier = modifier->getSelfReference().lock().staticCast<Modifier>();
|
|
|
|
_pendingTeardowns.push_back(teardown);
|
|
}
|
|
|
|
if (object->isStructural()) {
|
|
Structural *structural = static_cast<Structural *>(object);
|
|
|
|
// Task order is LIFO, so order is Hide -> Kill -> Parent Disabled
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kParentDisabled, 0), DynamicValue(), structural->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural, true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kKill, 0), DynamicValue(), structural->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, structural, true, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
if (structural->isElement() && static_cast<Element *>(structural)->isVisual())
|
|
static_cast<VisualElement *>(structural)->pushVisibilityChangeTask(this, false);
|
|
|
|
Teardown teardown;
|
|
teardown.structural = structural->getSelfReference().lock().staticCast<Structural>();
|
|
|
|
_pendingTeardowns.push_back(teardown);
|
|
}
|
|
}
|
|
|
|
void Runtime::executeCompleteTransitionToScene(const Common::SharedPtr<Structural> &targetScene) {
|
|
// NOTE: Transitioning to the same scene is allowed, Obsidian relies on this to avoid getting stuck
|
|
// after going up the wrong side in the Bureau chapter final area (i.e. after reaching the sky face).
|
|
|
|
if (_sceneStack.size() == 0)
|
|
_sceneStack.resize(1); // Reserve shared scene slot
|
|
|
|
Common::SharedPtr<Structural> targetSharedScene;
|
|
|
|
if (_sharedSceneWasSetExplicitly)
|
|
targetSharedScene = _activeSharedScene;
|
|
else
|
|
targetSharedScene = findDefaultSharedSceneForScene(targetScene.get());
|
|
|
|
for (const Common::SharedPtr<SceneTransitionHooks> &hooks : _hacks.sceneTransitionHooks)
|
|
hooks->onSceneTransitionSetup(this, _activeMainScene, targetScene);
|
|
|
|
if (targetScene == targetSharedScene)
|
|
error("Transitioned into a default shared scene, this is not supported");
|
|
|
|
if (_activeMainScene == targetSharedScene)
|
|
error("Transitioned into scene currently being used as a target scene, this is not supported");
|
|
|
|
for (size_t i = _sceneStack.size() - 1; i > 0; i--) {
|
|
Common::SharedPtr<Structural> stackedScene = _sceneStack[i].scene;
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneEnded, 0), _activeMainScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kHideAllElements));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentDisabled, 0), _activeMainScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
|
|
if (stackedScene == targetSharedScene)
|
|
error("Transitioned to a shared scene which was already on the stack as a normal scene. This is not supported.");
|
|
|
|
_sceneStack.remove_at(i);
|
|
}
|
|
|
|
if (targetSharedScene != _activeSharedScene) {
|
|
if (_activeSharedScene) {
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneEnded, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kHideAllElements));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentDisabled, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeSharedScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), targetSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), targetSharedScene.get(), true, true);
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kShowDefaultVisibleElements));
|
|
|
|
SceneStackEntry sharedSceneEntry;
|
|
sharedSceneEntry.scene = targetSharedScene;
|
|
|
|
_sceneStack[0] = sharedSceneEntry;
|
|
}
|
|
|
|
{
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), targetScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), targetScene.get(), true, true);
|
|
|
|
SceneStackEntry sceneEntry;
|
|
sceneEntry.scene = targetScene;
|
|
|
|
_sceneStack.push_back(sceneEntry);
|
|
}
|
|
|
|
// This might not be exactly where this belongs but it must go after Parent Enabled for sure, otherwise
|
|
// the wave puzzle in the Mac version of Obsidian completes instantly because Parent Enabled hasn't had
|
|
// a chance to clear the flags. It looks like what's supposed to happen is the cursor override gets
|
|
// cleared by the scene transition starting and the cursor reset happens after. Fix this later...
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetScene, LowLevelSceneStateTransitionAction::kAutoResetCursor));
|
|
|
|
_activeMainScene = targetScene;
|
|
_activeSharedScene = targetSharedScene;
|
|
|
|
// Scene transitions have to be set up by the destination scene
|
|
_sceneTransitionState = kSceneTransitionStateWaitingForDraw;
|
|
_activeSceneTransitionEffect = nullptr;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
}
|
|
|
|
void Runtime::executeHighLevelSceneReturn() {
|
|
if (_sceneStack.size() == 0)
|
|
_sceneStack.resize(1); // Reserve shared scene slot
|
|
|
|
if (_sceneReturnList.size() == 0) {
|
|
warning("A scene return was requested, but no scenes are in the scene return list");
|
|
return;
|
|
}
|
|
|
|
const SceneReturnListEntry &sceneReturn = _sceneReturnList.back();
|
|
|
|
if (sceneReturn.scene == _activeSharedScene)
|
|
error("Transitioned into the active shared scene as the main scene, this is not supported");
|
|
|
|
if (sceneReturn.scene != _activeMainScene) {
|
|
assert(_activeMainScene.get() != nullptr); // There should always be an active main scene after the first transition
|
|
|
|
if (sceneReturn.isAddToDestinationSceneTransition) {
|
|
// In this case we unload the active main scene and reactivate the old main
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneEnded, 0), _activeMainScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentDisabled, 0), _activeMainScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneReactivated, 0), sceneReturn.scene.get(), true, true);
|
|
|
|
for (uint i = 1; i < _sceneStack.size(); i++) {
|
|
if (_sceneStack[i].scene == _activeMainScene) {
|
|
_sceneStack.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_activeMainScene = sceneReturn.scene;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
} else {
|
|
executeCompleteTransitionToScene(sceneReturn.scene);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::executeHighLevelSceneTransition(const HighLevelSceneTransition &transition) {
|
|
if (_sceneStack.size() == 0)
|
|
_sceneStack.resize(1); // Reserve shared scene slot
|
|
|
|
// This replicates a bunch of quirks/bugs of mTropolis's scene transition logic,
|
|
// see the accompanying notes file. There are probably some missing cases related to
|
|
// shared scene, calling return/scene transitions during scene deactivation, or other
|
|
// edge cases that hopefully no games actually do!
|
|
switch (transition.type) {
|
|
case HighLevelSceneTransition::kTypeChangeToScene: {
|
|
const Common::SharedPtr<Structural> targetScene = transition.scene;
|
|
|
|
// This check may not be accurate, but we need to avoid adding the existing scene to the return list.
|
|
// SPQR depends on this behavior: Hitting Esc while in the menu will fire off another transition to
|
|
// the menu scene with addToReturnList set. We want to avoid adding the menu to the return list
|
|
// multiple times since it will prevent the exit button from working properly.
|
|
if ((transition.addToDestinationScene || transition.addToReturnList) && targetScene != _activeMainScene) {
|
|
SceneReturnListEntry returnListEntry;
|
|
returnListEntry.isAddToDestinationSceneTransition = transition.addToDestinationScene;
|
|
returnListEntry.scene = _activeMainScene;
|
|
_sceneReturnList.push_back(returnListEntry);
|
|
}
|
|
|
|
if (transition.addToDestinationScene) {
|
|
if (targetScene != _activeMainScene) {
|
|
Common::SharedPtr<Structural> targetSharedScene;
|
|
|
|
if (_sharedSceneWasSetExplicitly)
|
|
targetSharedScene = _activeSharedScene;
|
|
else
|
|
targetSharedScene = findDefaultSharedSceneForScene(targetScene.get());
|
|
|
|
if (targetScene == targetSharedScene)
|
|
error("Transitioned into a default shared scene, this is not supported");
|
|
|
|
if (_activeMainScene == targetSharedScene)
|
|
error("Transitioned into scene currently being used as a target scene, this is not supported");
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneDeactivated, 0), _activeMainScene.get(), true, true);
|
|
|
|
if (targetSharedScene != _activeSharedScene) {
|
|
if (_activeSharedScene) {
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneEnded, 0), _activeSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentDisabled, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeSharedScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), targetSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), targetSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kShowDefaultVisibleElements));
|
|
|
|
SceneStackEntry sharedSceneEntry;
|
|
sharedSceneEntry.scene = targetScene;
|
|
|
|
_sceneStack[0] = sharedSceneEntry;
|
|
}
|
|
|
|
|
|
bool sceneAlreadyInStack = false;
|
|
for (size_t i = _sceneStack.size() - 1; i > 0; i--) {
|
|
Common::SharedPtr<Structural> stackedScene = _sceneStack[i].scene;
|
|
if (stackedScene == targetScene) {
|
|
sceneAlreadyInStack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This is probably wrong if it's already in the stack, but transitioning to already-in-stack scenes is extremely buggy in mTropolis Player anyway
|
|
if (!sceneAlreadyInStack) {
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), targetScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), targetScene.get(), true, true);
|
|
|
|
SceneStackEntry sceneEntry;
|
|
sceneEntry.scene = targetScene;
|
|
|
|
_sceneStack.push_back(sceneEntry);
|
|
}
|
|
|
|
_activeMainScene = targetScene;
|
|
_activeSharedScene = targetSharedScene;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
}
|
|
} else {
|
|
executeCompleteTransitionToScene(targetScene);
|
|
}
|
|
} break;
|
|
case HighLevelSceneTransition::kTypeChangeSharedScene: {
|
|
Common::SharedPtr<Structural> targetSharedScene = transition.scene;
|
|
|
|
if (targetSharedScene != _activeSharedScene) {
|
|
if (_activeSharedScene) {
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneEnded, 0), _activeSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentDisabled, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeSharedScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), targetSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), targetSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kShowDefaultVisibleElements));
|
|
|
|
SceneStackEntry sharedSceneEntry;
|
|
sharedSceneEntry.scene = targetSharedScene;
|
|
|
|
_sceneStack[0] = sharedSceneEntry;
|
|
|
|
_activeSharedScene = targetSharedScene;
|
|
}
|
|
|
|
_sharedSceneWasSetExplicitly = true;
|
|
} break;
|
|
case HighLevelSceneTransition::kTypeForceLoadScene: {
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(transition.scene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kParentEnabled, 0), transition.scene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSceneStarted, 0), transition.scene.get(), true, true);
|
|
} break;
|
|
case HighLevelSceneTransition::kTypeRequestUnloadScene: {
|
|
bool isActiveScene = false;
|
|
for (const SceneStackEntry &stackEntry : _sceneStack) {
|
|
if (stackEntry.scene == transition.scene) {
|
|
isActiveScene = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isActiveScene)
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(transition.scene, LowLevelSceneStateTransitionAction::kUnload));
|
|
} break;
|
|
default:
|
|
error("Unknown high-level scene transition type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Runtime::executeSharedScenePostSceneChangeActions() {
|
|
Structural *subsection = _activeMainScene->getParent();
|
|
|
|
const Common::Array<Common::SharedPtr<Structural> > &subsectionScenes = subsection->getChildren();
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSharedSceneSceneChanged, 0), _activeSharedScene.get(), true, true);
|
|
if (subsectionScenes.size() > 1) {
|
|
if (_activeMainScene == subsectionScenes[subsectionScenes.size() - 1])
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSharedSceneNoNextScene, 0), _activeSharedScene.get(), true, true);
|
|
if (_activeMainScene == subsectionScenes[1])
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event(EventIDs::kSharedSceneNoPrevScene, 0), _activeSharedScene.get(), true, true);
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kShowDefaultVisibleElements));
|
|
}
|
|
|
|
void Runtime::recursiveDeactivateStructural(Structural *structural) {
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren()) {
|
|
recursiveDeactivateStructural(child.get());
|
|
}
|
|
|
|
structural->deactivate();
|
|
}
|
|
|
|
void Runtime::recursiveAutoPlayMedia(Structural *structural) {
|
|
if (structural->isElement())
|
|
static_cast<Element *>(structural)->triggerAutoPlay(this);
|
|
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren()) {
|
|
recursiveAutoPlayMedia(child.get());
|
|
}
|
|
}
|
|
|
|
void Runtime::recursiveActivateStructural(Structural *structural) {
|
|
structural->activate();
|
|
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren()) {
|
|
recursiveActivateStructural(child.get());
|
|
}
|
|
}
|
|
|
|
bool Runtime::isStructuralMouseInteractive(Structural *structural, MouseInteractivityTestType testType) {
|
|
if (structural->isElement()) {
|
|
Element *element = static_cast<Element *>(structural);
|
|
if (element->isVisual()) {
|
|
VisualElement *visual = static_cast<VisualElement *>(element);
|
|
if (visual->getDragMotionProperties())
|
|
return true; // Drag motion is always mouse interactive
|
|
}
|
|
}
|
|
|
|
for (const Common::SharedPtr<Modifier> &modifier : structural->getModifiers()) {
|
|
if (isModifierMouseInteractive(modifier.get(), testType))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Runtime::isModifierMouseInteractive(Modifier *modifier, MouseInteractivityTestType testType) {
|
|
static const EventIDs::EventID allEventIDs[] = {
|
|
EventIDs::kMouseUp,
|
|
EventIDs::kMouseDown,
|
|
EventIDs::kMouseOver,
|
|
EventIDs::kMouseOutside,
|
|
EventIDs::kMouseTrackedInside,
|
|
EventIDs::kMouseTracking,
|
|
EventIDs::kMouseTrackedOutside,
|
|
EventIDs::kMouseUpInside,
|
|
EventIDs::kMouseUpOutside
|
|
};
|
|
|
|
|
|
static const EventIDs::EventID mouseClickEventIDs[] = {
|
|
EventIDs::kMouseUp,
|
|
EventIDs::kMouseDown,
|
|
EventIDs::kMouseTrackedInside,
|
|
EventIDs::kMouseTracking,
|
|
EventIDs::kMouseTrackedOutside,
|
|
EventIDs::kMouseUpInside,
|
|
EventIDs::kMouseUpOutside
|
|
};
|
|
|
|
const EventIDs::EventID *evtIDs = nullptr;
|
|
size_t numEventIDs = 0;
|
|
|
|
if (testType == kMouseInteractivityTestAnything) {
|
|
evtIDs = allEventIDs;
|
|
numEventIDs = ARRAYSIZE(allEventIDs);
|
|
} else if (testType == kMouseInteractivityTestMouseClick) {
|
|
evtIDs = mouseClickEventIDs;
|
|
numEventIDs = ARRAYSIZE(mouseClickEventIDs);
|
|
}
|
|
|
|
for (size_t i = 0; i < numEventIDs; i++) {
|
|
EventIDs::EventID evtID = evtIDs[i];
|
|
if (modifier->respondsToEvent(Event(evtID, 0)))
|
|
return true;
|
|
}
|
|
|
|
IModifierContainer *propagationContainer = modifier->getMessagePropagationContainer();
|
|
if (propagationContainer) {
|
|
for (const Common::SharedPtr<Modifier> &child : propagationContainer->getModifiers()) {
|
|
if (isModifierMouseInteractive(child.get(), testType))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Runtime::recursiveFindMouseCollision(Structural *&bestResult, int32 &bestLayer, int32 &bestStackHeight, bool &bestDirect, Structural *candidate, int32 stackHeight, int32 relativeX, int32 relativeY, MouseInteractivityTestType testType) {
|
|
int32 childRelativeX = relativeX;
|
|
int32 childRelativeY = relativeY;
|
|
if (candidate->isElement()) {
|
|
Element *element = static_cast<Element *>(candidate);
|
|
if (element->isVisual()) {
|
|
VisualElement *visual = static_cast<VisualElement *>(candidate);
|
|
|
|
if (visual->isVisible()) {
|
|
int layer = visual->getLayer();
|
|
bool isDirect = visual->isDirectToScreen();
|
|
|
|
// Layering priority:
|
|
bool isInFront = false;
|
|
if (isDirect && !bestDirect)
|
|
isInFront = true;
|
|
else if (isDirect == bestDirect) {
|
|
if (layer > bestLayer)
|
|
isInFront = true;
|
|
else if (layer == bestLayer) {
|
|
if (stackHeight > bestStackHeight)
|
|
isInFront = true;
|
|
}
|
|
}
|
|
|
|
if (isInFront && visual->isMouseInsideDrawableArea(relativeX, relativeY) && isStructuralMouseInteractive(visual, testType) && visual->isMouseCollisionAtPoint(relativeX, relativeY)) {
|
|
bestResult = candidate;
|
|
bestLayer = layer;
|
|
bestStackHeight = stackHeight;
|
|
bestDirect = isDirect;
|
|
}
|
|
}
|
|
|
|
// Need to check: Does hiding an element also hide its children?
|
|
childRelativeX -= visual->getRelativeRect().left;
|
|
childRelativeY -= visual->getRelativeRect().top;
|
|
}
|
|
}
|
|
|
|
for (const Common::SharedPtr<Structural> &child : candidate->getChildren())
|
|
recursiveFindMouseCollision(bestResult, bestLayer, bestStackHeight, bestDirect, child.get(), stackHeight, childRelativeX, childRelativeY, testType);
|
|
}
|
|
|
|
void Runtime::queueEventAsLowLevelSceneStateTransitionAction(const Event &evt, Structural *root, bool cascade, bool relay) {
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(evt, DynamicValue(), Common::WeakPtr<RuntimeObject>()));
|
|
Common::SharedPtr<MessageDispatch> msg(new MessageDispatch(props, root, cascade, relay, false));
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(msg));
|
|
}
|
|
|
|
void Runtime::loadScene(const Common::SharedPtr<Structural> &scene) {
|
|
assert(scene->getSceneLoadState() != Structural::SceneLoadState::kNotAScene);
|
|
|
|
if (scene->getSceneLoadState() == Structural::SceneLoadState::kSceneNotLoaded) {
|
|
scene->setSceneLoadState(Structural::SceneLoadState::kSceneLoaded);
|
|
|
|
debug(1, "Loading scene '%s'", scene->getName().c_str());
|
|
Element *element = static_cast<Element *>(scene.get());
|
|
uint32 streamID = element->getStreamLocator() & 0xffff; // Not actually sure how many bits are legal here
|
|
|
|
Subsection *subsection = static_cast<Subsection *>(scene->getParent());
|
|
|
|
if (streamID == 0) {
|
|
debug(1, "Scene is empty");
|
|
} else {
|
|
_project->loadSceneFromStream(scene, streamID, getHacks());
|
|
debug(1, "Scene loaded OK, materializing objects...");
|
|
scene->materializeDescendents(this, subsection->getSceneLoadMaterializeScope());
|
|
debug(1, "Scene materialized OK");
|
|
}
|
|
|
|
recursiveActivateStructural(scene.get());
|
|
debug(1, "Structural elements activated OK");
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugger) {
|
|
_debugger->complainAboutUnfinished(scene.get());
|
|
_debugger->refreshSceneStatus();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
CORO_BEGIN_DEFINITION(Runtime::SendMessageOnVThreadCoroutine)
|
|
struct Locals {
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
CORO_AWAIT(params->runtime->sendMessageOnVThread(params->dispatch));
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
void Runtime::sendMessageOnVThread(const Common::SharedPtr<MessageDispatch> &dispatch) {
|
|
EventIDs::EventID eventID = dispatch->getMsg()->getEvent().eventType;
|
|
|
|
// 0 is normally not produced and is invalid, 1 is "None" and is an invalid message
|
|
if (eventID == 1 || eventID == 0)
|
|
return;
|
|
|
|
#ifndef DISABLE_TEXT_CONSOLE
|
|
const int msgDebugLevel = 3;
|
|
|
|
if (gDebugLevel >= msgDebugLevel) {
|
|
const char *nameStr = "";
|
|
int srcID = 0;
|
|
const char *destStr = "";
|
|
int destID = 0;
|
|
Common::SharedPtr<RuntimeObject> src = dispatch->getMsg()->getSource().lock();
|
|
|
|
if (src) {
|
|
srcID = src->getStaticGUID();
|
|
if (src->isStructural())
|
|
nameStr = static_cast<Structural *>(src.get())->getName().c_str();
|
|
else if (src->isModifier())
|
|
nameStr = static_cast<Modifier *>(src.get())->getName().c_str();
|
|
}
|
|
|
|
RuntimeObject *dest = dispatch->getRootPropagator();
|
|
if (dest) {
|
|
destID = dest->getStaticGUID();
|
|
if (dest->isStructural())
|
|
destStr = static_cast<Structural *>(dest)->getName().c_str();
|
|
else if (dest->isModifier())
|
|
destStr = static_cast<Modifier *>(dest)->getName().c_str();
|
|
}
|
|
|
|
const Event evt = dispatch->getMsg()->getEvent();
|
|
bool cascade = dispatch->isCascade();
|
|
bool relay = dispatch->isRelay();
|
|
|
|
Common::String msgDebugString;
|
|
msgDebugString = Common::String::format("(%i,%i)", evt.eventType, evt.eventInfo);
|
|
if (evt.eventType == EventIDs::kAuthorMessage && _project) {
|
|
msgDebugString += " '";
|
|
msgDebugString += _project->findAuthorMessageName(evt.eventInfo);
|
|
msgDebugString += "'";
|
|
} else {
|
|
const char *extType = nullptr;
|
|
switch (evt.eventType) {
|
|
case EventIDs::kElementEnableEdit:
|
|
extType = "Element Enable Edit";
|
|
break;
|
|
case EventIDs::kElementDisableEdit:
|
|
extType = "Element Disable Edit";
|
|
break;
|
|
case EventIDs::kElementSelect:
|
|
extType = "Element Select";
|
|
break;
|
|
case EventIDs::kElementDeselect:
|
|
extType = "Element Deselect";
|
|
break;
|
|
case EventIDs::kElementToggleSelect:
|
|
extType = "Element Toggle Select";
|
|
break;
|
|
case EventIDs::kElementUpdatedCalculated:
|
|
extType = "Element Updated Calculated";
|
|
break;
|
|
case EventIDs::kElementShow:
|
|
extType = "Element Show";
|
|
break;
|
|
case EventIDs::kElementHide:
|
|
extType = "Element Hide";
|
|
break;
|
|
case EventIDs::kElementScrollUp:
|
|
extType = "Element Scroll Up";
|
|
break;
|
|
case EventIDs::kElementScrollDown:
|
|
extType = "Element Scroll Down";
|
|
break;
|
|
case EventIDs::kElementScrollRight:
|
|
extType = "Element Scroll Right";
|
|
break;
|
|
case EventIDs::kElementScrollLeft:
|
|
extType = "Element Scroll Left";
|
|
break;
|
|
|
|
case EventIDs::kMotionStarted:
|
|
extType = "Motion Started";
|
|
break;
|
|
case EventIDs::kMotionEnded:
|
|
extType = "Motion Started";
|
|
break;
|
|
|
|
case EventIDs::kTransitionStarted:
|
|
extType = "Transition Started";
|
|
break;
|
|
case EventIDs::kTransitionEnded:
|
|
extType = "Transition Ended";
|
|
break;
|
|
|
|
case EventIDs::kMouseDown:
|
|
extType = "Mouse Down";
|
|
break;
|
|
case EventIDs::kMouseUp:
|
|
extType = "Mouse Up";
|
|
break;
|
|
case EventIDs::kMouseOver:
|
|
extType = "Mouse Over";
|
|
break;
|
|
case EventIDs::kMouseOutside:
|
|
extType = "Mouse Outside";
|
|
break;
|
|
case EventIDs::kMouseTrackedInside:
|
|
extType = "Mouse Tracked Inside";
|
|
break;
|
|
case EventIDs::kMouseTrackedOutside:
|
|
extType = "Mouse Tracked Outside";
|
|
break;
|
|
case EventIDs::kMouseTracking:
|
|
extType = "Mouse Tracking";
|
|
break;
|
|
case EventIDs::kMouseUpInside:
|
|
extType = "Mouse Up Inside";
|
|
break;
|
|
case EventIDs::kMouseUpOutside:
|
|
extType = "Mouse Up Outside";
|
|
break;
|
|
|
|
case EventIDs::kSceneStarted:
|
|
extType = "Scene Started";
|
|
break;
|
|
case EventIDs::kSceneEnded:
|
|
extType = "Scene Ended";
|
|
break;
|
|
case EventIDs::kSceneDeactivated:
|
|
extType = "Scene Deactivate";
|
|
break;
|
|
case EventIDs::kSceneReactivated:
|
|
extType = "Scene Reactivated";
|
|
break;
|
|
case EventIDs::kSceneTransitionEnded:
|
|
extType = "Scene Transition Ended";
|
|
break;
|
|
|
|
case EventIDs::kSharedSceneReturnedToScene:
|
|
extType = "Scene Returned To Scene";
|
|
break;
|
|
case EventIDs::kSharedSceneSceneChanged:
|
|
extType = "Scene Scene Changed";
|
|
break;
|
|
case EventIDs::kSharedSceneNoNextScene:
|
|
extType = "Shared Scene No Next Scene";
|
|
break;
|
|
case EventIDs::kSharedSceneNoPrevScene:
|
|
extType = "Shared Scene No Prev Scene";
|
|
break;
|
|
|
|
case EventIDs::kParentEnabled:
|
|
extType = "Parent Enabled";
|
|
break;
|
|
case EventIDs::kParentDisabled:
|
|
extType = "Parent Disabled";
|
|
break;
|
|
case EventIDs::kParentChanged:
|
|
extType = "Parent Changed";
|
|
break;
|
|
|
|
case EventIDs::kPreloadMedia:
|
|
extType = "Preload Media";
|
|
break;
|
|
case EventIDs::kFlushMedia:
|
|
extType = "Flush Media";
|
|
break;
|
|
case EventIDs::kPrerollMedia:
|
|
extType = "Preroll Media";
|
|
break;
|
|
|
|
case EventIDs::kCloseProject:
|
|
extType = "Close Project";
|
|
break;
|
|
|
|
case EventIDs::kUserTimeout:
|
|
extType = "User Timeout";
|
|
break;
|
|
case EventIDs::kProjectStarted:
|
|
extType = "Project Started";
|
|
break;
|
|
case EventIDs::kProjectEnded:
|
|
extType = "Project Ended";
|
|
break;
|
|
case EventIDs::kFlushAllMedia:
|
|
extType = "Flush All Media";
|
|
break;
|
|
|
|
case EventIDs::kAttribGet:
|
|
extType = "Attrib Get";
|
|
break;
|
|
case EventIDs::kAttribSet:
|
|
extType = "Attrib Set";
|
|
break;
|
|
|
|
case EventIDs::kClone:
|
|
extType = "Clone";
|
|
break;
|
|
case EventIDs::kKill:
|
|
extType = "Kill";
|
|
break;
|
|
|
|
case EventIDs::kPlay:
|
|
extType = "Play";
|
|
break;
|
|
case EventIDs::kStop:
|
|
extType = "Stop";
|
|
break;
|
|
case EventIDs::kPause:
|
|
extType = "Pause";
|
|
break;
|
|
case EventIDs::kUnpause:
|
|
extType = "Unpause";
|
|
break;
|
|
case EventIDs::kTogglePause:
|
|
extType = "Toggle Pause";
|
|
break;
|
|
case EventIDs::kAtFirstCel:
|
|
extType = "At First Cel";
|
|
break;
|
|
case EventIDs::kAtLastCel:
|
|
extType = "At Last Cell";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (extType) {
|
|
msgDebugString += " '";
|
|
msgDebugString += extType;
|
|
msgDebugString += "'";
|
|
}
|
|
}
|
|
|
|
Common::String valueStr;
|
|
const DynamicValue &payload = dispatch->getMsg()->getValue();
|
|
|
|
if (payload.getType() != DynamicValueTypes::kNull) {
|
|
switch (payload.getType()) {
|
|
case DynamicValueTypes::kBoolean:
|
|
valueStr = (payload.getBool() ? "true" : "false");
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
valueStr = Common::String::format("%i", payload.getInt());
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
valueStr = Common::String::format("%g", payload.getFloat());
|
|
break;
|
|
case DynamicValueTypes::kPoint:
|
|
valueStr = Common::String::format("(%i,%i)", payload.getPoint().x, payload.getPoint().y);
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
valueStr = Common::String::format("(%i thru %i)", payload.getIntRange().min, payload.getIntRange().max);
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
valueStr = Common::String::format("(%g deg %g mag)", payload.getVector().angleDegrees, payload.getVector().magnitude);
|
|
break;
|
|
case DynamicValueTypes::kString:
|
|
valueStr = "'" + payload.getString() + "'";
|
|
break;
|
|
case DynamicValueTypes::kList:
|
|
valueStr = "List";
|
|
break;
|
|
case DynamicValueTypes::kObject:
|
|
valueStr = "Object";
|
|
if (RuntimeObject *obj = payload.getObject().object.lock().get())
|
|
valueStr += Common::String::format(" %x", obj->getStaticGUID());
|
|
break;
|
|
case DynamicValueTypes::kLabel:
|
|
valueStr = Common::String::format("Label(%u,%u)", static_cast<uint>(payload.getLabel().superGroupID), static_cast<uint>(payload.getLabel().id));
|
|
if (const Common::String *labelName = _project->findNameOfLabel(payload.getLabel()))
|
|
valueStr = valueStr + "[\"" + (*labelName) + "\"]";
|
|
break;
|
|
default:
|
|
valueStr = "<BAD TYPE> (this is a bug!)";
|
|
break;
|
|
}
|
|
|
|
valueStr = " with value " + valueStr;
|
|
}
|
|
|
|
debug(msgDebugLevel, "Object %x '%s' posted message %s to %x '%s'%s mod: %s ele: %s", srcID, nameStr, msgDebugString.c_str(), destID, destStr, valueStr.c_str(), relay ? "all" : "first", cascade ? "all" : "targetOnly");
|
|
}
|
|
#endif
|
|
|
|
_vthread->pushCoroutine<DispatchMessageCoroutine>(this, dispatch);
|
|
}
|
|
|
|
CORO_BEGIN_DEFINITION(Runtime::DispatchMessageCoroutine)
|
|
struct Locals {
|
|
bool isTerminated = false;
|
|
RuntimeObject *rootObject = nullptr;
|
|
MessageDispatch::RootType rootType = MessageDispatch::RootType::Invalid;
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
locals->rootObject = params->dispatch->getRootWeakPtr().lock().get();
|
|
CORO_IF(locals->rootObject != nullptr)
|
|
locals->rootType = params->dispatch->getRootType();
|
|
CORO_IF (locals->rootType == MessageDispatch::RootType::Command)
|
|
CORO_AWAIT(static_cast<Structural *>(locals->rootObject)->asyncConsumeCommand(params->runtime, params->dispatch->getMsg()));
|
|
CORO_ELSE_IF (locals->rootType == MessageDispatch::RootType::Structural)
|
|
CORO_CALL(SendMessageToStructuralCoroutine, params->runtime, &locals->isTerminated, static_cast<Structural *>(locals->rootObject), params->dispatch.get());
|
|
CORO_ELSE
|
|
if (params->dispatch->getRootType() != MessageDispatch::RootType::Modifier)
|
|
error("Internal error: Message propagation target was something other than structural or modifier");
|
|
|
|
CORO_CALL(SendMessageToModifierCoroutine, params->runtime, &locals->isTerminated, static_cast<Modifier *>(locals->rootObject), params->dispatch.get());
|
|
CORO_END_IF
|
|
CORO_END_IF
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
CORO_BEGIN_DEFINITION(Runtime::SendMessageToStructuralCoroutine)
|
|
struct Locals {
|
|
const Common::Array<Common::SharedPtr<Structural> > *childrenArray = nullptr;
|
|
uint childIndex = 0;
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
// Send to the structural object. Structural objects only consume commands.
|
|
CORO_IF (params->structural->respondsToEvent(params->dispatch->getMsg()->getEvent()))
|
|
CORO_AWAIT(params->runtime->postConsumeCommandTask(params->structural, params->dispatch->getMsg()));
|
|
|
|
CORO_IF (!params->dispatch->isRelay())
|
|
*params->isTerminatedPtr = true;
|
|
CORO_RETURN;
|
|
CORO_END_IF
|
|
CORO_END_IF
|
|
|
|
// Send to modifiers
|
|
if (params->structural->getSceneLoadState() == Structural::SceneLoadState::kSceneNotLoaded)
|
|
params->runtime->hotLoadScene(params->structural);
|
|
|
|
CORO_IF (params->structural->getModifiers().size() > 0)
|
|
CORO_CALL(Runtime::SendMessageToModifierContainerCoroutine, params->runtime, params->isTerminatedPtr, params->structural, params->dispatch);
|
|
|
|
CORO_IF(*params->isTerminatedPtr)
|
|
CORO_RETURN;
|
|
CORO_END_IF
|
|
CORO_END_IF
|
|
|
|
// Send to children if cascade
|
|
CORO_IF(params->dispatch->isCascade())
|
|
CORO_FOR((locals->childrenArray = ¶ms->structural->getChildren()), (locals->childIndex < locals->childrenArray->size()), (locals->childIndex++))
|
|
CORO_CALL(Runtime::SendMessageToStructuralCoroutine, params->runtime, params->isTerminatedPtr, (*locals->childrenArray)[locals->childIndex].get(), params->dispatch);
|
|
|
|
CORO_IF (*params->isTerminatedPtr)
|
|
CORO_RETURN;
|
|
CORO_END_IF
|
|
CORO_END_FOR
|
|
CORO_END_IF
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
CORO_BEGIN_DEFINITION(Runtime::SendMessageToModifierContainerCoroutine)
|
|
struct Locals {
|
|
const Common::Array<Common::SharedPtr<Modifier> > *childrenArray = nullptr;
|
|
uint childIndex = 0;
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
locals->childrenArray = ¶ms->modifierContainer->getModifiers();
|
|
|
|
CORO_FOR((locals->childIndex = 0), (locals->childIndex < locals->childrenArray->size() && !*(params->isTerminatedPtr)), (locals->childIndex++))
|
|
CORO_CALL(SendMessageToModifierCoroutine, params->runtime, params->isTerminatedPtr, (*locals->childrenArray)[locals->childIndex].get(), params->dispatch);
|
|
CORO_END_FOR
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
CORO_BEGIN_DEFINITION(Runtime::SendMessageToModifierCoroutine)
|
|
struct Locals {
|
|
bool responds = false;
|
|
IModifierContainer *childContainer = nullptr;
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
// Handle the action in the VThread
|
|
locals->responds = params->modifier->respondsToEvent(params->dispatch->getMsg()->getEvent());
|
|
|
|
// Queue propagation to children, if any, when the VThread task is done
|
|
if (locals->responds && !params->dispatch->isRelay()) {
|
|
*params->isTerminatedPtr = true;
|
|
} else {
|
|
locals->childContainer = params->modifier->getMessagePropagationContainer();
|
|
}
|
|
|
|
// Post to the message action itself to VThread
|
|
CORO_IF (locals->responds)
|
|
debug(3, "Modifier %x '%s' consumed message (%i,%i)", params->modifier->getStaticGUID(), params->modifier->getName().c_str(), params->dispatch->getMsg()->getEvent().eventType, params->dispatch->getMsg()->getEvent().eventInfo);
|
|
CORO_AWAIT(params->runtime->postConsumeMessageTask(params->modifier, params->dispatch->getMsg()));
|
|
CORO_END_IF
|
|
|
|
CORO_IF(locals->childContainer && !(*params->isTerminatedPtr))
|
|
CORO_CALL(SendMessageToModifierContainerCoroutine, params->runtime, params->isTerminatedPtr, locals->childContainer, params->dispatch);
|
|
CORO_END_IF
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
VThreadState Runtime::dispatchKeyTask(const DispatchKeyTaskData &data) {
|
|
Common::SharedPtr<KeyEventDispatch> dispatchPtr = data.dispatch;
|
|
KeyEventDispatch &dispatch = *dispatchPtr.get();
|
|
|
|
if (dispatch.isTerminated())
|
|
return kVThreadReturn;
|
|
else {
|
|
// Requeue propagation after whatever happens with this propagation step
|
|
DispatchKeyTaskData *requeueData = _vthread->pushTask("Runtime::dispatchKeyTask", this, &Runtime::dispatchKeyTask);
|
|
requeueData->dispatch = dispatchPtr;
|
|
|
|
return dispatch.continuePropagating(this);
|
|
}
|
|
}
|
|
|
|
VThreadState Runtime::dispatchActionTask(const DispatchActionTaskData &data) {
|
|
switch (data.action)
|
|
{
|
|
case Actions::kDebugSkipMovies:
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
_project->debugSkipMovies();
|
|
#endif
|
|
break;
|
|
default:
|
|
warning("Unhandled action %i", static_cast<int>(data.action));
|
|
break;
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
VThreadState Runtime::consumeMessageTask(const ConsumeMessageTaskData &data) {
|
|
IMessageConsumer *consumer = data.consumer;
|
|
assert(consumer->respondsToEvent(data.message->getEvent()));
|
|
return consumer->consumeMessage(this, data.message);
|
|
}
|
|
|
|
VThreadState Runtime::consumeCommandTask(const ConsumeCommandTaskData &data) {
|
|
Structural *structural = data.structural;
|
|
return structural->asyncConsumeCommand(this, data.message);
|
|
}
|
|
|
|
VThreadState Runtime::updateMouseStateTask(const UpdateMouseStateTaskData &data) {
|
|
struct MessageToSend {
|
|
EventIDs::EventID eventID;
|
|
Structural *target;
|
|
};
|
|
|
|
Common::Array<MessageToSend> messagesToSend;
|
|
|
|
if (data.mouseDown) {
|
|
// Mouse down
|
|
Structural *tracked = nullptr;
|
|
int32 bestSceneStack = INT32_MIN;
|
|
int32 bestLayer = INT32_MIN;
|
|
bool bestDirect = false;
|
|
|
|
for (size_t ri = 0; ri < _sceneStack.size(); ri++) {
|
|
const SceneStackEntry &sceneStackEntry = _sceneStack[_sceneStack.size() - 1 - ri];
|
|
recursiveFindMouseCollision(tracked, bestSceneStack, bestLayer, bestDirect, sceneStackEntry.scene.get(), _sceneStack.size() - 1 - ri, _cachedMousePosition.x, _cachedMousePosition.y, kMouseInteractivityTestMouseClick);
|
|
}
|
|
|
|
if (tracked) {
|
|
_mouseTrackingObject = tracked->getSelfReference().staticCast<Structural>();
|
|
_mouseTrackingDragStart = _cachedMousePosition;
|
|
if (tracked->isElement() && static_cast<Element *>(tracked)->isVisual()) {
|
|
Common::Rect initialRect = static_cast<VisualElement *>(tracked)->getRelativeRect();
|
|
_mouseTrackingObjectInitialOrigin = Common::Point(initialRect.left, initialRect.top);
|
|
} else
|
|
_mouseTrackingObjectInitialOrigin = Common::Point(0, 0);
|
|
_trackedMouseOutside = false;
|
|
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseDown;
|
|
msg.target = tracked;
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
} else {
|
|
// Mouse up
|
|
Common::SharedPtr<Structural> tracked = _mouseTrackingObject.lock();
|
|
if (tracked) {
|
|
{
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseUp;
|
|
msg.target = tracked.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
|
|
{
|
|
MessageToSend msg;
|
|
msg.eventID = _trackedMouseOutside ? EventIDs::kMouseUpOutside : EventIDs::kMouseUpInside;
|
|
msg.target = tracked.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
|
|
_mouseTrackingObject.reset();
|
|
_trackedMouseOutside = false;
|
|
}
|
|
}
|
|
|
|
DynamicValue mousePtValue;
|
|
mousePtValue.setPoint(Common::Point(_cachedMousePosition.x, _cachedMousePosition.y));
|
|
|
|
for (size_t ri = 0; ri < messagesToSend.size(); ri++) {
|
|
const MessageToSend &msg = messagesToSend[messagesToSend.size() - 1 - ri];
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(Event(msg.eventID, 0), mousePtValue, nullptr));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(props, msg.target, false, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
|
|
VThreadState Runtime::updateMousePositionTask(const UpdateMousePositionTaskData &data) {
|
|
if (!_project)
|
|
return kVThreadReturn;
|
|
|
|
// This intentionally replicates a mTropolis bug/quirk where drag motion modifiers DO NOT
|
|
// prevent "Mouse Outside" events from occurring if the mouse is moved fast enough to get
|
|
// outside of the object. Also, note that "Mouse Outside" intentionally DOES NOT match up
|
|
// with the logic of "Mouse Up Inside" and "Mouse Up Outside" in updateMouseStateTask.
|
|
//
|
|
// e.g. if you drag an object under another object and then release the mouse, then
|
|
// what should happen is a Mouse Outside event is sent to the bottom object, and then
|
|
// a Mouse Up Inside event is sent when the button is released.
|
|
|
|
Structural *collisionItem = nullptr;
|
|
int32 bestSceneStack = INT32_MIN;
|
|
int32 bestLayer = INT32_MIN;
|
|
bool bestDirect = false;
|
|
|
|
if (_numMouseBlockers == 0) {
|
|
for (size_t ri = 0; ri < _sceneStack.size(); ri++) {
|
|
const SceneStackEntry &sceneStackEntry = _sceneStack[_sceneStack.size() - 1 - ri];
|
|
recursiveFindMouseCollision(collisionItem, bestSceneStack, bestLayer, bestDirect, sceneStackEntry.scene.get(), _sceneStack.size() - 1 - ri, data.x, data.y, kMouseInteractivityTestAnything);
|
|
}
|
|
}
|
|
|
|
Common::SharedPtr<Structural> newMouseOver;
|
|
Common::SharedPtr<Structural> oldMouseOver = _mouseOverObject.lock();
|
|
if (collisionItem)
|
|
newMouseOver = collisionItem->getSelfReference().lock().staticCast<Structural>();
|
|
|
|
struct MessageToSend {
|
|
EventIDs::EventID eventID;
|
|
Structural *target;
|
|
};
|
|
|
|
Common::Array<MessageToSend> messagesToSend;
|
|
|
|
if (newMouseOver != oldMouseOver) {
|
|
if (oldMouseOver) {
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseOutside;
|
|
msg.target = oldMouseOver.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
if (newMouseOver) {
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseOver;
|
|
msg.target = newMouseOver.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
|
|
_mouseOverObject = newMouseOver;
|
|
}
|
|
|
|
Common::SharedPtr<Structural> tracked = _mouseTrackingObject.lock();
|
|
|
|
|
|
if (tracked) {
|
|
{
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseTracking;
|
|
msg.target = tracked.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
|
|
assert(tracked->isElement());
|
|
Element *element = static_cast<Element *>(tracked.get());
|
|
assert(element->isVisual());
|
|
VisualElement *visual = static_cast<VisualElement *>(element);
|
|
Common::Point parentOrigin = visual->getParentOrigin();
|
|
int32 relativeX = data.x - parentOrigin.x;
|
|
int32 relativeY = data.y - parentOrigin.y;
|
|
bool mouseOutside = !visual->isMouseInsideDrawableArea(relativeX, relativeY) || !visual->isMouseCollisionAtPoint(relativeX, relativeY);
|
|
|
|
if (mouseOutside != _trackedMouseOutside) {
|
|
if (mouseOutside) {
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseTrackedOutside;
|
|
msg.target = tracked.get();
|
|
messagesToSend.push_back(msg);
|
|
} else {
|
|
MessageToSend msg;
|
|
msg.eventID = EventIDs::kMouseTrackedInside;
|
|
msg.target = tracked.get();
|
|
messagesToSend.push_back(msg);
|
|
}
|
|
|
|
_trackedMouseOutside = mouseOutside;
|
|
}
|
|
|
|
// TODO: Figure out the right location for this
|
|
if (element->isVisual()) {
|
|
Common::Point targetPoint = Common::Point(data.x - _mouseTrackingDragStart.x + _mouseTrackingObjectInitialOrigin.x, data.y - _mouseTrackingDragStart.y + _mouseTrackingObjectInitialOrigin.y);
|
|
static_cast<VisualElement *>(element)->handleDragMotion(this, _mouseTrackingObjectInitialOrigin, targetPoint);
|
|
}
|
|
}
|
|
|
|
DynamicValue mousePtValue;
|
|
mousePtValue.setPoint(Common::Point(data.x, data.y));
|
|
|
|
for (size_t ri = 0; ri < messagesToSend.size(); ri++) {
|
|
const MessageToSend &msg = messagesToSend[messagesToSend.size() - 1 - ri];
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(Event(msg.eventID, 0), mousePtValue, nullptr));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(props, msg.target, false, true, false));
|
|
sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
// Cursor element positions are only updated on mouse movement, not when initially set
|
|
bool needUpdateElementPosition = (_cachedMousePosition.x != data.x || _cachedMousePosition.y != data.y);
|
|
|
|
_cachedMousePosition.x = data.x;
|
|
_cachedMousePosition.y = data.y;
|
|
|
|
if (needUpdateElementPosition)
|
|
updateCursorElementPosition();
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
VThreadState Runtime::applyDefaultVisibility(const ApplyDefaultVisibilityTaskData &data) {
|
|
Event evt;
|
|
if (data.targetVisibility) {
|
|
if (data.element->isVisibleByDefault() == false || data.element->isVisible())
|
|
return kVThreadReturn;
|
|
|
|
evt = Event(EventIDs::kElementShow, 0);
|
|
} else {
|
|
if (!data.element->isVisible())
|
|
return kVThreadReturn;
|
|
|
|
evt = Event(EventIDs::kElementHide, 0);
|
|
}
|
|
|
|
// Visibility change events are sourced from the element
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(evt, DynamicValue(), data.element->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(props, data.element, false, false, true));
|
|
|
|
sendMessageOnVThread(dispatch);
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void Runtime::updateMainWindowCursor() {
|
|
const uint32 kHandPointUpID = 10005;
|
|
const uint32 kArrowID = 10011;
|
|
|
|
if (!_mainWindow.expired()) {
|
|
if (_haveCursorElement) {
|
|
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
|
|
if (_elementTrackedToCursor.expired()) {
|
|
mainWindow->setMouseVisible(true);
|
|
_elementTrackedToCursor.reset();
|
|
} else {
|
|
mainWindow->setMouseVisible(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32 selectedCursor = kArrowID;
|
|
if (!_mouseOverObject.expired())
|
|
selectedCursor = kHandPointUpID;
|
|
|
|
if (_haveModifierOverrideCursor)
|
|
selectedCursor = _modifierOverrideCursorID;
|
|
|
|
if (_project) {
|
|
Common::SharedPtr<CursorGraphicCollection> cursorGraphics = _project->getCursorGraphics();
|
|
if (cursorGraphics) {
|
|
Common::SharedPtr<CursorGraphic> graphic = cursorGraphics->getGraphicByID(selectedCursor);
|
|
if (graphic) {
|
|
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
|
|
mainWindow->setCursorGraphic(graphic);
|
|
mainWindow->setMouseVisible(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::queueMessage(const Common::SharedPtr<MessageDispatch>& dispatch) {
|
|
_messageQueue.push_back(dispatch);
|
|
}
|
|
|
|
void Runtime::queueOSEvent(const Common::SharedPtr<OSEvent> &osEvent) {
|
|
_osEventQueue.push_back(osEvent);
|
|
}
|
|
|
|
Scheduler &Runtime::getScheduler() {
|
|
return _scheduler;
|
|
}
|
|
|
|
void Runtime::getScenesInRenderOrder(Common::Array<Structural*> &scenes) const {
|
|
for (Common::Array<SceneStackEntry>::const_iterator it = _sceneStack.begin(), itEnd = _sceneStack.end(); it != itEnd; ++it) {
|
|
scenes.push_back(it->scene.get());
|
|
}
|
|
}
|
|
|
|
void Runtime::instantiateIfAlias(Common::SharedPtr<Modifier> &modifier, const Common::WeakPtr<RuntimeObject> &relinkParent) {
|
|
if (modifier->isAlias()) {
|
|
Common::SharedPtr<Modifier> templateModifier = _project->resolveAlias(static_cast<AliasModifier *>(modifier.get())->getAliasID());
|
|
if (!templateModifier) {
|
|
error("Failed to resolve alias");
|
|
}
|
|
|
|
Common::SharedPtr<Modifier> clonedModifier = templateModifier->shallowClone();
|
|
clonedModifier->setSelfReference(clonedModifier);
|
|
clonedModifier->setRuntimeGUID(allocateRuntimeGUID());
|
|
|
|
clonedModifier->setName(modifier->getName());
|
|
|
|
modifier = clonedModifier;
|
|
clonedModifier->setParent(relinkParent);
|
|
|
|
ModifierChildCloner cloner(this, clonedModifier);
|
|
clonedModifier->visitInternalReferences(&cloner);
|
|
|
|
// Aliased variables use the same variable storage, but are treated as distinct objects.
|
|
if (clonedModifier->isVariable()) {
|
|
assert(templateModifier->isVariable());
|
|
static_cast<VariableModifier *>(clonedModifier.get())->setStorage(static_cast<const VariableModifier *>(templateModifier.get())->getStorage());
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::SharedPtr<Window> Runtime::findTopWindow(int32 x, int32 y) const {
|
|
Common::SharedPtr<Window> bestWindow;
|
|
int bestStrata = 0;
|
|
for (const Common::SharedPtr<Window> &window : _windows) {
|
|
if ((!bestWindow || bestStrata <= window->getStrata()) && !window->isMouseTransparent() && x >= window->getX() && y >= window->getY()) {
|
|
int32 relX = x - window->getX();
|
|
int32 relY = y - window->getY();
|
|
if (relX < window->getWidth() && relY < window->getHeight()) {
|
|
bestStrata = window->getStrata();
|
|
bestWindow = window;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestWindow;
|
|
}
|
|
|
|
void Runtime::setVolume(double volume) {
|
|
Audio::Mixer *mixer = _system->getMixer();
|
|
mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, static_cast<int>(volume * Audio::Mixer::kMaxMixerVolume));
|
|
mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, static_cast<int>(volume * Audio::Mixer::kMaxMixerVolume));
|
|
mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, static_cast<int>(volume * Audio::Mixer::kMaxMixerVolume));
|
|
mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, static_cast<int>(volume * Audio::Mixer::kMaxMixerVolume));
|
|
}
|
|
|
|
void Runtime::onMouseDown(int32 x, int32 y, Actions::MouseButton mButton) {
|
|
_realMousePosition.x = x;
|
|
_realMousePosition.y = y;
|
|
|
|
Common::SharedPtr<Window> focusWindow = _mouseFocusWindow.lock();
|
|
if (!focusWindow) {
|
|
focusWindow = findTopWindow(x, y);
|
|
if (!focusWindow)
|
|
return;
|
|
|
|
// New focus window, clear all leftover mouse button flags
|
|
for (int i = 0; i < Actions::kMouseButtonCount; i++)
|
|
_mouseFocusFlags[i] = false;
|
|
|
|
_mouseFocusWindow = focusWindow;
|
|
}
|
|
|
|
focusWindow->onMouseDown(x - focusWindow->getX(), y - focusWindow->getY(), mButton);
|
|
|
|
_mouseFocusFlags[mButton] = true;
|
|
}
|
|
|
|
void Runtime::onMouseMove(int32 x, int32 y) {
|
|
_realMousePosition.x = x;
|
|
_realMousePosition.y = y;
|
|
|
|
Common::SharedPtr<Window> focusWindow = _mouseFocusWindow.lock();
|
|
|
|
if (!focusWindow)
|
|
focusWindow = findTopWindow(x, y);
|
|
|
|
if (focusWindow)
|
|
focusWindow->onMouseMove(x - focusWindow->getX(), y - focusWindow->getY());
|
|
}
|
|
|
|
void Runtime::onMouseUp(int32 x, int32 y, Actions::MouseButton mButton) {
|
|
_realMousePosition.x = x;
|
|
_realMousePosition.y = y;
|
|
|
|
Common::SharedPtr<Window> focusWindow = _mouseFocusWindow.lock();
|
|
if (!focusWindow)
|
|
return;
|
|
|
|
focusWindow->onMouseUp(x - focusWindow->getX(), y - focusWindow->getY(), mButton);
|
|
_mouseFocusFlags[mButton] = false;
|
|
bool anyTrue = false;
|
|
for (int i = 0; i < Actions::kMouseButtonCount; i++) {
|
|
if (_mouseFocusFlags[i]) {
|
|
anyTrue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!anyTrue)
|
|
_mouseFocusWindow.reset();
|
|
}
|
|
|
|
void Runtime::onKeyboardEvent(const Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) {
|
|
Common::SharedPtr<Window> focusWindow = _keyFocusWindow.lock();
|
|
if (focusWindow)
|
|
focusWindow->onKeyboardEvent(evtType, repeat, keyEvt);
|
|
}
|
|
|
|
|
|
void Runtime::onAction(MTropolis::Actions::Action action) {
|
|
Common::SharedPtr<Window> focusWindow = _keyFocusWindow.lock();
|
|
if (focusWindow)
|
|
focusWindow->onAction(action);
|
|
}
|
|
|
|
const Common::Point &Runtime::getCachedMousePosition() const {
|
|
return _cachedMousePosition;
|
|
}
|
|
|
|
void Runtime::setModifierCursorOverride(uint32 cursorID) {
|
|
if (!_haveModifierOverrideCursor || _modifierOverrideCursorID != cursorID) {
|
|
_haveModifierOverrideCursor = true;
|
|
_modifierOverrideCursorID = cursorID;
|
|
updateMainWindowCursor();
|
|
}
|
|
}
|
|
|
|
void Runtime::clearModifierCursorOverride() {
|
|
if (_haveModifierOverrideCursor) {
|
|
_haveModifierOverrideCursor = false;
|
|
updateMainWindowCursor();
|
|
}
|
|
}
|
|
|
|
void Runtime::forceCursorRefreshOnce() {
|
|
_forceCursorRefreshOnce = true;
|
|
}
|
|
|
|
void Runtime::setAutoResetCursor(bool enabled) {
|
|
_autoResetCursor = enabled;
|
|
}
|
|
|
|
uint Runtime::getMultiClickCount() const {
|
|
return _multiClickCount;
|
|
}
|
|
|
|
bool Runtime::isAwaitingSceneTransition() const {
|
|
return _sceneTransitionState != kSceneTransitionStateNotTransitioning;
|
|
}
|
|
|
|
Common::RandomSource *Runtime::getRandom() const {
|
|
return _random.get();
|
|
}
|
|
|
|
WorldManagerInterface *Runtime::getWorldManagerInterface() const {
|
|
return _worldManagerInterface.get();
|
|
}
|
|
|
|
AssetManagerInterface *Runtime::getAssetManagerInterface() const {
|
|
return _assetManagerInterface.get();
|
|
}
|
|
|
|
SystemInterface *Runtime::getSystemInterface() const {
|
|
return _systemInterface.get();
|
|
}
|
|
|
|
ISaveUIProvider *Runtime::getSaveProvider() const {
|
|
return _saveProvider;
|
|
}
|
|
|
|
ILoadUIProvider *Runtime::getLoadProvider() const {
|
|
return _loadProvider;
|
|
}
|
|
|
|
Audio::Mixer *Runtime::getAudioMixer() const {
|
|
return _mixer;
|
|
}
|
|
|
|
Hacks &Runtime::getHacks() {
|
|
return _hacks;
|
|
}
|
|
|
|
const Hacks &Runtime::getHacks() const {
|
|
return _hacks;
|
|
}
|
|
|
|
void Runtime::setSceneGraphDirty() {
|
|
_sceneGraphChanged = true;
|
|
}
|
|
|
|
void Runtime::clearSceneGraphDirty() {
|
|
_sceneGraphChanged = false;
|
|
}
|
|
|
|
bool Runtime::isSceneGraphDirty() const {
|
|
return _sceneGraphChanged;
|
|
}
|
|
|
|
void Runtime::addCollider(ICollider *collider) {
|
|
Common::SharedPtr<CollisionCheckState> state(new CollisionCheckState());
|
|
state->collider = collider;
|
|
|
|
_colliders.push_back(state);
|
|
}
|
|
|
|
void Runtime::removeCollider(ICollider *collider) {
|
|
size_t numColliders = _colliders.size();
|
|
for (size_t i = 0; i < numColliders; i++) {
|
|
if (_colliders[i]->collider == collider) {
|
|
_colliders.remove_at(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::checkCollisions(ICollider *optRestrictToCollider) {
|
|
if (!_colliders.size())
|
|
return;
|
|
|
|
Common::Array<ColliderInfo> collisionObjects;
|
|
for (size_t i = 0; i < _sceneStack.size(); i++)
|
|
recursiveFindColliders(_sceneStack[i].scene.get(), i, collisionObjects, 0, 0, true);
|
|
|
|
Common::sort(collisionObjects.begin(), collisionObjects.end(), sortColliderPredicate);
|
|
|
|
for (const Common::SharedPtr<CollisionCheckState> &collisionCheckPtr : _colliders) {
|
|
CollisionCheckState &colCheck = *collisionCheckPtr.get();
|
|
|
|
if (optRestrictToCollider && colCheck.collider != optRestrictToCollider)
|
|
continue;
|
|
|
|
Modifier *modifier = nullptr;
|
|
bool collideInFront;
|
|
bool collideBehind;
|
|
bool excludeParents;
|
|
colCheck.collider->getCollisionProperties(modifier, collideInFront, collideBehind, excludeParents);
|
|
|
|
Structural *owner = modifier->findStructuralOwner();
|
|
if (!owner)
|
|
continue;
|
|
|
|
if (!owner->isElement())
|
|
continue;
|
|
|
|
Element *element = static_cast<Element *>(owner);
|
|
if (!element->isVisual())
|
|
continue;
|
|
|
|
Common::Array<Common::WeakPtr<VisualElement> > collidingElements;
|
|
|
|
VisualElement *visual = static_cast<VisualElement *>(element);
|
|
if (visual->isVisible()) {
|
|
bool foundSelf = false;
|
|
size_t selfIndex = 0;
|
|
Common::Rect selfRect = Common::Rect(0, 0, 0, 0);
|
|
|
|
for (size_t i = 0; i < collisionObjects.size(); i++) {
|
|
if (collisionObjects[i].element == visual) {
|
|
selfIndex = i;
|
|
selfRect = collisionObjects[i].absRect;
|
|
foundSelf = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This should always be true
|
|
if (foundSelf) {
|
|
size_t minIndex = 0;
|
|
size_t maxIndex = collisionObjects.size();
|
|
if (!collideBehind)
|
|
minIndex = selfIndex + 1;
|
|
if (!collideInFront)
|
|
maxIndex = selfIndex;
|
|
|
|
for (size_t i = minIndex; i < maxIndex; i++) {
|
|
if (i == selfIndex)
|
|
continue;
|
|
|
|
const ColliderInfo &collisionObject = collisionObjects[i];
|
|
VisualElement *collisionObjectElement = collisionObject.element;
|
|
|
|
assert(collisionObjectElement != visual);
|
|
|
|
// Potential collision
|
|
if (!collisionObject.absRect.intersects(selfRect))
|
|
continue;
|
|
|
|
if (excludeParents) {
|
|
bool isParent = false;
|
|
|
|
Structural *parentSearch = visual->getParent();
|
|
while (parentSearch != nullptr) {
|
|
if (parentSearch == collisionObjectElement) {
|
|
isParent = true;
|
|
break;
|
|
}
|
|
parentSearch = parentSearch->getParent();
|
|
}
|
|
|
|
if (isParent)
|
|
continue;
|
|
}
|
|
|
|
collidingElements.push_back(collisionObjectElement->getSelfReference().staticCast<VisualElement>());
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::Array<Common::WeakPtr<VisualElement> > &oldCollidingElements = colCheck.activeElements;
|
|
|
|
bool shouldStop = false;
|
|
|
|
for (size_t oldIndex = 0; oldIndex < oldCollidingElements.size();) {
|
|
Common::SharedPtr<VisualElement> oldColElement = oldCollidingElements[oldIndex].lock();
|
|
if (oldColElement.get() == nullptr) {
|
|
collidingElements.remove_at(oldIndex);
|
|
continue;
|
|
}
|
|
|
|
bool isStillColliding = false;
|
|
for (size_t newIndex = 0; newIndex < collidingElements.size(); newIndex++) {
|
|
Common::SharedPtr<VisualElement> newColElement = collidingElements[newIndex].lock();
|
|
if (newColElement == oldColElement) {
|
|
isStillColliding = true;
|
|
collidingElements.remove_at(newIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isStillColliding)
|
|
oldCollidingElements.remove_at(oldIndex);
|
|
else
|
|
oldIndex++;
|
|
|
|
if (!shouldStop)
|
|
colCheck.collider->triggerCollision(this, oldColElement.get(), true, isStillColliding, shouldStop);
|
|
}
|
|
|
|
for (size_t newIndex = 0; newIndex < collidingElements.size(); newIndex++) {
|
|
Common::SharedPtr<VisualElement> colElement = collidingElements[newIndex].lock();
|
|
|
|
if (!shouldStop)
|
|
colCheck.collider->triggerCollision(this, colElement.get(), false, true, shouldStop);
|
|
|
|
oldCollidingElements.push_back(colElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::setCursorElement(const Common::WeakPtr<VisualElement> &element) {
|
|
_elementTrackedToCursor = element;
|
|
_haveCursorElement = !element.expired();
|
|
|
|
updateMainWindowCursor();
|
|
}
|
|
|
|
void Runtime::updateCursorElementPosition() {
|
|
Common::SharedPtr<VisualElement> element = _elementTrackedToCursor.lock();
|
|
if (!element)
|
|
return;
|
|
|
|
Common::Point elementPos = element->getGlobalPosition();
|
|
if (elementPos != _cachedMousePosition) {
|
|
VisualElement::OffsetTranslateTaskData *taskData = _vthread->pushTask("VisualElement::offsetTranslateTask", element.get(), &VisualElement::offsetTranslateTask);
|
|
taskData->dx = _cachedMousePosition.x - elementPos.x;
|
|
taskData->dy = _cachedMousePosition.y - elementPos.y;
|
|
}
|
|
}
|
|
|
|
void Runtime::addBoundaryDetector(IBoundaryDetector *boundaryDetector) {
|
|
BoundaryCheckState state;
|
|
state.currentContacts = 0;
|
|
state.detector = boundaryDetector;
|
|
state.position = Common::Point(0, 0);
|
|
state.positionResolved = false;
|
|
|
|
Modifier *modifier;
|
|
uint edgeFlags;
|
|
bool mustBeCompletelyOutside;
|
|
bool continuous;
|
|
boundaryDetector->getCollisionProperties(modifier, edgeFlags, mustBeCompletelyOutside, continuous);
|
|
|
|
_boundaryChecks.push_back(state);
|
|
}
|
|
|
|
void Runtime::removeBoundaryDetector(IBoundaryDetector *boundaryDetector) {
|
|
size_t numColliders = _boundaryChecks.size();
|
|
for (size_t i = 0; i < numColliders; i++) {
|
|
if (_boundaryChecks[i].detector == boundaryDetector) {
|
|
_boundaryChecks.remove_at(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::addPostEffect(IPostEffect *postEffect) {
|
|
_postEffects.push_back(postEffect);
|
|
}
|
|
|
|
void Runtime::removePostEffect(IPostEffect *postEffect) {
|
|
size_t numPostEffects = _postEffects.size();
|
|
for (size_t i = 0; i < numPostEffects; i++) {
|
|
if (_postEffects[i] == postEffect) {
|
|
_postEffects.remove_at(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Common::Array<IPostEffect *> &Runtime::getPostEffects() const {
|
|
return _postEffects;
|
|
}
|
|
|
|
const Palette &Runtime::getGlobalPalette() const {
|
|
return _globalPalette;
|
|
}
|
|
|
|
void Runtime::setGlobalPalette(const Palette &palette) {
|
|
if (_realDisplayMode <= kColorDepthMode8Bit)
|
|
g_system->getPaletteManager()->setPalette(palette.getPalette(), 0, 256);
|
|
else
|
|
setSceneGraphDirty();
|
|
|
|
_globalPalette = palette;
|
|
}
|
|
|
|
void Runtime::addMouseBlocker() {
|
|
_numMouseBlockers++;
|
|
}
|
|
|
|
void Runtime::removeMouseBlocker() {
|
|
assert(_numMouseBlockers > 0);
|
|
_numMouseBlockers--;
|
|
}
|
|
|
|
void Runtime::checkBoundaries() {
|
|
// Boundary Detection Messenger behavior is very quirky in mTropolis 1.1. Basically, if an object moves in the direction of
|
|
// the boundary, then it may trigger collision checks with the boundary. If it moves but does not move in the direction of
|
|
// the boundary, then it is considered no longer in contact with the boundary - period - which means it can trigger again
|
|
// once it moves in the boundary direction.
|
|
for (BoundaryCheckState &checkState : _boundaryChecks) {
|
|
Modifier *modifier;
|
|
uint edgeFlags;
|
|
bool mustBeCompletelyOutside;
|
|
bool continuous;
|
|
checkState.detector->getCollisionProperties(modifier, edgeFlags, mustBeCompletelyOutside, continuous);
|
|
|
|
Structural *structural = modifier->findStructuralOwner();
|
|
if (structural == nullptr || !structural->isElement() || !static_cast<Element *>(structural)->isVisual())
|
|
continue;
|
|
|
|
VisualElement *visual = static_cast<VisualElement *>(structural);
|
|
|
|
Common::Rect thisRect = visual->getRelativeRect();
|
|
Common::Point point(thisRect.left, thisRect.top);
|
|
|
|
if (!checkState.positionResolved) {
|
|
checkState.positionResolved = true;
|
|
checkState.position = point;
|
|
continue;
|
|
}
|
|
|
|
if (point == checkState.position)
|
|
continue;
|
|
|
|
Structural *parentStructural = visual->getParent();
|
|
if (parentStructural == nullptr || !parentStructural->isElement() || !static_cast<Element *>(parentStructural)->isVisual())
|
|
continue;
|
|
|
|
VisualElement *parentVisual = static_cast<VisualElement *>(parentStructural);
|
|
|
|
Common::Point delta = point - checkState.position;
|
|
|
|
int16 parentWidth = parentVisual->getRelativeRect().width();
|
|
int16 parentHeight = parentVisual->getRelativeRect().height();
|
|
|
|
uint contacts = 0;
|
|
if (delta.x < 0) {
|
|
int16 edge = mustBeCompletelyOutside ? thisRect.right : thisRect.left;
|
|
if (edge < 0)
|
|
contacts |= IBoundaryDetector::kEdgeLeft;
|
|
}
|
|
if (delta.y < 0) {
|
|
int16 edge = mustBeCompletelyOutside ? thisRect.bottom : thisRect.top;
|
|
if (edge < 0)
|
|
contacts |= IBoundaryDetector::kEdgeTop;
|
|
}
|
|
if (delta.x > 0) {
|
|
int16 edge = mustBeCompletelyOutside ? thisRect.left : thisRect.right;
|
|
if (edge >= parentWidth)
|
|
contacts |= IBoundaryDetector::kEdgeRight;
|
|
}
|
|
if (delta.y > 0) {
|
|
int16 edge = mustBeCompletelyOutside ? thisRect.top : thisRect.bottom;
|
|
if (edge >= parentHeight)
|
|
contacts |= IBoundaryDetector::kEdgeBottom;
|
|
}
|
|
|
|
uint activatedContacts = contacts;
|
|
|
|
// If non-continuous, then only activate new contacts
|
|
if (!continuous)
|
|
activatedContacts &= ~checkState.currentContacts;
|
|
|
|
checkState.position = point;
|
|
checkState.currentContacts = contacts;
|
|
|
|
if (activatedContacts & edgeFlags)
|
|
checkState.detector->triggerCollision(this);
|
|
}
|
|
}
|
|
|
|
void Runtime::recursiveFindColliders(Structural *structural, size_t sceneStackDepth, Common::Array<ColliderInfo> &colliders, int32 parentOriginX, int32 parentOriginY, bool isRoot) {
|
|
int32 childOffsetX = parentOriginX;
|
|
int32 childOffsetY = parentOriginY;
|
|
if (structural->isElement()) {
|
|
Element *element = static_cast<Element *>(structural);
|
|
if (element->isVisual()) {
|
|
VisualElement *visual = static_cast<VisualElement *>(element);
|
|
const Common::Rect &rect = visual->getRelativeRect();
|
|
|
|
childOffsetX += rect.left;
|
|
childOffsetY += rect.top;
|
|
|
|
// isRoot = Is a scene, and colliding with scenes is not allowed
|
|
if (!isRoot && visual->isVisible()) {
|
|
ColliderInfo colliderInfo;
|
|
colliderInfo.absRect = visual->getRelativeCollisionRect();
|
|
colliderInfo.absRect.translate(parentOriginX, parentOriginY);
|
|
colliderInfo.element = visual;
|
|
colliderInfo.layer = visual->getLayer();
|
|
colliderInfo.sceneStackDepth = sceneStackDepth;
|
|
|
|
colliders.push_back(colliderInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren())
|
|
recursiveFindColliders(child.get(), sceneStackDepth, colliders, childOffsetX, childOffsetY, false);
|
|
}
|
|
|
|
bool Runtime::sortColliderPredicate(const ColliderInfo &a, const ColliderInfo &b) {
|
|
if (a.layer != b.layer)
|
|
return a.layer < b.layer;
|
|
return a.sceneStackDepth < b.sceneStackDepth;
|
|
}
|
|
|
|
const Common::String *Runtime::resolveAttributeIDName(uint32 attribID) const {
|
|
Common::HashMap<uint32, Common::String>::const_iterator it = _getSetAttribIDsToAttribName.find(attribID);
|
|
if (it == _getSetAttribIDsToAttribName.end())
|
|
return nullptr;
|
|
else
|
|
return &it->_value;
|
|
}
|
|
|
|
const Common::WeakPtr<Window> &Runtime::getMainWindow() const {
|
|
return _mainWindow;
|
|
}
|
|
|
|
const Common::SharedPtr<Graphics::ManagedSurface> &Runtime::getSaveScreenshotOverride() const {
|
|
return _saveScreenshotOverride;
|
|
}
|
|
|
|
void Runtime::setSaveScreenshotOverride(const Common::SharedPtr<Graphics::ManagedSurface> &screenshot) {
|
|
_saveScreenshotOverride = screenshot;
|
|
}
|
|
|
|
bool Runtime::isIdle() const {
|
|
// The runtime is idle if nothing is happening except for scheduled events and the OS queue
|
|
if (_vthread->hasTasks())
|
|
return false;
|
|
|
|
if (_sceneTransitionState != kSceneTransitionStateNotTransitioning)
|
|
return false;
|
|
|
|
if (_forceCursorRefreshOnce)
|
|
return false;
|
|
|
|
if (_queuedProjectDesc)
|
|
return false;
|
|
|
|
if (_pendingTeardowns.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingLowLevelTransitions.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingClones.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingPostCloneShowChecks.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingShowClonedObject.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingParentChanges.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingKills.size() > 0)
|
|
return false;
|
|
|
|
if (_messageQueue.size() > 0)
|
|
return false;
|
|
|
|
if (_pendingSceneReturnCount > 0)
|
|
return false;
|
|
|
|
if (_pendingSceneTransitions.size() > 0)
|
|
return false;
|
|
|
|
if (_isQuitting)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
const Common::SharedPtr<SubtitleRenderer> &Runtime::getSubtitleRenderer() const {
|
|
return _subtitleRenderer;
|
|
}
|
|
|
|
void Runtime::queueCloneObject(const Common::WeakPtr<RuntimeObject> &obj) {
|
|
Common::SharedPtr<RuntimeObject> ptr = obj.lock();
|
|
|
|
// Cloning the same object multiple times doesn't work
|
|
for (const Common::WeakPtr<RuntimeObject> &candidate : _pendingClones)
|
|
if (candidate.lock() == ptr)
|
|
return;
|
|
|
|
_pendingClones.push_back(obj);
|
|
}
|
|
|
|
void Runtime::queueKillObject(const Common::WeakPtr<RuntimeObject> &obj) {
|
|
Common::SharedPtr<RuntimeObject> ptr = obj.lock();
|
|
|
|
for (const Common::WeakPtr<RuntimeObject> &candidate : _pendingKills)
|
|
if (candidate.lock() == ptr)
|
|
return;
|
|
|
|
_pendingKills.push_back(obj);
|
|
}
|
|
|
|
void Runtime::queueChangeObjectParent(const Common::WeakPtr<RuntimeObject> &obj, const Common::WeakPtr<RuntimeObject> &newParent) {
|
|
_pendingParentChanges.push_back(ObjectParentChange(obj, newParent));
|
|
}
|
|
|
|
void Runtime::hotLoadScene(Structural *structural) {
|
|
assert(structural->getSceneLoadState() != Structural::SceneLoadState::kNotAScene);
|
|
loadScene(structural->getSelfReference().lock().staticCast<Structural>());
|
|
}
|
|
|
|
void Runtime::ensureMainWindowExists() {
|
|
// Maybe there's a better spot for this
|
|
if (_mainWindow.expired() && _project) {
|
|
const ProjectPresentationSettings &presentationSettings = _project->getPresentationSettings();
|
|
|
|
int32 centeredX = (static_cast<int32>(_displayWidth) - static_cast<int32>(presentationSettings.width)) / 2;
|
|
int32 centeredY = (static_cast<int32>(_displayHeight) - static_cast<int32>(presentationSettings.height)) / 2;
|
|
|
|
centeredX += _hacks.mainWindowOffset.x;
|
|
centeredY += _hacks.mainWindowOffset.y;
|
|
|
|
Common::SharedPtr<Window> mainWindow(new MainWindow(WindowParameters(this, centeredX, centeredY, presentationSettings.width, presentationSettings.height, _displayModePixelFormats[_realDisplayMode])));
|
|
addWindow(mainWindow);
|
|
_mainWindow.reset(mainWindow);
|
|
|
|
_keyFocusWindow = mainWindow;
|
|
|
|
updateMainWindowCursor();
|
|
}
|
|
}
|
|
|
|
void Runtime::unloadProject() {
|
|
_activeMainScene.reset();
|
|
_activeSharedScene.reset();
|
|
_sceneStack.clear();
|
|
_sceneReturnList.clear();
|
|
_pendingLowLevelTransitions.clear();
|
|
_pendingSceneTransitions.clear();
|
|
_pendingTeardowns.clear();
|
|
_messageQueue.clear();
|
|
_vthread.reset(new VThread(_coroManager.get()));
|
|
|
|
if (!_mainWindow.expired()) {
|
|
removeWindow(_mainWindow.lock().get());
|
|
}
|
|
|
|
// These should be last
|
|
_project.reset();
|
|
_rootLinkingScope.reset();
|
|
|
|
_haveModifierOverrideCursor = false;
|
|
}
|
|
|
|
void Runtime::refreshPlayTime() {
|
|
_playTime = _system->getMillis() - _playTimeBase;
|
|
}
|
|
|
|
|
|
void Runtime::queueProject(const Common::SharedPtr<ProjectDescription> &desc) {
|
|
_queuedProjectDesc = desc;
|
|
}
|
|
|
|
void Runtime::closeProject() {
|
|
// TODO: There are actually some elaborate cases here involving opening projects, project return list,
|
|
// Project Ended message, etc. and Obsidian actually attempts to stop MIDI playback on project end.
|
|
// For now we just quit.
|
|
_isQuitting = true;
|
|
}
|
|
|
|
void Runtime::addVolume(int volumeID, const char *name, bool isMounted) {
|
|
VolumeState volume;
|
|
volume.name = name;
|
|
volume.isMounted = isMounted;
|
|
volume.volumeID = volumeID;
|
|
|
|
_volumes.push_back(volume);
|
|
}
|
|
|
|
bool Runtime::getVolumeState(const Common::String &name, int &outVolumeID, bool &outIsMounted) const {
|
|
for (const VolumeState &volume : _volumes) {
|
|
if (caseInsensitiveEqual(volume.name, name)) {
|
|
outVolumeID = volume.volumeID;
|
|
outIsMounted = volume.isMounted;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (_defaultVolumeState) {
|
|
outIsMounted = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Runtime::addSceneStateTransition(const HighLevelSceneTransition &transition) {
|
|
_pendingSceneTransitions.push_back(transition);
|
|
}
|
|
|
|
void Runtime::addSceneReturn() {
|
|
_pendingSceneReturnCount++;
|
|
}
|
|
|
|
void Runtime::setSceneTransitionEffect(bool isInDestinationScene, SceneTransitionEffect *effect) {
|
|
SceneTransitionEffect *target = isInDestinationScene ? &_destinationSceneTransitionEffect : &_sourceSceneTransitionEffect;
|
|
if (!effect)
|
|
*target = SceneTransitionEffect();
|
|
else
|
|
*target = *effect;
|
|
}
|
|
|
|
Project *Runtime::getProject() const {
|
|
return _project.get();
|
|
}
|
|
|
|
void Runtime::postConsumeMessageTask(IMessageConsumer *consumer, const Common::SharedPtr<MessageProperties> &msg) {
|
|
ConsumeMessageTaskData *params = _vthread->pushTask("Runtime::consumeMessageTask", this, &Runtime::consumeMessageTask);
|
|
params->consumer = consumer;
|
|
params->message = msg;
|
|
}
|
|
|
|
void Runtime::postConsumeCommandTask(Structural *structural, const Common::SharedPtr<MessageProperties> &msg) {
|
|
ConsumeCommandTaskData *params = _vthread->pushTask("Runtime::consumeMessageTask", this, &Runtime::consumeCommandTask);
|
|
params->structural = structural;
|
|
params->message = msg;
|
|
}
|
|
|
|
uint32 Runtime::allocateRuntimeGUID() {
|
|
return _nextRuntimeGUID++;
|
|
}
|
|
|
|
void Runtime::addWindow(const Common::SharedPtr<Window> &window) {
|
|
_windows.push_back(window);
|
|
}
|
|
|
|
void Runtime::removeWindow(Window *window) {
|
|
for (size_t i = 0; i < _windows.size(); i++) {
|
|
if (_windows[i].get() == window) {
|
|
window->detachFromRuntime();
|
|
_windows.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::setupDisplayMode(ColorDepthMode displayMode, const Graphics::PixelFormat &pixelFormat) {
|
|
_displayModeSupported[displayMode] = true;
|
|
_displayModePixelFormats[displayMode] = pixelFormat;
|
|
}
|
|
|
|
bool Runtime::isDisplayModeSupported(ColorDepthMode displayMode) const {
|
|
return _displayModeSupported[displayMode];
|
|
}
|
|
|
|
bool Runtime::switchDisplayMode(ColorDepthMode realDisplayMode, ColorDepthMode fakeDisplayMode) {
|
|
_fakeDisplayMode = fakeDisplayMode;
|
|
|
|
if (_realDisplayMode != realDisplayMode) {
|
|
_realDisplayMode = realDisplayMode;
|
|
_windows.clear();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Runtime::setDisplayResolution(uint16 width, uint16 height) {
|
|
_displayWidth = width;
|
|
_displayHeight = height;
|
|
}
|
|
|
|
void Runtime::getDisplayResolution(uint16 &outWidth, uint16 &outHeight) const {
|
|
outWidth = _displayWidth;
|
|
outHeight = _displayHeight;
|
|
}
|
|
|
|
ColorDepthMode Runtime::getRealColorDepth() const {
|
|
return _realDisplayMode;
|
|
}
|
|
|
|
ColorDepthMode Runtime::getFakeColorDepth() const {
|
|
return _fakeDisplayMode;
|
|
}
|
|
|
|
const Graphics::PixelFormat& Runtime::getRenderPixelFormat() const {
|
|
assert(_realDisplayMode != kColorDepthModeInvalid);
|
|
|
|
return _displayModePixelFormats[_realDisplayMode];
|
|
}
|
|
|
|
const Common::SharedPtr<Graphics::MacFontManager>& Runtime::getMacFontManager() const {
|
|
return _macFontMan;
|
|
}
|
|
|
|
const Common::SharedPtr<Structural> &Runtime::getActiveMainScene() const {
|
|
return _activeMainScene;
|
|
}
|
|
|
|
const Common::SharedPtr<Structural> &Runtime::getActiveSharedScene() const {
|
|
return _activeSharedScene;
|
|
}
|
|
|
|
void Runtime::getSceneStack(Common::Array<Common::SharedPtr<Structural> >& sceneStack) const {
|
|
sceneStack.clear();
|
|
for (const SceneStackEntry &stackEntry : _sceneStack)
|
|
sceneStack.push_back(stackEntry.scene);
|
|
}
|
|
|
|
bool Runtime::mustDraw() const {
|
|
if (_sceneTransitionState == kSceneTransitionStateWaitingForDraw)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint64 Runtime::getRealTime() const {
|
|
return _realTime;
|
|
}
|
|
|
|
uint64 Runtime::getPlayTime() const {
|
|
return _playTime;
|
|
}
|
|
|
|
VThread& Runtime::getVThread() const {
|
|
return *_vthread.get();
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
|
|
void Runtime::debugSetEnabled(bool enabled) {
|
|
if (enabled) {
|
|
if (!_debugger)
|
|
_debugger.reset(new Debugger(this));
|
|
} else {
|
|
_debugger.reset();
|
|
}
|
|
}
|
|
void Runtime::debugBreak() {
|
|
debugSetEnabled(true);
|
|
_debugger->setPaused(true);
|
|
}
|
|
|
|
Debugger *Runtime::debugGetDebugger() const {
|
|
return _debugger.get();
|
|
}
|
|
|
|
void Runtime::debugGetPrimaryTaskList(Common::Array<Common::SharedPtr<DebugPrimaryTaskList> > &primaryTaskLists) {
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> vthreadTaskList(new DebugPrimaryTaskList("Execute"));
|
|
primaryTaskLists.push_back(vthreadTaskList);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> projectQueueTaskList(new DebugPrimaryTaskList("Project queue"));
|
|
primaryTaskLists.push_back(projectQueueTaskList);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> messageQueueTaskList(new DebugPrimaryTaskList("Message queue"));
|
|
primaryTaskLists.push_back(messageQueueTaskList);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> teardownTaskList(new DebugPrimaryTaskList("Teardowns"));
|
|
primaryTaskLists.push_back(teardownTaskList);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> llstTasks(new DebugPrimaryTaskList("Low-level scene transitions"));
|
|
primaryTaskLists.push_back(llstTasks);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> hlstTasks(new DebugPrimaryTaskList("High-level scene transitions"));
|
|
primaryTaskLists.push_back(hlstTasks);
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<DebugPrimaryTaskList> scheduledEventsTasks(new DebugPrimaryTaskList("Scheduled events"));
|
|
primaryTaskLists.push_back(scheduledEventsTasks);
|
|
}
|
|
}
|
|
|
|
#endif /* MTROPOLIS_DEBUG_ENABLE */
|
|
|
|
const Common::Array<Common::SharedPtr<Modifier> > &IModifierContainer::getModifiers() const {
|
|
return const_cast<IModifierContainer &>(*this).getModifiers();
|
|
}
|
|
|
|
ChildLoaderContext::ChildLoaderContext() : remainingCount(0), type(kTypeUnknown) {
|
|
memset(&this->containerUnion, 0, sizeof(this->containerUnion));
|
|
}
|
|
|
|
ProjectPlugInRegistry::ProjectPlugInRegistry() {
|
|
}
|
|
|
|
void ProjectPlugInRegistry::registerPlugInModifier(const char *name, const Data::IPlugInModifierDataFactory *loader, const IPlugInModifierFactory *factory) {
|
|
_dataLoaderRegistry.registerLoader(name, loader);
|
|
_factoryRegistry[name] = factory;
|
|
}
|
|
|
|
const Data::PlugInModifierRegistry& ProjectPlugInRegistry::getDataLoaderRegistry() const {
|
|
return _dataLoaderRegistry;
|
|
}
|
|
|
|
const IPlugInModifierFactory *ProjectPlugInRegistry::findPlugInModifierFactory(const char *name) const {
|
|
Common::HashMap<Common::String, const IPlugInModifierFactory *>::const_iterator it = _factoryRegistry.find(name);
|
|
if (it == _factoryRegistry.end())
|
|
return nullptr;
|
|
return it->_value;
|
|
}
|
|
|
|
PlayMediaSignaller::PlayMediaSignaller() {
|
|
}
|
|
|
|
PlayMediaSignaller::~PlayMediaSignaller() {
|
|
}
|
|
|
|
void PlayMediaSignaller::playMedia(Runtime *runtime, Project *project) {
|
|
const size_t numReceivers = _receivers.size();
|
|
for (size_t i = 0; i < numReceivers; i++) {
|
|
_receivers[i]->playMedia(runtime, project);
|
|
}
|
|
}
|
|
|
|
void PlayMediaSignaller::addReceiver(IPlayMediaSignalReceiver *receiver) {
|
|
_receivers.push_back(receiver);
|
|
}
|
|
|
|
void PlayMediaSignaller::removeReceiver(IPlayMediaSignalReceiver *receiver) {
|
|
for (size_t i = 0; i < _receivers.size(); i++) {
|
|
if (_receivers[i] == receiver) {
|
|
_receivers.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
SegmentUnloadSignaller::SegmentUnloadSignaller(Project *project, int segmentIndex) : _project(project), _segmentIndex(segmentIndex) {
|
|
}
|
|
|
|
SegmentUnloadSignaller::~SegmentUnloadSignaller() {
|
|
}
|
|
|
|
void SegmentUnloadSignaller::onSegmentUnloaded() {
|
|
_project = nullptr;
|
|
|
|
// Need to be careful here because a receiver may unload this object, causing _receivers.size() to be invalid
|
|
const size_t numReceivers = _receivers.size();
|
|
for (size_t i = 0; i < numReceivers; i++) {
|
|
_receivers[i]->onSegmentUnloaded(_segmentIndex);
|
|
}
|
|
}
|
|
|
|
void SegmentUnloadSignaller::addReceiver(ISegmentUnloadSignalReceiver *receiver) {
|
|
_receivers.push_back(receiver);
|
|
}
|
|
|
|
void SegmentUnloadSignaller::removeReceiver(ISegmentUnloadSignalReceiver *receiver) {
|
|
for (size_t i = 0; i < _receivers.size(); i++) {
|
|
if (_receivers[i] == receiver) {
|
|
_receivers.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
KeyboardEventSignaller::KeyboardEventSignaller() {
|
|
}
|
|
|
|
KeyboardEventSignaller::~KeyboardEventSignaller() {
|
|
}
|
|
|
|
void KeyboardEventSignaller::onKeyboardEvent(Runtime *runtime, const KeyboardInputEvent &keyEvt) {
|
|
const size_t numReceivers = _receivers.size();
|
|
for (size_t i = 0; i < numReceivers; i++) {
|
|
_receivers[i]->onKeyboardEvent(runtime, keyEvt);
|
|
}
|
|
}
|
|
|
|
void KeyboardEventSignaller::addReceiver(IKeyboardEventReceiver *receiver) {
|
|
_receivers.push_back(receiver);
|
|
}
|
|
|
|
void KeyboardEventSignaller::removeReceiver(IKeyboardEventReceiver *receiver) {
|
|
for (size_t i = 0; i < _receivers.size(); i++) {
|
|
if (_receivers[i] == receiver) {
|
|
_receivers.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MediaCueState::MediaCueState() : minTime(0), maxTime(0), sourceModifier(nullptr), triggerTiming(kTriggerTimingStart) {
|
|
}
|
|
|
|
void MediaCueState::checkTimestampChange(Runtime *runtime, uint32 oldTS, uint32 newTS, bool continuousTimestamps, bool canTriggerDuring) {
|
|
bool entersRange = (static_cast<int32>(oldTS) < minTime && static_cast<int32>(newTS) >= minTime);
|
|
bool exitsRange = (static_cast<int32>(oldTS) <= maxTime && static_cast<int32>(newTS) > maxTime);
|
|
bool endsInRange = (static_cast<int32>(newTS) >= minTime && static_cast<int32>(newTS) <= maxTime);
|
|
|
|
bool shouldTrigger = false;
|
|
switch (triggerTiming) {
|
|
case kTriggerTimingStart:
|
|
shouldTrigger = continuousTimestamps ? entersRange : endsInRange;
|
|
break;
|
|
case kTriggerTimingEnd:
|
|
shouldTrigger = continuousTimestamps ? exitsRange : false;
|
|
break;
|
|
case kTriggerTimingDuring:
|
|
shouldTrigger = canTriggerDuring ? endsInRange : false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Given the positioning of this, there's not really a way for the immediate flag to have any effect?
|
|
if (shouldTrigger)
|
|
send.sendFromMessenger(runtime, sourceModifier->getMediaCueModifier(), sourceModifier->getMediaCueTriggerSource().lock().get(), incomingData, nullptr);
|
|
}
|
|
|
|
|
|
Project::LabelSuperGroup::LabelSuperGroup() : firstRootNodeIndex(0), numRootNodes(0), numTotalNodes(0), superGroupID(0) {
|
|
}
|
|
|
|
Project::LabelTree::LabelTree() : firstChildIndex(0), numChildren(0), id(0) {
|
|
}
|
|
|
|
Project::Segment::Segment() : weakStream(nullptr) {
|
|
}
|
|
|
|
Project::StreamDesc::StreamDesc() : streamType(kStreamTypeUnknown), segmentIndex(0), size(0), pos(0) {
|
|
}
|
|
|
|
Project::AssetDesc::AssetDesc() : typeCode(0), id(0), streamID(0), filePosition(0) {
|
|
}
|
|
|
|
Project::Project(Runtime *runtime)
|
|
: Structural(runtime), _projectFormat(Data::kProjectFormatUnknown),
|
|
_haveGlobalObjectInfo(false), _haveProjectStructuralDef(false), _playMediaSignaller(new PlayMediaSignaller()),
|
|
_keyboardEventSignaller(new KeyboardEventSignaller()),
|
|
_platform(kProjectPlatformUnknown), _rootArchive(nullptr), _runtimeVersion(kRuntimeVersion100) {
|
|
}
|
|
|
|
Project::~Project() {
|
|
// Project teardown can be chaotic, we need to get rid of things in an orderly fashion.
|
|
|
|
// Remove all modifiers and structural children, which should unhook anything referencing an asset
|
|
_modifiers.clear();
|
|
_children.clear();
|
|
|
|
// Remove all global modifiers
|
|
_globalModifiers.clear();
|
|
|
|
// Unhook assets assets
|
|
_assets.clear();
|
|
|
|
// Unhook plug-ins
|
|
_plugIns.clear();
|
|
|
|
// Unhook cursor graphics
|
|
_cursorGraphics.reset();
|
|
|
|
// Close all segment streams
|
|
for (size_t i = 0; i < _segments.size(); i++)
|
|
closeSegmentStream(i);
|
|
|
|
// Last of all, release project resources
|
|
_resources.reset();
|
|
}
|
|
|
|
VThreadState Project::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
if (Event(EventIDs::kCloseProject, 0).respondsTo(msg->getEvent())) {
|
|
runtime->closeProject();
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
return Structural::asyncConsumeCommand(runtime, msg);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Project::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "allowquit" || attrib == "allowquitkey") {
|
|
DynamicValueWriteDiscardHelper::create(result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return Structural::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
void Project::loadFromDescription(const ProjectDescription &desc, const Hacks &hacks) {
|
|
_resources = desc.getResources();
|
|
_cursorGraphics = desc.getCursorGraphics();
|
|
_subtitles = desc.getSubtitles();
|
|
_platform = desc.getPlatform();
|
|
_rootArchive = desc.getRootArchive();
|
|
_projectRootDir = desc.getProjectRootDir();
|
|
_runtimeVersion = desc.getRuntimeVersion();
|
|
|
|
debug(1, "Loading new project...");
|
|
|
|
const Common::Array<Common::SharedPtr<PlugIn> > &plugIns = desc.getPlugIns();
|
|
|
|
for (Common::Array<Common::SharedPtr<PlugIn> >::const_iterator it = plugIns.begin(), itEnd = plugIns.end(); it != itEnd; ++it) {
|
|
Common::SharedPtr<PlugIn> plugIn = (*it);
|
|
|
|
_plugIns.push_back(plugIn);
|
|
|
|
plugIn->registerModifiers(&_plugInRegistry);
|
|
}
|
|
|
|
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
|
|
|
|
size_t numSegments = desc.getSegments().size();
|
|
debug(1, "Loading %d segments", (int)numSegments);
|
|
_segments.resize(numSegments);
|
|
|
|
for (size_t i = 0; i < numSegments; i++) {
|
|
_segments[i].desc = desc.getSegments()[i];
|
|
}
|
|
|
|
// Try to open the first segment
|
|
openSegmentStream(0);
|
|
|
|
Common::SeekableReadStream *baseStream = _segments[0].weakStream;
|
|
uint16 startValue = baseStream->readUint16LE();
|
|
|
|
if (startValue == 1) {
|
|
// Windows format
|
|
_projectFormat = Data::kProjectFormatWindows;
|
|
} else if (startValue == 0) {
|
|
// Mac format
|
|
_projectFormat = Data::kProjectFormatMacintosh;
|
|
} else if (startValue == 8) {
|
|
// Cross-platform format
|
|
_projectFormat = Data::kProjectFormatNeutral;
|
|
} else {
|
|
warning("Unrecognized project segment header (startValue: %d)", startValue);
|
|
_projectFormat = Data::kProjectFormatWindows;
|
|
}
|
|
|
|
Common::SeekableSubReadStream stream(baseStream, 2, baseStream->size());
|
|
|
|
Data::DataReader catReader(2, stream, (_projectFormat == Data::kProjectFormatMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows, desc.getRuntimeVersion(), desc.isRuntimeVersionAuto());
|
|
|
|
uint32 magic = 0;
|
|
uint32 hdr1 = 0;
|
|
uint32 hdr2 = 0;
|
|
if (!catReader.readMultiple(magic, hdr1, hdr2) || magic != 0xaa55a5a5 || (hdr1 != 0 && hdr1 != 0x2000000) || hdr2 != 14) {
|
|
error("Unrecognized project segment header (%x, %x, %d)", magic, hdr1, hdr2);
|
|
}
|
|
|
|
if (hdr1 == 0x2000000 && _isRuntimeVersionAutoDetect && _runtimeVersion < kRuntimeVersion200) {
|
|
debug(1, "Version auto-detect: Detected as 2.0.0 from V2 project header");
|
|
catReader.setRuntimeVersion(kRuntimeVersion200);
|
|
_runtimeVersion = kRuntimeVersion200;
|
|
}
|
|
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(_plugInRegistry.getDataLoaderRegistry(), catReader, dataObject);
|
|
|
|
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectHeader) {
|
|
error("Expected project header but found something else");
|
|
}
|
|
|
|
Data::loadDataObject(plugInDataLoaderRegistry, catReader, dataObject);
|
|
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectCatalog) {
|
|
error("Expected project catalog but found something else");
|
|
}
|
|
|
|
// Catalog version can update version auto-detect
|
|
_runtimeVersion = catReader.getRuntimeVersion();
|
|
|
|
Data::ProjectCatalog *catalog = static_cast<Data::ProjectCatalog *>(dataObject.get());
|
|
|
|
_segments.resize(catalog->segments.size());
|
|
|
|
debug(1, "Catalog loaded OK, identified %i streams", static_cast<int>(catalog->streams.size()));
|
|
|
|
_streams.resize(catalog->streams.size());
|
|
for (size_t i = 0; i < _streams.size(); i++) {
|
|
StreamDesc &streamDesc = _streams[i];
|
|
const Data::ProjectCatalog::StreamDesc &srcStream = catalog->streams[i];
|
|
|
|
if (!strcmp(srcStream.streamType, "assetStream") || !strcmp(srcStream.streamType, "assetstream"))
|
|
streamDesc.streamType = kStreamTypeAsset;
|
|
else if (!strcmp(srcStream.streamType, "bootStream") || !strcmp(srcStream.streamType, "bootstream"))
|
|
streamDesc.streamType = kStreamTypeBoot;
|
|
else if (!strcmp(srcStream.streamType, "sceneStream") || !strcmp(srcStream.streamType, "scenestream"))
|
|
streamDesc.streamType = kStreamTypeScene;
|
|
else
|
|
streamDesc.streamType = kStreamTypeUnknown;
|
|
|
|
streamDesc.segmentIndex = srcStream.segmentIndexPlusOne - 1;
|
|
streamDesc.size = (_platform == kProjectPlatformMacintosh) ? srcStream.macSize : srcStream.winSize;
|
|
streamDesc.pos = (_platform == kProjectPlatformMacintosh) ? srcStream.macPos : srcStream.winPos;
|
|
}
|
|
|
|
// Locate the boot stream
|
|
size_t bootStreamIndex = 0;
|
|
bool foundBootStream = false;
|
|
for (size_t i = 0; i < _streams.size(); i++) {
|
|
if (_streams[i].streamType == kStreamTypeBoot) {
|
|
bootStreamIndex = i;
|
|
foundBootStream = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundBootStream) {
|
|
error("Failed to find boot stream");
|
|
}
|
|
|
|
debug(1, "Loading boot stream");
|
|
|
|
loadBootStream(bootStreamIndex, hacks);
|
|
|
|
debug(1, "Boot stream loaded successfully");
|
|
}
|
|
|
|
void Project::loadSceneFromStream(const Common::SharedPtr<Structural> &scene, uint32 streamID, const Hacks &hacks) {
|
|
if (streamID == 0 || streamID > _streams.size()) {
|
|
error("Invalid stream ID");
|
|
}
|
|
|
|
size_t streamIndex = streamID - 1;
|
|
|
|
const StreamDesc &streamDesc = _streams[streamIndex];
|
|
uint segmentIndex = streamDesc.segmentIndex;
|
|
|
|
openSegmentStream(segmentIndex);
|
|
|
|
Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
|
|
Data::DataReader reader(streamDesc.pos, stream, (_platform == kProjectPlatformMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows, _runtimeVersion, _isRuntimeVersionAutoDetect);
|
|
|
|
if (getRuntime()->getHacks().mtiHispaniolaDamagedStringHack && scene->getName() == "C01b : Main Deck Helm Kidnap")
|
|
reader.setPermitDamagedStrings(true);
|
|
|
|
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
|
|
|
|
{
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
|
|
|
|
if (dataObject == nullptr || dataObject->getType() != Data::DataObjectTypes::kStreamHeader) {
|
|
error("Scene stream header was missing");
|
|
}
|
|
}
|
|
|
|
ChildLoaderStack loaderStack;
|
|
AssetDefLoaderContext assetDefLoader;
|
|
|
|
{
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.filteredElements.filterFunc = Data::DataObjectTypes::isElement;
|
|
loaderContext.containerUnion.filteredElements.structural = scene.get();
|
|
loaderContext.remainingCount = 0;
|
|
loaderContext.type = ChildLoaderContext::kTypeFilteredElements;
|
|
|
|
loaderStack.contexts.push_back(loaderContext);
|
|
}
|
|
|
|
int numObjectsLoaded = 0;
|
|
while (stream.pos() != streamDesc.size) {
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
|
|
|
|
if (!dataObject) {
|
|
error("Failed to load stream");
|
|
}
|
|
|
|
Data::DataObjectTypes::DataObjectType dataObjectType = dataObject->getType();
|
|
|
|
if (Data::DataObjectTypes::isAsset(dataObjectType)) {
|
|
// Asset defs can appear anywhere
|
|
loadAssetDef(streamIndex, assetDefLoader, *dataObject.get());
|
|
} else if (dataObjectType == Data::DataObjectTypes::kAssetDataChunk) {
|
|
// Ignore
|
|
continue;
|
|
} else if (loaderStack.contexts.size() > 0) {
|
|
loadContextualObject(streamIndex, loaderStack, *dataObject.get());
|
|
} else {
|
|
error("Unexpectedly exited scene context in loader");
|
|
}
|
|
|
|
numObjectsLoaded++;
|
|
}
|
|
debug(9, "Loaded %d scene objects", numObjectsLoaded);
|
|
|
|
if ((loaderStack.contexts.size() == 1 && loaderStack.contexts[0].type != ChildLoaderContext::kTypeFilteredElements) || loaderStack.contexts.size() > 1) {
|
|
error("Scene stream loader finished in an expected state, something didn't finish loading");
|
|
}
|
|
|
|
scene->holdAssets(assetDefLoader.assets);
|
|
assignAssets(assetDefLoader.assets, hacks);
|
|
}
|
|
|
|
Common::SharedPtr<Modifier> Project::resolveAlias(uint32 aliasID) const {
|
|
if (aliasID == 0 || aliasID > _globalModifiers.getModifiers().size())
|
|
return Common::SharedPtr<Modifier>();
|
|
|
|
return _globalModifiers.getModifiers()[aliasID - 1];
|
|
}
|
|
|
|
Common::SharedPtr<Modifier> Project::findGlobalVarWithName(const Common::String &name) const {
|
|
for (const Common::SharedPtr<Modifier> &modifier : _globalModifiers.getModifiers()) {
|
|
if (modifier && modifier->isVariable() && MTropolis::caseInsensitiveEqual(name, modifier->getName()))
|
|
return modifier;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Project::materializeGlobalVariables(Runtime *runtime, ObjectLinkingScope *outerScope) {
|
|
for (Common::Array<Common::SharedPtr<Modifier> >::const_iterator it = _globalModifiers.getModifiers().begin(), itEnd = _globalModifiers.getModifiers().end(); it != itEnd; ++it) {
|
|
Modifier *modifier = it->get();
|
|
if (!modifier)
|
|
continue;
|
|
|
|
if (modifier->isVariable())
|
|
modifier->materialize(runtime, outerScope);
|
|
}
|
|
}
|
|
|
|
const ProjectPresentationSettings& Project::getPresentationSettings() const {
|
|
return _presentationSettings;
|
|
}
|
|
|
|
bool Project::isProject() const {
|
|
return true;
|
|
}
|
|
|
|
Common::String Project::getAssetNameByID(uint32 assetID) const {
|
|
if (assetID >= _assetsByID.size())
|
|
return Common::String();
|
|
|
|
return _assetsByID[assetID]->name;
|
|
}
|
|
|
|
Common::WeakPtr<Asset> Project::getAssetByID(uint32 assetID) const {
|
|
if (assetID >= _assetsByID.size())
|
|
return Common::WeakPtr<Asset>();
|
|
|
|
const AssetDesc *desc = _assetsByID[assetID];
|
|
if (desc == nullptr)
|
|
return Common::WeakPtr<Asset>();
|
|
|
|
return desc->asset;
|
|
}
|
|
|
|
void Project::forceLoadAsset(uint32 assetID, Common::Array<Common::SharedPtr<Asset> > &outHoldAssets) {
|
|
AssetDesc *assetDesc = _assetsByID[assetID];
|
|
uint32 streamID = assetDesc->streamID;
|
|
|
|
size_t streamIndex = streamID - 1;
|
|
|
|
const StreamDesc &streamDesc = _streams[streamIndex];
|
|
uint segmentIndex = streamDesc.segmentIndex;
|
|
|
|
openSegmentStream(segmentIndex);
|
|
|
|
Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
|
|
Data::DataReader reader(streamDesc.pos, stream, (_projectFormat == Data::kProjectFormatMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows, _runtimeVersion, _isRuntimeVersionAutoDetect);
|
|
|
|
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
|
|
|
|
reader.seek(assetDesc->filePosition - streamDesc.pos);
|
|
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
|
|
|
|
if (!dataObject) {
|
|
error("Failed to force-load asset data object");
|
|
}
|
|
|
|
Data::DataObjectTypes::DataObjectType dataObjectType = dataObject->getType();
|
|
|
|
if (!Data::DataObjectTypes::isAsset(dataObjectType)) {
|
|
error("Failed to force-load asset, the data object at the expected position wasn't an asset");
|
|
}
|
|
|
|
AssetDefLoaderContext assetDefLoader;
|
|
loadAssetDef(streamIndex, assetDefLoader, *dataObject.get());
|
|
|
|
assignAssets(assetDefLoader.assets, getRuntime()->getHacks());
|
|
|
|
outHoldAssets = Common::move(assetDefLoader.assets);
|
|
}
|
|
|
|
bool Project::getAssetIDByName(const Common::String &assetName, uint32 &outAssetID) const {
|
|
for (uint32 assetID = 0; assetID < _assetsByID.size(); assetID++) {
|
|
const AssetDesc *assetDesc = _assetsByID[assetID];
|
|
if (!assetDesc)
|
|
continue;
|
|
|
|
if (caseInsensitiveEqual(assetName, assetDesc->name)) {
|
|
outAssetID = assetID;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
size_t Project::getSegmentForStreamIndex(size_t streamIndex) const {
|
|
return _streams[streamIndex].segmentIndex;
|
|
}
|
|
|
|
void Project::openSegmentStream(int segmentIndex) {
|
|
if (segmentIndex < 0 || static_cast<size_t>(segmentIndex) > _segments.size()) {
|
|
error("Invalid segment index %i", segmentIndex);
|
|
}
|
|
|
|
Segment &segment = _segments[segmentIndex];
|
|
|
|
if (segment.weakStream)
|
|
return;
|
|
|
|
if (segment.desc.stream) {
|
|
segment.rcStream.reset();
|
|
segment.weakStream = segment.desc.stream;
|
|
} else {
|
|
Common::Path defaultPath = _projectRootDir.appendComponent(segment.desc.filePath);
|
|
|
|
if (_platform == kProjectPlatformMacintosh)
|
|
segment.rcStream.reset(Common::MacResManager::openFileOrDataFork(defaultPath, *_rootArchive));
|
|
else
|
|
segment.rcStream.reset(_rootArchive->createReadStreamForMember(defaultPath));
|
|
|
|
if (!segment.rcStream) {
|
|
warning("Segment '%s' isn't in the project directory", segment.desc.filePath.c_str());
|
|
|
|
Common::ArchiveMemberList memberList;
|
|
Common::ArchiveMemberPtr locatedMember;
|
|
|
|
_rootArchive->listMembers(memberList);
|
|
|
|
for (const Common::ArchiveMemberPtr &member : memberList) {
|
|
if (member->getFileName().equalsIgnoreCase(segment.desc.filePath)) {
|
|
if (locatedMember)
|
|
error("Segment '%s' exists multiple times in the workspace, and isn't in the project directory, couldn't disambiguate", segment.desc.filePath.c_str());
|
|
|
|
locatedMember = member;
|
|
}
|
|
}
|
|
|
|
if (!locatedMember)
|
|
error("Segment '%s' is missing from the workspace", segment.desc.filePath.c_str());
|
|
|
|
if (_platform == kProjectPlatformMacintosh)
|
|
segment.rcStream.reset(Common::MacResManager::openFileOrDataFork(locatedMember->getPathInArchive(), *_rootArchive));
|
|
else
|
|
segment.rcStream.reset(locatedMember->createReadStream());
|
|
|
|
if (!segment.rcStream)
|
|
error("Failed to open segment file %s", segment.desc.filePath.c_str());
|
|
}
|
|
|
|
segment.weakStream = segment.rcStream.get();
|
|
}
|
|
|
|
segment.unloadSignaller.reset(new SegmentUnloadSignaller(this, segmentIndex));
|
|
}
|
|
|
|
void Project::closeSegmentStream(int segmentIndex) {
|
|
Segment &segment = _segments[segmentIndex];
|
|
|
|
if (!segment.weakStream)
|
|
return;
|
|
|
|
segment.unloadSignaller->onSegmentUnloaded();
|
|
segment.unloadSignaller.reset();
|
|
segment.rcStream.reset();
|
|
segment.weakStream = nullptr;
|
|
}
|
|
|
|
Common::SeekableReadStream* Project::getStreamForSegment(int segmentIndex) {
|
|
return _segments[segmentIndex].weakStream;
|
|
}
|
|
|
|
const Common::String *Project::findNameOfLabel(const Label &label) const {
|
|
for (const LabelSuperGroup &superGroup : _labelSuperGroups) {
|
|
if (superGroup.superGroupID == label.superGroupID) {
|
|
size_t firstRootIndex = superGroup.firstRootNodeIndex;
|
|
size_t totalNodes = superGroup.numTotalNodes;
|
|
|
|
for (size_t i = 0; i < totalNodes; i++) {
|
|
const LabelTree &tree = _labelTree[i + firstRootIndex];
|
|
if (tree.id == label.id)
|
|
return &tree.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Common::SharedPtr<SegmentUnloadSignaller> Project::notifyOnSegmentUnload(int segmentIndex, ISegmentUnloadSignalReceiver *receiver) {
|
|
Common::SharedPtr<SegmentUnloadSignaller> signaller = _segments[segmentIndex].unloadSignaller;
|
|
if (signaller)
|
|
signaller->addReceiver(receiver);
|
|
return signaller;
|
|
}
|
|
|
|
void Project::onPostRender() {
|
|
_playMediaSignaller->playMedia(getRuntime(), this);
|
|
}
|
|
|
|
Common::SharedPtr<PlayMediaSignaller> Project::notifyOnPlayMedia(IPlayMediaSignalReceiver *receiver) {
|
|
_playMediaSignaller->addReceiver(receiver);
|
|
return _playMediaSignaller;
|
|
}
|
|
|
|
void Project::onKeyboardEvent(Runtime *runtime, const KeyboardInputEvent &keyEvt) {
|
|
_keyboardEventSignaller->onKeyboardEvent(runtime, keyEvt);
|
|
}
|
|
|
|
Common::SharedPtr<KeyboardEventSignaller> Project::notifyOnKeyboardEvent(IKeyboardEventReceiver *receiver) {
|
|
_keyboardEventSignaller->addReceiver(receiver);
|
|
return _keyboardEventSignaller;
|
|
}
|
|
|
|
const char *Project::findAuthorMessageName(uint32 id) const {
|
|
for (size_t i = 0; i < _labelSuperGroups.size(); i++) {
|
|
const LabelSuperGroup &sg = _labelSuperGroups[i];
|
|
if (sg.name == "Author Messages") {
|
|
size_t firstNode = sg.firstRootNodeIndex;
|
|
size_t numNodes = sg.numTotalNodes;
|
|
for (size_t j = 0; j < numNodes; j++) {
|
|
const LabelTree &treeNode = _labelTree[j + firstNode];
|
|
if (treeNode.id == id)
|
|
return treeNode.name.c_str();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
const Common::SharedPtr<CursorGraphicCollection> &Project::getCursorGraphics() const {
|
|
return _cursorGraphics;
|
|
}
|
|
|
|
void Project::loadBootStream(size_t streamIndex, const Hacks &hacks) {
|
|
const StreamDesc &streamDesc = _streams[streamIndex];
|
|
|
|
size_t segmentIndex = streamDesc.segmentIndex;
|
|
openSegmentStream(segmentIndex);
|
|
|
|
Common::SeekableSubReadStream stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size);
|
|
Data::DataReader reader(streamDesc.pos, stream, (_platform == kProjectPlatformMacintosh) ? Data::kDataFormatMacintosh : Data::kDataFormatWindows, _runtimeVersion, _isRuntimeVersionAutoDetect);
|
|
|
|
ChildLoaderStack loaderStack;
|
|
AssetDefLoaderContext assetDefLoader;
|
|
|
|
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
|
|
|
|
int numObjectsLoaded = 0;
|
|
while (stream.pos() != streamDesc.size) {
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
|
|
|
|
if (!dataObject) {
|
|
error("Failed to load project boot data");
|
|
}
|
|
|
|
Data::DataObjectTypes::DataObjectType dataObjectType = dataObject->getType();
|
|
|
|
if (Data::DataObjectTypes::isAsset(dataObjectType)) {
|
|
// Asset defs can appear anywhere
|
|
loadAssetDef(streamIndex, assetDefLoader, *dataObject.get());
|
|
} else if (dataObjectType == Data::DataObjectTypes::kAssetDataChunk) {
|
|
// Ignore
|
|
continue;
|
|
} else if (loaderStack.contexts.size() > 0) {
|
|
loadContextualObject(streamIndex, loaderStack, *dataObject.get());
|
|
} else {
|
|
// Root-level objects
|
|
switch (dataObject->getType()) {
|
|
case Data::DataObjectTypes::kPresentationSettings:
|
|
loadPresentationSettings(*static_cast<const Data::PresentationSettings *>(dataObject.get()));
|
|
break;
|
|
case Data::DataObjectTypes::kAssetCatalog:
|
|
loadAssetCatalog(*static_cast<const Data::AssetCatalog *>(dataObject.get()));
|
|
break;
|
|
case Data::DataObjectTypes::kGlobalObjectInfo:
|
|
loadGlobalObjectInfo(loaderStack, *static_cast<const Data::GlobalObjectInfo *>(dataObject.get()));
|
|
break;
|
|
case Data::DataObjectTypes::kProjectLabelMap:
|
|
loadLabelMap(*static_cast<const Data::ProjectLabelMap *>(dataObject.get()));
|
|
break;
|
|
case Data::DataObjectTypes::kProjectStructuralDef: {
|
|
if (_haveProjectStructuralDef)
|
|
error("Multiple project structural defs");
|
|
|
|
const Data::ProjectStructuralDef *def = static_cast<const Data::ProjectStructuralDef *>(dataObject.get());
|
|
_name = def->name;
|
|
_guid = def->guid;
|
|
|
|
_haveProjectStructuralDef = true;
|
|
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.structural = this;
|
|
loaderContext.remainingCount = 0;
|
|
loaderContext.type = ChildLoaderContext::kTypeProject;
|
|
|
|
loaderStack.contexts.push_back(loaderContext);
|
|
|
|
initAdditionalSegments(def->name);
|
|
} break;
|
|
case Data::DataObjectTypes::kStreamHeader:
|
|
case Data::DataObjectTypes::kUnknown19:
|
|
case Data::DataObjectTypes::kUnknown2B:
|
|
// Ignore
|
|
break;
|
|
default:
|
|
error("Unexpected object type in boot stream");
|
|
}
|
|
}
|
|
|
|
numObjectsLoaded++;
|
|
}
|
|
|
|
debug(9, "Loaded %d boot objects", numObjectsLoaded);
|
|
|
|
if (loaderStack.contexts.size() != 1 || loaderStack.contexts[0].type != ChildLoaderContext::kTypeProject) {
|
|
error("Boot stream loader finished in an expected state, something didn't finish loading");
|
|
}
|
|
|
|
holdAssets(assetDefLoader.assets);
|
|
assignAssets(assetDefLoader.assets, hacks);
|
|
}
|
|
|
|
const SubtitleTables &Project::getSubtitles() const {
|
|
return _subtitles;
|
|
}
|
|
|
|
RuntimeVersion Project::getRuntimeVersion() const {
|
|
return _runtimeVersion;
|
|
}
|
|
|
|
ProjectPlatform Project::getPlatform() const {
|
|
return _platform;
|
|
}
|
|
|
|
void Project::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
error("Cloning a project is not supported");
|
|
}
|
|
|
|
Common::SharedPtr<Structural> Project::shallowClone() const {
|
|
error("Cloning a project is not supported");
|
|
return nullptr;
|
|
}
|
|
|
|
void Project::loadPresentationSettings(const Data::PresentationSettings &presentationSettings) {
|
|
_presentationSettings.bitsPerPixel = presentationSettings.bitsPerPixel;
|
|
if (_presentationSettings.bitsPerPixel != 8 && _presentationSettings.bitsPerPixel != 16) {
|
|
error("Unsupported bit depth");
|
|
}
|
|
_presentationSettings.width = presentationSettings.dimensions.x;
|
|
_presentationSettings.height = presentationSettings.dimensions.y;
|
|
}
|
|
|
|
void Project::loadAssetCatalog(const Data::AssetCatalog &assetCatalog) {
|
|
_assetsByID.clear();
|
|
_realAssets.clear();
|
|
_assetNameToID.clear();
|
|
|
|
size_t numRealAssets = 0;
|
|
for (size_t i = 0; i < assetCatalog.assets.size(); i++) {
|
|
const Data::AssetCatalog::AssetInfo &assetInfo = assetCatalog.assets[i];
|
|
if ((assetInfo.flags1 & Data::AssetCatalog::kFlag1Deleted) == 0)
|
|
numRealAssets++;
|
|
}
|
|
|
|
_realAssets.resize(numRealAssets);
|
|
_assetsByID.resize(assetCatalog.assets.size() + 1);
|
|
|
|
_assetsByID[0] = nullptr;
|
|
|
|
numRealAssets = 0;
|
|
for (size_t i = 0; i < assetCatalog.assets.size(); i++) {
|
|
const Data::AssetCatalog::AssetInfo &assetInfo = assetCatalog.assets[i];
|
|
if (assetInfo.flags1 & Data::AssetCatalog::kFlag1Deleted) {
|
|
_assetsByID[i + 1] = nullptr;
|
|
} else {
|
|
AssetDesc &assetDesc = _realAssets[numRealAssets++];
|
|
|
|
assetDesc.id = i + 1;
|
|
assetDesc.name = assetInfo.name;
|
|
|
|
if (assetCatalog.haveRev4Fields)
|
|
assetDesc.typeCode = assetInfo.rev4Fields.assetType;
|
|
else
|
|
assetDesc.typeCode = 0;
|
|
|
|
assetDesc.streamID = assetInfo.streamID;
|
|
assetDesc.filePosition = assetInfo.filePosition;
|
|
|
|
_assetsByID[assetDesc.id] = &assetDesc;
|
|
if (!assetDesc.name.empty())
|
|
_assetNameToID[assetDesc.name] = assetDesc.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Project::loadGlobalObjectInfo(ChildLoaderStack &loaderStack, const Data::GlobalObjectInfo& globalObjectInfo) {
|
|
if (_haveGlobalObjectInfo)
|
|
error("Multiple global object infos");
|
|
|
|
_haveGlobalObjectInfo = true;
|
|
|
|
if (globalObjectInfo.numGlobalModifiers > 0) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.modifierContainer = &_globalModifiers;
|
|
loaderContext.remainingCount = globalObjectInfo.numGlobalModifiers;
|
|
loaderContext.type = ChildLoaderContext::kTypeCountedModifierList;
|
|
|
|
loaderStack.contexts.push_back(loaderContext);
|
|
}
|
|
}
|
|
|
|
Common::SharedPtr<Modifier> Project::loadModifierObject(ModifierLoaderContext &loaderContext, const Data::DataObject &dataObject) {
|
|
// Special case for debris
|
|
if (dataObject.getType() == Data::DataObjectTypes::kDebris)
|
|
return nullptr;
|
|
|
|
Common::SharedPtr<Modifier> modifier;
|
|
|
|
// Special case for plug-ins
|
|
if (dataObject.getType() == Data::DataObjectTypes::kPlugInModifier) {
|
|
const Data::PlugInModifier &plugInData = static_cast<const Data::PlugInModifier &>(dataObject);
|
|
|
|
const IPlugInModifierFactory *factory = _plugInRegistry.findPlugInModifierFactory(plugInData.modifierName);
|
|
if (!factory)
|
|
error("Unknown or unsupported plug-in modifier type");
|
|
|
|
modifier = factory->createModifier(loaderContext, plugInData);
|
|
} else {
|
|
SIModifierFactory *factory = getModifierFactoryForDataObjectType(dataObject.getType());
|
|
|
|
if (!factory)
|
|
error("Unknown or unsupported modifier type, or non-modifier encountered where a modifier was expected");
|
|
|
|
modifier = factory->createModifier(loaderContext, dataObject);
|
|
}
|
|
if (!modifier)
|
|
error("Modifier object failed to load");
|
|
assert(modifier->getModifierFlags().flagsWereLoaded);
|
|
|
|
uint32 guid = modifier->getStaticGUID();
|
|
const Common::HashMap<uint32, Common::SharedPtr<ModifierHooks> > &hooksMap = getRuntime()->getHacks().modifierHooks;
|
|
Common::HashMap<uint32, Common::SharedPtr<ModifierHooks> >::const_iterator hooksIt = hooksMap.find(guid);
|
|
if (hooksIt != hooksMap.end()) {
|
|
modifier->setHooks(hooksIt->_value);
|
|
hooksIt->_value->onCreate(modifier.get());
|
|
}
|
|
|
|
return modifier;
|
|
}
|
|
|
|
void Project::loadLabelMap(const Data::ProjectLabelMap &projectLabelMap) {
|
|
debug(1, "Loading label map...");
|
|
|
|
_labelSuperGroups.resize(projectLabelMap.numSuperGroups);
|
|
|
|
size_t totalLabels = 0;
|
|
for (size_t i = 0; i < projectLabelMap.numSuperGroups; i++) {
|
|
_labelSuperGroups[i].numTotalNodes = 0;
|
|
for (size_t j = 0; j < projectLabelMap.superGroups[i].numChildren; j++)
|
|
totalLabels += recursiveCountLabels(projectLabelMap.superGroups[i].tree[j]);
|
|
}
|
|
|
|
Common::Array<const Data::ProjectLabelMap::LabelTree *> treeQueue;
|
|
treeQueue.resize(totalLabels);
|
|
_labelTree.resize(totalLabels);
|
|
|
|
// Expand label tree into a breadth-first tree but cluster all super-groups
|
|
size_t insertionOffset = 0;
|
|
size_t dequeueOffset = 0;
|
|
for (size_t i = 0; i < projectLabelMap.numSuperGroups; i++) {
|
|
const Data::ProjectLabelMap::SuperGroup &dataSG = projectLabelMap.superGroups[i];
|
|
LabelSuperGroup &sg = _labelSuperGroups[i];
|
|
|
|
sg.name = dataSG.name;
|
|
sg.superGroupID = dataSG.id;
|
|
|
|
sg.firstRootNodeIndex = insertionOffset;
|
|
sg.numRootNodes = dataSG.numChildren;
|
|
|
|
for (size_t j = 0; j < dataSG.numChildren; j++)
|
|
treeQueue[insertionOffset++] = &dataSG.tree[j];
|
|
|
|
while (dequeueOffset < insertionOffset) {
|
|
const Data::ProjectLabelMap::LabelTree &dataTree = *treeQueue[dequeueOffset];
|
|
LabelTree &labelTree = _labelTree[dequeueOffset];
|
|
|
|
labelTree.id = dataTree.id;
|
|
labelTree.name = dataTree.name;
|
|
dequeueOffset++;
|
|
|
|
labelTree.firstChildIndex = insertionOffset;
|
|
labelTree.numChildren = dataTree.numChildren;
|
|
for (size_t j = 0; j < dataTree.numChildren; j++)
|
|
treeQueue[insertionOffset++] = &dataTree.children[j];
|
|
}
|
|
|
|
sg.numTotalNodes = insertionOffset - sg.firstRootNodeIndex;
|
|
}
|
|
|
|
debug(1, "Loaded %i labels and %i supergroups", static_cast<int>(_labelTree.size()), static_cast<int>(_labelSuperGroups.size()));
|
|
}
|
|
|
|
size_t Project::recursiveCountLabels(const Data::ProjectLabelMap::LabelTree& tree) {
|
|
size_t numLabels = 1; // For the node itself
|
|
for (size_t i = 0; i < tree.numChildren; i++)
|
|
numLabels += recursiveCountLabels(tree.children[i]);
|
|
return numLabels;
|
|
}
|
|
|
|
ObjectLinkingScope *Project::getPersistentStructuralScope() {
|
|
return &_structuralScope;
|
|
}
|
|
|
|
ObjectLinkingScope *Project::getPersistentModifierScope() {
|
|
return &_modifierScope;
|
|
}
|
|
|
|
void Project::assignAssets(const Common::Array<Common::SharedPtr<Asset> >& assets, const Hacks &hacks) {
|
|
for (Common::Array<Common::SharedPtr<Asset> >::const_iterator it = assets.begin(), itEnd = assets.end(); it != itEnd; ++it) {
|
|
Common::SharedPtr<Asset> asset = *it;
|
|
uint32 assetID = asset->getAssetID();
|
|
|
|
if (assetID >= _assetsByID.size()) {
|
|
warning("Bad asset ID %u", assetID);
|
|
continue;
|
|
}
|
|
|
|
AssetDesc *desc = _assetsByID[assetID];
|
|
if (desc == nullptr) {
|
|
warning("Asset attempting to use deleted asset slot %u", assetID);
|
|
continue;
|
|
}
|
|
|
|
if (desc->asset.expired()) {
|
|
desc->asset = asset;
|
|
|
|
for (const Common::SharedPtr<AssetHooks> &hook : hacks.assetHooks)
|
|
hook->onLoaded(asset.get(), desc->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Project::initAdditionalSegments(const Common::String &projectName) {
|
|
for (uint segmentIndex = 1; segmentIndex < _segments.size(); segmentIndex++) {
|
|
Segment &segment = _segments[segmentIndex];
|
|
|
|
Common::String segmentName = projectName + Common::String::format("%i", static_cast<int>(segmentIndex + 1));
|
|
|
|
if (_projectFormat == Data::kProjectFormatNeutral) {
|
|
segmentName += ".mxx";
|
|
} else if (_projectFormat == Data::kProjectFormatWindows) {
|
|
if (_runtimeVersion >= kRuntimeVersion200)
|
|
segmentName += ".mxw";
|
|
else
|
|
segmentName += ".mpx";
|
|
} else if (_projectFormat == Data::kProjectFormatMacintosh) {
|
|
if (_runtimeVersion >= kRuntimeVersion200)
|
|
segmentName += ".mxm";
|
|
}
|
|
|
|
// Attempt to find the segment
|
|
segment.desc.filePath = segmentName;
|
|
segment.desc.volumeID = segmentIndex;
|
|
}
|
|
}
|
|
|
|
void Project::loadContextualObject(size_t streamIndex, ChildLoaderStack &stack, const Data::DataObject &dataObject) {
|
|
ChildLoaderContext &topContext = stack.contexts.back();
|
|
const Data::DataObjectTypes::DataObjectType dataObjectType = dataObject.getType();
|
|
|
|
// The stack entry must always be popped before loading the object because the load process may descend into more children,
|
|
// such as when behaviors are nested.
|
|
switch (topContext.type) {
|
|
case ChildLoaderContext::kTypeCountedModifierList: {
|
|
IModifierContainer *container = topContext.containerUnion.modifierContainer;
|
|
|
|
if ((--topContext.remainingCount) == 0)
|
|
stack.contexts.pop_back();
|
|
|
|
ModifierLoaderContext loaderContext(&stack);
|
|
|
|
container->appendModifier(loadModifierObject(loaderContext, dataObject));
|
|
} break;
|
|
case ChildLoaderContext::kTypeFlagTerminatedModifierList: {
|
|
IModifierContainer *container = topContext.containerUnion.modifierContainer;
|
|
|
|
size_t modifierListContextOffset = stack.contexts.size() - 1;
|
|
|
|
ModifierLoaderContext loaderContext(&stack);
|
|
|
|
Common::SharedPtr<Modifier> modifier = loadModifierObject(loaderContext, dataObject);
|
|
|
|
if (modifier->getModifierFlags().isLastModifier)
|
|
stack.contexts.remove_at(modifierListContextOffset);
|
|
|
|
container->appendModifier(modifier);
|
|
} break;
|
|
case ChildLoaderContext::kTypeProject: {
|
|
Structural *project = topContext.containerUnion.structural;
|
|
|
|
if (dataObjectType == Data::DataObjectTypes::kSectionStructuralDef) {
|
|
|
|
const Data::SectionStructuralDef §ionObject = static_cast<const Data::SectionStructuralDef &>(dataObject);
|
|
|
|
Common::SharedPtr<Structural> section(new Section());
|
|
section->setSelfReference(section);
|
|
|
|
if (!static_cast<Section *>(section.get())->load(sectionObject))
|
|
error("Failed to load section");
|
|
|
|
project->addChild(section);
|
|
section->setParent(project);
|
|
|
|
// For some reason all section objects have the "no more siblings" structural flag.
|
|
// There doesn't appear to be any indication of how many section objects there will
|
|
// be either.
|
|
//if (sectionObject.structuralFlags & Data::StructuralFlags::kNoMoreSiblings)
|
|
// stack.contexts.pop_back();
|
|
|
|
if (sectionObject.structuralFlags & Data::StructuralFlags::kHasChildren) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.structural = section.get();
|
|
loaderContext.remainingCount = 0;
|
|
loaderContext.type = ChildLoaderContext::kTypeSection;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
|
|
if (sectionObject.structuralFlags & Data::StructuralFlags::kHasModifiers) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.modifierContainer = section.get();
|
|
loaderContext.type = ChildLoaderContext::kTypeFlagTerminatedModifierList;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
} else if (Data::DataObjectTypes::isModifier(dataObjectType)) {
|
|
ModifierLoaderContext loaderContext(&stack);
|
|
project->appendModifier(loadModifierObject(loaderContext, dataObject));
|
|
} else {
|
|
error("Unexpected object type in this context");
|
|
}
|
|
} break;
|
|
case ChildLoaderContext::kTypeSection: {
|
|
Structural *section = topContext.containerUnion.structural;
|
|
|
|
if (dataObject.getType() == Data::DataObjectTypes::kSubsectionStructuralDef) {
|
|
const Data::SubsectionStructuralDef &subsectionObject = static_cast<const Data::SubsectionStructuralDef &>(dataObject);
|
|
|
|
Common::SharedPtr<Structural> subsection(new Subsection());
|
|
subsection->setSelfReference(subsection);
|
|
|
|
if (!static_cast<Subsection *>(subsection.get())->load(subsectionObject))
|
|
error("Failed to load subsection");
|
|
|
|
section->addChild(subsection);
|
|
|
|
if (subsectionObject.structuralFlags & Data::StructuralFlags::kNoMoreSiblings)
|
|
stack.contexts.pop_back();
|
|
|
|
if (subsectionObject.structuralFlags & Data::StructuralFlags::kHasChildren) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.filteredElements.structural = subsection.get();
|
|
loaderContext.containerUnion.filteredElements.filterFunc = Data::DataObjectTypes::isValidSceneRootElement;
|
|
loaderContext.remainingCount = 0;
|
|
loaderContext.type = ChildLoaderContext::kTypeFilteredElements;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
|
|
if (subsectionObject.structuralFlags & Data::StructuralFlags::kHasModifiers) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.modifierContainer = subsection.get();
|
|
loaderContext.type = ChildLoaderContext::kTypeFlagTerminatedModifierList;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
} else if (Data::DataObjectTypes::isModifier(dataObjectType)) {
|
|
ModifierLoaderContext loaderContext(&stack);
|
|
section->appendModifier(loadModifierObject(loaderContext, dataObject));
|
|
} else {
|
|
error("Unexpected object type in this context");
|
|
}
|
|
} break;
|
|
case ChildLoaderContext::kTypeFilteredElements: {
|
|
Structural *container = topContext.containerUnion.filteredElements.structural;
|
|
|
|
if (topContext.containerUnion.filteredElements.filterFunc(dataObjectType)) {
|
|
const Data::StructuralDef &structuralDef = static_cast<const Data::StructuralDef &>(dataObject);
|
|
|
|
SIElementFactory *elementFactory = getElementFactoryForDataObjectType(dataObjectType);
|
|
if (!elementFactory) {
|
|
error("No element factory defined for structural object");
|
|
}
|
|
|
|
ElementLoaderContext elementLoaderContext(getRuntime(), this, streamIndex);
|
|
Common::SharedPtr<Element> element = elementFactory->createElement(elementLoaderContext, dataObject);
|
|
|
|
uint32 guid = element->getStaticGUID();
|
|
const Common::HashMap<uint32, Common::SharedPtr<StructuralHooks> > &hooksMap = getRuntime()->getHacks().structuralHooks;
|
|
Common::HashMap<uint32, Common::SharedPtr<StructuralHooks> >::const_iterator hooksIt = hooksMap.find(guid);
|
|
if (hooksIt == hooksMap.end()) {
|
|
Common::SharedPtr<StructuralHooks> defaultStructuralHooks = getRuntime()->getHacks().defaultStructuralHooks;
|
|
if (defaultStructuralHooks) {
|
|
element->setHooks(defaultStructuralHooks);
|
|
defaultStructuralHooks->onCreate(element.get());
|
|
}
|
|
} else {
|
|
element->setHooks(hooksIt->_value);
|
|
hooksIt->_value->onCreate(element.get());
|
|
}
|
|
|
|
container->addChild(element);
|
|
|
|
if (structuralDef.structuralFlags & Data::StructuralFlags::kNoMoreSiblings)
|
|
stack.contexts.pop_back();
|
|
|
|
if (structuralDef.structuralFlags & Data::StructuralFlags::kHasChildren) {
|
|
ChildLoaderContext loaderContext;
|
|
// Visual elements can contain non-visual element children, but non-visual elements
|
|
// can only contain non-visual element children
|
|
loaderContext.containerUnion.filteredElements.filterFunc = element->isVisual() ? Data::DataObjectTypes::isElement : Data::DataObjectTypes::isNonVisualElement;
|
|
loaderContext.containerUnion.filteredElements.structural = element.get();
|
|
loaderContext.remainingCount = 0;
|
|
loaderContext.type = ChildLoaderContext::kTypeFilteredElements;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
|
|
if (structuralDef.structuralFlags & Data::StructuralFlags::kHasModifiers) {
|
|
ChildLoaderContext loaderContext;
|
|
loaderContext.containerUnion.modifierContainer = element.get();
|
|
loaderContext.type = ChildLoaderContext::kTypeFlagTerminatedModifierList;
|
|
|
|
stack.contexts.push_back(loaderContext);
|
|
}
|
|
} else if (Data::DataObjectTypes::isModifier(dataObjectType)) {
|
|
ModifierLoaderContext loaderContext(&stack);
|
|
container->appendModifier(loadModifierObject(loaderContext, dataObject));
|
|
} else {
|
|
error("Unexpected object type in this context");
|
|
}
|
|
} break;
|
|
default:
|
|
error("Tried to load a contextual object outside of a context");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Project::loadAssetDef(size_t streamIndex, AssetDefLoaderContext& context, const Data::DataObject& dataObject) {
|
|
assert(Data::DataObjectTypes::isAsset(dataObject.getType()));
|
|
|
|
SIAssetFactory *factory = getAssetFactoryForDataObjectType(dataObject.getType());
|
|
if (!factory) {
|
|
error("Unimplemented asset type");
|
|
return;
|
|
}
|
|
|
|
AssetLoaderContext loaderContext(streamIndex);
|
|
Common::SharedPtr<Asset> asset = factory->createAsset(loaderContext, dataObject);
|
|
if (!asset) {
|
|
warning("An asset failed to load");
|
|
return;
|
|
}
|
|
context.assets.push_back(asset);
|
|
}
|
|
|
|
bool Section::load(const Data::SectionStructuralDef &data) {
|
|
_name = data.name;
|
|
_guid = data.guid;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Section::isSection() const {
|
|
return true;
|
|
}
|
|
|
|
Common::SharedPtr<Structural> Section::shallowClone() const {
|
|
error("Cloning sections is not supported");
|
|
return nullptr;
|
|
}
|
|
|
|
void Section::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
error("Cloning sections is not supported");
|
|
}
|
|
|
|
ObjectLinkingScope *Section::getPersistentStructuralScope() {
|
|
return &_structuralScope;
|
|
}
|
|
|
|
ObjectLinkingScope *Section::getPersistentModifierScope() {
|
|
return &_modifierScope;
|
|
}
|
|
|
|
bool Subsection::load(const Data::SubsectionStructuralDef &data) {
|
|
_name = data.name;
|
|
_guid = data.guid;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Subsection::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
return Structural::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
bool Subsection::isSubsection() const {
|
|
return true;
|
|
}
|
|
|
|
Common::SharedPtr<Structural> Subsection::shallowClone() const {
|
|
error("Cloning subsections is not supported");
|
|
return nullptr;
|
|
}
|
|
|
|
void Subsection::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
error("Cloning subsections is not supported");
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getSceneLoadMaterializeScope() {
|
|
return getPersistentStructuralScope();
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getPersistentStructuralScope() {
|
|
return &_structuralScope;
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getPersistentModifierScope() {
|
|
return &_modifierScope;
|
|
}
|
|
|
|
Element::Element() : _streamLocator(0), _sectionID(0), _haveCheckedAutoPlay(false) {
|
|
}
|
|
|
|
Element::Element(const Element &other)
|
|
: Structural(other), _streamLocator(other._streamLocator), _sectionID(other._sectionID)
|
|
// Don't copy checked autoplay or mediacues lists
|
|
, _haveCheckedAutoPlay(false), _mediaCues() {
|
|
}
|
|
|
|
bool Element::canAutoPlay() const {
|
|
return false;
|
|
}
|
|
|
|
void Element::queueAutoPlayEvents(Runtime *runtime, bool isAutoPlaying) {
|
|
if (isAutoPlaying) {
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kPlay, 0), DynamicValue(), getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, false, true));
|
|
runtime->queueMessage(dispatch);
|
|
}
|
|
}
|
|
|
|
bool Element::isElement() const {
|
|
return true;
|
|
}
|
|
|
|
uint32 Element::getStreamLocator() const {
|
|
return _streamLocator;
|
|
}
|
|
|
|
void Element::addMediaCue(MediaCueState *mediaCue) {
|
|
_mediaCues.push_back(mediaCue);
|
|
}
|
|
|
|
void Element::removeMediaCue(const MediaCueState *mediaCue) {
|
|
for (size_t i = 0; i < _mediaCues.size(); i++) {
|
|
if (_mediaCues[i] == mediaCue) {
|
|
_mediaCues.remove_at(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Element::triggerAutoPlay(Runtime *runtime) {
|
|
if (_haveCheckedAutoPlay)
|
|
return;
|
|
|
|
_haveCheckedAutoPlay = true;
|
|
|
|
queueAutoPlayEvents(runtime, canAutoPlay());
|
|
}
|
|
|
|
void Element::tryAutoSetName(Runtime *runtime, Project *project) {
|
|
}
|
|
|
|
bool Element::resolveMediaMarkerLabel(const Label& label, int32 &outResolution) const {
|
|
return false;
|
|
}
|
|
|
|
void Element::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
Structural::visitInternalReferences(visitor);
|
|
}
|
|
|
|
VisualElementTransitionProperties::VisualElementTransitionProperties() : _isDirty(true), _alpha(255) {
|
|
}
|
|
|
|
uint8 VisualElementTransitionProperties::getAlpha() const {
|
|
return _alpha;
|
|
}
|
|
|
|
void VisualElementTransitionProperties::setAlpha(uint8 alpha) {
|
|
_isDirty = true;
|
|
_alpha = alpha;
|
|
}
|
|
|
|
bool VisualElementTransitionProperties::isDirty() const {
|
|
return _isDirty;
|
|
}
|
|
|
|
void VisualElementTransitionProperties::clearDirty() {
|
|
_isDirty = false;
|
|
}
|
|
|
|
VisualElementRenderProperties::VisualElementRenderProperties()
|
|
: _inkMode(kInkModeDefault), _shape(kShapeRect), _foreColor(0, 0, 0), _backColor(255, 255, 255),
|
|
_borderColor(0, 0, 0), _shadowColor(0, 0, 0), _borderSize(0), _shadowSize(0), _isDirty(true) {
|
|
}
|
|
|
|
VisualElementRenderProperties::InkMode VisualElementRenderProperties::getInkMode() const {
|
|
return _inkMode;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setInkMode(InkMode inkMode) {
|
|
_isDirty = true;
|
|
_inkMode = inkMode;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setShape(Shape shape) {
|
|
_isDirty = true;
|
|
_shape = shape;
|
|
}
|
|
|
|
VisualElementRenderProperties::Shape VisualElementRenderProperties::getShape() const {
|
|
return _shape;
|
|
}
|
|
|
|
const ColorRGB8 &VisualElementRenderProperties::getForeColor() const {
|
|
return _foreColor;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setForeColor(const ColorRGB8 &color) {
|
|
_isDirty = true;
|
|
_foreColor = color;
|
|
}
|
|
|
|
const ColorRGB8 &VisualElementRenderProperties::getBackColor() const {
|
|
return _backColor;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setBackColor(const ColorRGB8 &color) {
|
|
_isDirty = true;
|
|
_backColor = color;
|
|
}
|
|
|
|
const ColorRGB8 &VisualElementRenderProperties::getBorderColor() const {
|
|
return _borderColor;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setBorderColor(const ColorRGB8 &color) {
|
|
_isDirty = true;
|
|
_borderColor = color;
|
|
}
|
|
|
|
const ColorRGB8 &VisualElementRenderProperties::getShadowColor() const {
|
|
return _shadowColor;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setShadowColor(const ColorRGB8 &color) {
|
|
_isDirty = true;
|
|
_shadowColor = color;
|
|
}
|
|
|
|
uint16 VisualElementRenderProperties::getBorderSize() const {
|
|
return _borderSize;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setBorderSize(uint16 size) {
|
|
_isDirty = true;
|
|
_borderSize = size;
|
|
}
|
|
|
|
uint16 VisualElementRenderProperties::getShadowSize() const {
|
|
return _shadowSize;
|
|
}
|
|
|
|
void VisualElementRenderProperties::setShadowSize(uint16 size) {
|
|
_isDirty = true;
|
|
_shadowSize = size;
|
|
}
|
|
|
|
const Common::Array<Common::Point> &VisualElementRenderProperties::getPolyPoints() const {
|
|
return _polyPoints;
|
|
}
|
|
|
|
Common::Array<Common::Point> &VisualElementRenderProperties::modifyPolyPoints() {
|
|
_isDirty = true;
|
|
return _polyPoints;
|
|
}
|
|
|
|
bool VisualElementRenderProperties::isDirty() const {
|
|
return _isDirty;
|
|
}
|
|
|
|
void VisualElementRenderProperties::clearDirty() {
|
|
_isDirty = false;
|
|
}
|
|
|
|
VisualElementRenderProperties &VisualElementRenderProperties::operator=(const VisualElementRenderProperties &other) {
|
|
_inkMode = other._inkMode;
|
|
_shape = other._shape;
|
|
_foreColor = other._foreColor;
|
|
_backColor = other._backColor;
|
|
_borderSize = other._borderSize;
|
|
_borderColor = other._borderColor;
|
|
_shadowSize = other._shadowSize;
|
|
_shadowColor = other._shadowColor;
|
|
|
|
_polyPoints = other._polyPoints;
|
|
|
|
_isDirty = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
VisualElement::VisualElement()
|
|
: _rect(0, 0, 0, 0), _cachedAbsoluteOrigin(Common::Point(0, 0)), _contentsDirty(true), _directToScreen(false), _visible(false), _visibleByDefault(true), _layer(0),
|
|
_topLeftBevelShading(0), _bottomRightBevelShading(0), _interiorShading(0), _bevelSize(0) {
|
|
}
|
|
|
|
VisualElement::VisualElement(const VisualElement &other)
|
|
: Element(other), _directToScreen(other._directToScreen), _visible(other._visible), _visibleByDefault(other._visibleByDefault)
|
|
, _rect(other._rect), _cachedAbsoluteOrigin(other._cachedAbsoluteOrigin), _layer(other._layer), _topLeftBevelShading(other._topLeftBevelShading)
|
|
, _bottomRightBevelShading(other._bottomRightBevelShading), _interiorShading(other._interiorShading), _bevelSize(other._bevelSize)
|
|
, _dragProps(nullptr), _renderProps(other._renderProps), _primaryGraphicModifier(nullptr), _transitionProps(other._transitionProps)
|
|
, _palette(other._palette), _prevRect(other._prevRect), _contentsDirty(true) {
|
|
}
|
|
|
|
bool VisualElement::isVisual() const {
|
|
return true;
|
|
}
|
|
|
|
bool VisualElement::isTextLabel() const {
|
|
return false;
|
|
}
|
|
|
|
bool VisualElement::isVisible() const {
|
|
return _visible;
|
|
}
|
|
|
|
bool VisualElement::isVisibleByDefault() const {
|
|
return _visibleByDefault;
|
|
}
|
|
|
|
void VisualElement::setVisible(Runtime *runtime, bool visible) {
|
|
if (_visible != visible) {
|
|
runtime->setSceneGraphDirty();
|
|
_visible = visible;
|
|
}
|
|
}
|
|
|
|
bool VisualElement::isDirectToScreen() const {
|
|
return _directToScreen;
|
|
}
|
|
|
|
void VisualElement::setDirectToScreen(bool directToScreen) {
|
|
if (_directToScreen != directToScreen) {
|
|
_contentsDirty = true;
|
|
_directToScreen = directToScreen;
|
|
}
|
|
}
|
|
|
|
uint16 VisualElement::getLayer() const {
|
|
return _layer;
|
|
}
|
|
|
|
void VisualElement::setLayer(uint16 layer) {
|
|
if (_layer != layer) {
|
|
_contentsDirty = true;
|
|
_layer = layer;
|
|
}
|
|
}
|
|
|
|
CORO_BEGIN_DEFINITION(VisualElement::VisualElementConsumeCommandCoroutine)
|
|
struct Locals {
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
CORO_IF(Event(EventIDs::kElementShow, 0).respondsTo(params->msg->getEvent()))
|
|
if (!params->self->_visible) {
|
|
params->self->_visible = true;
|
|
params->runtime->setSceneGraphDirty();
|
|
}
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementShow, 0), DynamicValue(), params->self->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
|
|
|
|
CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
|
|
|
|
CORO_RETURN;
|
|
CORO_ELSE_IF(Event(EventIDs::kElementHide, 0).respondsTo(params->msg->getEvent()))
|
|
if (params->self->_visible) {
|
|
params->self->_visible = false;
|
|
|
|
if (params->self->_hooks)
|
|
params->self->_hooks->onHidden(params->self, params->self->_visible);
|
|
|
|
params->runtime->setSceneGraphDirty();
|
|
}
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(EventIDs::kElementHide, 0), DynamicValue(), params->self->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
|
|
|
|
CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
|
|
|
|
CORO_RETURN;
|
|
CORO_END_IF
|
|
|
|
CORO_CALL(Element::ElementConsumeCommandCoroutine, params->self, params->runtime, params->msg);
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
VThreadState VisualElement::asyncConsumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
runtime->getVThread().pushCoroutine<VisualElementConsumeCommandCoroutine>(this, runtime, msg);
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
bool VisualElement::respondsToEvent(const Event &evt) const {
|
|
if (Event(EventIDs::kAuthorMessage, 13).respondsTo(evt)) {
|
|
if (getRuntime()->getHacks().mtiSceneReturnHack && getParent() && getParent()->isSubsection())
|
|
return true;
|
|
}
|
|
|
|
return Element::respondsToEvent(evt);
|
|
}
|
|
|
|
VThreadState VisualElement::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
if (Event(EventIDs::kAuthorMessage, 13).respondsTo(msg->getEvent())) {
|
|
if (getRuntime()->getHacks().mtiSceneReturnHack) {
|
|
if (getParent() && getParent()->isSubsection()) {
|
|
runtime->addSceneStateTransition(HighLevelSceneTransition(this->getSelfReference().lock().staticCast<Structural>(), HighLevelSceneTransition::kTypeChangeToScene, false, false));
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Element::consumeMessage(runtime, msg);
|
|
}
|
|
|
|
bool VisualElement::isMouseInsideDrawableArea(int32 relativeX, int32 relativeY) const {
|
|
if (relativeX < _rect.left || relativeX >= _rect.right || relativeY < _rect.top || relativeY >= _rect.bottom)
|
|
return false;
|
|
|
|
// NOTE: This is actually incomplete, graphic modifiers are supposed to mask out the drawable area for non-rect
|
|
// shapes, so what we SHOULD be doing here is generating a mask and hoisting the mask gen code out of
|
|
// GraphicModifier to more common code, and check the mask here.
|
|
//
|
|
// For now, we just use this for click detection.
|
|
relativeX -= _rect.left;
|
|
relativeY -= _rect.top;
|
|
|
|
switch (_renderProps.getShape()) {
|
|
case VisualElementRenderProperties::kShapePolygon:
|
|
case VisualElementRenderProperties::kShapeStar: {
|
|
Common::Point starPoints[10];
|
|
const Common::Point *polyPoints = nullptr;
|
|
size_t numPolyPoints = 0;
|
|
|
|
if (_renderProps.getShape() == VisualElementRenderProperties::kShapeStar) {
|
|
int16 width = _rect.width();
|
|
int16 height = _rect.height();
|
|
starPoints[0] = Common::Point(width / 2, 0);
|
|
starPoints[1] = Common::Point(width * 2 / 3, height / 3);
|
|
starPoints[2] = Common::Point(width, height / 3);
|
|
starPoints[3] = Common::Point(width * 3 / 4, height / 2);
|
|
starPoints[4] = Common::Point(width, height);
|
|
starPoints[5] = Common::Point(width / 2, height * 2 / 3);
|
|
starPoints[6] = Common::Point(0, height);
|
|
starPoints[7] = Common::Point(width / 4, height / 2);
|
|
starPoints[8] = Common::Point(0, height / 3);
|
|
starPoints[9] = Common::Point(width / 3, height / 3);
|
|
polyPoints = starPoints;
|
|
numPolyPoints = 10;
|
|
} else {
|
|
numPolyPoints = _renderProps.getPolyPoints().size();
|
|
if (numPolyPoints > 0)
|
|
polyPoints = &_renderProps.getPolyPoints()[0];
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool insideMask = false;
|
|
for (size_t edgeIndex = 2; edgeIndex < numPolyPoints; edgeIndex++) {
|
|
const Common::Point *points[3] = {&polyPoints[0], &polyPoints[edgeIndex - 1], &polyPoints[edgeIndex]};
|
|
|
|
int32 rays[3][2];
|
|
int32 normals[3][2];
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
const Common::Point *nextPoint = points[(i + 1) % 3];
|
|
rays[i][0] = nextPoint->x - points[i]->x;
|
|
rays[i][1] = nextPoint->y - points[i]->y;
|
|
|
|
normals[i][0] = -rays[i][1];
|
|
normals[i][1] = rays[i][0];
|
|
}
|
|
|
|
int32 cDist = rays[1][0] * normals[0][0] + rays[1][1] * normals[0][1];
|
|
if (cDist == 0)
|
|
continue; // Degenerate triangle
|
|
|
|
if (cDist < 0) {
|
|
// Counter-clockwise triangle, flip normals
|
|
for (int i = 0; i < 3; i++) {
|
|
normals[i][0] = -normals[i][0];
|
|
normals[i][1] = -normals[i][1];
|
|
}
|
|
}
|
|
|
|
bool inFrontOfAll = true;
|
|
for (int i = 0; i < 3; i++) {
|
|
int32 nx = normals[i][0];
|
|
int32 ny = normals[i][1];
|
|
int32 dist = (relativeX - points[i]->x) * nx + (relativeY - points[i]->y) * ny;
|
|
bool isInFront = (dist > 0);
|
|
if (dist == 0) {
|
|
if (nx != 0)
|
|
isInFront = (nx >= 0);
|
|
else
|
|
isInFront = (ny >= 0);
|
|
}
|
|
|
|
if (!isInFront) {
|
|
inFrontOfAll = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (inFrontOfAll)
|
|
insideMask = !insideMask;
|
|
}
|
|
|
|
return insideMask;
|
|
} break;
|
|
case VisualElementRenderProperties::kShapeRect:
|
|
// Rect is always in collision if inside of the rect
|
|
return true;
|
|
|
|
case VisualElementRenderProperties::kShapeOval: {
|
|
int32 w = _rect.width();
|
|
int32 h = _rect.height();
|
|
|
|
int32 dcx = relativeX * 2 - w;
|
|
int32 dcy = relativeY * 2 - h;
|
|
dcx *= h;
|
|
dcy *= w;
|
|
|
|
int32 expandedRadius = w * h;
|
|
|
|
return (dcx * dcx + dcy * dcy <= expandedRadius * expandedRadius);
|
|
} break;
|
|
|
|
case VisualElementRenderProperties::kShapeRoundedRect:
|
|
// Rounded rect corners are 13x13 at maximum
|
|
|
|
default:
|
|
warning("Unsupported shape type for checking mouse collision");
|
|
return false;
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VisualElement::isMouseCollisionAtPoint(int32 relativeX, int32 relativeY) const {
|
|
return true;
|
|
}
|
|
|
|
bool VisualElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "visible") {
|
|
result.setBool(_visible);
|
|
return true;
|
|
} else if (attrib == "direct") {
|
|
result.setBool(_directToScreen);
|
|
return true;
|
|
} else if (attrib == "position") {
|
|
result.setPoint(Common::Point(_rect.left, _rect.top));
|
|
return true;
|
|
} else if (attrib == "centerposition") {
|
|
result.setPoint(getCenterPosition());
|
|
return true;
|
|
} else if (attrib == "size") {
|
|
result.setPoint(Common::Point(_rect.width(), _rect.height()));
|
|
return true;
|
|
} else if (attrib == "width") {
|
|
result.setInt(_rect.right - _rect.left);
|
|
return true;
|
|
} else if (attrib == "height") {
|
|
result.setInt(_rect.bottom - _rect.top);
|
|
return true;
|
|
} else if (attrib == "globalposition") {
|
|
result.setPoint(getGlobalPosition());
|
|
return true;
|
|
} else if (attrib == "layer") {
|
|
result.setInt(_layer);
|
|
return true;
|
|
}
|
|
|
|
return Element::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "visible") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetVisibility, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "direct") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetDirect, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "position") {
|
|
DynamicValueWriteOrRefAttribFuncHelper<VisualElement, &VisualElement::scriptSetPosition, &VisualElement::scriptWriteRefPositionAttribute>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "size") {
|
|
DynamicValueWriteOrRefAttribFuncHelper<VisualElement, &VisualElement::scriptSetSize, &VisualElement::scriptWriteRefSizeAttribute>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "centerposition") {
|
|
DynamicValueWriteOrRefAttribFuncHelper<VisualElement, &VisualElement::scriptSetCenterPosition, &VisualElement::scriptWriteRefCenterPositionAttribute>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "width") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetWidth, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "height") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetHeight, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "layer") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetLayer, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "invalidaterect") {
|
|
// Not sure what this does, MTI uses it frequently
|
|
DynamicValueWriteDiscardHelper::create(writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return Element::writeRefAttribute(thread, writeProxy, attrib);
|
|
}
|
|
|
|
const Common::Rect &VisualElement::getRelativeRect() const {
|
|
return _rect;
|
|
}
|
|
|
|
Common::Rect VisualElement::getRelativeCollisionRect() const {
|
|
return getRelativeRect();
|
|
}
|
|
|
|
void VisualElement::setRelativeRect(const Common::Rect &rect) {
|
|
_rect = rect;
|
|
}
|
|
|
|
Common::Point VisualElement::getParentOrigin() const {
|
|
Common::Point pos = Common::Point(0, 0);
|
|
if (_parent && _parent->isElement()) {
|
|
Element *element = static_cast<Element *>(_parent);
|
|
if (element->isVisual()) {
|
|
pos = static_cast<VisualElement *>(element)->getGlobalPosition();
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
Common::Point VisualElement::getGlobalPosition() const {
|
|
Common::Point pos = getParentOrigin();
|
|
|
|
pos.x += _rect.left;
|
|
pos.y += _rect.top;
|
|
|
|
return pos;
|
|
}
|
|
|
|
const Common::Point &VisualElement::getCachedAbsoluteOrigin() const {
|
|
return _cachedAbsoluteOrigin;
|
|
}
|
|
|
|
void VisualElement::setCachedAbsoluteOrigin(const Common::Point &absOrigin) {
|
|
_cachedAbsoluteOrigin = absOrigin;
|
|
}
|
|
|
|
void VisualElement::setDragMotionProperties(const Common::SharedPtr<DragMotionProperties> &dragProps) {
|
|
_dragProps = dragProps;
|
|
}
|
|
|
|
const Common::SharedPtr<DragMotionProperties> &VisualElement::getDragMotionProperties() const {
|
|
return _dragProps;
|
|
}
|
|
|
|
void VisualElement::handleDragMotion(Runtime *runtime, const Common::Point &initialPoint, const Common::Point &targetPointRef) {
|
|
if (!_dragProps)
|
|
return;
|
|
|
|
Common::Point targetPoint = targetPointRef;
|
|
|
|
// NOTE: Constraints do not override insets if the object is out of bounds
|
|
if (_dragProps->constraintDirection == kConstraintDirectionHorizontal)
|
|
targetPoint.y = initialPoint.y;
|
|
if (_dragProps->constraintDirection == kConstraintDirectionVertical)
|
|
targetPoint.x = initialPoint.x;
|
|
|
|
if (_dragProps->constrainToParent && _parent && _parent->isElement() && static_cast<Element *>(_parent)->isVisual()) {
|
|
Common::Rect constrainInset = _dragProps->constraintMargin;
|
|
|
|
Common::Rect parentRect = static_cast<VisualElement *>(_parent)->getRelativeRect();
|
|
|
|
// rect.width - inset.right
|
|
int32 minX = constrainInset.left;
|
|
int32 minY = constrainInset.top;
|
|
int32 maxX = parentRect.width() - constrainInset.right - _rect.width();
|
|
int32 maxY = parentRect.height() - constrainInset.bottom - _rect.height();
|
|
|
|
// TODO: Handle "squished" case where max < min, it does work but it's weird
|
|
if (targetPoint.x < minX)
|
|
targetPoint.x = minX;
|
|
|
|
if (targetPoint.y < minY)
|
|
targetPoint.y = minY;
|
|
|
|
if (targetPoint.x > maxX)
|
|
targetPoint.x = maxX;
|
|
|
|
if (targetPoint.y > maxY)
|
|
targetPoint.y = maxY;
|
|
|
|
if (_hooks)
|
|
_hooks->onSetPosition(runtime, this, Common::Point(_rect.left, _rect.top), targetPoint);
|
|
|
|
offsetTranslate(targetPoint.x - _rect.left, targetPoint.y - _rect.top, false);
|
|
}
|
|
}
|
|
|
|
VThreadState VisualElement::offsetTranslateTask(const OffsetTranslateTaskData &data) {
|
|
offsetTranslate(data.dx, data.dy, false);
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void VisualElement::setTransitionProperties(const VisualElementTransitionProperties &props) {
|
|
_transitionProps = props;
|
|
}
|
|
|
|
const VisualElementTransitionProperties &VisualElement::getTransitionProperties() const {
|
|
return _transitionProps;
|
|
}
|
|
|
|
void VisualElement::setRenderProperties(const VisualElementRenderProperties &props, const Common::WeakPtr<GraphicModifier> &primaryGraphicModifier) {
|
|
_renderProps = props;
|
|
_primaryGraphicModifier = primaryGraphicModifier;
|
|
}
|
|
|
|
const VisualElementRenderProperties &VisualElement::getRenderProperties() const {
|
|
return _renderProps;
|
|
}
|
|
|
|
const Common::WeakPtr<GraphicModifier> &VisualElement::getPrimaryGraphicModifier() const {
|
|
return _primaryGraphicModifier;
|
|
}
|
|
|
|
void VisualElement::setShading(int16 topLeftBevelShading, int16 bottomRightBevelShading, int16 interiorShading, uint32 bevelSize) {
|
|
if (_topLeftBevelShading != topLeftBevelShading || _bottomRightBevelShading != bottomRightBevelShading || _interiorShading != interiorShading || _bevelSize != bevelSize) {
|
|
_topLeftBevelShading = topLeftBevelShading;
|
|
_bottomRightBevelShading = bottomRightBevelShading;
|
|
_interiorShading = interiorShading;
|
|
_bevelSize = bevelSize;
|
|
|
|
_contentsDirty = true;
|
|
}
|
|
}
|
|
|
|
bool VisualElement::needsRender() const {
|
|
if (_renderProps.isDirty() || _prevRect != _rect || _contentsDirty)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void VisualElement::finalizeRender() {
|
|
_renderProps.clearDirty();
|
|
_prevRect = _rect;
|
|
_contentsDirty = false;
|
|
}
|
|
|
|
void VisualElement::setPalette(const Common::SharedPtr<Palette> &palette) {
|
|
_palette = palette;
|
|
_contentsDirty = true;
|
|
}
|
|
|
|
const Common::SharedPtr<Palette> &VisualElement::getPalette() const {
|
|
return _palette;
|
|
}
|
|
|
|
void VisualElement::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
Element::visitInternalReferences(visitor);
|
|
}
|
|
|
|
|
|
void VisualElement::pushVisibilityChangeTask(Runtime * runtime, bool desiredVisibility) {
|
|
ChangeFlagTaskData *changeVisibilityTask = runtime->getVThread().pushTask("VisualElement::changeVisibilityTask", this, &VisualElement::changeVisibilityTask);
|
|
changeVisibilityTask->desiredFlag = true;
|
|
changeVisibilityTask->runtime = runtime;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
void VisualElement::debugInspect(IDebugInspectionReport *report) const {
|
|
report->declareDynamic("layer", Common::String::format("%i", static_cast<int>(_layer)));
|
|
report->declareDynamic("relRect", Common::String::format("(%i,%i)-(%i,%i)", static_cast<int>(_rect.left), static_cast<int>(_rect.top), static_cast<int>(_rect.right), static_cast<int>(_rect.bottom)));
|
|
report->declareDynamic("directToScreen", Common::String(_directToScreen ? "true" : "false"));
|
|
report->declareDynamic("visible", Common::String(_visible ? "true" : "false"));
|
|
|
|
Element::debugInspect(report);
|
|
}
|
|
#endif
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetVisibility(MiniscriptThread *thread, const DynamicValue &result) {
|
|
// FIXME: Need to make this fire Show/Hide events??
|
|
if (result.getType() == DynamicValueTypes::kBoolean) {
|
|
const bool targetValue = result.getBool();
|
|
|
|
// Weird quirk: The element's visible flag reads as "false" until initial Show events fire, but
|
|
// setting "visible" here prevents it from showing. This is necessary for the vidbots in Obsidian's
|
|
// bureau area, to make them appear turned off initially.
|
|
_visibleByDefault = targetValue;
|
|
|
|
if (_visible != targetValue) {
|
|
_visible = targetValue;
|
|
thread->getRuntime()->setSceneGraphDirty();
|
|
}
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
bool VisualElement::loadCommon(const Common::String &name, uint32 guid, const Data::Rect &rect, uint32 elementFlags, uint16 layer, uint32 streamLocator, uint16 sectionID) {
|
|
if (!rect.toScummVMRect(_rect))
|
|
return false;
|
|
|
|
_name = name;
|
|
_guid = guid;
|
|
_visibleByDefault = ((elementFlags & Data::ElementFlags::kHidden) == 0); // Element isn't actually flagged as visible until after Scene Changed, when Show commands are fired
|
|
_directToScreen = ((elementFlags & Data::ElementFlags::kNotDirectToScreen) == 0);
|
|
_streamLocator = streamLocator;
|
|
_sectionID = sectionID;
|
|
_layer = layer;
|
|
|
|
return true;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetDirect(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() == DynamicValueTypes::kBoolean) {
|
|
_directToScreen = value.getBool();
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetPosition(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() == DynamicValueTypes::kPoint) {
|
|
Common::Point destPoint = value.getPoint();
|
|
|
|
if (_hooks)
|
|
_hooks->onSetPosition(thread->getRuntime(), this, Common::Point(_rect.left, _rect.top), destPoint);
|
|
|
|
int32 xDelta = destPoint.x - _rect.left;
|
|
int32 yDelta = destPoint.y - _rect.top;
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
offsetTranslate(xDelta, yDelta, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
// Assigning non-point values to position silently fails
|
|
// Obsidian relies on this behavior due to a bug in the air puzzle completion script
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetPositionX(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
int32 asInteger = 0;
|
|
if (!dest.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::Point updatedPoint = Common::Point(asInteger, _rect.top);
|
|
if (_hooks)
|
|
_hooks->onSetPosition(thread->getRuntime(), this, Common::Point(_rect.left, _rect.top), updatedPoint);
|
|
int32 xDelta = updatedPoint.x - _rect.left;
|
|
int32 yDelta = updatedPoint.y - _rect.top;
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
offsetTranslate(xDelta, yDelta, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetPositionY(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
int32 asInteger = 0;
|
|
if (!dest.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::Point updatedPoint = Common::Point(_rect.left, asInteger);
|
|
if (_hooks)
|
|
_hooks->onSetPosition(thread->getRuntime(), this, Common::Point(_rect.left, _rect.top), updatedPoint);
|
|
|
|
int32 xDelta = updatedPoint.x - _rect.left;
|
|
int32 yDelta = updatedPoint.y - _rect.top;
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
offsetTranslate(xDelta, yDelta, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetCenterPosition(MiniscriptThread *thread, const DynamicValue &value) {
|
|
if (value.getType() == DynamicValueTypes::kPoint) {
|
|
const Common::Point destPoint = value.getPoint();
|
|
const Common::Point &srcPoint = getCenterPosition();
|
|
int32 xDelta = destPoint.x - srcPoint.x;
|
|
int32 yDelta = destPoint.y - srcPoint.y;
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
offsetTranslate(xDelta, yDelta, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetCenterPositionX(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
int32 asInteger = 0;
|
|
if (!dest.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
int32 xDelta = asInteger - getCenterPosition().x;
|
|
|
|
if (xDelta != 0)
|
|
offsetTranslate(xDelta, 0, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetCenterPositionY(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
int32 asInteger = 0;
|
|
if (!dest.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
int32 yDelta = asInteger - getCenterPosition().y;
|
|
|
|
if (yDelta != 0)
|
|
offsetTranslate(0, yDelta, false);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetSize(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (value.getType() == DynamicValueTypes::kPoint) {
|
|
Common::Point pt = value.getPoint();
|
|
|
|
if (_rect.bottom - _rect.top != asInteger || _rect.right - _rect.left != asInteger) {
|
|
_rect.right = _rect.left + pt.x;
|
|
_rect.bottom = _rect.top + pt.y;
|
|
|
|
thread->getRuntime()->setSceneGraphDirty();
|
|
}
|
|
} else {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
|
|
debugger->notify(kDebugSeverityError, "'size' value wasn't a point");
|
|
#endif
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetWidth(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (!value.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
if (_rect.right - _rect.left != asInteger) {
|
|
_rect.right = _rect.left + asInteger;
|
|
|
|
thread->getRuntime()->setSceneGraphDirty();
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetHeight(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (!value.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
if (_rect.bottom - _rect.top != asInteger) {
|
|
_rect.bottom = _rect.top + asInteger;
|
|
|
|
thread->getRuntime()->setSceneGraphDirty();
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetLayer(MiniscriptThread *thread, const DynamicValue &value) {
|
|
int32 asInteger = 0;
|
|
if (!value.roundToInt(asInteger))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
if (asInteger != _layer) {
|
|
VisualElement *scene = findScene();
|
|
|
|
// If a layer is assigned and a colliding element exists, then the layers are swapped
|
|
if (scene) {
|
|
VisualElement *collision = recursiveFindItemWithLayer(scene, asInteger);
|
|
if (collision)
|
|
collision->_layer = _layer;
|
|
}
|
|
_layer = asInteger;
|
|
|
|
thread->getRuntime()->setSceneGraphDirty();
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptWriteRefPositionAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetPositionX, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "y") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetPositionY, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptWriteRefCenterPositionAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetCenterPositionX, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "y") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetCenterPositionY, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptWriteRefSizeAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetWidth, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "y") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetHeight, true>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void VisualElement::offsetTranslate(int32 xDelta, int32 yDelta, bool cachedOriginOnly) {
|
|
if (!cachedOriginOnly) {
|
|
_rect.left += xDelta;
|
|
_rect.right += xDelta;
|
|
_rect.top += yDelta;
|
|
_rect.bottom += yDelta;
|
|
}
|
|
|
|
_cachedAbsoluteOrigin.x += xDelta;
|
|
_cachedAbsoluteOrigin.y += yDelta;
|
|
|
|
for (const Common::SharedPtr<Structural> &child : _children) {
|
|
if (child->isElement()) {
|
|
Element *element = static_cast<Element *>(child.get());
|
|
if (element->isVisual())
|
|
static_cast<VisualElement *>(element)->offsetTranslate(xDelta, yDelta, true);
|
|
}
|
|
}
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
_contentsDirty = true;
|
|
}
|
|
|
|
Common::Point VisualElement::getCenterPosition() const {
|
|
return Common::Point((_rect.left + _rect.right) / 2, (_rect.top + _rect.bottom) / 2);
|
|
}
|
|
|
|
CORO_BEGIN_DEFINITION(VisualElement::ChangeVisibilityCoroutine)
|
|
struct Locals {
|
|
};
|
|
|
|
CORO_BEGIN_FUNCTION
|
|
CORO_IF(params->self->_visible != params->desiredFlag)
|
|
params->self->setVisible(params->runtime, params->desiredFlag);
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(params->desiredFlag ? EventIDs::kElementShow : EventIDs::kElementHide, 0), DynamicValue(), params->self->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, params->self, false, true, false));
|
|
|
|
CORO_CALL(Runtime::SendMessageOnVThreadCoroutine, params->runtime, dispatch);
|
|
CORO_END_IF
|
|
CORO_END_FUNCTION
|
|
CORO_END_DEFINITION
|
|
|
|
VThreadState VisualElement::changeVisibilityTask(const ChangeFlagTaskData &taskData) {
|
|
if (_visible != taskData.desiredFlag) {
|
|
setVisible(taskData.runtime, taskData.desiredFlag);
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event(taskData.desiredFlag ? EventIDs::kElementShow : EventIDs::kElementHide, 0), DynamicValue(), getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
|
|
taskData.runtime->sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
VisualElement *VisualElement::recursiveFindItemWithLayer(VisualElement *element, int32 layer) {
|
|
if (element->_layer == layer)
|
|
return element;
|
|
|
|
for (const Common::SharedPtr<Structural> &child : element->getChildren()) {
|
|
if (child->isElement()) {
|
|
Element *childElement = static_cast<Element *>(child.get());
|
|
if (childElement->isVisual()) {
|
|
VisualElement *result = recursiveFindItemWithLayer(static_cast<VisualElement *>(childElement), layer);
|
|
if (result)
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void VisualElement::renderShading(Graphics::Surface &surf) const {
|
|
uint32 bevelSize = _bevelSize;
|
|
uint32 w = surf.w;
|
|
uint32 h = surf.h;
|
|
|
|
uint32 maxHBevel = (w + 1) / 2;
|
|
uint32 maxVBevel = (h + 1) / 2;
|
|
|
|
if (bevelSize > maxHBevel)
|
|
bevelSize = maxHBevel;
|
|
if (bevelSize > maxVBevel)
|
|
bevelSize = maxVBevel;
|
|
|
|
uint32 rMask = surf.format.ARGBToColor(0, 255, 0, 0);
|
|
uint32 gMask = surf.format.ARGBToColor(0, 0, 255, 0);
|
|
uint32 bMask = surf.format.ARGBToColor(0, 0, 0, 255);
|
|
|
|
byte bytesPerPixel = surf.format.bytesPerPixel;
|
|
|
|
if (_topLeftBevelShading != 0) {
|
|
bool isBrighten = (_topLeftBevelShading > 0);
|
|
|
|
uint32 rAdd = quantizeShading(rMask, _topLeftBevelShading);
|
|
uint32 gAdd = quantizeShading(gMask, _topLeftBevelShading);
|
|
uint32 bAdd = quantizeShading(bMask, _topLeftBevelShading);
|
|
|
|
// Top bar
|
|
for (uint y = 0; y < bevelSize; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(0, y), w - y, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
|
|
// Left bar
|
|
uint leftBarEndY = h + 1 - bevelSize;
|
|
for (uint y = bevelSize; y < leftBarEndY; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(0, y), bevelSize, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
|
|
// Lower diagonal
|
|
for (uint y = leftBarEndY; y < h; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(0, y), bevelSize - 1 - (y - leftBarEndY), rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
}
|
|
|
|
if (_bottomRightBevelShading != 0) {
|
|
bool isBrighten = (_bottomRightBevelShading > 0);
|
|
|
|
uint32 rAdd = quantizeShading(rMask, _bottomRightBevelShading);
|
|
uint32 gAdd = quantizeShading(gMask, _bottomRightBevelShading);
|
|
uint32 bAdd = quantizeShading(bMask, _bottomRightBevelShading);
|
|
|
|
// Upper diagonal
|
|
for (uint y = 1; y < bevelSize; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(w - y, y), y, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
|
|
uint rightBarEndY = h - bevelSize;
|
|
if (rightBarEndY < bevelSize)
|
|
rightBarEndY = bevelSize;
|
|
|
|
uint rightBarX = w - bevelSize;
|
|
if (rightBarX < bevelSize)
|
|
rightBarX = bevelSize;
|
|
|
|
// Right bar
|
|
for (uint y = bevelSize; y < rightBarEndY; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(rightBarX, y), w - rightBarX, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
|
|
// Bottom bar
|
|
for (uint y = rightBarEndY; y < h; y++) {
|
|
uint bottomBarStartX = bevelSize - (y - rightBarEndY);
|
|
renderShadingScanlineDynamic(surf.getBasePtr(bottomBarStartX, y), w - bottomBarStartX, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
}
|
|
}
|
|
|
|
if (_interiorShading != 0) {
|
|
uint32 startX = bevelSize;
|
|
uint32 endX = w - bevelSize;
|
|
uint32 startY = bevelSize;
|
|
uint32 endY = h - bevelSize;
|
|
|
|
if (startX < endX && startY < endY) {
|
|
bool isBrighten = (_bottomRightBevelShading > 0);
|
|
|
|
uint32 rAdd = quantizeShading(rMask, _bottomRightBevelShading);
|
|
uint32 gAdd = quantizeShading(gMask, _bottomRightBevelShading);
|
|
uint32 bAdd = quantizeShading(bMask, _bottomRightBevelShading);
|
|
|
|
for (uint y = startY; y < endY; y++)
|
|
renderShadingScanlineDynamic(surf.getBasePtr(startX, y), endX - startX, rMask, rAdd, gMask, gAdd, bMask, bAdd, isBrighten, bytesPerPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 VisualElement::quantizeShading(uint32 mask, int16 shading) {
|
|
uint32 absShading = (shading < 0) ? static_cast<int32>(-shading) : static_cast<int32>(shading);
|
|
|
|
if ((mask & 0xff) == 0) {
|
|
// Nothing in the low bytes, so shift down to avoid upper bits overflow
|
|
return ((mask >> 8) * absShading) & mask;
|
|
}
|
|
|
|
// Something was in the low bits, so avoid lower bits underflow instead
|
|
return ((mask * absShading) >> 8) & mask;
|
|
}
|
|
|
|
void VisualElement::renderShadingScanlineDynamic(void *data, size_t numElements, uint32 rMask, uint32 rAdd, uint32 gMask, uint32 gAdd, uint32 bMask, uint32 bAdd, bool isBrighten, byte bytesPerPixel) {
|
|
if (isBrighten) {
|
|
switch (bytesPerPixel) {
|
|
case 2:
|
|
renderBrightenScanline<uint16>(static_cast<uint16 *>(data), numElements, rMask, rAdd, gMask, gAdd, bMask, bAdd);
|
|
break;
|
|
case 4:
|
|
renderBrightenScanline<uint32>(static_cast<uint32 *>(data), numElements, rMask, rAdd, gMask, gAdd, bMask, bAdd);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
switch (bytesPerPixel) {
|
|
case 2:
|
|
renderDarkenScanline<uint16>(static_cast<uint16 *>(data), numElements, rMask, rAdd, gMask, gAdd, bMask, bAdd);
|
|
break;
|
|
case 4:
|
|
renderDarkenScanline<uint32>(static_cast<uint32 *>(data), numElements, rMask, rAdd, gMask, gAdd, bMask, bAdd);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class TElement>
|
|
void VisualElement::renderBrightenScanline(TElement *element, size_t numElements, TElement rMask, TElement rAdd, TElement gMask, TElement gAdd, TElement bMask, TElement bAdd) {
|
|
TElement rLimit = rMask - rAdd;
|
|
TElement gLimit = gMask - gAdd;
|
|
TElement bLimit = bMask - bAdd;
|
|
|
|
while (numElements > 0) {
|
|
TElement v = *element;
|
|
|
|
if ((v & rMask) > rLimit)
|
|
v |= rMask;
|
|
else
|
|
v += rAdd;
|
|
|
|
if ((v & gMask) > gLimit)
|
|
v |= gMask;
|
|
else
|
|
v += gAdd;
|
|
|
|
if ((v & bMask) > bLimit)
|
|
v |= bMask;
|
|
else
|
|
v += bAdd;
|
|
|
|
*element = v;
|
|
|
|
numElements--;
|
|
element++;
|
|
}
|
|
}
|
|
|
|
template<class TElement>
|
|
void VisualElement::renderDarkenScanline(TElement *element, size_t numElements, TElement rMask, TElement rSub, TElement gMask, TElement gSub, TElement bMask, TElement bSub) {
|
|
TElement rZero = ~rMask;
|
|
TElement gZero = ~gMask;
|
|
TElement bZero = ~bMask;
|
|
|
|
while (numElements > 0) {
|
|
TElement v = *element;
|
|
|
|
if ((v & rMask) < rSub)
|
|
v &= rZero;
|
|
else
|
|
v -= rSub;
|
|
|
|
if ((v & gMask) < gSub)
|
|
v &= gZero;
|
|
else
|
|
v -= gSub;
|
|
|
|
if ((v & bMask) < bSub)
|
|
v &= bZero;
|
|
else
|
|
v -= bSub;
|
|
|
|
*element = v;
|
|
|
|
numElements--;
|
|
element++;
|
|
}
|
|
}
|
|
|
|
bool NonVisualElement::isVisual() const {
|
|
return false;
|
|
}
|
|
|
|
bool NonVisualElement::loadCommon(const Common::String &name, uint32 guid, uint32 elementFlags) {
|
|
_name = name;
|
|
_guid = guid;
|
|
_streamLocator = 0;
|
|
_sectionID = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
ModifierFlags::ModifierFlags() : isLastModifier(false), flagsWereLoaded(false) {
|
|
}
|
|
|
|
bool ModifierFlags::load(const uint32 dataModifierFlags) {
|
|
isLastModifier = ((dataModifierFlags & 0x2) != 0);
|
|
flagsWereLoaded = true;
|
|
return true;
|
|
}
|
|
|
|
ModifierSaveLoad::~ModifierSaveLoad() {
|
|
}
|
|
|
|
void ModifierSaveLoad::save(Modifier *modifier, Common::WriteStream *stream) {
|
|
const Common::String &name = modifier->getName();
|
|
|
|
stream->writeUint32BE(modifier->getStaticGUID());
|
|
stream->writeUint16BE(name.size());
|
|
stream->writeString(name);
|
|
|
|
saveInternal(stream);
|
|
}
|
|
|
|
bool ModifierSaveLoad::load(Modifier *modifier, Common::ReadStream *stream, uint32 saveFileVersion) {
|
|
uint32 checkGUID = stream->readUint32BE();
|
|
uint16 nameLen = stream->readUint16BE();
|
|
|
|
if (stream->err())
|
|
return false;
|
|
|
|
const Common::String &name = modifier->getName();
|
|
|
|
if (name.size() != nameLen)
|
|
return false;
|
|
|
|
Common::Array<char> checkName;
|
|
checkName.resize(nameLen);
|
|
|
|
if (nameLen > 0) {
|
|
stream->read(&checkName[0], nameLen);
|
|
if (stream->err() || memcmp(&checkName[0], name.c_str(), nameLen))
|
|
return false;
|
|
}
|
|
|
|
if (modifier->getStaticGUID() != checkGUID)
|
|
return false;
|
|
|
|
return loadInternal(stream, saveFileVersion);
|
|
}
|
|
|
|
ModifierHooks::~ModifierHooks() {
|
|
}
|
|
|
|
void ModifierHooks::onCreate(Modifier *modifier) {
|
|
}
|
|
|
|
Modifier::Modifier() : _parent(nullptr) {
|
|
}
|
|
|
|
Modifier::~Modifier() {
|
|
}
|
|
|
|
bool Modifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "parent") {
|
|
result.setObject(_parent);
|
|
return true;
|
|
} else if (attrib == "subsection") {
|
|
RuntimeObject *scan = _parent.lock().get();
|
|
while (scan) {
|
|
if (scan->isSubsection()) {
|
|
result.setObject(scan->getSelfReference());
|
|
return true;
|
|
}
|
|
|
|
if (scan->isStructural())
|
|
scan = static_cast<Structural *>(scan)->getParent();
|
|
else if (scan->isModifier())
|
|
scan = static_cast<Modifier *>(scan)->getParent().lock().get();
|
|
else
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
} else if (attrib == "name") {
|
|
result.setString(_name);
|
|
return true;
|
|
} else if (attrib == "element") {
|
|
Structural *owner = findStructuralOwner();
|
|
result.setObject(owner ? owner->getSelfReference() : Common::WeakPtr<RuntimeObject>());
|
|
return true;
|
|
} else if (attrib == "previous") {
|
|
Modifier *sibling = findPrevSibling();
|
|
if (sibling)
|
|
result.setObject(sibling->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
} else if (attrib == "next") {
|
|
Modifier *sibling = findNextSibling();
|
|
if (sibling)
|
|
result.setObject(sibling->getSelfReference());
|
|
else
|
|
result.clear();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Modifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "parent") {
|
|
DynamicValueWriteObjectHelper::create(_parent.lock().get(), writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "name") {
|
|
DynamicValueWriteStringHelper::create(&_name, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return RuntimeObject::writeRefAttribute(thread, writeProxy, attrib);
|
|
}
|
|
|
|
void Modifier::materialize(Runtime *runtime, ObjectLinkingScope *outerScope) {
|
|
ObjectLinkingScope innerScope;
|
|
innerScope.setParent(outerScope);
|
|
|
|
ModifierInnerScopeBuilder innerScopeBuilder(runtime, this, &innerScope);
|
|
this->visitInternalReferences(&innerScopeBuilder);
|
|
|
|
ModifierChildMaterializer childMaterializer(runtime, &innerScope);
|
|
this->visitInternalReferences(&childMaterializer);
|
|
|
|
linkInternalReferences(outerScope);
|
|
setRuntimeGUID(runtime->allocateRuntimeGUID());
|
|
}
|
|
|
|
bool Modifier::isAlias() const {
|
|
return false;
|
|
}
|
|
|
|
bool Modifier::isVariable() const {
|
|
return false;
|
|
}
|
|
|
|
bool Modifier::isBehavior() const {
|
|
return false;
|
|
}
|
|
|
|
bool Modifier::isCompoundVariable() const {
|
|
return false;
|
|
}
|
|
|
|
bool Modifier::isKeyboardMessenger() const {
|
|
return false;
|
|
}
|
|
|
|
Common::SharedPtr<ModifierSaveLoad> Modifier::getSaveLoad(Runtime *runtime) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool Modifier::isModifier() const {
|
|
return true;
|
|
}
|
|
|
|
IModifierContainer *Modifier::getMessagePropagationContainer() {
|
|
return nullptr;
|
|
}
|
|
|
|
IModifierContainer *Modifier::getChildContainer() {
|
|
return nullptr;
|
|
}
|
|
|
|
const Common::WeakPtr<RuntimeObject>& Modifier::getParent() const {
|
|
return _parent;
|
|
}
|
|
|
|
void Modifier::setParent(const Common::WeakPtr<RuntimeObject> &parent) {
|
|
_parent = parent;
|
|
}
|
|
|
|
Modifier *Modifier::findNextSibling() const {
|
|
RuntimeObject *parent = getParent().lock().get();
|
|
if (parent) {
|
|
IModifierContainer *container = nullptr;
|
|
if (parent->isModifier())
|
|
container = static_cast<Modifier *>(parent)->getChildContainer();
|
|
else if (parent->isStructural())
|
|
container = static_cast<Structural *>(parent);
|
|
|
|
if (container)
|
|
{
|
|
const Common::Array<Common::SharedPtr<Modifier> > &neighborhood = container->getModifiers();
|
|
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;
|
|
}
|
|
|
|
Modifier *Modifier::findPrevSibling() const {
|
|
RuntimeObject *parent = getParent().lock().get();
|
|
if (parent) {
|
|
IModifierContainer *container = nullptr;
|
|
if (parent->isModifier())
|
|
container = static_cast<Modifier *>(parent)->getChildContainer();
|
|
else if (parent->isStructural())
|
|
container = static_cast<Structural *>(parent);
|
|
|
|
if (container) {
|
|
const Common::Array<Common::SharedPtr<Modifier> > &neighborhood = container->getModifiers();
|
|
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;
|
|
}
|
|
|
|
bool Modifier::respondsToEvent(const Event &evt) const {
|
|
return false;
|
|
}
|
|
|
|
VThreadState Modifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
// If you're here, a message type was reported as responsive by respondsToEvent but consumeMessage wasn't overridden
|
|
assert(false);
|
|
return kVThreadError;
|
|
}
|
|
|
|
void Modifier::setName(const Common::String& name) {
|
|
_name = name;
|
|
}
|
|
|
|
const Common::String& Modifier::getName() const {
|
|
return _name;
|
|
}
|
|
|
|
const ModifierFlags& Modifier::getModifierFlags() const {
|
|
return _modifierFlags;
|
|
}
|
|
|
|
void Modifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
}
|
|
|
|
bool Modifier::loadPlugInHeader(const PlugInModifierLoaderContext &plugInContext) {
|
|
_guid = plugInContext.plugInModifierData.guid;
|
|
_name = plugInContext.plugInModifierData.name;
|
|
_modifierFlags.load(plugInContext.plugInModifierData.modifierFlags);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Modifier::recursiveCollectObjectsMatchingCriteria(Common::Array<Common::WeakPtr<RuntimeObject> > &results, bool (*evalFunc)(void *userData, RuntimeObject *object), void *userData, bool onlyEnabled) {
|
|
if (evalFunc(userData, this))
|
|
results.push_back(getSelfReference());
|
|
|
|
IModifierContainer *childContainer = nullptr;
|
|
if (onlyEnabled)
|
|
childContainer = getMessagePropagationContainer();
|
|
else
|
|
childContainer = getChildContainer();
|
|
|
|
if (childContainer) {
|
|
for (const Common::SharedPtr<Modifier> &child : childContainer->getModifiers())
|
|
child->recursiveCollectObjectsMatchingCriteria(results, evalFunc, userData, onlyEnabled);
|
|
}
|
|
}
|
|
|
|
void Modifier::setHooks(const Common::SharedPtr<ModifierHooks> &hooks) {
|
|
_hooks = hooks;
|
|
}
|
|
|
|
const Common::SharedPtr<ModifierHooks> &Modifier::getHooks() const {
|
|
return _hooks;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
|
|
SupportStatus Modifier::debugGetSupportStatus() const {
|
|
return kSupportStatusNone;
|
|
}
|
|
|
|
const Common::String &Modifier::debugGetName() const {
|
|
return _name;
|
|
}
|
|
|
|
void Modifier::debugInspect(IDebugInspectionReport *report) const {
|
|
if (report->declareStatic("type"))
|
|
report->declareStaticContents(debugGetTypeName());
|
|
if (report->declareStatic("guid"))
|
|
report->declareStaticContents(Common::String::format("%x", getStaticGUID()));
|
|
if (report->declareStatic("runtimeID"))
|
|
report->declareStaticContents(Common::String::format("%x", getRuntimeGUID()));
|
|
}
|
|
|
|
#endif /* MTROPOLIS_DEBUG_ENABLE */
|
|
|
|
VariableStorage::~VariableStorage() {
|
|
}
|
|
|
|
VariableModifier::VariableModifier(const Common::SharedPtr<VariableStorage> &storage) : _storage(storage) {
|
|
}
|
|
|
|
VariableModifier::VariableModifier(const VariableModifier &other) : Modifier(other), _storage(other._storage->clone()) {
|
|
}
|
|
|
|
bool VariableModifier::isVariable() const {
|
|
return true;
|
|
}
|
|
|
|
bool VariableModifier::isListVariable() const {
|
|
return false;
|
|
}
|
|
|
|
|
|
Common::SharedPtr<ModifierSaveLoad> VariableModifier::getSaveLoad(Runtime *runtime) {
|
|
return _storage->getSaveLoad(runtime);
|
|
}
|
|
|
|
const Common::SharedPtr<VariableStorage> &VariableModifier::getStorage() const {
|
|
return _storage;
|
|
}
|
|
|
|
void VariableModifier::setStorage(const Common::SharedPtr<VariableStorage> &storage) {
|
|
_storage = storage;
|
|
}
|
|
|
|
bool VariableModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
|
|
if (attrib == "value") {
|
|
varGetValue(result);
|
|
return true;
|
|
}
|
|
|
|
return Modifier::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
void VariableModifier::disable(Runtime *runtime) {
|
|
}
|
|
|
|
DynamicValueWriteProxy VariableModifier::createWriteProxy() {
|
|
DynamicValueWriteProxy proxy;
|
|
proxy.pod.objectRef = this;
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<VariableModifier::WriteProxyInterface>::getInstance();
|
|
return proxy;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
void VariableModifier::debugInspect(IDebugInspectionReport *report) const {
|
|
Modifier::debugInspect(report);
|
|
|
|
if (report->declareStatic("storage"))
|
|
report->declareStaticContents(Common::String::format("%p", static_cast<void *>(_storage.get())));
|
|
}
|
|
#endif
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) {
|
|
if (!static_cast<VariableModifier *>(objectRef)->varSetValue(thread, dest))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &dest, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
|
|
return static_cast<VariableModifier *>(objectRef)->writeRefAttribute(thread, dest, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &dest, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
|
|
return static_cast<VariableModifier *>(objectRef)->writeRefAttributeIndexed(thread, dest, attrib, index);
|
|
}
|
|
|
|
bool Modifier::loadTypicalHeader(const Data::TypicalModifierHeader &typicalHeader) {
|
|
if (!_modifierFlags.load(typicalHeader.modifierFlags))
|
|
return false;
|
|
_guid = typicalHeader.guid;
|
|
_name = typicalHeader.name;
|
|
|
|
return true;
|
|
}
|
|
|
|
Structural *Modifier::findStructuralOwner() const {
|
|
RuntimeObject *scan = _parent.lock().get();
|
|
while (scan) {
|
|
if (scan->isModifier())
|
|
scan = static_cast<Modifier *>(scan)->_parent.lock().get();
|
|
else if (scan->isStructural())
|
|
return static_cast<Structural *>(scan);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Modifier::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
}
|
|
|
|
} // End of namespace MTropolis
|