MTROPOLIS: Text rendering and Obsidian WordMixer

This commit is contained in:
elasota 2022-05-18 18:15:58 -04:00 committed by Eugene Sandulenko
parent f9c294fd83
commit 1050da2a12
16 changed files with 774 additions and 81 deletions

View File

@ -22,6 +22,7 @@
#include "mtropolis/assets.h"
#include "mtropolis/asset_factory.h"
#include "graphics/managed_surface.h"
#include "graphics/surface.h"
#include "audio/audiostream.h"
@ -1078,7 +1079,7 @@ bool TextAsset::load(AssetLoaderContext &context, const Data::TextAsset &data) {
if (!_bitmapRect.load(data.bitmapRect))
return false;
_bitmapData.reset(new Graphics::Surface());
_bitmapData.reset(new Graphics::ManagedSurface());
uint16 width = _bitmapRect.getWidth();
uint16 height = _bitmapRect.getHeight();
@ -1132,7 +1133,7 @@ bool TextAsset::isBitmap() const {
return _isBitmap;
}
const Common::SharedPtr<Graphics::Surface>& TextAsset::getBitmapSurface() const {
const Common::SharedPtr<Graphics::ManagedSurface>& TextAsset::getBitmapSurface() const {
return _bitmapData;
}

View File

@ -283,7 +283,7 @@ public:
AssetType getAssetType() const override;
bool isBitmap() const;
const Common::SharedPtr<Graphics::Surface> &getBitmapSurface() const;
const Common::SharedPtr<Graphics::ManagedSurface> &getBitmapSurface() const;
const Common::String &getString() const;
const Common::Array<MacFormattingSpan> &getMacFormattingSpans() const;
@ -292,7 +292,7 @@ private:
TextAlignment _alignment;
bool _isBitmap;
Common::SharedPtr<Graphics::Surface> _bitmapData;
Common::SharedPtr<Graphics::ManagedSurface> _bitmapData;
Common::String _stringData;
Common::Array<MacFormattingSpan> _macFormattingSpans;

View File

@ -29,6 +29,10 @@
#include "video/qt_decoder.h"
#include "common/substream.h"
#include "graphics/macgui/macfontmanager.h"
#include "graphics/fontman.h"
#include "graphics/font.h"
#include "graphics/managed_surface.h"
namespace MTropolis {
@ -958,12 +962,16 @@ MiniscriptInstructionOutcome MToonElement::scriptSetRate(MiniscriptThread *threa
}
TextLabelElement::TextLabelElement() : _needsRender(false), _isBitmap(false) {
TextLabelElement::TextLabelElement() : _needsRender(false), _isBitmap(false), _macFontID(0), _size(12), _alignment(kTextAlignmentLeft) {
}
TextLabelElement::~TextLabelElement() {
}
bool TextLabelElement::isTextLabel() const {
return true;
}
bool TextLabelElement::load(ElementLoaderContext &context, const Data::TextLabelElement &data) {
if (!loadCommon(data.name, data.guid, data.rect1, data.elementFlags, data.layer, 0, data.sectionID))
return false;
@ -976,13 +984,62 @@ bool TextLabelElement::load(ElementLoaderContext &context, const Data::TextLabel
}
bool TextLabelElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "text") {
result.setString(_text);
return true;
}
return VisualElement::readAttribute(thread, result, attrib);
}
bool TextLabelElement::readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) {
if (attrib == "line") {
int32 asInteger = 0;
if (!index.roundToInt(asInteger) || asInteger < 1) {
thread->error("Invalid text label line index");
return false;
}
size_t lineIndex = asInteger - 1;
uint32 startPos;
uint32 endPos;
if (findLineRange(lineIndex, startPos, endPos))
result.setString(_text.substr(startPos, endPos - startPos));
else
result.setString("");
return true;
}
return VisualElement::readAttributeIndexed(thread, result, attrib, index);
}
MiniscriptInstructionOutcome TextLabelElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
if (attrib == "text") {
DynamicValueWriteFuncHelper<TextLabelElement, &TextLabelElement::scriptSetText>::create(this, writeProxy);
return kMiniscriptInstructionOutcomeContinue;
}
return VisualElement::writeRefAttribute(thread, writeProxy, attrib);
}
MiniscriptInstructionOutcome TextLabelElement::writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib, const DynamicValue &index) {
if (attrib == "line") {
int32 asInteger = 0;
if (!index.roundToInt(asInteger) || asInteger < 1) {
thread->error("Invalid text label line set index");
return kMiniscriptInstructionOutcomeFailed;
}
writeProxy.pod.ifc = &TextLabelLineWriteInterface::_instance;
writeProxy.pod.objectRef = this;
writeProxy.pod.ptrOrOffset = asInteger - 1;
return kMiniscriptInstructionOutcomeContinue;
}
return VisualElement::writeRefAttributeIndexed(thread, writeProxy, attrib, index);
}
void TextLabelElement::activate() {
Project *project = _runtime->getProject();
Common::SharedPtr<Asset> asset = project->getAssetByID(_assetID).lock();
@ -1012,10 +1069,251 @@ void TextLabelElement::activate() {
void TextLabelElement::deactivate() {
}
void TextLabelElement::render(Window *window) {
if (!_visible)
return;
int renderWidth = _rect.getWidth();
int renderHeight = _rect.getHeight();
if (_renderedText) {
if (renderWidth != _renderedText->w || renderHeight != _renderedText->h)
_needsRender = true;
}
if (_needsRender) {
_needsRender = false;
_renderedText.reset();
_renderedText.reset(new Graphics::ManagedSurface());
_renderedText->create(renderWidth, renderHeight, Graphics::PixelFormat::createFormatCLUT8());
_renderedText->fillRect(Common::Rect(0, 0, renderWidth, renderHeight), 0);
const Graphics::Font *font = nullptr;
if (_fontFamilyName.size() > 0) {
font = FontMan.getFontByName(_fontFamilyName.c_str());
} else if (_macFontID != 0) {
// TODO: Formatting spans
int slant = 0;
// FIXME/HACK: These aren't public...
if (_styleFlags.bold)
slant |= 1;
if (_styleFlags.italic)
slant |= 2;
if (_styleFlags.underline)
slant |= 4;
if (_styleFlags.outline)
slant |= 8;
if (_styleFlags.shadow)
slant |= 16;
if (_styleFlags.condensed)
slant |= 32;
if (_styleFlags.expanded)
slant |= 64;
// FIXME/HACK: This is a stupid way to make getFont return null on failure
font = _runtime->getMacFontManager()->getFont(Graphics::MacFont(_macFontID, _size, slant, static_cast<Graphics::FontManager::FontUsage>(-1)));
}
if (!font)
font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
int height = font->getFontHeight();
int ascent = font->getFontAscent();
Graphics::TextAlign textAlign = Graphics::kTextAlignLeft;
switch (_alignment) {
case kTextAlignmentLeft:
textAlign = Graphics::kTextAlignLeft;
break;
case kTextAlignmentCenter:
textAlign = Graphics::kTextAlignCenter;
break;
case kTextAlignmentRight:
textAlign = Graphics::kTextAlignRight;
break;
default:
break;
}
int line = 0;
uint32 lineStart = 0;
while (lineStart < _text.size()) {
bool noMoreLines = false;
uint32 lineEndPos = _text.find('\r', lineStart);
if (lineEndPos == Common::String::npos) {
lineEndPos = _text.size();
noMoreLines = true;
}
Common::String lineStr;
if (lineStart != 0 || lineEndPos != _text.size())
lineStr = _text.substr(lineStart, lineEndPos - lineStart);
else
lineStr = _text;
// Split the line into sublines
while (lineStr.size() > 0) {
size_t lineCommitted = 0;
bool prevWasWhitespace = true;
bool hasWhitespaceAtStart = false;
for (size_t i = 0; i <= lineStr.size(); i++) {
bool isWhitespace = (i == lineStr.size() || lineStr[i] < ' ');
if (isWhitespace) {
if (!prevWasWhitespace) {
int width = font->getStringWidth(lineStr.substr(0, i));
if (width > renderWidth)
break;
}
lineCommitted = i + 1;
}
prevWasWhitespace = isWhitespace;
}
if (lineCommitted > lineStr.size())
lineCommitted = lineStr.size();
// Too little space for anything
if (lineCommitted == 0) {
lineCommitted = 1;
for (size_t i = 2; i <= lineStr.size(); i++) {
int width = font->getStringWidth(lineStr.substr(0, i));
if (width > renderWidth)
break;
lineCommitted = i;
}
}
font->drawString(_renderedText.get(), lineStr.substr(0, lineCommitted), 0, line * height + (height - ascent) / 2, renderWidth, 1, textAlign, 0, false);
if (lineCommitted == lineStr.size())
lineStr.clear();
else {
lineStr = lineStr.substr(lineCommitted);
line++;
}
}
if (noMoreLines)
break;
line++;
lineStart = lineEndPos + 1;
}
}
Graphics::ManagedSurface *target = window->getSurface().get();
Common::Rect srcRect(0, 0, renderWidth, renderHeight);
Common::Rect destRect(_cachedAbsoluteOrigin.x, _cachedAbsoluteOrigin.y, _cachedAbsoluteOrigin.x + _rect.getWidth(), _cachedAbsoluteOrigin.y + _rect.getHeight());
const uint32 opaqueColor = 0xff000000;
const uint32 drawPalette[2] = {0, opaqueColor};
if (_renderedText) {
_renderedText->setPalette(drawPalette, 0, 2);
target->transBlitFrom(*_renderedText.get(), srcRect, destRect, 0);
}
}
void TextLabelElement::setTextStyle(uint16 macFontID, const Common::String &fontFamilyName, uint size, TextAlignment alignment, const TextStyleFlags &styleFlags) {
_needsRender = true;
_macFontID = macFontID;
_fontFamilyName = fontFamilyName;
_size = size;
_alignment = alignment;
_styleFlags = styleFlags;
}
MiniscriptInstructionOutcome TextLabelElement::scriptSetText(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kString) {
thread->error("Tried to set a text label element's text to something that wasn't a string");
return kMiniscriptInstructionOutcomeFailed;
}
_text = value.getString();
_needsRender = true;
_macFormattingSpans.clear();
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome TextLabelElement::scriptSetLine(MiniscriptThread *thread, size_t lineIndex, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kString) {
thread->error("Tried to set a text label element's text to something that wasn't a string");
return kMiniscriptInstructionOutcomeFailed;
}
uint32 startPos;
uint32 endPos;
if (findLineRange(lineIndex, startPos, endPos))
_text = _text.substr(0, startPos) + value.getString() + _text.substr(endPos, _text.size() - endPos);
else {
size_t numLines = countLines();
while (numLines <= lineIndex) {
_text += '\r';
numLines++;
}
_text += value.getString();
}
_needsRender = true;
_macFormattingSpans.clear();
return kMiniscriptInstructionOutcomeContinue;
}
bool TextLabelElement::findLineRange(size_t lineIndex, uint32 &outStartPos, uint32 &outEndPos) const {
uint32 lineStart = 0;
uint32 lineEnd = _text.size();
size_t linesToScan = lineIndex + 1;
while (linesToScan > 0) {
linesToScan--;
lineEnd = _text.find('\r', lineStart);
if (lineEnd == Common::String::npos) {
lineEnd = _text.size();
break;
}
}
if (linesToScan > 0)
return false;
outStartPos = lineStart;
outEndPos = lineEnd;
return true;
}
size_t TextLabelElement::countLines() const {
size_t numLines = 1;
for (char c : _text)
if (c == '\r')
numLines++;
return numLines;
}
MiniscriptInstructionOutcome TextLabelElement::TextLabelLineWriteInterface::write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) const {
return static_cast<TextLabelElement *>(objectRef)->scriptSetLine(thread, ptrOrOffset, dest);
}
MiniscriptInstructionOutcome TextLabelElement::TextLabelLineWriteInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) const {
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome TextLabelElement::TextLabelLineWriteInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const {
return kMiniscriptInstructionOutcomeFailed;
}
TextLabelElement::TextLabelLineWriteInterface TextLabelElement::TextLabelLineWriteInterface::_instance;
SoundElement::SoundElement() : _finishTime(0), _shouldPlayIfNotPaused(true), _needsReset(true) {
}

View File

@ -239,22 +239,42 @@ public:
TextLabelElement();
~TextLabelElement();
bool isTextLabel() const override;
bool load(ElementLoaderContext &context, const Data::TextLabelElement &data);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
bool readAttributeIndexed(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib, const DynamicValue &index) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
MiniscriptInstructionOutcome writeRefAttributeIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib, const DynamicValue &index) override;
void activate() override;
void deactivate() override;
void render(Window *window) override;
void setTextStyle(uint16 macFontID, const Common::String &fontFamilyName, uint size, TextAlignment alignment, const TextStyleFlags &styleFlags);
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Text Label Element"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; }
#endif
private:
struct TextLabelLineWriteInterface : public IDynamicValueWriteInterface {
MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) const override;
MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) const override;
MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const override;
static TextLabelLineWriteInterface _instance;
};
MiniscriptInstructionOutcome scriptSetText(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetLine(MiniscriptThread *thread, size_t lineIndex, const DynamicValue &value);
bool findLineRange(size_t lineNum, uint32 &outStartPos, uint32 &outEndPos) const;
size_t countLines() const;
bool _cacheBitmap;
bool _needsRender;
@ -262,8 +282,18 @@ private:
uint32 _assetID;
Common::String _text;
uint16 _macFontID;
Common::String _fontFamilyName;
uint _size;
TextAlignment _alignment;
TextStyleFlags _styleFlags;
Common::Array<MacFormattingSpan> _macFormattingSpans;
Common::SharedPtr<Graphics::Surface> _renderedText; // NOTE: This may be a pre-rendered instance that is read-only. Rendering must create a new surface!
// NOTE: This may be a surface loaded from data, so it must not be altered.
// If you need to render again, recreate the surface. If you want to change
// this behavior, please add a flag indicating that it is from the asset.
Common::SharedPtr<Graphics::ManagedSurface> _renderedText;
Runtime *_runtime;
};
@ -275,8 +305,8 @@ public:
bool load(ElementLoaderContext &context, const Data::SoundElement &data);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) override;
VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg);

View File

@ -484,7 +484,7 @@ MiniscriptInstructionOutcome Set::execute(MiniscriptThread *thread) const {
}
// Convert value
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0, false);
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0, true);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
@ -1679,18 +1679,26 @@ PushGlobal::PushGlobal(uint32 globalID, bool isLValue) : _globalID(globalID), _i
}
MiniscriptInstructionOutcome PushGlobal::execute(MiniscriptThread *thread) const {
DynamicValue value;
thread->pushValue(DynamicValue());
DynamicValue &value = thread->getStackValueFromTop(0).value;
switch (_globalID) {
case kGlobalRefElement:
case kGlobalRefSection:
case kGlobalRefScene:
case kGlobalRefProject:
return executeFindFilteredParent(thread);
return executeFindFilteredParent(thread, value);
case kGlobalRefModifier:
value.setObject(thread->getModifier()->getSelfReference());
break;
case kGlobalRefIncomingData:
value = thread->getMessageProperties()->getValue();
if (_isLValue) {
DynamicValueWriteProxy proxy;
thread->createWriteIncomingDataProxy(proxy);
value.setWriteProxy(proxy);
} else
value = thread->getMessageProperties()->getValue();
break;
case kGlobalRefSource:
value.setObject(ObjectReference(thread->getMessageProperties()->getSource()));
@ -1713,12 +1721,10 @@ MiniscriptInstructionOutcome PushGlobal::execute(MiniscriptThread *thread) const
return kMiniscriptInstructionOutcomeFailed;
}
thread->pushValue(value);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome PushGlobal::executeFindFilteredParent(MiniscriptThread *thread) const {
MiniscriptInstructionOutcome PushGlobal::executeFindFilteredParent(MiniscriptThread *thread, DynamicValue &result) const {
Common::WeakPtr<RuntimeObject> ref = thread->getModifier()->getSelfReference();
for (;;) {
Common::SharedPtr<RuntimeObject> obj = ref.lock();
@ -1762,10 +1768,7 @@ MiniscriptInstructionOutcome PushGlobal::executeFindFilteredParent(MiniscriptThr
}
}
DynamicValue value;
value.setObject(ref);
thread->pushValue(value);
result.setObject(ref);
return kMiniscriptInstructionOutcomeContinue;
}
@ -1927,6 +1930,31 @@ bool MiniscriptThread::evaluateTruthOfResult(bool &isTrue) {
return true;
}
void MiniscriptThread::createWriteIncomingDataProxy(DynamicValueWriteProxy &proxy) {
proxy.pod.ifc = &IncomingDataWriteInterface::_instance;
proxy.pod.objectRef = this;
proxy.pod.ptrOrOffset = 0;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) const {
thread->_msgProps->setValue(value);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) const {
// TODO: Generic refAttrib for dynamic values
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const {
// TODO: Generic refAttribIndexed for dynamic values
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptThread::IncomingDataWriteInterface MiniscriptThread::IncomingDataWriteInterface::_instance;
VThreadState MiniscriptThread::resumeTask(const ResumeTaskData &data) {
return data.thread->resume(data);
}

View File

@ -351,7 +351,7 @@ namespace MiniscriptInstructions {
kGlobalRefActiveScene = 11,
};
MiniscriptInstructionOutcome executeFindFilteredParent(MiniscriptThread *thread) const;
MiniscriptInstructionOutcome executeFindFilteredParent(MiniscriptThread *thread, DynamicValue &result) const;
uint32 _globalID;
bool _isLValue;
@ -409,7 +409,17 @@ public:
bool evaluateTruthOfResult(bool &isTrue);
void createWriteIncomingDataProxy(DynamicValueWriteProxy &proxy);
private:
struct IncomingDataWriteInterface : public IDynamicValueWriteInterface {
MiniscriptInstructionOutcome write(MiniscriptThread *thread, const DynamicValue &dest, void *objectRef, uintptr ptrOrOffset) const override;
MiniscriptInstructionOutcome refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) const override;
MiniscriptInstructionOutcome refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) const override;
static IncomingDataWriteInterface _instance;
};
struct ResumeTaskData {
Common::SharedPtr<MiniscriptThread> thread;
};

View File

@ -24,6 +24,8 @@
#include "mtropolis/modifier_factory.h"
#include "mtropolis/saveload.h"
#include "mtropolis/elements.h"
#include "common/memstream.h"
namespace MTropolis {
@ -321,7 +323,7 @@ bool MessengerModifier::respondsToEvent(const Event &evt) const {
VThreadState MessengerModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (_when.respondsTo(msg->getEvent())) {
_sendSpec.sendFromMessenger(runtime, this);
_sendSpec.sendFromMessenger(runtime, this, msg->getValue());
}
return kVThreadReturn;
@ -645,6 +647,7 @@ VThreadState IfMessengerModifier::consumeMessage(Runtime *runtime, const Common:
EvaluateAndSendTaskData *evalAndSendData = runtime->getVThread().pushTask("IfMessengerModifier::evaluateAndSendTask", this, &IfMessengerModifier::evaluateAndSendTask);
evalAndSendData->thread = thread;
evalAndSendData->runtime = runtime;
evalAndSendData->incomingData = msg->getValue();
MiniscriptThread::runOnVThread(runtime->getVThread(), thread);
}
@ -680,7 +683,7 @@ VThreadState IfMessengerModifier::evaluateAndSendTask(const EvaluateAndSendTaskD
return kVThreadError;
if (isTrue)
_sendSpec.sendFromMessenger(taskData.runtime, this);
_sendSpec.sendFromMessenger(taskData.runtime, this, taskData.incomingData);
return kVThreadReturn;
}
@ -726,6 +729,9 @@ VThreadState TimerMessengerModifier::consumeMessage(Runtime *runtime, const Comm
debug(3, "Timer %x '%s' scheduled to execute in %i milliseconds", getStaticGUID(), getName().c_str(), realMilliseconds);
if (!_scheduledEvent) {
_scheduledEvent = runtime->getScheduler().scheduleMethod<TimerMessengerModifier, &TimerMessengerModifier::trigger>(runtime->getPlayTime() + realMilliseconds, this);
_incomingData = msg->getValue();
if (_incomingData.getType() == DynamicValueTypes::kList)
_incomingData.setList(_incomingData.getList()->clone());
}
}
@ -757,7 +763,7 @@ void TimerMessengerModifier::trigger(Runtime *runtime) {
} else
_scheduledEvent.reset();
_sendSpec.sendFromMessenger(runtime, this);
_sendSpec.sendFromMessenger(runtime, this, _incomingData);
}
bool BoundaryDetectionMessengerModifier::load(ModifierLoaderContext &context, const Data::BoundaryDetectionMessengerModifier &data) {
@ -1014,16 +1020,13 @@ bool KeyboardMessengerModifier::checkKeyEventTrigger(Runtime *runtime, Common::E
void KeyboardMessengerModifier::dispatchMessage(Runtime *runtime, const Common::String &charStr) {
Common::SharedPtr<MessageProperties> msgProps;
if (_sendSpec.with.getType() == DynamicValueTypes::kIncomingData) {
if (charStr.size() != 1)
warning("Keyboard messenger is supposed to send the character code, but they key was a special key and we haven't implemented conversion of those keycodes");
DynamicValue charStrValue;
charStrValue.setString(charStr);
_sendSpec.sendFromMessengerWithCustomData(runtime, this, charStrValue);
} else {
_sendSpec.sendFromMessenger(runtime, this);
}
if (charStr.size() != 1)
warning("Keyboard messenger is supposed to send the character code, but they key was a special key and we haven't implemented conversion of those keycodes");
DynamicValue charStrValue;
charStrValue.setString(charStr);
_sendSpec.sendFromMessenger(runtime, this, charStrValue);
}
void KeyboardMessengerModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
@ -1034,20 +1037,6 @@ void KeyboardMessengerModifier::linkInternalReferences(ObjectLinkingScope *scope
_sendSpec.linkInternalReferences(scope);
}
TextStyleModifier::StyleFlags::StyleFlags() : bold(false), italic(false), underline(false), outline(false), shadow(false), condensed(false), expanded(false) {
}
bool TextStyleModifier::StyleFlags::load(uint8 dataStyleFlags) {
bold = ((dataStyleFlags & 0x01) != 0);
italic = ((dataStyleFlags & 0x02) != 0);
underline = ((dataStyleFlags & 0x03) != 0);
outline = ((dataStyleFlags & 0x04) != 0);
shadow = ((dataStyleFlags & 0x10) != 0);
condensed = ((dataStyleFlags & 0x20) != 0);
expanded = ((dataStyleFlags & 0x40) != 0);
return true;
}
bool TextStyleModifier::load(ModifierLoaderContext &context, const Data::TextStyleModifier &data) {
if (!loadTypicalHeader(data.modHeader))
return false;
@ -1057,12 +1046,59 @@ bool TextStyleModifier::load(ModifierLoaderContext &context, const Data::TextSty
_macFontID = data.macFontID;
_size = data.size;
_alignment = static_cast<Alignment>(data.alignment);
_fontFamilyName = data.fontFamilyName;
if (!_styleFlags.load(data.flags))
return false;
switch (data.alignment) {
case 0:
_alignment = kTextAlignmentLeft;
break;
case 1:
_alignment = kTextAlignmentCenter;
break;
case 0xffff:
_alignment = kTextAlignmentRight;
break;
default:
warning("Unrecognized text alignment");
return false;
}
return true;
}
bool TextStyleModifier::respondsToEvent(const Event &evt) const {
if (_applyWhen.respondsTo(evt) || _removeWhen.respondsTo(evt))
return true;
return Modifier::respondsToEvent(evt);
}
VThreadState TextStyleModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) {
if (_applyWhen.respondsTo(msg->getEvent())) {
Structural *owner = findStructuralOwner();
if (owner && owner->isElement()) {
Element *element = static_cast<Element *>(owner);
if (element->isVisual()) {
VisualElement *visualElement = static_cast<VisualElement *>(element);
if (visualElement->isTextLabel()) {
static_cast<TextLabelElement *>(visualElement)->setTextStyle(_macFontID, _fontFamilyName, _size, _alignment, _styleFlags);
}
}
}
return kVThreadReturn;
} else if (_removeWhen.respondsTo(msg->getEvent())) {
// Doesn't actually do anything
return kVThreadReturn;
}
return Modifier::consumeMessage(runtime, msg);
}
Common::SharedPtr<Modifier> TextStyleModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new TextStyleModifier(*this));
}

View File

@ -22,6 +22,7 @@
#ifndef MTROPOLIS_MODIFIERS_H
#define MTROPOLIS_MODIFIERS_H
#include "mtropolis/render.h"
#include "mtropolis/runtime.h"
#include "mtropolis/data.h"
@ -349,6 +350,7 @@ private:
struct EvaluateAndSendTaskData {
Common::SharedPtr<MiniscriptThread> thread;
Runtime *runtime;
DynamicValue incomingData;
};
Common::SharedPtr<Modifier> shallowClone() const override;
@ -391,6 +393,7 @@ private:
MessengerSendSpec _sendSpec;
uint32 _milliseconds;
bool _looping;
DynamicValue _incomingData;
Common::SharedPtr<ScheduledEvent> _scheduledEvent;
};
@ -521,24 +524,8 @@ class TextStyleModifier : public Modifier {
public:
bool load(ModifierLoaderContext &context, const Data::TextStyleModifier &data);
enum Alignment {
kAlignmentLeft = 0,
kAlignmentCenter = 1,
kAlignmentRight = 0xffff,
};
struct StyleFlags {
bool bold : 1;
bool italic : 1;
bool underline : 1;
bool outline : 1;
bool shadow : 1;
bool condensed : 1;
bool expanded : 1;
StyleFlags();
bool load(uint8 dataStyleFlags);
};
bool respondsToEvent(const Event &evt) const override;
VThreadState consumeMessage(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Text Style Modifier"; }
@ -551,7 +538,8 @@ private:
uint16 _size;
ColorRGB8 _textColor;
ColorRGB8 _backgroundColor;
Alignment _alignment;
TextAlignment _alignment;
TextStyleFlags _styleFlags;
Event _applyWhen;
Event _removeWhen;
Common::String _fontFamilyName;

View File

@ -85,8 +85,18 @@ bool TextWorkModifier::readAttribute(MiniscriptThread *thread, DynamicValue &res
result.setInt(index);
return true;
} else if (attrib == "numword") {
thread->error("Not-yet-implemented TextWork attribute 'numword'");
return false;
int numWords = 0;
bool lastWasWhitespace = true;
for (size_t i = 0; i < _string.size(); i++) {
char c = _string[i];
bool isWhitespace = (c <= ' ');
if (lastWasWhitespace && !isWhitespace)
numWords++;
lastWasWhitespace = isWhitespace;
}
result.setInt(numWords);
return true;
}
return Modifier::readAttribute(thread, result, attrib);
@ -106,11 +116,11 @@ MiniscriptInstructionOutcome TextWorkModifier::writeRefAttribute(MiniscriptThrea
DynamicValueWriteStringHelper::create(&_token, result);
return kMiniscriptInstructionOutcomeContinue;
} else if (attrib == "firstword") {
thread->error("Not-yet-implemented TextWork attrib 'firstword'");
return kMiniscriptInstructionOutcomeFailed;
DynamicValueWriteFuncHelper<TextWorkModifier, &TextWorkModifier::scriptSetFirstWord>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
} else if (attrib == "lastword") {
thread->error("Not-yet-implemented TextWork attrib 'lastword'");
return kMiniscriptInstructionOutcomeFailed;
DynamicValueWriteFuncHelper<TextWorkModifier, &TextWorkModifier::scriptSetLastWord>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
return Modifier::writeRefAttribute(thread, result, attrib);
@ -120,6 +130,75 @@ Common::SharedPtr<Modifier> TextWorkModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new TextWorkModifier(*this));
}
MiniscriptInstructionOutcome TextWorkModifier::scriptSetFirstWord(MiniscriptThread *thread, const DynamicValue &value) {
// This and lastword are only used in tandem with lastword, exact functionality is unclear since it's
// also used in tandem with "output" which is normally used with firstchar+lastchar.
// We attempt to emulate it by setting firstchar+lastchar to the correct values
int32 asInteger = 0;
if (!value.roundToInt(asInteger))
return kMiniscriptInstructionOutcomeFailed;
int numWords = 0;
bool lastWasWhitespace = true;
for (size_t i = 0; i < _string.size(); i++) {
char c = _string[i];
bool isWhitespace = (c <= ' ');
if (lastWasWhitespace && !isWhitespace) {
numWords++;
if (numWords == asInteger) {
_firstChar = i + 1;
return kMiniscriptInstructionOutcomeContinue;
}
}
lastWasWhitespace = isWhitespace;
}
thread->error("Invalid index for 'firstword'");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome TextWorkModifier::scriptSetLastWord(MiniscriptThread* thread, const DynamicValue& value) {
int32 asInteger = 0;
if (!value.roundToInt(asInteger))
return kMiniscriptInstructionOutcomeFailed;
int numWordEnds = 0;
bool lastWasWhitespace = true;
for (size_t i = 0; i < _string.size(); i++) {
char c = _string[i];
bool isWhitespace = (c <= ' ');
if (!lastWasWhitespace && isWhitespace) {
numWordEnds++;
if (numWordEnds == asInteger) {
_firstChar = i - 1;
return kMiniscriptInstructionOutcomeContinue;
}
}
lastWasWhitespace = isWhitespace;
if (numWordEnds == asInteger) {
_lastChar = i;
return kMiniscriptInstructionOutcomeContinue;
}
}
if (!lastWasWhitespace) {
numWordEnds++;
if (numWordEnds == asInteger) {
_lastChar = _string.size();
return kMiniscriptInstructionOutcomeContinue;
}
}
thread->error("Invalid index for 'firstword'");
return kMiniscriptInstructionOutcomeFailed;
}
DictionaryModifier::DictionaryModifier() : _plugIn(nullptr) {
}
@ -257,13 +336,155 @@ Common::SharedPtr<Modifier> DictionaryModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new DictionaryModifier(*this));
}
WordMixerModifier::WordMixerModifier() {
WordMixerModifier::WordMixerModifier() : _matches(0), _result(0) {
}
bool WordMixerModifier::load(const PlugInModifierLoaderContext &context, const Data::Obsidian::WordMixerModifier &data) {
_plugIn = static_cast<const ObsidianPlugIn *>(context.plugIn);
return true;
}
bool WordMixerModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "result") {
result.setInt(_result);
return true;
}
if (attrib == "matches") {
result.setInt(_matches);
return true;
}
if (attrib == "output") {
result.setString(_output);
return true;
}
return Modifier::readAttribute(thread, result, attrib);
}
MiniscriptInstructionOutcome WordMixerModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
if (attrib == "input") {
DynamicValueWriteFuncHelper<WordMixerModifier, &WordMixerModifier::scriptSetInput>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
if (attrib == "search") {
DynamicValueWriteFuncHelper<WordMixerModifier, &WordMixerModifier::scriptSetSearch>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
return Modifier::writeRefAttribute(thread, result, attrib);
}
MiniscriptInstructionOutcome WordMixerModifier::scriptSetInput(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kString) {
thread->error("Invalid type for WordMixer input attribute");
return kMiniscriptInstructionOutcomeFailed;
}
_input = value.getString();
Common::Array<char> sourceChars;
for (char c : _input) {
if (c > ' ')
sourceChars.push_back(invariantToLower(c));
}
Common::Array<bool> charIsUsed;
charIsUsed.resize(sourceChars.size());
const Common::Array<WordGameData::WordBucket> &wordBuckets = _plugIn->getWordGameData()->getWordBuckets();
_output.clear();
_matches = 0;
size_t numWordBuckets = wordBuckets.size();
for (size_t rbucket = 0; rbucket < numWordBuckets; rbucket++) {
size_t wordLength = numWordBuckets - 1 - rbucket;
const WordGameData::WordBucket &bucket = wordBuckets[wordLength];
size_t numWords = bucket.wordIndexes.size();
for (size_t wi = 0; wi < numWords; wi++) {
const char *wordChars = &bucket.chars[bucket.spacing * wi];
for (bool &b : charIsUsed)
b = false;
bool isMatch = true;
for (size_t ci = 0; ci < wordLength; ci++) {
const char wordChar = wordChars[ci];
bool foundAvailableSource = false;
for (size_t srci = 0; srci < sourceChars.size(); srci++) {
if (sourceChars[srci] == wordChar && !charIsUsed[srci]) {
foundAvailableSource = true;
charIsUsed[srci] = true;
break;
}
}
if (!foundAvailableSource) {
isMatch = false;
break;
}
}
if (isMatch) {
if (_matches > 0)
_output += ' ';
_output += Common::String(wordChars, wordLength);
_matches++;
}
}
if (_matches > 0)
break;
}
if (_matches == 0)
_output = "xxx";
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome WordMixerModifier::scriptSetSearch(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kBoolean) {
thread->error("Invalid type for WordMixer search attribute");
return kMiniscriptInstructionOutcomeFailed;
}
if (!value.getBool())
return kMiniscriptInstructionOutcomeContinue;
size_t searchLength = _input.size();
const Common::Array<WordGameData::WordBucket> &buckets = _plugIn->getWordGameData()->getWordBuckets();
_result = 0;
if (searchLength < buckets.size()) {
const WordGameData::WordBucket &bucket = buckets[searchLength];
bool found = false;
for (size_t wi = 0; wi < bucket.wordIndexes.size(); wi++) {
const char *wordChars = &bucket.chars[wi * bucket.spacing];
bool isMatch = true;
for (size_t ci = 0; ci < searchLength; ci++) {
if (invariantToLower(_input[ci]) != wordChars[ci]) {
isMatch = false;
break;
}
}
if (isMatch) {
_result = 1;
break;
}
}
}
return kMiniscriptInstructionOutcomeContinue;
}
Common::SharedPtr<Modifier> WordMixerModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new WordMixerModifier(*this));
}

View File

@ -74,6 +74,9 @@ public:
private:
Common::SharedPtr<Modifier> shallowClone() const override;
MiniscriptInstructionOutcome scriptSetFirstWord(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetLastWord(MiniscriptThread *thread, const DynamicValue &value);
Common::String _string;
Common::String _token;
@ -115,12 +118,25 @@ public:
bool load(const PlugInModifierLoaderContext &context, const Data::Obsidian::WordMixerModifier &data);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
MiniscriptInstructionOutcome writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib);
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "WordMixer Modifier"; }
#endif
private:
Common::SharedPtr<Modifier> shallowClone() const override;
MiniscriptInstructionOutcome scriptSetInput(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetSearch(MiniscriptThread *thread, const DynamicValue &value);
Common::String _input;
Common::String _output;
int _matches;
int _result;
const ObsidianPlugIn *_plugIn;
};
class ObsidianPlugIn : public MTropolis::PlugIn {

View File

@ -66,9 +66,13 @@ DataReadErrorCode TextWorkModifier::load(PlugIn& plugIn, const PlugInModifier& p
}
DataReadErrorCode WordMixerModifier::load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) {
if (prefix.plugInRevision != 1)
if (prefix.plugInRevision != 0)
return kDataReadErrorUnsupportedRevision;
// Looks like this contains matches, but don't really need them...
if (!reader.skip(prefix.subObjectSize))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
}

View File

@ -963,8 +963,11 @@ Common::SharedPtr<ModifierSaveLoad> ListVariableModifier::getSaveLoad() {
bool ListVariableModifier::varSetValue(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() == DynamicValueTypes::kList)
_list = value.getList()->clone();
else
return false;
else {
if (!_list)
_list.reset(new DynamicList());
return _list->setAtIndex(0, value);
}
return true;
}

View File

@ -78,6 +78,20 @@ inline int expand5To8(int value) {
return (value * 33) >> 2;
}
TextStyleFlags::TextStyleFlags() : bold(false), italic(false), underline(false), outline(false), shadow(false), condensed(false), expanded(false) {
}
bool TextStyleFlags::load(uint8 dataStyleFlags) {
bold = ((dataStyleFlags & 0x01) != 0);
italic = ((dataStyleFlags & 0x02) != 0);
underline = ((dataStyleFlags & 0x03) != 0);
outline = ((dataStyleFlags & 0x04) != 0);
shadow = ((dataStyleFlags & 0x10) != 0);
condensed = ((dataStyleFlags & 0x20) != 0);
expanded = ((dataStyleFlags & 0x40) != 0);
return true;
}
MacFontFormatting::MacFontFormatting() : fontID(0), fontFlags(0), size(12) {
}

View File

@ -47,6 +47,19 @@ enum TextAlignment {
kTextAlignmentRight,
};
struct TextStyleFlags {
bool bold : 1;
bool italic : 1;
bool underline : 1;
bool outline : 1;
bool shadow : 1;
bool condensed : 1;
bool expanded : 1;
TextStyleFlags();
bool load(uint8 dataStyleFlags);
};
struct MacFontFormatting {
MacFontFormatting();
MacFontFormatting(uint16 fontID, uint8 fontFlags, uint16 size);

View File

@ -1559,7 +1559,7 @@ void DynamicValue::initFromOther(const DynamicValue &other) {
switch (other._type) {
case DynamicValueTypes::kNull:
case DynamicValueTypes::kIncomingData:
case DynamicValueTypes::kIncomingData: // FIXME: Get rid of this
break;
case DynamicValueTypes::kInteger:
_value.asInt = other._value.asInt;
@ -1860,8 +1860,11 @@ void MessengerSendSpec::resolveVariableObjectType(RuntimeObject *obj, Common::We
}
}
void MessengerSendSpec::sendFromMessenger(Runtime *runtime, Modifier *sender) const {
sendFromMessengerWithCustomData(runtime, sender, this->with);
void MessengerSendSpec::sendFromMessenger(Runtime *runtime, Modifier *sender, const DynamicValue &incomingData) const {
if (this->with.getType() == DynamicValueTypes::kIncomingData)
sendFromMessengerWithCustomData(runtime, sender, incomingData);
else
sendFromMessengerWithCustomData(runtime, sender, this->with);
}
void MessengerSendSpec::sendFromMessengerWithCustomData(Runtime *runtime, Modifier *sender, const DynamicValue &data) const {
@ -2230,6 +2233,13 @@ const Common::WeakPtr<RuntimeObject>& MessageProperties::getSource() const {
return _source;
}
void MessageProperties::setValue(const DynamicValue &value) {
if (value.getType() == DynamicValueTypes::kList)
_value.setList(value.getList()->clone());
else
_value = value;
}
WorldManagerInterface::WorldManagerInterface() {
}
@ -5986,6 +5996,10 @@ bool VisualElement::isVisual() const {
return true;
}
bool VisualElement::isTextLabel() const {
return false;
}
bool VisualElement::isVisible() const {
return _visible;
}
@ -6575,6 +6589,20 @@ bool Modifier::loadTypicalHeader(const Data::TypicalModifierHeader &typicalHeade
return true;
}
Structural *Modifier::findStructuralOwner() const {
RuntimeObject *scan = _parent.lock().get();
while (scan) {
if (scan->isModifier())
scan = static_cast<Modifier *>(scan)->_parent.lock().get();
else if (scan->isStructural())
return static_cast<Structural *>(scan);
else
return nullptr;
}
return nullptr;
}
void Modifier::linkInternalReferences(ObjectLinkingScope *scope) {
}

View File

@ -1047,7 +1047,7 @@ struct MessengerSendSpec {
static void resolveVariableObjectType(RuntimeObject *obj, Common::WeakPtr<Structural> &outStructuralDest, Common::WeakPtr<Modifier> &outModifierDest);
void sendFromMessenger(Runtime *runtime, Modifier *sender) const;
void sendFromMessenger(Runtime *runtime, Modifier *sender, const DynamicValue &incomingData) const;
void sendFromMessengerWithCustomData(Runtime *runtime, Modifier *sender, const DynamicValue &data) const;
Event send;
@ -1769,6 +1769,8 @@ struct MessageProperties {
const DynamicValue &getValue() const;
const Common::WeakPtr<RuntimeObject> &getSource() const;
void setValue(const DynamicValue &value);
private:
Event _evt;
DynamicValue _value;
@ -2212,6 +2214,7 @@ public:
VisualElement();
bool isVisual() const override;
virtual bool isTextLabel() const;
bool isVisible() const;
bool isDirectToScreen() const;