/* 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 . * */ #include "common/random.h" #include "common/config-manager.h" #include "common/file.h" #include "common/macresman.h" #include "common/translation.h" #include "gui/dialog.h" #include "gui/imagealbum-dialog.h" #include "graphics/palette.h" #include "image/bmp.h" #include "image/pict.h" #include "mtropolis/miniscript.h" #include "mtropolis/plugin/standard.h" #include "mtropolis/plugins.h" #include "mtropolis/miniscript.h" namespace MTropolis { namespace Standard { CursorModifier::CursorModifier() : _cursorID(0) { } bool CursorModifier::respondsToEvent(const Event &evt) const { return _applyWhen.respondsTo(evt) || _removeWhen.respondsTo(evt); } VThreadState CursorModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { // As with mTropolis, this doesn't support stacking cursors if (_applyWhen.respondsTo(msg->getEvent())) { runtime->setModifierCursorOverride(_cursorID); } if (_removeWhen.respondsTo(msg->getEvent())) { // This doesn't call "disable" because the behavior is actually different. // Disabling a cursor modifier doesn't seem to remove it. runtime->clearModifierCursorOverride(); } return kVThreadReturn; } void CursorModifier::disable(Runtime *runtime) { } bool CursorModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::CursorModifier &data) { if (data.applyWhen.type != Data::PlugInTypeTaggedValue::kEvent || data.cursorIDAsLabel.type != Data::PlugInTypeTaggedValue::kLabel) return false; if (!_applyWhen.load(data.applyWhen.value.asEvent)) return false; if (data.haveRemoveWhen) { if (!_removeWhen.load(data.removeWhen.value.asEvent)) return false; } if (data.cursorIDAsLabel.type != Data::PlugInTypeTaggedValue::kLabel) return false; _cursorID = data.cursorIDAsLabel.value.asLabel.labelID; return true; } Common::SharedPtr CursorModifier::shallowClone() const { Common::SharedPtr clone(new CursorModifier(*this)); return clone; } const char *CursorModifier::getDefaultName() const { return "Cursor Modifier"; } STransCtModifier::STransCtModifier() : _transitionType(0), _transitionDirection(0), _steps(0), _duration(0), _fullScreen(false) { } bool STransCtModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::STransCtModifier &data) { if (data.enableWhen.type != Data::PlugInTypeTaggedValue::kEvent || data.disableWhen.type != Data::PlugInTypeTaggedValue::kEvent || data.transitionType.type != Data::PlugInTypeTaggedValue::kInteger || data.transitionDirection.type != Data::PlugInTypeTaggedValue::kInteger || data.steps.type != Data::PlugInTypeTaggedValue::kInteger || data.duration.type != Data::PlugInTypeTaggedValue::kInteger || data.fullScreen.type != Data::PlugInTypeTaggedValue::kBoolean) return false; if (!_enableWhen.load(data.enableWhen.value.asEvent) || !_disableWhen.load(data.disableWhen.value.asEvent)) return false; _transitionType = data.transitionType.value.asInt; _transitionDirection = data.transitionDirection.value.asInt; _steps = data.steps.value.asInt; _duration = data.duration.value.asInt; _fullScreen = data.fullScreen.value.asBoolean; return true; } bool STransCtModifier::respondsToEvent(const Event &evt) const { return _enableWhen.respondsTo(evt) || _disableWhen.respondsTo(evt); } VThreadState STransCtModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { if (_enableWhen.respondsTo(msg->getEvent())) { SceneTransitionEffect effect; effect._duration = _duration / 10; effect._steps = _steps; if (SceneTransitionTypes::loadFromData(effect._transitionType, _transitionType) && SceneTransitionDirections::loadFromData(effect._transitionDirection, _transitionDirection)) { // Duration doesn't seem to affect wipe transitions for some reason. // In Obsidian, this mostly effects 180-degree turns. // Good place to test this is in the corners of the Bureau library, where it's 0, // but some cases where it is set (e.g. the Spider control room) have the same duration anyway. if (effect._transitionType == SceneTransitionTypes::kWipe) effect._duration = 500; runtime->setSceneTransitionEffect(false, &effect); } else { warning("Source-scene transition had invalid data"); } } if (_disableWhen.respondsTo(msg->getEvent())) disable(runtime); return kVThreadReturn; } void STransCtModifier::disable(Runtime *runtime) { runtime->setSceneTransitionEffect(false, nullptr); } bool STransCtModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) { if (attrib == "rate") { if (_duration <= (kMaxDuration / 100)) result.setInt(100); else if (_duration >= kMaxDuration) result.setInt(1); else result.setInt((kMaxDuration + (_duration / 2)) / _duration); return true; } else if (attrib == "steps") { result.setInt(_steps); return true; } return Modifier::readAttribute(thread, result, attrib); } MiniscriptInstructionOutcome STransCtModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) { if (attrib == "rate") { DynamicValueWriteFuncHelper::create(this, result); return kMiniscriptInstructionOutcomeContinue; } else if (attrib == "steps") { DynamicValueWriteFuncHelper::create(this, result); return kMiniscriptInstructionOutcomeContinue; } return Modifier::writeRefAttribute(thread, result, attrib); } Common::SharedPtr STransCtModifier::shallowClone() const { return Common::SharedPtr(new STransCtModifier(*this)); } const char *STransCtModifier::getDefaultName() const { return "STransCt"; // Probably wrong } MiniscriptInstructionOutcome STransCtModifier::scriptSetRate(MiniscriptThread *thread, const DynamicValue &value) { int32 asInteger = 0; if (!value.roundToInt(asInteger)) return kMiniscriptInstructionOutcomeFailed; if (asInteger < 1) asInteger = 1; else if (asInteger > 100) asInteger = 100; if (asInteger == 100) _duration = 0; else _duration = kMaxDuration / asInteger; return kMiniscriptInstructionOutcomeContinue; } MiniscriptInstructionOutcome STransCtModifier::scriptSetSteps(MiniscriptThread *thread, const DynamicValue &value) { int32 asInteger = 0; if (!value.roundToInt(asInteger)) return kMiniscriptInstructionOutcomeFailed; if (asInteger < 4) asInteger = 4; else if (asInteger > 256) asInteger = 100; _steps = asInteger; return kMiniscriptInstructionOutcomeContinue; } MediaCueMessengerModifier::CueSourceUnion::CueSourceUnion() : asUnset(0) { } MediaCueMessengerModifier::CueSourceUnion::~CueSourceUnion() { } template void MediaCueMessengerModifier::CueSourceUnion::construct(const T &value) { T *field = &(this->*TMember); new (field) T(value); } template void MediaCueMessengerModifier::CueSourceUnion::destruct() { T *field = &(this->*TMember); field->~T(); } MediaCueMessengerModifier::MediaCueMessengerModifier() : _isActive(false), _cueSourceType(kCueSourceInvalid) { _mediaCue.sourceModifier = this; } MediaCueMessengerModifier::MediaCueMessengerModifier(const MediaCueMessengerModifier &other) : Modifier(other), _cueSourceType(other._cueSourceType), _cueSourceModifier(other._cueSourceModifier), _enableWhen(other._enableWhen), _disableWhen(other._disableWhen), _mediaCue(other._mediaCue), _isActive(other._isActive) { _cueSource.destruct(); switch (_cueSourceType) { case kCueSourceInteger: _cueSource.construct(other._cueSource.asInt); break; case kCueSourceIntegerRange: _cueSource.construct(other._cueSource.asIntRange); break; case kCueSourceVariableReference: _cueSource.construct(other._cueSource.asVarRefGUID); break; case kCueSourceLabel: _cueSource.construct(other._cueSource.asLabel); break; case kCueSourceString: _cueSource.construct(other._cueSource.asString); break; default: _cueSource.construct(0); break; } } MediaCueMessengerModifier::~MediaCueMessengerModifier() { destructCueSource(); } void MediaCueMessengerModifier::destructCueSource() { switch (_cueSourceType) { case kCueSourceInteger: _cueSource.destruct(); break; case kCueSourceIntegerRange: _cueSource.destruct(); break; case kCueSourceVariableReference: _cueSource.destruct(); break; case kCueSourceLabel: _cueSource.destruct(); break; case kCueSourceString: _cueSource.destruct(); break; default: _cueSource.destruct(); break; } } bool MediaCueMessengerModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::MediaCueMessengerModifier &data) { if (data.enableWhen.type != Data::PlugInTypeTaggedValue::kEvent) return false; if (!_enableWhen.load(data.enableWhen.value.asEvent)) return false; if (data.disableWhen.type != Data::PlugInTypeTaggedValue::kEvent) return false; if (!_disableWhen.load(data.disableWhen.value.asEvent)) return false; if (data.triggerTiming.type != Data::PlugInTypeTaggedValue::kInteger) return false; _mediaCue.triggerTiming = static_cast(data.triggerTiming.value.asInt); if (data.nonStandardMessageFlags.type != Data::PlugInTypeTaggedValue::kInteger) return false; int32 msgFlags = data.nonStandardMessageFlags.value.asInt; MessageFlags messageFlags; messageFlags.immediate = ((msgFlags & Data::Standard::MediaCueMessengerModifier::kMessageFlagImmediate) != 0); messageFlags.cascade = ((msgFlags & Data::Standard::MediaCueMessengerModifier::kMessageFlagCascade) != 0); messageFlags.relay = ((msgFlags & Data::Standard::MediaCueMessengerModifier::kMessageFlagRelay) != 0); if (!_mediaCue.send.load(data.sendEvent, messageFlags, data.with, data.destination)) return false; destructCueSource(); switch (data.executeAt.type) { case Data::PlugInTypeTaggedValue::kInteger: _cueSource.construct(data.executeAt.value.asInt); _cueSourceType = kCueSourceInteger; break; case Data::PlugInTypeTaggedValue::kIntegerRange: { IntRange intRange; if (!intRange.load(data.executeAt.value.asIntRange)) return false; _cueSource.construct(intRange); _cueSourceType = kCueSourceIntegerRange; } break; case Data::PlugInTypeTaggedValue::kVariableReference: _cueSource.construct(data.executeAt.value.asVarRefGUID); _cueSourceType = kCueSourceVariableReference; break; case Data::PlugInTypeTaggedValue::kLabel: { Label label; if (!label.load(data.executeAt.value.asLabel)) return false; _cueSource.construct(label); _cueSourceType = kCueSourceLabel; } break; case Data::PlugInTypeTaggedValue::kString: _cueSource.construct(data.executeAt.value.asString); _cueSourceType = kCueSourceString; break; default: _cueSource.construct(0); _cueSourceType = kCueSourceInvalid; return false; } return true; } bool MediaCueMessengerModifier::respondsToEvent(const Event &evt) const { return _enableWhen.respondsTo(evt) || _disableWhen.respondsTo(evt); } VThreadState MediaCueMessengerModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { if (_enableWhen.respondsTo(msg->getEvent())) { Structural *owner = findStructuralOwner(); if (owner && owner->isElement()) { Element *element = static_cast(owner); switch (_cueSourceType) { case kCueSourceInteger: _mediaCue.minTime = _mediaCue.maxTime = _cueSource.asInt; break; case kCueSourceIntegerRange: _mediaCue.minTime = _cueSource.asIntRange.min; _mediaCue.maxTime = _cueSource.asIntRange.max; break; case kCueSourceLabel: { int32 resolved = 0; if (element->resolveMediaMarkerLabel(_cueSource.asLabel, resolved)) _mediaCue.minTime = _mediaCue.maxTime = resolved; else { warning("Failed to resolve media cue marker label"); return kVThreadError; } } break; case kCueSourceVariableReference: { Modifier *modifier = _cueSourceModifier.lock().get(); if (!modifier->isVariable()) { warning("Media cue source variable couldn't be resolved"); return kVThreadReturn; } DynamicValue value; static_cast(modifier)->varGetValue(value); switch (value.getType()) { case DynamicValueTypes::kInteger: _mediaCue.minTime = _mediaCue.maxTime = value.getInt(); break; case DynamicValueTypes::kIntegerRange: _mediaCue.minTime = value.getIntRange().min; _mediaCue.maxTime = value.getIntRange().max; break; case DynamicValueTypes::kFloat: _mediaCue.minTime = _mediaCue.maxTime = static_cast(round(value.getFloat())); break; default: warning("Media cue variable was not a usable type"); return kVThreadError; } } break; default: assert(false); // Something wasn't handled in the loader return kVThreadReturn; } element->addMediaCue(&_mediaCue); _isActive = true; } } if (_disableWhen.respondsTo(msg->getEvent())) { disable(runtime); } return kVThreadReturn; } void MediaCueMessengerModifier::disable(Runtime *runtime) { if (_isActive) { Structural *owner = findStructuralOwner(); if (owner && owner->isElement()) static_cast(owner)->removeMediaCue(&_mediaCue); _isActive = false; } } Modifier *MediaCueMessengerModifier::getMediaCueModifier() { return this; } Common::WeakPtr MediaCueMessengerModifier::getMediaCueTriggerSource() const { return _cueSourceModifier; } Common::SharedPtr MediaCueMessengerModifier::shallowClone() const { Common::SharedPtr clone(new MediaCueMessengerModifier(*this)); clone->_isActive = false; clone->_mediaCue.sourceModifier = clone.get(); clone->_mediaCue.incomingData = DynamicValue(); return clone; } const char *MediaCueMessengerModifier::getDefaultName() const { return "Media Cue Messenger"; } void MediaCueMessengerModifier::linkInternalReferences(ObjectLinkingScope *scope) { if (_cueSourceType == kCueSourceVariableReference) { Common::WeakPtr obj = scope->resolve(_cueSource.asVarRefGUID); RuntimeObject *objPtr = obj.lock().get(); if (objPtr && objPtr->isModifier()) _cueSourceModifier = obj.staticCast(); } _mediaCue.send.linkInternalReferences(scope); } void MediaCueMessengerModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) { visitor->visitWeakModifierRef(_cueSourceModifier); _mediaCue.send.visitInternalReferences(visitor); } ObjectReferenceVariableModifier::ObjectReferenceVariableModifier() : VariableModifier(Common::SharedPtr(new ObjectReferenceVariableStorage())) { } bool ObjectReferenceVariableModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::ObjectReferenceVariableModifier &data) { if (data.setToSourceParentWhen.type != Data::PlugInTypeTaggedValue::kEvent) return false; if (!_setToSourceParentWhen.load(data.setToSourceParentWhen.value.asEvent)) return false; ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); if (data.objectPath.type == Data::PlugInTypeTaggedValue::kString) storage->_objectPath = data.objectPath.value.asString; else if (data.objectPath.type != Data::PlugInTypeTaggedValue::kNull) return false; storage->_object.reset(); return true; } // Object reference variables are somewhat unusual in that they don't store a simple value, // they instead have "object" and "path" attributes AND as a value, they resolve to the // modifier itself. bool ObjectReferenceVariableModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); if (attrib == "path") { result.setString(storage->_objectPath); return true; } if (attrib == "object") { if (storage->_object.object.expired()) resolve(thread->getRuntime()); if (storage->_object.object.expired()) result.clear(); else result.setObject(storage->_object); return true; } return VariableModifier::readAttribute(thread, result, attrib); } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) { if (attrib == "path") { DynamicValueWriteFuncHelper::create(this, result); return kMiniscriptInstructionOutcomeContinue; } if (attrib == "object") { result.pod.ptrOrOffset = 0; result.pod.objectRef = this; result.pod.ifc = DynamicValueWriteInterfaceGlue::getInstance(); return kMiniscriptInstructionOutcomeContinue; } return VariableModifier::writeRefAttribute(thread, result, attrib); } bool ObjectReferenceVariableModifier::varSetValue(MiniscriptThread *thread, const DynamicValue &value) { switch (value.getType()) { case DynamicValueTypes::kNull: case DynamicValueTypes::kObject: return scriptSetObject(thread, value) == kMiniscriptInstructionOutcomeContinue; case DynamicValueTypes::kString: return scriptSetPath(thread, value) == kMiniscriptInstructionOutcomeContinue; default: return false; } } void ObjectReferenceVariableModifier::varGetValue(DynamicValue &dest) const { dest.setObject(getSelfReference()); } #ifdef MTROPOLIS_DEBUG_ENABLE void ObjectReferenceVariableModifier::debugInspect(IDebugInspectionReport *report) const { VariableModifier::debugInspect(report); ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); report->declareDynamic("path", storage->_objectPath); report->declareDynamic("fullPath", storage->_fullPath); } #endif Common::SharedPtr ObjectReferenceVariableModifier::shallowClone() const { return Common::SharedPtr(new ObjectReferenceVariableModifier(*this)); } const char *ObjectReferenceVariableModifier::getDefaultName() const { return "Object Reference Variable"; } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::scriptSetPath(MiniscriptThread *thread, const DynamicValue &value) { if (value.getType() != DynamicValueTypes::kString) return kMiniscriptInstructionOutcomeFailed; ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); storage->_objectPath = value.getString(); storage->_object.reset(); return kMiniscriptInstructionOutcomeContinue; } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::scriptSetObject(MiniscriptThread *thread, const DynamicValue &value) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); if (value.getType() == DynamicValueTypes::kNull) { storage->_object.reset(); storage->_objectPath.clear(); storage->_fullPath.clear(); return kMiniscriptInstructionOutcomeContinue; } else if (value.getType() == DynamicValueTypes::kObject) { Common::SharedPtr obj = value.getObject().object.lock(); if (!obj) return scriptSetObject(thread, DynamicValue()); if (!computeObjectPath(obj.get(), storage->_fullPath)) return scriptSetObject(thread, DynamicValue()); storage->_objectPath = storage->_fullPath; storage->_object.object = obj; return kMiniscriptInstructionOutcomeContinue; } else return kMiniscriptInstructionOutcomeFailed; } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::scriptObjectRefAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); resolve(thread->getRuntime()); if (storage->_object.object.expired()) { thread->error("Attempted to reference an attribute of an object variable object, but the reference is dead"); return kMiniscriptInstructionOutcomeFailed; } return storage->_object.object.lock()->writeRefAttribute(thread, proxy, attrib); } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::scriptObjectRefAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, const Common::String &attrib, const DynamicValue &index) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); resolve(thread->getRuntime()); if (storage->_object.object.expired()) { thread->error("Attempted to reference an attribute of an object variable object, but the reference is dead"); return kMiniscriptInstructionOutcomeFailed; } return storage->_object.object.lock()->writeRefAttributeIndexed(thread, proxy, attrib, index); } void ObjectReferenceVariableModifier::resolve(Runtime *runtime) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); if (!storage->_object.object.expired()) return; storage->_fullPath.clear(); storage->_object.reset(); if (storage->_objectPath.size() == 0) return; if (storage->_objectPath[0] == '/') resolveAbsolutePath(runtime); else if (storage->_objectPath[0] == '.') resolveRelativePath(runtime, this, storage->_objectPath, 0); else warning("Object reference variable had an unknown path format"); if (!storage->_object.object.expired()) { if (!computeObjectPath(storage->_object.object.lock().get(), storage->_fullPath)) { storage->_object.reset(); } } } void ObjectReferenceVariableModifier::resolveRelativePath(Runtime *runtime, RuntimeObject *obj, const Common::String &path, size_t startPos) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); bool haveNextLevel = true; size_t nextLevelPos = startPos; while (haveNextLevel) { startPos = nextLevelPos; size_t endPos = path.find('/', startPos); if (endPos == Common::String::npos) { haveNextLevel = false; endPos = path.size(); } else { nextLevelPos = endPos + 1; } Common::String levelName = path.substr(startPos, endPos - startPos); // This is technically more forgiving than mTropolis, which only allows ".." chains at the start of the path // Adjust this if it turns out to be a problem... if (levelName == "..") { obj = getObjectParent(obj); if (obj == nullptr) return; continue; } const Common::Array > *modifierChildren = nullptr; const Common::Array > *structuralChildren = nullptr; if (obj->isStructural()) { Structural *structural = static_cast(obj); if (structural->getSceneLoadState() == Structural::SceneLoadState::kSceneNotLoaded) runtime->hotLoadScene(structural); modifierChildren = &structural->getModifiers(); structuralChildren = &structural->getChildren(); } else if (obj->isModifier()) { Modifier *modifier = static_cast(obj); IModifierContainer *childContainer = modifier->getChildContainer(); if (childContainer) modifierChildren = &childContainer->getModifiers(); } bool foundMatch = false; if (modifierChildren) { for (const Common::SharedPtr &modifier : *modifierChildren) { if (caseInsensitiveEqual(levelName, modifier->getName())) { foundMatch = true; obj = modifier.get(); break; } } } if (structuralChildren && !foundMatch) { for (const Common::SharedPtr &structural : *structuralChildren) { if (caseInsensitiveEqual(levelName, structural->getName())) { foundMatch = true; obj = structural.get(); break; } } } if (!foundMatch) return; } storage->_object.object = obj->getSelfReference(); } void ObjectReferenceVariableModifier::resolveAbsolutePath(Runtime *runtime) { ObjectReferenceVariableStorage *storage = static_cast(_storage.get()); assert(storage->_objectPath[0] == '/'); RuntimeObject *project = this; for (;;) { RuntimeObject *parent = getObjectParent(project); if (!parent) break; project = parent; } if (!project->isProject()) return; // Some sort of detached object size_t prefixEnd = 0; bool foundPrefix = false; if (runtime->getHacks().ignoreMismatchedProjectNameInObjectLookups) { size_t slashOffset = storage->_objectPath.findFirstOf('/', 1); if (slashOffset != Common::String::npos) { prefixEnd = slashOffset; foundPrefix = true; } } else { Common::String projectPrefixes[2] = { "/" + static_cast(project)->getName(), "/"}; for (const Common::String &prefix : projectPrefixes) { if (storage->_objectPath.size() >= prefix.size() && caseInsensitiveEqual(storage->_objectPath.substr(0, prefix.size()), prefix)) { prefixEnd = prefix.size(); foundPrefix = true; break; } } } if (!foundPrefix) return; // If the object path is longer, then there must be a slash separator, otherwise this doesn't match the project if (prefixEnd == storage->_objectPath.size()) { storage->_object = ObjectReference(project->getSelfReference()); return; } if (storage->_objectPath[prefixEnd] != '/') return; return resolveRelativePath(runtime, project, storage->_objectPath, prefixEnd + 1); } bool ObjectReferenceVariableModifier::computeObjectPath(RuntimeObject *obj, Common::String &outPath) { Common::String pathForThis = "/"; if (obj->isStructural()) { Structural *structural = static_cast(obj); pathForThis += structural->getName(); } else if (obj->isModifier()) { Modifier *modifier = static_cast(obj); pathForThis += modifier->getName(); } RuntimeObject *parent = getObjectParent(obj); if (parent) { Common::String pathForParent; if (!computeObjectPath(parent, pathForParent)) return false; outPath = pathForParent + pathForThis; } else outPath = pathForThis; return true; } RuntimeObject *ObjectReferenceVariableModifier::getObjectParent(RuntimeObject *obj) { if (obj->isStructural()) { Structural *structural = static_cast(obj); return structural->getParent(); } else if (obj->isModifier()) { Modifier *modifier = static_cast(obj); return modifier->getParent().lock().get(); } return nullptr; } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::ObjectWriteInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) { return static_cast(objectRef)->scriptSetObject(thread, value); } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::ObjectWriteInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) { return static_cast(objectRef)->scriptObjectRefAttrib(thread, proxy, attrib); } MiniscriptInstructionOutcome ObjectReferenceVariableModifier::ObjectWriteInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) { return static_cast(objectRef)->scriptObjectRefAttribIndexed(thread, proxy, attrib, index); } ObjectReferenceVariableStorage::SaveLoad::SaveLoad(ObjectReferenceVariableStorage *storage) : _storage(storage) { _objectPath = _storage->_objectPath; } ObjectReferenceVariableStorage::ObjectReferenceVariableStorage() { } Common::SharedPtr ObjectReferenceVariableStorage::getSaveLoad(Runtime *runtime) { return Common::SharedPtr(new SaveLoad(this)); } Common::SharedPtr ObjectReferenceVariableStorage::clone() const { return Common::SharedPtr(new ObjectReferenceVariableStorage(*this)); } void ObjectReferenceVariableStorage::SaveLoad::commitLoad() const { _storage->_object.reset(); _storage->_fullPath.clear(); _storage->_objectPath = _objectPath; } void ObjectReferenceVariableStorage::SaveLoad::saveInternal(Common::WriteStream *stream) const { stream->writeUint32BE(_objectPath.size()); stream->writeString(_objectPath); } bool ObjectReferenceVariableStorage::SaveLoad::loadInternal(Common::ReadStream *stream, uint32 saveFileVersion) { uint32 stringLen = stream->readUint32BE(); if (stream->err()) return false; _objectPath.clear(); if (stringLen) { Common::Array strChars; strChars.resize(stringLen); stream->read(&strChars[0], stringLen); if (stream->err()) return false; _objectPath = Common::String(&strChars[0], stringLen); } return true; } ListVariableModifier::ListVariableModifier() : VariableModifier(Common::SharedPtr(new ListVariableStorage())) { } bool ListVariableModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::ListVariableModifier &data) { ListVariableStorage *storage = static_cast(_storage.get()); bool loadData = true; storage->_preferredContentType = DynamicValueTypes::kInvalid; switch (data.contentsType) { case Data::Standard::ListVariableModifier::kContentsTypeInteger: storage->_preferredContentType = DynamicValueTypes::kInteger; break; case Data::Standard::ListVariableModifier::kContentsTypePoint: storage->_preferredContentType = DynamicValueTypes::kPoint; break; case Data::Standard::ListVariableModifier::kContentsTypeRange: storage->_preferredContentType = DynamicValueTypes::kIntegerRange; break; case Data::Standard::ListVariableModifier::kContentsTypeFloat: storage->_preferredContentType = DynamicValueTypes::kFloat; break; case Data::Standard::ListVariableModifier::kContentsTypeString: storage->_preferredContentType = DynamicValueTypes::kString; break; case Data::Standard::ListVariableModifier::kContentsTypeObject: storage->_preferredContentType = DynamicValueTypes::kObject; if (data.persistentValuesGarbled) { // Ignore and let the game fix it } else { warning("Loading object reference lists from data is not implemented"); } loadData = false; break; case Data::Standard::ListVariableModifier::kContentsTypeVector: storage->_preferredContentType = DynamicValueTypes::kVector; break; case Data::Standard::ListVariableModifier::kContentsTypeBoolean: storage->_preferredContentType = DynamicValueTypes::kBoolean; break; default: warning("Unknown list data type"); return false; } storage->_list->forceType(storage->_preferredContentType); if (loadData) { if (!data.havePersistentData || data.numValues == 0) return true; for (size_t i = 0; i < data.numValues; i++) { DynamicValue dynValue; if (!dynValue.loadConstant(data.values[i])) return false; if (dynValue.getType() != storage->_preferredContentType) { warning("List mod initialization element had the wrong type"); return false; } if (!storage->_list->setAtIndex(i, dynValue)) { warning("Failed to initialize list modifier, value was rejected"); return false; } } } return true; } bool ListVariableModifier::isListVariable() const { return true; } bool ListVariableModifier::varSetValue(MiniscriptThread *thread, const DynamicValue &value) { ListVariableStorage *storage = static_cast(_storage.get()); if (value.getType() == DynamicValueTypes::kList) { // Source value is a list. In this case, it must be convertible, or an error occurs. Common::SharedPtr sourceList = value.getList(); Common::SharedPtr newList(new DynamicList()); for (size_t i = 0; i < sourceList->getSize(); i++) { DynamicValue sourceElement; sourceList->getAtIndex(i, sourceElement); DynamicValue convertedElement; if (!sourceElement.convertToType(storage->_preferredContentType, convertedElement)) { thread->error("Failed to convert list when assigning to a list variable"); return false; } newList->setAtIndex(i, convertedElement); } storage->_list = newList; } else if (value.getType() == DynamicValueTypes::kObject) { // Source value is an object. In this case, it must be another list, otherwise this fails without an error. RuntimeObject *obj = value.getObject().object.lock().get(); if (obj && obj->isModifier() && static_cast(obj)->isVariable() && static_cast(obj)->isListVariable()) { Common::SharedPtr sourceList = static_cast(static_cast(obj)->_storage.get())->_list; Common::SharedPtr newList(new DynamicList()); bool failed = false; for (size_t i = 0; i < sourceList->getSize(); i++) { DynamicValue sourceElement; sourceList->getAtIndex(i, sourceElement); DynamicValue convertedElement; if (!sourceElement.convertToType(storage->_preferredContentType, convertedElement)) { warning("Failed to convert list when assigning to a list variable. (Non-fatal since it was directly assigned.)"); failed = true; break; } newList->setAtIndex(i, convertedElement); } if (!failed) storage->_list = newList; } } else { // Source value is a non-list. In this case, it must be exactly the correct type, except for numbers. DynamicValue convertedValue; if (value.convertToType(storage->_preferredContentType, convertedValue)) { Common::SharedPtr newList(new DynamicList()); newList->setAtIndex(0, convertedValue); storage->_list = newList; } else { thread->error("Can't assign incompatible value type to a list variable"); return false; } } return true; } void ListVariableModifier::varGetValue(DynamicValue &dest) const { dest.setObject(this->getSelfReference()); } bool ListVariableModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) { ListVariableStorage *storage = static_cast(_storage.get()); if (attrib == "count") { result.setInt(storage->_list->getSize()); return true; } else if (attrib == "random") { if (storage->_list->getSize() == 0) return false; size_t index = thread->getRuntime()->getRandom()->getRandomNumber(storage->_list->getSize() - 1); return storage->_list->getAtIndex(index, result); } else if (attrib == "shuffle") { storage->_list = storage->_list->clone(); Common::RandomSource *rng = thread->getRuntime()->getRandom(); size_t listSize = storage->_list->getSize(); for (size_t i = 1; i < listSize; i++) { size_t sourceIndex = i - 1; size_t destIndex = sourceIndex + rng->getRandomNumber(static_cast(listSize - i)); if (sourceIndex != destIndex) { DynamicValue srcValue; DynamicValue destValue; (void)storage->_list->getAtIndex(sourceIndex, srcValue); (void)storage->_list->getAtIndex(destIndex, destValue); (void)storage->_list->setAtIndex(destIndex, srcValue); (void)storage->_list->setAtIndex(sourceIndex, destValue); } } result.setInt(listSize); return true; } return Modifier::readAttribute(thread, result, attrib); } bool ListVariableModifier::readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) { ListVariableStorage *storage = static_cast(_storage.get()); if (attrib == "value") { size_t realIndex = 0; return storage->_list->dynamicValueToIndex(realIndex, index) && storage->_list->getAtIndex(realIndex, result); } else if (attrib == "delete") { size_t realIndex = 0; if (!storage->_list->dynamicValueToIndex(realIndex, index)) return false; if (!storage->_list->getAtIndex(realIndex, result)) return false; storage->_list = storage->_list->clone(); storage->_list->deleteAtIndex(realIndex); return true; } return Modifier::readAttributeIndexed(thread, result, attrib, index); } MiniscriptInstructionOutcome ListVariableModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) { if (attrib == "count") { DynamicValueWriteFuncHelper::create(this, writeProxy); return kMiniscriptInstructionOutcomeContinue; } return VariableModifier::writeRefAttribute(thread, writeProxy, attrib); } MiniscriptInstructionOutcome ListVariableModifier::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib, const DynamicValue &index) { ListVariableStorage *storage = static_cast(_storage.get()); if (attrib == "value") { size_t realIndex = 0; if (!storage->_list->dynamicValueToIndex(realIndex, index)) return kMiniscriptInstructionOutcomeFailed; storage->_list->createWriteProxyForIndex(realIndex, writeProxy); writeProxy.containerList = storage->_list; return kMiniscriptInstructionOutcomeContinue; } return kMiniscriptInstructionOutcomeFailed; } #ifdef MTROPOLIS_DEBUG_ENABLE void ListVariableModifier::debugInspect(IDebugInspectionReport *report) const { VariableModifier::debugInspect(report); ListVariableStorage *storage = static_cast(_storage.get()); size_t listSize = storage->_list->getSize(); for (size_t i = 0; i < listSize; i++) { int cardinal = i + 1; switch (storage->_list->getType()) { case DynamicValueTypes::kInteger: report->declareLoose(Common::String::format("[%i] = %i", cardinal, storage->_list->getInt()[i])); break; case DynamicValueTypes::kFloat: report->declareLoose(Common::String::format("[%i] = %g", cardinal, storage->_list->getFloat()[i])); break; case DynamicValueTypes::kPoint: report->declareLoose(Common::String::format("[%i] = ", cardinal) + pointToString(storage->_list->getPoint()[i])); break; case DynamicValueTypes::kIntegerRange: report->declareLoose(Common::String::format("[%i] = ", cardinal) + storage->_list->getIntRange()[i].toString()); break; case DynamicValueTypes::kBoolean: report->declareLoose(Common::String::format("[%i] = %s", cardinal, storage->_list->getBool()[i] ? "true" : "false")); break; case DynamicValueTypes::kVector: report->declareLoose(Common::String::format("[%i] = ", cardinal) + storage->_list->getVector()[i].toString()); break; case DynamicValueTypes::kLabel: report->declareLoose(Common::String::format("[%i] = Label?", cardinal)); break; case DynamicValueTypes::kEvent: report->declareLoose(Common::String::format("[%i] = Event?", cardinal)); break; case DynamicValueTypes::kString: report->declareLoose(Common::String::format("[%i] = ", cardinal) + storage->_list->getString()[i]); break; case DynamicValueTypes::kList: report->declareLoose(Common::String::format("[%i] = List", cardinal)); break; case DynamicValueTypes::kObject: { RuntimeObject *obj = storage->_list->getObjectReference()[i].object.lock().get(); if (obj) report->declareLoose(Common::String::format("[%i] = Object %x", cardinal, static_cast(obj->getRuntimeGUID()))); else report->declareLoose(Common::String::format("[%i] = Object (Invalid)", cardinal)); } break; default: report->declareLoose(Common::String::format("[%i] = ", cardinal)); break; } } } #endif MiniscriptInstructionOutcome ListVariableModifier::scriptSetCount(MiniscriptThread *thread, const DynamicValue &value) { ListVariableStorage *storage = static_cast(_storage.get()); int32 asInteger = 0; if (!value.roundToInt(asInteger)) { thread->error("Tried to set a list variable count to something other than an integer"); return kMiniscriptInstructionOutcomeFailed; } if (asInteger < 0) { thread->error("Tried to set a list variable count to a negative value"); return kMiniscriptInstructionOutcomeFailed; } size_t newSize = asInteger; if (newSize > storage->_list->getSize()) { storage->_list->expandToMinimumSize(newSize); } else if (newSize < storage->_list->getSize()) { storage->_list->truncateToSize(newSize); } return kMiniscriptInstructionOutcomeContinue; } Common::SharedPtr ListVariableModifier::shallowClone() const { return Common::SharedPtr(new ListVariableModifier(*this)); } const char *ListVariableModifier::getDefaultName() const { return "List Variable"; } ListVariableStorage::ListVariableStorage() : _preferredContentType(DynamicValueTypes::kInteger), _list(new DynamicList()) { } Common::SharedPtr ListVariableStorage::getSaveLoad(Runtime *runtime) { return Common::SharedPtr(new SaveLoad(this)); } Common::SharedPtr ListVariableStorage::clone() const { ListVariableStorage *newInstance = new ListVariableStorage(); newInstance->_list = Common::SharedPtr(new DynamicList(*_list)); newInstance->_preferredContentType = _preferredContentType; return Common::SharedPtr(newInstance); } ListVariableStorage::SaveLoad::SaveLoad(ListVariableStorage *storage) : _storage(storage), _list(storage->_list) { } void ListVariableStorage::SaveLoad::commitLoad() const { // We don't support deserializing object references (yet?), so just leave the existing values. // In Obsidian at least, this doesn't matter. if (_list->getType() != DynamicValueTypes::kObject) _storage->_list = _list; } void ListVariableStorage::SaveLoad::saveInternal(Common::WriteStream *stream) const { recursiveWriteList(_list.get(), stream); } bool ListVariableStorage::SaveLoad::loadInternal(Common::ReadStream *stream, uint32 saveFileVersion) { Common::SharedPtr list = recursiveReadList(stream); if (list) { _list = list; return true; } else { return false; } } void ListVariableStorage::SaveLoad::recursiveWriteList(DynamicList *list, Common::WriteStream *stream) { stream->writeUint32BE(list->getType()); stream->writeUint32BE(list->getSize()); size_t listSize = list->getSize(); for (size_t i = 0; i < listSize; i++) { switch (list->getType()) { case DynamicValueTypes::kInteger: stream->writeSint32BE(list->getInt()[i]); break; case DynamicValueTypes::kPoint: { const Common::Point &pt = list->getPoint()[i]; stream->writeSint16BE(pt.x); stream->writeSint16BE(pt.y); } break; case DynamicValueTypes::kIntegerRange: { const IntRange &range = list->getIntRange()[i]; stream->writeSint32BE(range.min); stream->writeSint32BE(range.max); } break; case DynamicValueTypes::kFloat: stream->writeDoubleBE(list->getFloat()[i]); break; case DynamicValueTypes::kString: { const Common::String &str = list->getString()[i]; stream->writeUint32BE(str.size()); stream->writeString(str); } break; case DynamicValueTypes::kVector: { const AngleMagVector &vec = list->getVector()[i]; stream->writeDoubleBE(vec.angleDegrees); stream->writeDoubleBE(vec.magnitude); } break; case DynamicValueTypes::kBoolean: stream->writeByte(list->getBool()[i] ? 1 : 0); break; case DynamicValueTypes::kObject: break; default: error("Can't figure out how to write a saved variable"); break; } } } Common::SharedPtr ListVariableStorage::SaveLoad::recursiveReadList(Common::ReadStream *stream) { Common::SharedPtr list; list.reset(new DynamicList()); uint32 typeCode = stream->readUint32BE(); uint32 size = stream->readUint32BE(); if (stream->err()) return nullptr; list->forceType(static_cast(typeCode)); for (size_t i = 0; i < size; i++) { DynamicValue val; switch (typeCode) { case DynamicValueTypes::kInteger: { int32 i32 = stream->readSint32BE(); val.setInt(i32); } break; case DynamicValueTypes::kPoint: { Common::Point pt; pt.x = stream->readSint16BE(); pt.y = stream->readSint16BE(); val.setPoint(pt); } break; case DynamicValueTypes::kIntegerRange: { IntRange range; range.min = stream->readSint32BE(); range.max = stream->readSint32BE(); val.setIntRange(range); } break; case DynamicValueTypes::kFloat: { double f; f = stream->readDoubleBE(); val.setFloat(f); } break; case DynamicValueTypes::kString: { uint32 strLen = stream->readUint32BE(); if (stream->err()) return nullptr; Common::String str; if (strLen > 0) { Common::Array chars; chars.resize(strLen); stream->read(&chars[0], strLen); str = Common::String(&chars[0], strLen); } val.setString(str); } break; case DynamicValueTypes::kVector: { AngleMagVector vec; vec.angleDegrees = stream->readDoubleBE(); vec.magnitude = stream->readDoubleBE(); val.setVector(vec); } break; case DynamicValueTypes::kBoolean: { byte b = stream->readByte(); val.setBool(b != 0); } break; case DynamicValueTypes::kObject: { val.setObject(Common::WeakPtr()); } break; default: error("Can't figure out how to write a saved variable"); break; } if (stream->err()) return nullptr; list->setAtIndex(i, val); } return list; } bool SysInfoModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::SysInfoModifier &data) { return true; } bool SysInfoModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) { if (attrib == "bitdepth") { ColorDepthMode colorDepth = thread->getRuntime()->getFakeColorDepth(); switch (colorDepth) { case kColorDepthMode1Bit: result.setInt(1); break; case kColorDepthMode2Bit: result.setInt(2); break; case kColorDepthMode4Bit: result.setInt(4); break; case kColorDepthMode8Bit: result.setInt(8); break; case kColorDepthMode16Bit: result.setInt(16); break; case kColorDepthMode32Bit: result.setInt(32); break; default: return false; } return true; } else if (attrib == "screensize") { uint16 width, height; thread->getRuntime()->getDisplayResolution(width, height); Common::Point hacksSize = thread->getRuntime()->getHacks().reportDisplaySize; if (hacksSize.x != 0) width = hacksSize.x; if (hacksSize.y != 0) height = hacksSize.y; result.setPoint(Common::Point(width, height)); return true; } else if (attrib == "currentram") { result.setInt(256 * 1024 * 1024); return true; } else if (attrib == "architecture") { ProjectPlatform platform = thread->getRuntime()->getProject()->getPlatform(); if (platform == kProjectPlatformWindows) result.setString("80x86"); else if (platform == kProjectPlatformMacintosh) result.setString("PowerPC"); // MC680x0 for 68k else { thread->error("Couldn't resolve architecture"); return false; } return true; } else if (attrib == "sysversion") { ProjectPlatform platform = thread->getRuntime()->getProject()->getPlatform(); if (platform == kProjectPlatformMacintosh) result.setString("9.0.4"); else if (platform == kProjectPlatformWindows) result.setString("4.0"); // Windows version? MindGym checks for < 4 else { thread->error("Couldn't resolve architecture"); return false; } return true; } else if (attrib == "processor" || attrib == "nativecpu") { ProjectPlatform platform = thread->getRuntime()->getProject()->getPlatform(); if (platform == kProjectPlatformMacintosh) result.setString("604"); // PowerPC 604 else if (platform == kProjectPlatformWindows) result.setString("Pentium"); else { thread->error("Couldn't resolve architecture"); return false; } return true; } return false; } Common::SharedPtr SysInfoModifier::shallowClone() const { return Common::SharedPtr(new SysInfoModifier(*this)); } const char *SysInfoModifier::getDefaultName() const { return "SysInfo Modifier"; } PanningModifier::PanningModifier() { } PanningModifier::~PanningModifier() { } bool PanningModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::PanningModifier &data) { return true; } bool PanningModifier::respondsToEvent(const Event &evt) const { return false; } VThreadState PanningModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { return kVThreadReturn; } void PanningModifier::disable(Runtime *runtime) { } #ifdef MTROPOLIS_DEBUG_ENABLE void PanningModifier::debugInspect(IDebugInspectionReport *report) const { Modifier::debugInspect(report); } #endif Common::SharedPtr PanningModifier::shallowClone() const { return Common::SharedPtr(new PanningModifier(*this)); } const char *PanningModifier::getDefaultName() const { return "Panning Modifier"; // ??? } FadeModifier::FadeModifier() { } FadeModifier::~FadeModifier() { } bool FadeModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::FadeModifier &data) { return true; } void FadeModifier::disable(Runtime *runtime) { } Common::SharedPtr FadeModifier::shallowClone() const { return Common::SharedPtr(new FadeModifier(*this)); } const char *FadeModifier::getDefaultName() const { return "Fade Modifier"; // ??? } class PrintModifierImageSupplier : public GUI::ImageAlbumImageSupplier { public: PrintModifierImageSupplier(const Common::String &inputPath, bool isMacVersion); bool loadImageSlot(uint slot, const Graphics::Surface *&outSurface, bool &outHasPalette, Graphics::Palette &outPalette, GUI::ImageAlbumImageMetadata &outMetadata) override; void releaseImageSlot(uint slot) override; uint getNumSlots() const override; Common::U32String getDefaultFileNameForSlot(uint slot) const override; bool getFileFormatForImageSlot(uint slot, Common::FormatInfo::FormatID &outFormat) const override; Common::SeekableReadStream *createReadStreamForSlot(uint slot) override; private: Common::String _path; Common::SharedPtr _decoder; bool _isMacVersion; }; PrintModifierImageSupplier::PrintModifierImageSupplier(const Common::String &inputPath, bool isMacVersion) : _path(inputPath), _isMacVersion(isMacVersion) { if (isMacVersion) _decoder.reset(new Image::PICTDecoder()); else _decoder.reset(new Image::BitmapDecoder()); } bool PrintModifierImageSupplier::loadImageSlot(uint slot, const Graphics::Surface *&outSurface, bool &outHasPalette, Graphics::Palette &outPalette, GUI::ImageAlbumImageMetadata &outMetadata) { Common::ScopedPtr dataStream(createReadStreamForSlot(slot)); if (!dataStream) return false; if (!_decoder->loadStream(*dataStream)) { warning("Failed to decode print file"); return false; } dataStream.reset(); outSurface = _decoder->getSurface(); outHasPalette = _decoder->hasPalette(); if (_decoder->hasPalette()) outPalette.set(_decoder->getPalette(), 0, _decoder->getPalette().size()); outMetadata = GUI::ImageAlbumImageMetadata(); outMetadata._orientation = GUI::kImageAlbumImageOrientationLandscape; outMetadata._viewTransformation = GUI::kImageAlbumViewTransformationRotate90CW; return true; } void PrintModifierImageSupplier::releaseImageSlot(uint slot) { _decoder->destroy(); } uint PrintModifierImageSupplier::getNumSlots() const { return 1; } Common::U32String PrintModifierImageSupplier::getDefaultFileNameForSlot(uint slot) const { Common::String filename = _path; size_t lastColonPos = filename.findLastOf(':'); if (lastColonPos != Common::String::npos) filename = filename.substr(lastColonPos + 1); size_t lastDotPos = filename.findLastOf('.'); if (lastDotPos != Common::String::npos) filename = filename.substr(0, lastDotPos); if (_isMacVersion) filename += Common::U32String(".pict"); else filename += Common::U32String(".bmp"); return filename.decode(Common::kASCII); } bool PrintModifierImageSupplier::getFileFormatForImageSlot(uint slot, Common::FormatInfo::FormatID &outFormat) const { if (slot != 0) return false; if (_isMacVersion) outFormat = Common::FormatInfo::kPICT; else outFormat = Common::FormatInfo::kBMP; return true; } Common::SeekableReadStream *PrintModifierImageSupplier::createReadStreamForSlot(uint slot) { if (slot != 0) return nullptr; size_t lastColonPos = _path.findLastOf(':'); Common::String filename; if (lastColonPos == Common::String::npos) filename = _path; else filename = _path.substr(lastColonPos + 1); Common::Path path(Common::String("MPZ_MTI/") + filename); if (_isMacVersion) { // Color images have res fork data so we must load from the data fork return Common::MacResManager::openFileOrDataFork(path); } else { // Win versions are just files Common::File *f = new Common::File(); if (!f->open(path)) { delete f; return nullptr; } return f; } } PrintModifier::PrintModifier() { } PrintModifier::~PrintModifier() { } bool PrintModifier::respondsToEvent(const Event &evt) const { return _executeWhen.respondsTo(evt); } VThreadState PrintModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { if (_executeWhen.respondsTo(msg->getEvent())) { PrintModifierImageSupplier imageSupplier(_filePath, runtime->getProject()->getPlatform() == kProjectPlatformMacintosh); Common::ScopedPtr dialog(GUI::createImageAlbumDialog(_("Image Viewer"), &imageSupplier, 0)); dialog->runModal(); } return kVThreadReturn; } void PrintModifier::disable(Runtime *runtime) { } MiniscriptInstructionOutcome PrintModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) { if (attrib == "showdialog") { // This is only ever set to "false" DynamicValueWriteDiscardHelper::create(writeProxy); return kMiniscriptInstructionOutcomeContinue; } else if (attrib == "filepath") { DynamicValueWriteStringHelper::create(&_filePath, writeProxy); return kMiniscriptInstructionOutcomeContinue; } return Modifier::writeRefAttribute(thread, writeProxy, attrib); } bool PrintModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::PrintModifier &data) { if (data.executeWhen.type != Data::PlugInTypeTaggedValue::kEvent) return false; if (data.filePath.type != Data::PlugInTypeTaggedValue::kString) return false; _filePath = data.filePath.value.asString; if (!_executeWhen.load(data.executeWhen.value.asEvent)) return false; return true; } #ifdef MTROPOLIS_DEBUG_ENABLE void PrintModifier::debugInspect(IDebugInspectionReport *report) const { } #endif Common::SharedPtr PrintModifier::shallowClone() const { return Common::SharedPtr(new PrintModifier(*this)); } const char *PrintModifier::getDefaultName() const { return "Print Modifier"; } NavigateModifier::NavigateModifier() { } NavigateModifier::~NavigateModifier() { } bool NavigateModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::NavigateModifier &data) { return true; } bool NavigateModifier::respondsToEvent(const Event &evt) const { return false; } VThreadState NavigateModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { return kVThreadReturn; } void NavigateModifier::disable(Runtime *runtime) { } #ifdef MTROPOLIS_DEBUG_ENABLE void NavigateModifier::debugInspect(IDebugInspectionReport *report) const { Modifier::debugInspect(report); } #endif Common::SharedPtr NavigateModifier::shallowClone() const { return Common::SharedPtr(new NavigateModifier(*this)); } const char *NavigateModifier::getDefaultName() const { return "Navigate Modifier"; // ??? } OpenTitleModifier::OpenTitleModifier() : _addToReturnList(false){ } OpenTitleModifier::~OpenTitleModifier() { } bool OpenTitleModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::OpenTitleModifier &data) { if (data.executeWhen.type != Data::PlugInTypeTaggedValue::kEvent || data.pathOrUrl.type != Data::PlugInTypeTaggedValue::kString || data.addToReturnList.type != Data::PlugInTypeTaggedValue::kInteger) return false; if (!_executeWhen.load(data.executeWhen.value.asEvent)) return false; _pathOrUrl = data.pathOrUrl.value.asString; _addToReturnList = static_cast(data.addToReturnList.value.asInt); return true; } bool OpenTitleModifier::respondsToEvent(const Event &evt) const { return _executeWhen.respondsTo(evt); } VThreadState OpenTitleModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { #ifdef MTROPOLIS_DEBUG_ENABLE if (Debugger *debugger = runtime->debugGetDebugger()) debugger->notify(kDebugSeverityWarning, "Open Title modifier was executed, which isn't implemented yet"); #endif return kVThreadReturn; } void OpenTitleModifier::disable(Runtime *runtime) { } #ifdef MTROPOLIS_DEBUG_ENABLE void OpenTitleModifier::debugInspect(IDebugInspectionReport *report) const { Modifier::debugInspect(report); } #endif Common::SharedPtr OpenTitleModifier::shallowClone() const { return Common::SharedPtr(new OpenTitleModifier(*this)); } const char *OpenTitleModifier::getDefaultName() const { return "Open Title Modifier"; // ??? } OpenAppModifier::OpenAppModifier() { } OpenAppModifier::~OpenAppModifier() { } bool OpenAppModifier::load(const PlugInModifierLoaderContext &context, const Data::Standard::OpenAppModifier &data) { return true; } bool OpenAppModifier::respondsToEvent(const Event &evt) const { return false; } VThreadState OpenAppModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { return kVThreadReturn; } void OpenAppModifier::disable(Runtime *runtime) { } #ifdef MTROPOLIS_DEBUG_ENABLE void OpenAppModifier::debugInspect(IDebugInspectionReport *report) const { Modifier::debugInspect(report); } #endif Common::SharedPtr OpenAppModifier::shallowClone() const { return Common::SharedPtr(new OpenAppModifier(*this)); } const char *OpenAppModifier::getDefaultName() const { return "Open App Modifier"; // ??? } StandardPlugInHacks::StandardPlugInHacks() : allowGarbledListModData(false) { } StandardPlugIn::StandardPlugIn() : _cursorModifierFactory(this) , _sTransCtModifierFactory(this) , _mediaCueModifierFactory(this) , _objRefVarModifierFactory(this) , _listVarModifierFactory(this) , _sysInfoModifierFactory(this) , _panningModifierFactory(this) , _fadeModifierFactory(this) , _printModifierFactory(this) , _navigateModifierFactory(this) , _openTitleModifierFactory(this) , _openAppModifierFactory(this) { } StandardPlugIn::~StandardPlugIn() { } void StandardPlugIn::registerModifiers(IPlugInModifierRegistrar *registrar) const { registrar->registerPlugInModifier("CursorMod", &_cursorModifierFactory); registrar->registerPlugInModifier("STransCt", &_sTransCtModifierFactory); registrar->registerPlugInModifier("MediaCue", &_mediaCueModifierFactory); registrar->registerPlugInModifier("ObjRefP", &_objRefVarModifierFactory); registrar->registerPlugInModifier("ListMod", &_listVarModifierFactory); registrar->registerPlugInModifier("SysInfo", &_sysInfoModifierFactory); registrar->registerPlugInModifier("Print", &_printModifierFactory); registrar->registerPlugInModifier("panning", &_panningModifierFactory); registrar->registerPlugInModifier("fade", &_fadeModifierFactory); registrar->registerPlugInModifier("Navigate", &_navigateModifierFactory); registrar->registerPlugInModifier("OpenTitle", &_openTitleModifierFactory); registrar->registerPlugInModifier("openApp", &_openAppModifierFactory); } const StandardPlugInHacks &StandardPlugIn::getHacks() const { return _hacks; } StandardPlugInHacks &StandardPlugIn::getHacks() { return _hacks; } } // End of namespace Standard namespace PlugIns { Common::SharedPtr createStandard() { return Common::SharedPtr(new Standard::StandardPlugIn()); } } // End of namespace PlugIns } // End of namespace MTropolis