mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
5084 lines
158 KiB
C++
5084 lines
158 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 "mtropolis/runtime.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"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/file.h"
|
|
#include "common/random.h"
|
|
#include "common/substream.h"
|
|
#include "common/system.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
#include "graphics/managed_surface.h"
|
|
#include "graphics/surface.h"
|
|
#include "graphics/wincursor.h"
|
|
#include "graphics/maccursor.h"
|
|
#include "graphics/macgui/macfontmanager.h"
|
|
|
|
#include "audio/mixer.h"
|
|
|
|
namespace MTropolis {
|
|
|
|
class ModifierInnerScopeBuilder : public IStructuralReferenceVisitor {
|
|
public:
|
|
ModifierInnerScopeBuilder(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;
|
|
};
|
|
|
|
ModifierInnerScopeBuilder::ModifierInnerScopeBuilder(ObjectLinkingScope *scope) : _scope(scope) {
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
assert(false);
|
|
}
|
|
|
|
void ModifierInnerScopeBuilder::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
_scope->addObject(modifier->getStaticGUID(), 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, Modifier *modifier, 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;
|
|
Modifier *_modifier;
|
|
ObjectLinkingScope *_outerScope;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
ModifierChildMaterializer::ModifierChildMaterializer(Runtime *runtime, Modifier *modifier, ObjectLinkingScope *outerScope)
|
|
: _runtime(runtime), _modifier(modifier), _outerScope(outerScope) {
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitChildStructuralRef(Common::SharedPtr<Structural> &structural) {
|
|
assert(false);
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitChildModifierRef(Common::SharedPtr<Modifier> &modifier) {
|
|
_runtime->instantiateIfAlias(modifier, _modifier->getSelfReference());
|
|
modifier->materialize(_runtime, _outerScope);
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitWeakStructuralRef(Common::WeakPtr<Structural> &structural) {
|
|
// Do nothing
|
|
}
|
|
|
|
void ModifierChildMaterializer::visitWeakModifierRef(Common::WeakPtr<Modifier> &modifier) {
|
|
// Do nothing
|
|
}
|
|
|
|
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) {
|
|
modifier = modifier->shallowClone();
|
|
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
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
bool Point16::load(const Data::Point &point) {
|
|
x = point.x;
|
|
y = point.y;
|
|
|
|
return true;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome Point16::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "x") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&x, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "y") {
|
|
DynamicValueWriteIntegerHelper<int16>::create(&y, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
bool Rect16::load(const Data::Rect &rect) {
|
|
top = rect.top;
|
|
left = rect.left;
|
|
bottom = rect.bottom;
|
|
right = rect.right;
|
|
|
|
if (bottom < top || right < left)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IntRange::load(const Data::IntRange &range) {
|
|
max = range.max;
|
|
min = range.min;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IntRange::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "start") {
|
|
DynamicValueWriteIntegerHelper<int32>::create(&min, proxy);
|
|
return true;
|
|
}
|
|
if (attrib == "y") {
|
|
DynamicValueWriteIntegerHelper<int32>::create(&max, proxy);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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 * 510 + 1) / 131070;
|
|
this->g = (color.green * 510 + 1) / 131070;
|
|
this->b = (color.blue * 510 + 1) / 131070;
|
|
|
|
return true;
|
|
}
|
|
|
|
MessageFlags::MessageFlags() : relay(true), cascade(true), immediate(true) {
|
|
}
|
|
|
|
DynamicListContainerBase::~DynamicListContainerBase() {
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(int32 &value) {
|
|
value = 0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(double &value) {
|
|
value = 0.0;
|
|
}
|
|
|
|
void DynamicListDefaultSetter::defaultSet(Point16 &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.angleRadians = 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 Point16 *&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 Point16 &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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool DynamicListContainer<VarReference>::setAtIndex(size_t index, const DynamicValue &dynValue) {
|
|
if (dynValue.getType() != DynamicValueTypes::kVariableReference)
|
|
return false;
|
|
|
|
size_t requiredSize = index + 1;
|
|
|
|
if (_array.size() < requiredSize) {
|
|
size_t prevSize = _array.size();
|
|
_array.resize(requiredSize);
|
|
_strings.resize(requiredSize);
|
|
|
|
for (size_t i = prevSize; i < index; i++) {
|
|
_array[i].guid = 0;
|
|
}
|
|
|
|
const VarReference &varRef = dynValue.getVarReference();
|
|
_array[index].guid = varRef.guid;
|
|
_strings[index] = *varRef.source;
|
|
|
|
rebuildStringPointers();
|
|
} else {
|
|
const VarReference &varRef = dynValue.getVarReference();
|
|
_array[index].guid = varRef.guid;
|
|
_strings[index] = *varRef.source;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListContainer<VarReference>::expandToMinimumSize(size_t sz) {
|
|
if (_array.size() < sz) {
|
|
size_t prevSize = _array.size();
|
|
_array.resize(sz);
|
|
_strings.resize(sz);
|
|
|
|
for (size_t i = prevSize; i < sz; prevSize++) {
|
|
_array[i].guid = 0;
|
|
_array[i].source = nullptr;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DynamicListContainer<VarReference>::getAtIndex(size_t index, DynamicValue &dynValue) const {
|
|
// TODO: Refactor this whole thing to use linkInternalReferences
|
|
if (index >= _array.size())
|
|
return false;
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
void DynamicListContainer<VarReference>::setFrom(const DynamicListContainerBase &other) {
|
|
const DynamicListContainer<VarReference> &otherTyped = static_cast<const DynamicListContainer<VarReference> &>(other);
|
|
|
|
_array = otherTyped._array;
|
|
_strings = otherTyped._strings;
|
|
rebuildStringPointers();
|
|
}
|
|
|
|
const void *DynamicListContainer<VarReference>::getConstArrayPtr() const {
|
|
return &_array;
|
|
}
|
|
|
|
void *DynamicListContainer<VarReference>::getArrayPtr() {
|
|
return &_array;
|
|
}
|
|
|
|
size_t DynamicListContainer<VarReference>::getSize() const {
|
|
return _array.size();
|
|
}
|
|
|
|
bool DynamicListContainer<VarReference>::compareEqual(const DynamicListContainerBase &other) const {
|
|
const DynamicListContainer<VarReference> &otherTyped = static_cast<const DynamicListContainer<VarReference> &>(other);
|
|
return _array == otherTyped._array;
|
|
}
|
|
|
|
DynamicListContainerBase *DynamicListContainer<VarReference>::clone() const {
|
|
return new DynamicListContainer<VarReference>(*this);
|
|
}
|
|
|
|
void DynamicListContainer<VarReference>::rebuildStringPointers() {
|
|
assert(_strings.size() == _array.size());
|
|
|
|
size_t numStrings = _array.size();
|
|
for (size_t i = 0; i < numStrings; i++) {
|
|
_array[i].source = &_strings[i];
|
|
}
|
|
}
|
|
|
|
DynamicList::DynamicList() : _type(DynamicValueTypes::kEmpty), _container(nullptr) {
|
|
}
|
|
|
|
DynamicList::DynamicList(const DynamicList &other) : _type(DynamicValueTypes::kEmpty), _container(nullptr) {
|
|
initFromOther(other);
|
|
}
|
|
|
|
DynamicList::~DynamicList() {
|
|
clear();
|
|
}
|
|
|
|
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<Point16> &DynamicList::getPoint() const {
|
|
assert(_type == DynamicValueTypes::kPoint);
|
|
return *static_cast<const Common::Array<Point16> *>(_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<VarReference> &DynamicList::getVarReference() const {
|
|
assert(_type == DynamicValueTypes::kVariableReference);
|
|
return *static_cast<const Common::Array<VarReference> *>(_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<Point16> &DynamicList::getPoint() {
|
|
assert(_type == DynamicValueTypes::kPoint);
|
|
return *static_cast<Common::Array<Point16> *>(_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<VarReference> &DynamicList::getVarReference() {
|
|
assert(_type == DynamicValueTypes::kVariableReference);
|
|
return *static_cast<Common::Array<VarReference> *>(_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 != nullptr && _container->getSize() != 0)
|
|
return false;
|
|
else {
|
|
clear();
|
|
changeToType(value.getType());
|
|
return _container->setAtIndex(index, value);
|
|
}
|
|
} else {
|
|
return _container->setAtIndex(index, value);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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 > UINT32_MAX)
|
|
return false;
|
|
|
|
outIndex = static_cast<size_t>(rounded);
|
|
} else if (value.getType() == DynamicValueTypes::kInteger) {
|
|
int32 i = value.getInt();
|
|
if (i < 1)
|
|
return false;
|
|
static_cast<size_t>(i - 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DynamicList &DynamicList::operator=(const DynamicList &other) {
|
|
if (this != &other) {
|
|
clear();
|
|
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();
|
|
|
|
return clonedList;
|
|
}
|
|
|
|
void DynamicList::createWriteProxyForIndex(size_t index, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ifc = &WriteProxyInterface::_instance;
|
|
proxy.pod.objectRef = this;
|
|
proxy.pod.ptrOrOffset = index;
|
|
}
|
|
|
|
bool DynamicList::changeToType(DynamicValueTypes::DynamicValueType type) {
|
|
switch (type) {
|
|
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<Point16>();
|
|
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::kVariableReference:
|
|
_container = new DynamicListContainer<VarReference>();
|
|
break;
|
|
case DynamicValueTypes::kIncomingData:
|
|
_container = new DynamicListContainer<void>();
|
|
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;
|
|
}
|
|
|
|
_type = type;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DynamicList::clear() {
|
|
_type = DynamicValueTypes::kEmpty;
|
|
if (_container)
|
|
delete _container;
|
|
_container = nullptr;
|
|
}
|
|
|
|
void DynamicList::initFromOther(const DynamicList &other) {
|
|
assert(_container == nullptr);
|
|
assert(_type == DynamicValueTypes::kEmpty);
|
|
|
|
if (other._type != DynamicValueTypes::kEmpty) {
|
|
changeToType(other._type);
|
|
_container->setFrom(*other._container);
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr_t ptrOrOffset) const {
|
|
if (!static_cast<DynamicList *>(objectRef)->setAtIndex(ptrOrOffset, value))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib) const {
|
|
DynamicList *list = static_cast<DynamicList *>(objectRef);
|
|
bool succeeded = false;
|
|
switch (list->getType()) {
|
|
case DynamicValueTypes::kPoint:
|
|
list->expandToMinimumSize(ptrOrOffset + 1);
|
|
succeeded = list->getPoint()[ptrOrOffset].refAttrib(thread, proxy, attrib);
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
list->expandToMinimumSize(ptrOrOffset + 1);
|
|
succeeded = list->getIntRange()[ptrOrOffset].refAttrib(thread, proxy, attrib);
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
list->expandToMinimumSize(ptrOrOffset + 1);
|
|
succeeded = list->getVector()[ptrOrOffset].refAttrib(thread, proxy, attrib);
|
|
case DynamicValueTypes::kObject: {
|
|
if (list->getSize() <= ptrOrOffset)
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
|
|
Common::SharedPtr<RuntimeObject> obj = list->getObjectReference()[ptrOrOffset].object.lock();
|
|
proxy.containerList.reset();
|
|
succeeded = (obj && obj->writeRefAttribute(thread, proxy, attrib));
|
|
} break;
|
|
default:
|
|
succeeded = false;
|
|
break;
|
|
}
|
|
|
|
if (succeeded)
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicList::WriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const {
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
DynamicList::WriteProxyInterface DynamicList::WriteProxyInterface::_instance;
|
|
|
|
DynamicValue::DynamicValue() : _type(DynamicValueTypes::kNull) {
|
|
}
|
|
|
|
DynamicValue::DynamicValue(const DynamicValue &other) : _type(DynamicValueTypes::kNull) {
|
|
initFromOther(other);
|
|
}
|
|
|
|
DynamicValue::~DynamicValue() {
|
|
clear();
|
|
}
|
|
|
|
bool DynamicValue::load(const Data::InternalTypeTaggedValue &data, const Common::String &varSource, const Common::String &varString) {
|
|
switch (data.type) {
|
|
case Data::InternalTypeTaggedValue::kNull:
|
|
_type = DynamicValueTypes::kNull;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kIncomingData:
|
|
_type = DynamicValueTypes::kIncomingData;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kInteger:
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.asInt = data.value.asInteger;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kString:
|
|
_type = DynamicValueTypes::kString;
|
|
_str = varString;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kPoint:
|
|
_type = DynamicValueTypes::kPoint;
|
|
if (!_value.asPoint.load(data.value.asPoint))
|
|
return false;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kIntegerRange:
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
if (!_value.asIntRange.load(data.value.asIntegerRange))
|
|
return false;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kFloat:
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.asFloat = data.value.asFloat.toDouble();
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kBool:
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.asBool = (data.value.asBool != 0);
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kVariableReference:
|
|
_type = DynamicValueTypes::kVariableReference;
|
|
_value.asVarReference.guid = data.value.asVariableReference.guid;
|
|
_value.asVarReference.source = &_str;
|
|
_str = varSource;
|
|
break;
|
|
case Data::InternalTypeTaggedValue::kLabel:
|
|
_type = DynamicValueTypes::kLabel;
|
|
if (!_value.asLabel.load(data.value.asLabel))
|
|
return false;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DynamicValue::load(const Data::PlugInTypeTaggedValue &data) {
|
|
switch (data.type) {
|
|
case Data::PlugInTypeTaggedValue::kNull:
|
|
_type = DynamicValueTypes::kNull;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kIncomingData:
|
|
_type = DynamicValueTypes::kIncomingData;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kInteger:
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.asInt = data.value.asInt;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kIntegerRange:
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
if (!_value.asIntRange.load(data.value.asIntRange))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kFloat:
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.asFloat = data.value.asFloat.toDouble();
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kBoolean:
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.asBool = (data.value.asBoolean != 0);
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kEvent:
|
|
_type = DynamicValueTypes::kEvent;
|
|
if (!_value.asEvent.load(data.value.asEvent))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kLabel:
|
|
_type = DynamicValueTypes::kLabel;
|
|
if (!_value.asLabel.load(data.value.asLabel))
|
|
return false;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kString:
|
|
_type = DynamicValueTypes::kString;
|
|
_str = data.str;
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kVariableReference:
|
|
_type = DynamicValueTypes::kVariableReference;
|
|
_value.asVarReference.guid = data.value.asVarRefGUID;
|
|
_value.asVarReference.source = &_str;
|
|
_str.clear(); // Extra data doesn't seem to correlate to this
|
|
break;
|
|
case Data::PlugInTypeTaggedValue::kPoint:
|
|
_type = DynamicValueTypes::kPoint;
|
|
if (!_value.asPoint.load(data.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 Point16 &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 VarReference &DynamicValue::getVarReference() const {
|
|
assert(_type == DynamicValueTypes::kVariableReference);
|
|
return _value.asVarReference;
|
|
}
|
|
|
|
const Common::String &DynamicValue::getString() const {
|
|
assert(_type == DynamicValueTypes::kString);
|
|
return _str;
|
|
}
|
|
|
|
const bool &DynamicValue::getBool() const {
|
|
assert(_type == DynamicValueTypes::kBoolean);
|
|
return _value.asBool;
|
|
}
|
|
|
|
const Common::SharedPtr<DynamicList> &DynamicValue::getList() const {
|
|
assert(_type == DynamicValueTypes::kList);
|
|
return _list;
|
|
}
|
|
|
|
const ObjectReference &DynamicValue::getObject() const {
|
|
assert(_type == DynamicValueTypes::kObject);
|
|
return _obj;
|
|
}
|
|
|
|
const DynamicValueReadProxyPOD &DynamicValue::getReadProxyPOD() const {
|
|
assert(_type == DynamicValueTypes::kReadProxy);
|
|
return _value.asReadProxy;
|
|
}
|
|
|
|
const DynamicValueWriteProxyPOD &DynamicValue::getWriteProxyPOD() const {
|
|
assert(_type == DynamicValueTypes::kWriteProxy);
|
|
return _value.asWriteProxy;
|
|
}
|
|
|
|
|
|
DynamicValueReadProxy DynamicValue::getReadProxyTEMP() const {
|
|
assert(_type == DynamicValueTypes::kReadProxy);
|
|
|
|
DynamicValueReadProxy proxy;
|
|
proxy.pod = _value.asReadProxy;
|
|
proxy.containerList = _list;
|
|
return proxy;
|
|
}
|
|
|
|
DynamicValueWriteProxy DynamicValue::getWriteProxyTEMP() const {
|
|
assert(_type == DynamicValueTypes::kWriteProxy);
|
|
|
|
DynamicValueWriteProxy proxy;
|
|
proxy.pod = _value.asWriteProxy;
|
|
proxy.containerList = _list;
|
|
return proxy;
|
|
}
|
|
|
|
const Common::SharedPtr<DynamicList> &DynamicValue::getReadProxyContainer() const {
|
|
assert(_type == DynamicValueTypes::kReadProxy);
|
|
return _list;
|
|
}
|
|
|
|
const Common::SharedPtr<DynamicList> &DynamicValue::getWriteProxyContainer() const {
|
|
assert(_type == DynamicValueTypes::kWriteProxy);
|
|
return _list;
|
|
}
|
|
|
|
void DynamicValue::setInt(int32 value) {
|
|
if (_type != DynamicValueTypes::kInteger)
|
|
clear();
|
|
_type = DynamicValueTypes::kInteger;
|
|
_value.asInt = value;
|
|
}
|
|
|
|
void DynamicValue::setFloat(double value) {
|
|
if (_type != DynamicValueTypes::kFloat)
|
|
clear();
|
|
_type = DynamicValueTypes::kFloat;
|
|
_value.asFloat = value;
|
|
}
|
|
|
|
void DynamicValue::setPoint(const Point16 &value) {
|
|
if (_type != DynamicValueTypes::kPoint)
|
|
clear();
|
|
_type = DynamicValueTypes::kPoint;
|
|
_value.asPoint = value;
|
|
}
|
|
|
|
void DynamicValue::setIntRange(const IntRange &value) {
|
|
if (_type != DynamicValueTypes::kIntegerRange)
|
|
clear();
|
|
_type = DynamicValueTypes::kIntegerRange;
|
|
_value.asIntRange = value;
|
|
}
|
|
|
|
void DynamicValue::setVector(const AngleMagVector &value) {
|
|
if (_type != DynamicValueTypes::kVector)
|
|
clear();
|
|
_type = DynamicValueTypes::kVector;
|
|
_value.asVector = value;
|
|
}
|
|
|
|
void DynamicValue::setLabel(const Label &value) {
|
|
if (_type != DynamicValueTypes::kLabel)
|
|
clear();
|
|
_type = DynamicValueTypes::kLabel;
|
|
_value.asLabel = value;
|
|
}
|
|
|
|
void DynamicValue::setEvent(const Event &value) {
|
|
if (_type != DynamicValueTypes::kEvent)
|
|
clear();
|
|
_type = DynamicValueTypes::kEvent;
|
|
_value.asEvent = value;
|
|
}
|
|
|
|
void DynamicValue::setVarReference(const VarReference &value) {
|
|
if (_type != DynamicValueTypes::kVariableReference)
|
|
clear();
|
|
_type = DynamicValueTypes::kVariableReference;
|
|
_value.asVarReference.guid = value.guid;
|
|
_value.asVarReference.source = &_str;
|
|
_str = *value.source;
|
|
}
|
|
|
|
void DynamicValue::setString(const Common::String &value) {
|
|
if (_type != DynamicValueTypes::kString)
|
|
clear();
|
|
_type = DynamicValueTypes::kString;
|
|
_str = value;
|
|
}
|
|
|
|
void DynamicValue::setBool(bool value) {
|
|
if (_type != DynamicValueTypes::kBoolean)
|
|
clear();
|
|
_type = DynamicValueTypes::kBoolean;
|
|
_value.asBool = value;
|
|
}
|
|
|
|
void DynamicValue::setList(const Common::SharedPtr<DynamicList> &value) {
|
|
if (_type != DynamicValueTypes::kList)
|
|
clear();
|
|
_type = DynamicValueTypes::kList;
|
|
_list = value;
|
|
}
|
|
|
|
void DynamicValue::setReadProxy(const DynamicValueReadProxy &readProxy) {
|
|
Common::SharedPtr<DynamicList> listRef = readProxy.containerList; // Back up list ref in case this is a self-assign
|
|
if (_type != DynamicValueTypes::kReadProxy)
|
|
clear();
|
|
_type = DynamicValueTypes::kReadProxy;
|
|
_value.asReadProxy = readProxy.pod;
|
|
_list = listRef;
|
|
}
|
|
|
|
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.asWriteProxy = writeProxy.pod;
|
|
_list = listRef;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void DynamicValue::setObject(const ObjectReference &value) {
|
|
if (_type != DynamicValueTypes::kObject)
|
|
clear();
|
|
_type = DynamicValueTypes::kObject;
|
|
_obj = value;
|
|
}
|
|
|
|
void DynamicValue::setObject(const Common::WeakPtr<RuntimeObject> &value) {
|
|
setObject(ObjectReference(value));
|
|
}
|
|
|
|
void DynamicValue::swap(DynamicValue &other) {
|
|
internalSwap(_type, other._type);
|
|
internalSwap(_str, other._str);
|
|
internalSwap(_list, other._list);
|
|
internalSwap(_obj, other._obj);
|
|
|
|
ValueUnion tempValue;
|
|
memcpy(&tempValue, &_value, sizeof(ValueUnion));
|
|
memcpy(&_value, &other._value, sizeof(ValueUnion));
|
|
memcpy(&other._value, &tempValue, sizeof(ValueUnion));
|
|
}
|
|
|
|
DynamicValue &DynamicValue::operator=(const DynamicValue &other) {
|
|
if (this != &other) {
|
|
DynamicValue temp(other);
|
|
swap(temp);
|
|
}
|
|
|
|
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 == other._value.asPoint;
|
|
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::kVariableReference:
|
|
return _value.asVarReference == other._value.asVarReference;
|
|
case DynamicValueTypes::kIncomingData:
|
|
return true;
|
|
case DynamicValueTypes::kString:
|
|
return _str == other._str;
|
|
case DynamicValueTypes::kBoolean:
|
|
return _value.asBool == other._value.asBool;
|
|
case DynamicValueTypes::kList:
|
|
return (*_list.get()) == (*other._list.get());
|
|
case DynamicValueTypes::kObject:
|
|
return _obj == other._obj;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
assert(false);
|
|
return false;
|
|
}
|
|
|
|
void DynamicValue::clear() {
|
|
_list.reset();
|
|
_obj.reset();
|
|
_str.clear();
|
|
_type = DynamicValueTypes::kNull;
|
|
}
|
|
|
|
void DynamicValue::initFromOther(const DynamicValue &other) {
|
|
assert(_type == DynamicValueTypes::kNull);
|
|
|
|
switch (other._type) {
|
|
case DynamicValueTypes::kNull:
|
|
case DynamicValueTypes::kIncomingData:
|
|
break;
|
|
case DynamicValueTypes::kInteger:
|
|
_value.asInt = other._value.asInt;
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
_value.asFloat = other._value.asFloat;
|
|
break;
|
|
case DynamicValueTypes::kPoint:
|
|
_value.asPoint = other._value.asPoint;
|
|
break;
|
|
case DynamicValueTypes::kIntegerRange:
|
|
_value.asIntRange = other._value.asIntRange;
|
|
break;
|
|
case DynamicValueTypes::kVector:
|
|
_value.asVector = other._value.asVector;
|
|
break;
|
|
case DynamicValueTypes::kLabel:
|
|
_value.asLabel = other._value.asLabel;
|
|
break;
|
|
case DynamicValueTypes::kEvent:
|
|
_value.asEvent = other._value.asEvent;
|
|
break;
|
|
case DynamicValueTypes::kVariableReference:
|
|
_value.asVarReference = other._value.asVarReference;
|
|
_str = other._str;
|
|
_value.asVarReference.source = &_str;
|
|
break;
|
|
case DynamicValueTypes::kString:
|
|
_str = other._str;
|
|
break;
|
|
case DynamicValueTypes::kBoolean:
|
|
_value.asBool = other._value.asBool;
|
|
break;
|
|
case DynamicValueTypes::kList:
|
|
_list = other._list;
|
|
break;
|
|
case DynamicValueTypes::kObject:
|
|
_obj = other._obj;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
_type = other._type;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr_t ptrOrOffset) const {
|
|
Common::String &dest = *static_cast<Common::String *>(objectRef);
|
|
switch (value.getType()) {
|
|
case DynamicValueTypes::kString:
|
|
dest = value.getString();
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib) const {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteStringHelper::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void DynamicValueWriteStringHelper::create(Common::String *strValue, DynamicValueWriteProxy &proxy) {
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.objectRef = strValue;
|
|
proxy.pod.ifc = &_instance;
|
|
}
|
|
|
|
DynamicValueWriteStringHelper DynamicValueWriteStringHelper::_instance;
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr_t ptrOrOffset) const {
|
|
thread->error("Can't write to read-only object value");
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib) const {
|
|
return static_cast<RuntimeObject *>(objectRef)->writeRefAttribute(thread, proxy, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome DynamicValueWriteObjectHelper::refAttribIndexed(MiniscriptThread* thread, DynamicValueWriteProxy& proxy, void* objectRef, uintptr_t ptrOrOffset, const Common::String& attrib, const DynamicValue& index) const {
|
|
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 = &DynamicValueWriteObjectHelper::_instance;
|
|
proxy.pod.objectRef = obj;
|
|
proxy.pod.ptrOrOffset = 0;
|
|
}
|
|
|
|
DynamicValueWriteObjectHelper DynamicValueWriteObjectHelper::_instance;
|
|
|
|
MessengerSendSpec::MessengerSendSpec() : destination(0), _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;
|
|
}
|
|
}
|
|
|
|
void MessengerSendSpec::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
|
|
visitor->visitWeakModifierRef(resolvedModifierDest);
|
|
visitor->visitWeakStructuralRef(resolvedStructuralDest);
|
|
}
|
|
|
|
void MessengerSendSpec::resolveDestination(Runtime *runtime, Modifier *sender, Common::WeakPtr<Structural> &outStructuralDest, Common::WeakPtr<Modifier> &outModifierDest) const {
|
|
outStructuralDest.reset();
|
|
outModifierDest.reset();
|
|
|
|
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 kMessageDestChildren:
|
|
case kMessageDestElementsParent:
|
|
case kMessageDestSubsection:
|
|
case kMessageDestSourcesParent:
|
|
case kMessageDestBehavior:
|
|
case kMessageDestNextElement:
|
|
case kMessageDestPrevElement:
|
|
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 mesenger 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) const {
|
|
sendFromMessengerWithCustomData(runtime, sender, this->with);
|
|
}
|
|
|
|
void MessengerSendSpec::sendFromMessengerWithCustomData(Runtime *runtime, Modifier *sender, const DynamicValue &data) const {
|
|
Common::SharedPtr<MessageProperties> props(new MessageProperties(this->send, data, sender->getSelfReference()));
|
|
|
|
Common::WeakPtr<Modifier> modifierDestRef;
|
|
Common::WeakPtr<Structural> structuralDestRef;
|
|
|
|
resolveDestination(runtime, sender, structuralDestRef, modifierDestRef);
|
|
|
|
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::create() {
|
|
Event evt;
|
|
evt.eventInfo = 0;
|
|
evt.eventType = EventIDs::kNothing;
|
|
|
|
return evt;
|
|
}
|
|
|
|
Event Event::create(EventIDs::EventID eventType, uint32 eventInfo) {
|
|
Event evt;
|
|
evt.eventType = eventType;
|
|
evt.eventInfo = eventInfo;
|
|
|
|
return evt;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome AngleMagVector::scriptSetAngleDegrees(MiniscriptThread *thread, const DynamicValue &value) {
|
|
double degrees = 0.0;
|
|
switch (value.getType())
|
|
{
|
|
case DynamicValueTypes::kInteger:
|
|
degrees = static_cast<double>(value.getInt());
|
|
break;
|
|
case DynamicValueTypes::kFloat:
|
|
degrees = value.getFloat();
|
|
break;
|
|
default:
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
angleRadians = degrees * (M_PI / 180.0);
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
;
|
|
}
|
|
|
|
void AngleMagVector::scriptGetAngleDegrees(DynamicValue &value) const {
|
|
value.setFloat(angleRadians * (180.0 / M_PI));
|
|
}
|
|
|
|
|
|
MiniscriptInstructionOutcome AngleMagVector::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) {
|
|
if (attrib == "angle") {
|
|
DynamicValueWriteFuncHelper<AngleMagVector, &AngleMagVector::scriptSetAngleDegrees>::create(this, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
if (attrib == "magnitude") {
|
|
DynamicValueWriteFloatHelper<double>::create(&magnitude, proxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
void IPlugInModifierRegistrar::registerPlugInModifier(const char *name, const IPlugInModifierFactoryAndDataFactory *loaderFactory) {
|
|
return this->registerPlugInModifier(name, loaderFactory, loaderFactory);
|
|
}
|
|
|
|
PlugIn::~PlugIn() {
|
|
}
|
|
|
|
ProjectResources::~ProjectResources() {
|
|
}
|
|
|
|
CursorGraphicCollection::CursorGraphicCollection() {
|
|
}
|
|
|
|
CursorGraphicCollection::~CursorGraphicCollection() {
|
|
}
|
|
|
|
void CursorGraphicCollection::addWinCursorGroup(uint32 cursorGroupID, const Common::SharedPtr<Graphics::WinCursorGroup> &cursorGroup) {
|
|
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)
|
|
_cursorGraphics[cursorGroupID] = cursorGroup->cursors[1].cursor;
|
|
else
|
|
_cursorGraphics[cursorGroupID] = cursorGroup->cursors.back().cursor;
|
|
_winCursorGroups.push_back(cursorGroup);
|
|
}
|
|
}
|
|
|
|
void CursorGraphicCollection::addMacCursor(uint32 cursorID, const Common::SharedPtr<Graphics::MacCursor> &cursor) {
|
|
_cursorGraphics[cursorID] = cursor.get();
|
|
_macCursors.push_back(cursor);
|
|
}
|
|
|
|
|
|
ProjectDescription::ProjectDescription(ProjectPlatform platform) : _language(Common::EN_ANY), _platform(platform) {
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void ProjectDescription::setLanguage(const Common::Language &language) {
|
|
_language = language;
|
|
}
|
|
|
|
const Common::Language &ProjectDescription::getLanguage() const {
|
|
return _language;
|
|
}
|
|
|
|
ProjectPlatform ProjectDescription::getPlatform() const {
|
|
return _platform;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome RuntimeObject::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib, const DynamicValue &index) {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
WorldManagerInterface::WorldManagerInterface() {
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return RuntimeObject::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome WorldManagerInterface::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
|
|
if (attrib == "currentscene") {
|
|
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setCurrentScene>::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;
|
|
}
|
|
|
|
thread->getRuntime()->addSceneStateTransition(HighLevelSceneTransition(scene->getSelfReference().lock().staticCast<Structural>(), HighLevelSceneTransition::kTypeChangeToScene, false, false));
|
|
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
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>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "gamemode") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setGameMode>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "mastervolume") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setMasterVolume>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "monitorbitdepth") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setMonitorBitDepth>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "volumename") {
|
|
DynamicValueWriteFuncHelper<SystemInterface, &SystemInterface::setVolumeName>::create(this, result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return RuntimeObject::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
int32 SystemInterface::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 SystemInterface::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;
|
|
}
|
|
}
|
|
|
|
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 = SystemInterface::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;
|
|
}
|
|
|
|
Structural::Structural() : _parent(nullptr), _paused(false) {
|
|
}
|
|
|
|
Structural::~Structural() {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugInspector)
|
|
_debugInspector->onDestroyed();
|
|
#endif
|
|
}
|
|
|
|
ProjectPresentationSettings::ProjectPresentationSettings() : width(640), height(480), bitsPerPixel(8) {
|
|
}
|
|
|
|
bool Structural::isStructural() const {
|
|
return true;
|
|
}
|
|
|
|
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 == "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;
|
|
}
|
|
|
|
return RuntimeObject::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
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>::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 == "parent") {
|
|
// NOTE: Re-parenting objects is allowed by mTropolis but we don't currently support that.
|
|
Structural *parent = getParent();
|
|
if (parent) {
|
|
DynamicValueWriteObjectHelper::create(getParent(), result);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else {
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
}
|
|
|
|
return RuntimeObject::writeRefAttribute(thread, result, attrib);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void Structural::setParent(Structural *parent) {
|
|
_parent = parent;
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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);
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
_debugInspector.reset(this->debugCreateInspector());
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
VThreadState Structural::consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
|
|
warning("Command type %i was ignored", msg->getEvent().eventType);
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
void Structural::activate() {
|
|
}
|
|
|
|
void Structural::deactivate() {
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
SupportStatus Structural::debugGetSupportStatus() const {
|
|
return kSupportStatusNone;
|
|
}
|
|
|
|
const Common::String &Structural::debugGetName() const {
|
|
return _name;
|
|
}
|
|
|
|
Common::SharedPtr<DebugInspector> Structural::debugGetInspector() const {
|
|
return _debugInspector;
|
|
}
|
|
|
|
DebugInspector *Structural::debugCreateInspector() {
|
|
return new DebugInspector(this);
|
|
}
|
|
#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();
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(targetValue ? EventIDs::kPause : EventIDs::kUnpause, 0), DynamicValue(), getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
|
|
thread->getRuntime()->sendMessageOnVThread(dispatch);
|
|
|
|
return kMiniscriptInstructionOutcomeYieldToVThreadNoRetry;
|
|
}
|
|
|
|
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)
|
|
_nameToObject[toCaseInsensitive(name)] = 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);
|
|
|
|
warning("Couldn't resolve static GUID %x", 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);
|
|
|
|
warning("Couldn't resolve object name '%s'", name.c_str());
|
|
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
|
|
return resolve(name, isNameAlreadyInsensitive);
|
|
}
|
|
|
|
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> &scene, Type type, bool addToDestinationScene, bool addToReturnList)
|
|
: scene(scene), type(type), addToDestinationScene(addToDestinationScene), addToReturnList(addToReturnList) {
|
|
}
|
|
|
|
MessageDispatch::MessageDispatch(const Common::SharedPtr<MessageProperties> &msgProps, Structural *root, bool cascade, bool relay, bool couldBeCommand)
|
|
: _cascade(cascade), _relay(relay), _terminated(false), _msg(msgProps), _isCommand(false) {
|
|
if (couldBeCommand && EventIDs::isCommand(msgProps->getEvent().eventType)) {
|
|
_isCommand = true;
|
|
|
|
PropagationStack topEntry;
|
|
topEntry.index = 0;
|
|
topEntry.propagationStage = PropagationStack::kStageSendCommand;
|
|
topEntry.ptr.structural = root;
|
|
|
|
_propagationStack.push_back(topEntry);
|
|
} else {
|
|
PropagationStack topEntry;
|
|
topEntry.index = 0;
|
|
topEntry.propagationStage = PropagationStack::kStageSendToStructuralSelf;
|
|
topEntry.ptr.structural = root;
|
|
|
|
_propagationStack.push_back(topEntry);
|
|
}
|
|
}
|
|
|
|
MessageDispatch::MessageDispatch(const Common::SharedPtr<MessageProperties> &msgProps, Modifier *root, bool cascade, bool relay, bool couldBeCommand)
|
|
: _cascade(cascade), _relay(relay), _terminated(false), _msg(msgProps), _isCommand(false) {
|
|
if (couldBeCommand && EventIDs::isCommand(msgProps->getEvent().eventType)) {
|
|
_isCommand = true;
|
|
// Can't actually send this so don't even bother. Modifiers are not allowed to respond to commands.
|
|
} else {
|
|
PropagationStack topEntry;
|
|
topEntry.index = 0;
|
|
topEntry.propagationStage = PropagationStack::kStageSendToModifier;
|
|
topEntry.ptr.modifier = root;
|
|
|
|
_isCommand = (couldBeCommand && EventIDs::isCommand(msgProps->getEvent().eventType));
|
|
|
|
_propagationStack.push_back(topEntry);
|
|
}
|
|
}
|
|
|
|
bool MessageDispatch::isTerminated() const {
|
|
return _terminated;
|
|
}
|
|
|
|
VThreadState MessageDispatch::continuePropagating(Runtime *runtime) {
|
|
// By the point this function is called, continuePropagating has been re-posted to the VThread,
|
|
// so any propagation state changed in this function will be handled after any VThread tasks
|
|
// posted here.
|
|
while (_propagationStack.size() > 0) {
|
|
PropagationStack &stackTop = _propagationStack.back();
|
|
|
|
switch (stackTop.propagationStage)
|
|
{
|
|
case PropagationStack::kStageSendToModifier: {
|
|
Modifier *modifier = stackTop.ptr.modifier;
|
|
_propagationStack.pop_back();
|
|
|
|
// Handle the action in the VThread
|
|
bool responds = modifier->respondsToEvent(_msg->getEvent());
|
|
|
|
// Queue propagation to children, if any, when the VThread task is done
|
|
if (responds && !_relay) {
|
|
_terminated = true;
|
|
} else {
|
|
IModifierContainer *childContainer = modifier->getMessagePropagationContainer();
|
|
if (childContainer && childContainer->getModifiers().size() > 0) {
|
|
PropagationStack childPropagation;
|
|
childPropagation.propagationStage = PropagationStack::kStageSendToModifierContainer;
|
|
childPropagation.index = 0;
|
|
childPropagation.ptr.modifierContainer = childContainer;
|
|
_propagationStack.push_back(childPropagation);
|
|
}
|
|
}
|
|
|
|
// Post to the message action itself to VThread
|
|
if (responds)
|
|
runtime->postConsumeMessageTask(modifier, _msg);
|
|
|
|
return kVThreadReturn;
|
|
} break;
|
|
case PropagationStack::kStageSendToModifierContainer: {
|
|
IModifierContainer *container = stackTop.ptr.modifierContainer;
|
|
const Common::Array<Common::SharedPtr<Modifier> > &children = container->getModifiers();
|
|
if (stackTop.index >= children.size()) {
|
|
_propagationStack.pop_back();
|
|
} else {
|
|
Common::SharedPtr<Modifier> target = children[stackTop.index++];
|
|
|
|
PropagationStack modifierPropagation;
|
|
modifierPropagation.propagationStage = PropagationStack::kStageSendToModifier;
|
|
modifierPropagation.index = 0;
|
|
modifierPropagation.ptr.modifier = target.get();
|
|
_propagationStack.push_back(modifierPropagation);
|
|
}
|
|
} break;
|
|
case PropagationStack::kStageSendToStructuralChildren: {
|
|
Structural *structural = stackTop.ptr.structural;
|
|
const Common::Array<Common::SharedPtr<Structural> > &children = structural->getChildren();
|
|
if (stackTop.index >= children.size()) {
|
|
_propagationStack.pop_back();
|
|
} else {
|
|
PropagationStack childPropagation;
|
|
childPropagation.propagationStage = PropagationStack::kStageSendToStructuralSelf;
|
|
childPropagation.index = 0;
|
|
childPropagation.ptr.structural = children[stackTop.index++].get();
|
|
_propagationStack.push_back(childPropagation);
|
|
}
|
|
} break;
|
|
case PropagationStack::kStageSendToStructuralSelf: {
|
|
Structural *structural = stackTop.ptr.structural;
|
|
stackTop.propagationStage = PropagationStack::kStageSendToStructuralModifiers;
|
|
stackTop.index = 0;
|
|
stackTop.ptr.structural = structural;
|
|
|
|
bool responds = structural->respondsToEvent(_msg->getEvent());
|
|
|
|
if (responds && !_relay) {
|
|
_terminated = true;
|
|
}
|
|
|
|
if (responds)
|
|
runtime->postConsumeMessageTask(structural, _msg);
|
|
|
|
return kVThreadReturn;
|
|
} break;
|
|
case PropagationStack::kStageSendCommand: {
|
|
Structural *structural = stackTop.ptr.structural;
|
|
_propagationStack.pop_back();
|
|
_terminated = true;
|
|
|
|
runtime->postConsumeCommandTask(structural, _msg);
|
|
|
|
return kVThreadReturn;
|
|
} break;
|
|
case PropagationStack::kStageSendToStructuralModifiers: {
|
|
Structural *structural = stackTop.ptr.structural;
|
|
|
|
// Once done with modifiers, propagate to children if set to cascade
|
|
if (_cascade) {
|
|
stackTop.propagationStage = PropagationStack::kStageSendToStructuralChildren;
|
|
stackTop.index = 0;
|
|
stackTop.ptr.structural = structural;
|
|
} else {
|
|
_propagationStack.pop_back();
|
|
}
|
|
|
|
if (structural->getModifiers().size() > 0) {
|
|
PropagationStack modifierContainerPropagation;
|
|
modifierContainerPropagation.propagationStage = PropagationStack::kStageSendToModifierContainer;
|
|
modifierContainerPropagation.index = 0;
|
|
modifierContainerPropagation.ptr.modifierContainer = structural;
|
|
_propagationStack.push_back(modifierContainerPropagation);
|
|
}
|
|
} break;
|
|
default:
|
|
return kVThreadError;
|
|
}
|
|
}
|
|
|
|
_terminated = true;
|
|
|
|
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 (Common::Array<Common::SharedPtr<ScheduledEvent>>::iterator it = _events.begin(), itEnd = _events.end(); it != itEnd; ++it) {
|
|
it->get()->_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
|
|
};
|
|
|
|
Runtime::SceneStackEntry::SceneStackEntry() {
|
|
}
|
|
|
|
Runtime::Runtime(OSystem *system) : _nextRuntimeGUID(1), _realDisplayMode(kColorDepthModeInvalid), _fakeDisplayMode(kColorDepthModeInvalid),
|
|
_displayWidth(1024), _displayHeight(768), _realTimeBase(0), _playTimeBase(0), _sceneTransitionState(kSceneTransitionStateNotTransitioning),
|
|
_system(system), _lastFrameCursor(nullptr), _defaultCursor(new DefaultCursor()), _platform(kProjectPlatformUnknown) {
|
|
_random.reset(new Common::RandomSource("mtropolis"));
|
|
|
|
_vthread.reset(new VThread());
|
|
|
|
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[Actions::kMouseButtonCount] = false;
|
|
|
|
_worldManagerInterface.reset(new WorldManagerInterface());
|
|
_worldManagerInterface->setSelfReference(_worldManagerInterface);
|
|
|
|
_assetManagerInterface.reset(new AssetManagerInterface());
|
|
_assetManagerInterface->setSelfReference(_assetManagerInterface);
|
|
|
|
_systemInterface.reset(new SystemInterface());
|
|
_systemInterface->setSelfReference(_systemInterface);
|
|
}
|
|
|
|
bool Runtime::runFrame() {
|
|
uint32 timeMillis = _system->getMillis();
|
|
|
|
uint32 realMSec = timeMillis - _realTimeBase - _realTime;
|
|
uint32 playMSec = timeMillis - _playTimeBase - _playTime;
|
|
|
|
_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 (_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);
|
|
|
|
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);
|
|
|
|
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::create(EventIDs::kProjectStarted, 0), DynamicValue(), _project->getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> psDispatch(new MessageDispatch(psProps, _project.get(), false, true, false));
|
|
queueMessage(psDispatch);
|
|
|
|
_pendingSceneTransitions.push_back(HighLevelSceneTransition(firstSubsection->getChildren()[1], HighLevelSceneTransition::kTypeChangeToScene, false, false));
|
|
continue;
|
|
}
|
|
|
|
if (_messageQueue.size() > 0) {
|
|
Common::SharedPtr<MessageDispatch> msg = _messageQueue[0];
|
|
_messageQueue.remove_at(0);
|
|
|
|
sendMessageOnVThread(msg);
|
|
continue;
|
|
}
|
|
|
|
// Teardowns must only occur during idle conditions where there are no queued message and 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();
|
|
continue;
|
|
}
|
|
|
|
if (_pendingLowLevelTransitions.size() > 0) {
|
|
LowLevelSceneStateTransitionAction transition = _pendingLowLevelTransitions[0];
|
|
_pendingLowLevelTransitions.remove_at(0);
|
|
|
|
executeLowLevelSceneStateTransition(transition);
|
|
continue;
|
|
}
|
|
|
|
if (_pendingSceneTransitions.size() > 0) {
|
|
HighLevelSceneTransition transition = _pendingSceneTransitions[0];
|
|
_pendingSceneTransitions.remove_at(0);
|
|
|
|
executeHighLevelSceneTransition(transition);
|
|
continue;
|
|
}
|
|
|
|
if (_sceneTransitionState == kSceneTransitionStateWaitingForDraw) {
|
|
if (_sceneTransitionEffect.duration == 0) {
|
|
// This needs to skip past the transition phase and hit the next condition
|
|
_sceneTransitionEndTime = _playTime;
|
|
_sceneTransitionState = kSceneTransitionStateTransitioning;
|
|
} else {
|
|
_sceneTransitionState = kSceneTransitionStateDrawingTargetFrame;
|
|
_sceneTransitionEndTime = _playTime + _sceneTransitionEffect.duration / 10;
|
|
}
|
|
}
|
|
|
|
if (_sceneTransitionState == kSceneTransitionStateTransitioning && _playTime >= _sceneTransitionEndTime) {
|
|
_sceneTransitionState = kSceneTransitionStateNotTransitioning;
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneTransitionEnded, 0), _activeMainScene.get(), true, true);
|
|
continue;
|
|
}
|
|
|
|
{
|
|
Common::SharedPtr<ScheduledEvent> firstScheduledEvent = _scheduler.getFirstEvent();
|
|
if (firstScheduledEvent && firstScheduledEvent->getScheduledTime() <= _playTime) {
|
|
_scheduler.descheduleFirstEvent();
|
|
|
|
firstScheduledEvent->activate(this);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Ran out of actions
|
|
break;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugger)
|
|
_debugger->runFrame(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()));
|
|
|
|
{
|
|
Common::SharedPtr<Window> mainWindow = _mainWindow.lock();
|
|
if (mainWindow)
|
|
Render::renderProject(this, mainWindow.get());
|
|
}
|
|
|
|
const size_t numWindows = _windows.size();
|
|
WindowSortingBucket singleBucket;
|
|
Common::Array<WindowSortingBucket> multipleBuckets;
|
|
WindowSortingBucket *sortedBuckets = &singleBucket;
|
|
|
|
if (numWindows < 2)
|
|
sortedBuckets = &singleBucket;
|
|
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();
|
|
|
|
Graphics::Cursor *cursor = nullptr;
|
|
|
|
// ...
|
|
if (cursor == nullptr)
|
|
cursor = _defaultCursor.get();
|
|
|
|
if (cursor != _lastFrameCursor) {
|
|
_lastFrameCursor = cursor;
|
|
|
|
CursorMan.showMouse(true);
|
|
CursorMan.replaceCursor(cursor);
|
|
}
|
|
|
|
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) {
|
|
Common::SharedPtr<Structural> structural = teardown.structural.lock();
|
|
if (!structural)
|
|
return; // Already gone
|
|
|
|
recursiveDeactivateStructural(structural.get());
|
|
|
|
if (teardown.onlyRemoveChildren) {
|
|
structural->removeAllChildren();
|
|
structural->removeAllModifiers();
|
|
structural->removeAllAssets();
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
void Runtime::executeLowLevelSceneStateTransition(const LowLevelSceneStateTransitionAction &action) {
|
|
switch (action.getActionType())
|
|
{
|
|
case LowLevelSceneStateTransitionAction::kSendMessage:
|
|
sendMessageOnVThread(action.getMessage());
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kLoad:
|
|
loadScene(action.getScene());
|
|
break;
|
|
case LowLevelSceneStateTransitionAction::kUnload: {
|
|
Teardown teardown;
|
|
teardown.onlyRemoveChildren = true;
|
|
teardown.structural = action.getScene();
|
|
|
|
_pendingTeardowns.push_back(teardown);
|
|
} break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Runtime::executeCompleteTransitionToScene(const Common::SharedPtr<Structural> &targetScene) {
|
|
if (targetScene == _activeMainScene)
|
|
return;
|
|
|
|
if (_sceneStack.size() == 0)
|
|
_sceneStack.resize(1); // Reserve shared scene slot
|
|
|
|
Common::SharedPtr<Structural> 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");
|
|
|
|
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;
|
|
} else {
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneEnded, 0), _activeMainScene.get(), false, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(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::create(EventIDs::kSceneEnded, 0), _activeSharedScene.get(), false, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentDisabled, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeSharedScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentEnabled, 0), targetSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneStarted, 0), targetSharedScene.get(), false, true);
|
|
|
|
SceneStackEntry sharedSceneEntry;
|
|
sharedSceneEntry.scene = targetSharedScene;
|
|
|
|
_sceneStack[0] = sharedSceneEntry;
|
|
}
|
|
|
|
if (!sceneAlreadyInStack) {
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentEnabled, 0), targetScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneStarted, 0), targetScene.get(), false, true);
|
|
|
|
SceneStackEntry sceneEntry;
|
|
sceneEntry.scene = targetScene;
|
|
|
|
_sceneStack.push_back(sceneEntry);
|
|
}
|
|
|
|
_activeMainScene = targetScene;
|
|
_activeSharedScene = targetSharedScene;
|
|
|
|
// Scene transitions have to be set up by the destination scene
|
|
_sceneTransitionState = kSceneTransitionStateWaitingForDraw;
|
|
_sceneTransitionEffect.transitionType = kTransitionTypeNone;
|
|
_sceneTransitionEffect.duration = 0;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
}
|
|
|
|
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::kTypeReturn: {
|
|
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::create(EventIDs::kSceneEnded, 0), _activeMainScene.get(), false, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentDisabled, 0), _activeMainScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeMainScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneReactivated, 0), sceneReturn.scene.get(), false, true);
|
|
|
|
_activeMainScene = sceneReturn.scene;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
} else {
|
|
executeCompleteTransitionToScene(sceneReturn.scene);
|
|
}
|
|
}
|
|
} break;
|
|
case HighLevelSceneTransition::kTypeChangeToScene: {
|
|
const Common::SharedPtr<Structural> targetScene = transition.scene;
|
|
|
|
if (transition.addToDestinationScene || !transition.addToReturnList) {
|
|
SceneReturnListEntry returnListEntry;
|
|
returnListEntry.isAddToDestinationSceneTransition = transition.addToDestinationScene;
|
|
returnListEntry.scene = _activeMainScene;
|
|
_sceneReturnList.push_back(returnListEntry);
|
|
}
|
|
|
|
if (transition.addToDestinationScene) {
|
|
if (targetScene != _activeMainScene) {
|
|
Common::SharedPtr<Structural> 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::create(EventIDs::kSceneDeactivated, 0), _activeMainScene.get(), false, true);
|
|
|
|
if (targetSharedScene != _activeSharedScene) {
|
|
if (_activeSharedScene) {
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneEnded, 0), _activeSharedScene.get(), false, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentDisabled, 0), _activeSharedScene.get(), true, true);
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(_activeSharedScene, LowLevelSceneStateTransitionAction::kUnload));
|
|
}
|
|
|
|
_pendingLowLevelTransitions.push_back(LowLevelSceneStateTransitionAction(targetSharedScene, LowLevelSceneStateTransitionAction::kLoad));
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kParentEnabled, 0), targetSharedScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneStarted, 0), targetSharedScene.get(), false, true);
|
|
|
|
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::create(EventIDs::kParentEnabled, 0), targetScene.get(), true, true);
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSceneStarted, 0), targetScene.get(), false, true);
|
|
|
|
SceneStackEntry sceneEntry;
|
|
sceneEntry.scene = targetScene;
|
|
|
|
_sceneStack.push_back(sceneEntry);
|
|
}
|
|
|
|
_activeMainScene = targetScene;
|
|
_activeSharedScene = targetSharedScene;
|
|
|
|
executeSharedScenePostSceneChangeActions();
|
|
}
|
|
} else {
|
|
executeCompleteTransitionToScene(targetScene);
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Runtime::executeSharedScenePostSceneChangeActions() {
|
|
Structural *subsection = _activeMainScene->getParent();
|
|
|
|
const Common::Array<Common::SharedPtr<Structural> > &subsectionScenes = subsection->getChildren();
|
|
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSharedSceneSceneChanged, 0), _activeSharedScene.get(), false, true);
|
|
if (subsectionScenes.size() > 1) {
|
|
if (_activeMainScene == subsectionScenes[subsectionScenes.size() - 1])
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSharedSceneNoNextScene, 0), _activeSharedScene.get(), false, true);
|
|
if (_activeMainScene == subsectionScenes[1])
|
|
queueEventAsLowLevelSceneStateTransitionAction(Event::create(EventIDs::kSharedSceneNoPrevScene, 0), _activeSharedScene.get(), false, true);
|
|
}
|
|
}
|
|
|
|
void Runtime::recursiveDeactivateStructural(Structural *structural) {
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren()) {
|
|
recursiveDeactivateStructural(child.get());
|
|
}
|
|
|
|
structural->deactivate();
|
|
}
|
|
|
|
void Runtime::recursiveActivateStructural(Structural *structural) {
|
|
structural->activate();
|
|
|
|
for (const Common::SharedPtr<Structural> &child : structural->getChildren()) {
|
|
recursiveActivateStructural(child.get());
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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());
|
|
|
|
_project->loadSceneFromStream(scene, streamID);
|
|
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
|
|
|
|
refreshPlayTime();
|
|
}
|
|
|
|
void Runtime::sendMessageOnVThread(const Common::SharedPtr<MessageDispatch> &dispatch) {
|
|
DispatchMethodTaskData *taskData = _vthread->pushTask("Runtime::dispatchMessageTask", this, &Runtime::dispatchMessageTask);
|
|
taskData->dispatch = dispatch;
|
|
}
|
|
|
|
VThreadState Runtime::dispatchMessageTask(const DispatchMethodTaskData &data) {
|
|
Common::SharedPtr<MessageDispatch> dispatchPtr = data.dispatch;
|
|
MessageDispatch &dispatch = *dispatchPtr.get();
|
|
|
|
if (dispatch.isTerminated())
|
|
return kVThreadReturn;
|
|
else {
|
|
// Requeue propagation after whatever happens with this propagation step
|
|
DispatchMethodTaskData *requeueData = _vthread->pushTask("Runtime::dispatchMessageTask", this, &Runtime::dispatchMessageTask);
|
|
requeueData->dispatch = dispatchPtr;
|
|
|
|
return dispatch.continuePropagating(this);
|
|
}
|
|
}
|
|
|
|
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->consumeCommand(this, data.message);
|
|
}
|
|
|
|
void Runtime::queueMessage(const Common::SharedPtr<MessageDispatch>& dispatch) {
|
|
_messageQueue.push_back(dispatch);
|
|
}
|
|
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");
|
|
}
|
|
|
|
if (!modifier->isVariable()) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
ProjectPlatform Runtime::getPlatform() const {
|
|
return _platform;
|
|
}
|
|
|
|
void Runtime::onMouseDown(int32 x, int32 y, Actions::MouseButton mButton) {
|
|
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) {
|
|
Common::SharedPtr<Window> focusWindow = _mouseFocusWindow.lock();
|
|
if (!focusWindow)
|
|
focusWindow = findTopWindow(x, y);
|
|
|
|
if (focusWindow)
|
|
focusWindow->onMouseMove(x - focusWindow->getX(), y - focusWindow->getY());
|
|
|
|
// TODO: Change mouse to focus window's cursor
|
|
}
|
|
|
|
void Runtime::onMouseUp(int32 x, int32 y, Actions::MouseButton mButton) {
|
|
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) {
|
|
if (_project)
|
|
_project->onKeyboardEvent(this, evtType, repeat, keyEvt);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
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;
|
|
Common::SharedPtr<Window> mainWindow(new Window(WindowParameters(this, centeredX, centeredY, presentationSettings.width, presentationSettings.height, _displayModePixelFormats[_realDisplayMode])));
|
|
addWindow(mainWindow);
|
|
_mainWindow.reset(mainWindow);
|
|
}
|
|
}
|
|
|
|
void Runtime::unloadProject() {
|
|
_activeMainScene.reset();
|
|
_activeSharedScene.reset();
|
|
_sceneStack.clear();
|
|
_sceneReturnList.clear();
|
|
_pendingLowLevelTransitions.clear();
|
|
_pendingSceneTransitions.clear();
|
|
_pendingTeardowns.clear();
|
|
_messageQueue.clear();
|
|
_vthread.reset(new VThread());
|
|
|
|
if (!_mainWindow.expired()) {
|
|
removeWindow(_mainWindow.lock().get());
|
|
}
|
|
|
|
// These should be last
|
|
_project.reset();
|
|
_rootLinkingScope.reset();
|
|
}
|
|
|
|
void Runtime::refreshPlayTime() {
|
|
_playTime = _system->getMillis() - _playTimeBase;
|
|
}
|
|
|
|
|
|
void Runtime::queueProject(const Common::SharedPtr<ProjectDescription> &desc) {
|
|
_queuedProjectDesc = desc;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Runtime::addSceneStateTransition(const HighLevelSceneTransition &transition) {
|
|
_pendingSceneTransitions.push_back(transition);
|
|
}
|
|
|
|
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) {
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
PostRenderSignaller::PostRenderSignaller() {
|
|
}
|
|
|
|
PostRenderSignaller::~PostRenderSignaller() {
|
|
}
|
|
|
|
void PostRenderSignaller::onPostRender(Runtime *runtime, Project *project) {
|
|
const size_t numReceivers = _receivers.size();
|
|
for (size_t i = 0; i < numReceivers; i++) {
|
|
_receivers[i]->onPostRender(runtime, project);
|
|
}
|
|
}
|
|
|
|
void PostRenderSignaller::addReceiver(IPostRenderSignalReceiver *receiver) {
|
|
_receivers.push_back(receiver);
|
|
}
|
|
|
|
void PostRenderSignaller::removeReceiver(IPostRenderSignalReceiver* 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, Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) {
|
|
const size_t numReceivers = _receivers.size();
|
|
for (size_t i = 0; i < numReceivers; i++) {
|
|
_receivers[i]->onKeyboardEvent(runtime, evtType, repeat, 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Project::Segment::Segment() : weakStream(nullptr) {
|
|
}
|
|
|
|
Project::Project(Runtime *runtime)
|
|
: _runtime(runtime), _projectFormat(Data::kProjectFormatUnknown), _isBigEndian(false),
|
|
_haveGlobalObjectInfo(false), _haveProjectStructuralDef(false), _postRenderSignaller(new PostRenderSignaller()),
|
|
_keyboardEventSignaller(new KeyboardEventSignaller()) {
|
|
}
|
|
|
|
Project::~Project() {
|
|
for (size_t i = 0; i < _segments.size(); i++)
|
|
closeSegmentStream(i);
|
|
}
|
|
|
|
void Project::loadFromDescription(const ProjectDescription& desc) {
|
|
_resources = desc.getResources();
|
|
|
|
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();
|
|
_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_t startValue = baseStream->readUint16LE();
|
|
|
|
if (startValue == 1) {
|
|
// Windows format
|
|
_isBigEndian = false;
|
|
_projectFormat = Data::kProjectFormatWindows;
|
|
} else if (startValue == 0) {
|
|
// Mac format
|
|
_isBigEndian = true;
|
|
_projectFormat = Data::kProjectFormatMacintosh;
|
|
} else {
|
|
error("Unrecognized project segment header");
|
|
}
|
|
|
|
Common::SeekableSubReadStreamEndian stream(baseStream, 2, baseStream->size(), _isBigEndian);
|
|
if (stream.readUint32() != 0xaa55a5a5 || stream.readUint32() != 0 || stream.readUint32() != 14) {
|
|
error("Unrecognized project segment header");
|
|
}
|
|
|
|
Data::DataReader reader(2, stream, _projectFormat);
|
|
|
|
Common::SharedPtr<Data::DataObject> dataObject;
|
|
Data::loadDataObject(_plugInRegistry.getDataLoaderRegistry(), reader, dataObject);
|
|
|
|
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectHeader) {
|
|
error("Expected project header but found something else");
|
|
}
|
|
|
|
Data::loadDataObject(plugInDataLoaderRegistry, reader, dataObject);
|
|
if (!dataObject || dataObject->getType() != Data::DataObjectTypes::kProjectCatalog) {
|
|
error("Expected project catalog but found something else");
|
|
}
|
|
|
|
Data::ProjectCatalog *catalog = static_cast<Data::ProjectCatalog *>(dataObject.get());
|
|
|
|
if (catalog->segments.size() != desc.getSegments().size()) {
|
|
error("Project declared a different number of segments than the project description provided");
|
|
}
|
|
|
|
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"))
|
|
streamDesc.streamType = kStreamTypeAsset;
|
|
else if (!strcmp(srcStream.streamType, "bootStream"))
|
|
streamDesc.streamType = kStreamTypeBoot;
|
|
else if (!strcmp(srcStream.streamType, "sceneStream"))
|
|
streamDesc.streamType = kStreamTypeScene;
|
|
else
|
|
streamDesc.streamType = kStreamTypeUnknown;
|
|
|
|
streamDesc.segmentIndex = srcStream.segmentIndexPlusOne - 1;
|
|
streamDesc.size = srcStream.size;
|
|
streamDesc.pos = srcStream.pos;
|
|
}
|
|
|
|
// 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);
|
|
|
|
debug(1, "Boot stream loaded successfully");
|
|
}
|
|
|
|
void Project::loadSceneFromStream(const Common::SharedPtr<Structural>& scene, uint32 streamID) {
|
|
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::SeekableSubReadStreamEndian stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
|
|
Data::DataReader reader(streamDesc.pos, stream, _projectFormat);
|
|
|
|
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);
|
|
}
|
|
|
|
size_t 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++;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Common::SharedPtr<Modifier> Project::resolveAlias(uint32 aliasID) const {
|
|
if (aliasID == 0 || aliasID > _globalModifiers.getModifiers().size())
|
|
return Common::SharedPtr<Modifier>();
|
|
|
|
return _globalModifiers.getModifiers()[aliasID - 1];
|
|
}
|
|
|
|
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::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;
|
|
}
|
|
|
|
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::File *f = new Common::File();
|
|
segment.rcStream.reset(f);
|
|
segment.weakStream = f;
|
|
|
|
if (!f->open(segment.desc.filePath)) {
|
|
error("Failed to open segment file %s", segment.desc.filePath.c_str());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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() {
|
|
_postRenderSignaller->onPostRender(_runtime, this);
|
|
}
|
|
|
|
Common::SharedPtr<PostRenderSignaller> Project::notifyOnPostRender(IPostRenderSignalReceiver *receiver) {
|
|
_postRenderSignaller->addReceiver(receiver);
|
|
return _postRenderSignaller;
|
|
}
|
|
|
|
void Project::onKeyboardEvent(Runtime *runtime, const Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) {
|
|
_keyboardEventSignaller->onKeyboardEvent(runtime, evtType, repeat, keyEvt);
|
|
}
|
|
|
|
Common::SharedPtr<KeyboardEventSignaller> Project::notifyOnKeyboardEvent(IKeyboardEventReceiver *receiver) {
|
|
_keyboardEventSignaller->addReceiver(receiver);
|
|
return _keyboardEventSignaller;
|
|
}
|
|
|
|
void Project::loadBootStream(size_t streamIndex) {
|
|
const StreamDesc &streamDesc = _streams[streamIndex];
|
|
|
|
size_t segmentIndex = streamDesc.segmentIndex;
|
|
openSegmentStream(segmentIndex);
|
|
|
|
Common::SeekableSubReadStreamEndian stream(_segments[segmentIndex].weakStream, streamDesc.pos, streamDesc.pos + streamDesc.size, _isBigEndian);
|
|
Data::DataReader reader(streamDesc.pos, stream, _projectFormat);
|
|
|
|
ChildLoaderStack loaderStack;
|
|
AssetDefLoaderContext assetDefLoader;
|
|
|
|
const Data::PlugInModifierRegistry &plugInDataLoaderRegistry = _plugInRegistry.getDataLoaderRegistry();
|
|
|
|
size_t 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);
|
|
} break;
|
|
case Data::DataObjectTypes::kStreamHeader:
|
|
case Data::DataObjectTypes::kUnknown19:
|
|
// Ignore
|
|
break;
|
|
default:
|
|
error("Unexpected object type in boot stream");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void Project::loadPresentationSettings(const Data::PresentationSettings &presentationSettings) {
|
|
_presentationSettings.bitsPerPixel = presentationSettings.bitsPerPixel;
|
|
if (_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;
|
|
assetDesc.typeCode = assetInfo.assetType;
|
|
|
|
_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 {
|
|
IModifierFactory *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);
|
|
}
|
|
assert(modifier->getModifierFlags().flagsWereLoaded);
|
|
if (!modifier)
|
|
error("Modifier object failed to load");
|
|
|
|
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;
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
IElementFactory *elementFactory = getElementFactoryForDataObjectType(dataObjectType);
|
|
if (!elementFactory) {
|
|
error("No element factory defined for structural object");
|
|
}
|
|
|
|
ElementLoaderContext elementLoaderContext(_runtime, streamIndex);
|
|
Common::SharedPtr<Element> element = elementFactory->createElement(elementLoaderContext, dataObject);
|
|
|
|
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 = container;
|
|
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()));
|
|
|
|
IAssetFactory *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;
|
|
}
|
|
|
|
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) {
|
|
if (attrib == "subsection") {
|
|
// Unclear why this is necessary
|
|
result.setObject(getSelfReference());
|
|
return true;
|
|
}
|
|
|
|
return Structural::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
bool Subsection::isSubsection() const {
|
|
return true;
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getSceneLoadMaterializeScope() {
|
|
return getPersistentStructuralScope();
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getPersistentStructuralScope() {
|
|
return &_structuralScope;
|
|
}
|
|
|
|
ObjectLinkingScope *Subsection::getPersistentModifierScope() {
|
|
return &_modifierScope;
|
|
}
|
|
|
|
bool Element::isElement() const {
|
|
return true;
|
|
}
|
|
|
|
uint32 Element::getStreamLocator() const {
|
|
return _streamLocator;
|
|
}
|
|
|
|
VisualElement::VisualElement() : _rect(Rect16::create(0, 0, 0, 0)), _cachedAbsoluteOrigin(Point16::create(0, 0)) {
|
|
}
|
|
|
|
bool VisualElement::isVisual() const {
|
|
return true;
|
|
}
|
|
|
|
bool VisualElement::isVisible() const {
|
|
return _visible;
|
|
}
|
|
|
|
bool VisualElement::isDirectToScreen() const {
|
|
return _directToScreen;
|
|
}
|
|
|
|
uint16 VisualElement::getLayer() const {
|
|
return _layer;
|
|
}
|
|
|
|
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(Point16::create(_rect.left, _rect.top));
|
|
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;
|
|
}
|
|
|
|
return Element::readAttribute(thread, result, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
|
|
if (attrib == "visible") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetVisibility>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "direct") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetDirect>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
} else if (attrib == "position") {
|
|
DynamicValueWriteFuncHelper<VisualElement, &VisualElement::scriptSetPosition>::create(this, writeProxy);
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
return Element::writeRefAttribute(thread, writeProxy, attrib);
|
|
}
|
|
|
|
const Rect16 &VisualElement::getRelativeRect() const {
|
|
return _rect;
|
|
}
|
|
|
|
Point16 VisualElement::getGlobalPosition() const {
|
|
Point16 pos = Point16::create(0, 0);
|
|
if (_parent && _parent->isElement()) {
|
|
Element *element = static_cast<Element *>(_parent);
|
|
if (element->isVisual()) {
|
|
pos = static_cast<VisualElement *>(element)->getGlobalPosition();
|
|
}
|
|
}
|
|
|
|
pos.x += _rect.left;
|
|
pos.y += _rect.top;
|
|
|
|
return pos;
|
|
}
|
|
|
|
const Point16 &VisualElement::getCachedAbsoluteOrigin() const {
|
|
return _cachedAbsoluteOrigin;
|
|
}
|
|
|
|
void VisualElement::setCachedAbsoluteOrigin(const Point16 &absOrigin) {
|
|
_cachedAbsoluteOrigin = absOrigin;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetVisibility(MiniscriptThread *thread, const DynamicValue &result) {
|
|
// FIXME: Need to make this fire Show/Hide events!
|
|
if (result.getType() == DynamicValueTypes::kBoolean) {
|
|
_visible = result.getBool();
|
|
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.load(rect))
|
|
return false;
|
|
|
|
_name = name;
|
|
_guid = guid;
|
|
_visible = ((elementFlags & Data::ElementFlags::kHidden) == 0);
|
|
_directToScreen = ((elementFlags & Data::ElementFlags::kNotDirectToScreen) == 0);
|
|
_streamLocator = streamLocator;
|
|
_sectionID = sectionID;
|
|
_layer = layer;
|
|
|
|
return true;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetDirect(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
if (dest.getType() == DynamicValueTypes::kBoolean) {
|
|
_directToScreen = dest.getBool();
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VisualElement::scriptSetPosition(MiniscriptThread *thread, const DynamicValue &dest) {
|
|
if (dest.getType() == DynamicValueTypes::kPoint) {
|
|
const Point16 &destPoint = dest.getPoint();
|
|
int32 xDelta = destPoint.x - _rect.left;
|
|
int32 yDelta = destPoint.y - _rect.right;
|
|
|
|
if (xDelta != 0 || yDelta != 0)
|
|
offsetTranslate(xDelta, yDelta, false);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
VThreadState VisualElement::changeVisibilityTask(const ChangeFlagTaskData &taskData) {
|
|
if (_visible != taskData.desiredFlag) {
|
|
_visible = taskData.desiredFlag;
|
|
|
|
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(Event::create(_visible ? EventIDs::kElementHide : EventIDs::kElementShow, 0), DynamicValue(), getSelfReference()));
|
|
Common::SharedPtr<MessageDispatch> dispatch(new MessageDispatch(msgProps, this, false, true, false));
|
|
taskData.runtime->sendMessageOnVThread(dispatch);
|
|
}
|
|
|
|
return kVThreadReturn;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Modifier::Modifier() : _parent(nullptr) {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
_debugger = nullptr;
|
|
#endif
|
|
}
|
|
|
|
Modifier::~Modifier() {
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
if (_debugInspector)
|
|
_debugInspector->onDestroyed();
|
|
#endif
|
|
}
|
|
|
|
void Modifier::materialize(Runtime *runtime, ObjectLinkingScope *outerScope) {
|
|
ObjectLinkingScope innerScope;
|
|
innerScope.setParent(outerScope);
|
|
|
|
ModifierInnerScopeBuilder innerScopeBuilder(&innerScope);
|
|
this->visitInternalReferences(&innerScopeBuilder);
|
|
|
|
ModifierChildMaterializer childMaterializer(runtime, this, &innerScope);
|
|
this->visitInternalReferences(&childMaterializer);
|
|
|
|
linkInternalReferences(outerScope);
|
|
setRuntimeGUID(runtime->allocateRuntimeGUID());
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
_debugger = runtime->debugGetDebugger();
|
|
_debugInspector.reset(this->debugCreateInspector());
|
|
#endif
|
|
}
|
|
|
|
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::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;
|
|
}
|
|
|
|
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 overrided
|
|
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;
|
|
}
|
|
|
|
#ifdef MTROPOLIS_DEBUG_ENABLE
|
|
|
|
SupportStatus Modifier::debugGetSupportStatus() const {
|
|
return kSupportStatusNone;
|
|
}
|
|
|
|
const Common::String &Modifier::debugGetName() const {
|
|
return _name;
|
|
}
|
|
|
|
Common::SharedPtr<DebugInspector> Modifier::debugGetInspector() const {
|
|
return _debugInspector;
|
|
}
|
|
|
|
DebugInspector *Modifier::debugCreateInspector() {
|
|
return new DebugInspector(this);
|
|
|
|
}
|
|
#endif /* MTROPOLIS_DEBUG_ENABLE */
|
|
|
|
bool VariableModifier::isVariable() const {
|
|
return true;
|
|
}
|
|
|
|
DynamicValueWriteProxy VariableModifier::createWriteProxy() {
|
|
DynamicValueWriteProxy proxy;
|
|
proxy.pod.objectRef = this;
|
|
proxy.pod.ptrOrOffset = 0;
|
|
proxy.pod.ifc = &VariableModifier::WriteProxyInterface::_instance;
|
|
return proxy;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr_t ptrOrOffset) const {
|
|
if (!static_cast<VariableModifier *>(objectRef)->varSetValue(thread, dest))
|
|
return kMiniscriptInstructionOutcomeFailed;
|
|
return kMiniscriptInstructionOutcomeContinue;
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &dest, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib) const {
|
|
return static_cast<VariableModifier *>(objectRef)->writeRefAttribute(thread, dest, attrib);
|
|
}
|
|
|
|
MiniscriptInstructionOutcome VariableModifier::WriteProxyInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &dest, void *objectRef, uintptr_t ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const {
|
|
return static_cast<VariableModifier *>(objectRef)->writeRefAttributeIndexed(thread, dest, attrib, index);
|
|
}
|
|
|
|
VariableModifier::WriteProxyInterface VariableModifier::WriteProxyInterface::_instance;
|
|
|
|
bool Modifier::loadTypicalHeader(const Data::TypicalModifierHeader &typicalHeader) {
|
|
if (!_modifierFlags.load(typicalHeader.modifierFlags))
|
|
return false;
|
|
_guid = typicalHeader.guid;
|
|
_name = typicalHeader.name;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Modifier::linkInternalReferences(ObjectLinkingScope *scope) {
|
|
}
|
|
|
|
} // End of namespace MTropolis
|