MTROPOLIS: Load data for Obsidian word games

This commit is contained in:
elasota 2022-05-13 17:44:17 -04:00 committed by Eugene Sandulenko
parent 790ed24635
commit 62accd5196
14 changed files with 579 additions and 14 deletions

View File

@ -856,6 +856,36 @@ MiniscriptInstructionOutcome Or::execute(MiniscriptThread *thread) const {
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Neg::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0, false);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &value = thread->getStackValueFromTop(0).value;
switch (value.getType()) {
case DynamicValueTypes::kFloat:
value.setFloat(-value.getFloat());
break;
case DynamicValueTypes::kInteger: {
int32 i = value.getInt();
if (i == (0 - 1 -0x7fffffff))
value.setFloat(-static_cast<double>(i));
else
value.setInt(-i);
} break;
default:
thread->error("Couldn't negate a value of a non-numeric type");
return kMiniscriptInstructionOutcomeFailed;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Not::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");

View File

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

View File

@ -1315,7 +1315,13 @@ bool IntegerVariableModifier::varSetValue(MiniscriptThread *thread, const Dynami
_value = static_cast<int32>(floor(value.getFloat() + 0.5));
else if (value.getType() == DynamicValueTypes::kInteger)
_value = value.getInt();
else
else if (value.getType() == DynamicValueTypes::kString) {
// Should this scan %lf to a double and round it instead?
int i;
if (!sscanf(value.getString().c_str(), "%i", &i))
return false;
_value = i;
} else
return false;
return true;
@ -1385,6 +1391,30 @@ void IntegerRangeVariableModifier::varGetValue(MiniscriptThread *thread, Dynamic
dest.setIntRange(_range);
}
bool IntegerRangeVariableModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "start") {
result.setInt(_range.min);
return true;
}
if (attrib == "end") {
result.setInt(_range.max);
return true;
}
return Modifier::readAttribute(thread, result, attrib);
}
MiniscriptInstructionOutcome IntegerRangeVariableModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
if (attrib == "start") {
DynamicValueWriteIntegerHelper<int32>::create(&_range.min, result);
return kMiniscriptInstructionOutcomeContinue;
}
if (attrib == "end") {
DynamicValueWriteIntegerHelper<int32>::create(&_range.max, result);
return kMiniscriptInstructionOutcomeContinue;
}
return Modifier::writeRefAttribute(thread, result, attrib);
}
#ifdef MTROPOLIS_DEBUG_ENABLE
void IntegerRangeVariableModifier::debugInspect(IDebugInspectionReport *report) const {
VariableModifier::debugInspect(report);

View File

@ -473,6 +473,7 @@ public:
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Keyboard Messenger Modifier"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
#endif
private:
@ -738,6 +739,9 @@ public:
bool varSetValue(MiniscriptThread *thread, const DynamicValue &value) override;
void varGetValue(MiniscriptThread *thread, DynamicValue &dest) const override;
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 "Integer Range Variable Modifier"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }

View File

@ -28,6 +28,7 @@
#include "mtropolis/plugins.h"
#include "mtropolis/plugin/standard.h"
#include "mtropolis/plugin/obsidian.h"
#include "common/config-manager.h"
#include "common/debug.h"
@ -49,6 +50,49 @@
namespace MTropolis {
static Common::SharedPtr<Obsidian::WordGameData> loadWinObsidianWordGameData() {
Common::File f;
if (!f.open("RSGKit.r95")) {
error("Couldn't open word game data file");
return nullptr;
}
Common::SharedPtr<Obsidian::WordGameData> wgData(new Obsidian::WordGameData());
Obsidian::WordGameLoadBucket buckets[] = {
{0, 0}, // 0 letters
{0x63D54, 0x63D5C}, // 1 letter
{0x63BF8, 0x63CA4}, // 2 letter
{0x627D8, 0x631E8}, // 3 letter
{0x5C2C8, 0x60628}, // 4 letter
{0x52F4C, 0x5919C}, // 5 letter
{0x47A64, 0x4F2FC}, // 6 letter
{0x3BC98, 0x43B20}, // 7 letter
{0x2DA78, 0x38410}, // 8 letter
{0x218F8, 0x2AA18}, // 9 letter
{0x19D78, 0x1FA18}, // 10 letter
{0x15738, 0x18BE8}, // 11 letter
{0x128A8, 0x14DE8}, // 12 letter
{0x1129C, 0x1243C}, // 13 letter
{0x10974, 0x110C4}, // 14 letter
{0x105EC, 0x108BC}, // 15 letter
{0x10454, 0x105A8}, // 16 letter
{0x103A8, 0x10434}, // 17 letter
{0x10348, 0x10398}, // 18 letter
{0, 0}, // 19 letter
{0x10328, 0x10340}, // 20 letter
{0x102EC, 0x1031C}, // 21 letter
{0x102D0, 0x102E8}, // 22 letter
};
if (!wgData->load(&f, buckets, 23, 4, true)) {
error("Failed to load word game data file");
return nullptr;
}
return wgData;
}
static bool loadCursorsFromPE(CursorGraphicCollection &cursorGraphics, Common::SeekableReadStream *stream) {
Common::SharedPtr<Common::WinResources> winRes(Common::WinResources::createFromEXE(stream));
if (!winRes) {
@ -131,6 +175,7 @@ struct MacObsidianResources : public ProjectResources {
void setup();
Common::SeekableReadStream *getSegmentStream(int index) const;
const Common::SharedPtr<CursorGraphicCollection> &getCursorGraphics() const;
const Common::SharedPtr<Obsidian::WordGameData> &getWordGameData() const;
private:
Common::MacResManager _installerResMan;
@ -141,6 +186,7 @@ private:
Common::SeekableReadStream *_segmentStreams[6];
Common::SharedPtr<CursorGraphicCollection> _cursorGraphics;
Common::SharedPtr<Obsidian::WordGameData> _wordGameData;
};
MacObsidianResources::MacObsidianResources() : _installerArchive(nullptr), _installerDataForkStream(nullptr) {
@ -197,6 +243,49 @@ void MacObsidianResources::setup() {
error("Failed to read cursor resources from file '%s'", fileName);
}
debug(1, "Loading word games...");
{
Common::ArchiveMemberPtr rsgKit = _installerArchive->getMember(Common::Path("RSGKit.rPP"));
if (!rsgKit)
error("Couldn't find word game file in installer archive");
_wordGameData.reset(new Obsidian::WordGameData());
Common::SharedPtr<Common::SeekableReadStream> stream(rsgKit->createReadStream());
if (!stream)
error("Failed to open word game file");
Obsidian::WordGameLoadBucket buckets[] = {
{0, 0}, // 0 letters
{0xD7C8, 0xD7CC}, // 1 letter
{0xD7CC, 0xD84D}, // 2 letter
{0xD84D, 0xE25D}, // 3 letter
{0x1008C, 0x12AA8}, // 4 letter
{0x14C58, 0x19614}, // 5 letter
{0x1C73C, 0x230C1}, // 6 letter
{0x26D10, 0x2EB98}, // 7 letter
{0x32ADC, 0x3AA0E}, // 8 letter
{0x3E298, 0x45B88}, // 9 letter
{0x48BE8, 0x4E0D0}, // 10 letter
{0x4FFB0, 0x53460}, // 11 letter
{0x545F0, 0x56434}, // 12 letter
{0x56D84, 0x57CF0}, // 13 letter
{0x58158, 0x58833}, // 14 letter
{0x58A08, 0x58CD8}, // 15 letter
{0x58D8C, 0x58EAD}, // 16 letter
{0x58EF4, 0x58F72}, // 17 letter
{0x58F90, 0x58FDC}, // 18 letter
{0, 0}, // 19 letter
{0x58FEC, 0x59001}, // 20 letter
{0x59008, 0x59034}, // 21 letter
{0x5903C, 0x59053}, // 22 letter
};
if (!_wordGameData->load(stream.get(), buckets, 23, 1, false))
error("Failed to load word game data");
}
debug(1, "Finished unpacking installer resources");
}
@ -204,10 +293,14 @@ Common::SeekableReadStream *MacObsidianResources::getSegmentStream(int index) co
return _segmentStreams[index];
}
const Common::SharedPtr<CursorGraphicCollection>& MacObsidianResources::getCursorGraphics() const {
const Common::SharedPtr<CursorGraphicCollection> &MacObsidianResources::getCursorGraphics() const {
return _cursorGraphics;
}
const Common::SharedPtr<Obsidian::WordGameData>& MacObsidianResources::getWordGameData() const {
return _wordGameData;
}
MacObsidianResources::~MacObsidianResources() {
for (int i = 0; i < 6; i++)
delete _segmentStreams[i];
@ -307,13 +400,15 @@ Common::Error MTropolisEngine::run() {
}
}
Common::SharedPtr<Obsidian::WordGameData> wgData = loadWinObsidianWordGameData();
desc->setCursorGraphics(cursors);
Common::SharedPtr<MTropolis::PlugIn> standardPlugIn = PlugIns::createStandard();
static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
desc->addPlugIn(standardPlugIn);
desc->addPlugIn(PlugIns::createObsidian());
desc->addPlugIn(PlugIns::createObsidian(wgData));
_runtime->queueProject(desc);
@ -345,7 +440,7 @@ Common::Error MTropolisEngine::run() {
static_cast<Standard::StandardPlugIn *>(standardPlugIn.get())->getHacks().allowGarbledListModData = true;
desc->addPlugIn(standardPlugIn);
desc->addPlugIn(PlugIns::createObsidian());
desc->addPlugIn(PlugIns::createObsidian(resources->getWordGameData()));
desc->setResources(resPtr);
desc->setCursorGraphics(resources->getCursorGraphics());

View File

@ -116,26 +116,281 @@ MiniscriptInstructionOutcome TextWorkModifier::writeRefAttribute(MiniscriptThrea
return Modifier::writeRefAttribute(thread, result, attrib);
}
Common::SharedPtr<Modifier> TextWorkModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new TextWorkModifier(*this));
}
ObsidianPlugIn::ObsidianPlugIn() : _movementModifierFactory(this), _rectShiftModifierFactory(this), _textWorkModifierFactory(this) {
DictionaryModifier::DictionaryModifier() : _plugIn(nullptr) {
}
bool DictionaryModifier::load(const PlugInModifierLoaderContext &context, const Data::Obsidian::DictionaryModifier &data) {
if (data.str.type != Data::PlugInTypeTaggedValue::kString)
return false;
_str = data.str.str;
if (data.index.type != Data::PlugInTypeTaggedValue::kInteger)
return false;
_index = data.index.value.asInt;
_isIndexResolved = true;
_plugIn = static_cast<ObsidianPlugIn *>(context.plugIn);
return true;
}
bool DictionaryModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "index") {
resolveStringIndex();
result.setInt(_index);
return true;
}
if (attrib == "string") {
result.setString(_str);
return true;
}
return Modifier::readAttribute(thread, result, attrib);
}
MiniscriptInstructionOutcome DictionaryModifier::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &result, const Common::String &attrib) {
if (attrib == "index") {
DynamicValueWriteFuncHelper<DictionaryModifier, &DictionaryModifier::scriptSetIndex>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
if (attrib == "string") {
DynamicValueWriteFuncHelper<DictionaryModifier, &DictionaryModifier::scriptSetString>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
return Modifier::writeRefAttribute(thread, result, attrib);
}
void DictionaryModifier::resolveStringIndex() {
if (_isIndexResolved)
return;
_index = 0;
_isIndexResolved = true;
const Common::Array<WordGameData::WordBucket> &wordBuckets = _plugIn->getWordGameData()->getWordBuckets();
size_t strLength = _str.size();
if (strLength >= wordBuckets.size())
return;
const WordGameData::WordBucket &bucket = wordBuckets[strLength];
size_t lowOffsetInclusive = 0;
size_t highOffsetExclusive = bucket.wordIndexes.size();
const char *strChars = _str.c_str();
// Binary search
while (lowOffsetInclusive != highOffsetExclusive) {
const size_t midOffset = (lowOffsetInclusive + highOffsetExclusive) / 2;
const char *chars = &bucket.chars[bucket.spacing * midOffset];
bool isMidGreater = false;
bool isMidLess = false;
for (size_t i = 0; i < strLength; i++) {
if (chars[i] > strChars[i]) {
isMidGreater = true;
break;
} else if (chars[i] < strChars[i]) {
isMidLess = true;
break;
}
}
if (isMidLess)
lowOffsetInclusive = midOffset + 1;
else if (isMidGreater)
highOffsetExclusive = midOffset;
else {
_index = bucket.wordIndexes[midOffset] + 1;
break;
}
}
}
MiniscriptInstructionOutcome DictionaryModifier::scriptSetString(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kString) {
thread->error("Tried to set dictionary string to something that wasn't a string");
return kMiniscriptInstructionOutcomeFailed;
}
if (_str != value.getString()) {
_str = value.getString();
_isIndexResolved = false;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome DictionaryModifier::scriptSetIndex(MiniscriptThread *thread, const DynamicValue &value) {
int32 asInteger = 0;
if (!value.roundToInt(asInteger)) {
thread->error("Tried to set dictionary index to something that wasn't a number");
return kMiniscriptInstructionOutcomeFailed;
}
_index = asInteger;
if (_index < 1)
_str.clear();
else {
const size_t indexAdjusted = _index - 1;
const Common::Array<WordGameData::SortedWord> &sortedWords = _plugIn->getWordGameData()->getSortedWords();
if (indexAdjusted >= sortedWords.size())
_str.clear();
else
_str = Common::String(sortedWords[indexAdjusted].chars, sortedWords[indexAdjusted].length);
}
_isIndexResolved = true;
return kMiniscriptInstructionOutcomeContinue;
}
Common::SharedPtr<Modifier> DictionaryModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new DictionaryModifier(*this));
}
WordMixerModifier::WordMixerModifier() {
}
bool WordMixerModifier::load(const PlugInModifierLoaderContext &context, const Data::Obsidian::WordMixerModifier &data) {
return true;
}
Common::SharedPtr<Modifier> WordMixerModifier::shallowClone() const {
return Common::SharedPtr<Modifier>(new WordMixerModifier(*this));
}
ObsidianPlugIn::ObsidianPlugIn(const Common::SharedPtr<WordGameData> &wgData)
: _movementModifierFactory(this), _rectShiftModifierFactory(this), _textWorkModifierFactory(this),
_dictionaryModifierFactory(this), _wordMixerModifierFactory(this), _wgData(wgData) {
}
void ObsidianPlugIn::registerModifiers(IPlugInModifierRegistrar *registrar) const {
registrar->registerPlugInModifier("Movement", &_movementModifierFactory);
registrar->registerPlugInModifier("rectshift", &_rectShiftModifierFactory);
registrar->registerPlugInModifier("TextWork", &_textWorkModifierFactory);
registrar->registerPlugInModifier("Dictionary", &_dictionaryModifierFactory);
registrar->registerPlugInModifier("WordMixer", &_wordMixerModifierFactory);
}
const Common::SharedPtr<WordGameData>& ObsidianPlugIn::getWordGameData() const {
return _wgData;
}
bool WordGameData::load(Common::SeekableReadStream *stream, const WordGameLoadBucket *buckets, uint numBuckets, uint alignment, bool backwards) {
_buckets.resize(numBuckets);
size_t totalWords = 0;
for (size_t i = 0; i < numBuckets; i++) {
const WordGameLoadBucket &inBucket = buckets[i];
WordBucket &outBucket = _buckets[i];
uint32 sizeBytes = inBucket.endAddress - inBucket.startAddress;
uint wordLength = i;
uint spacing = (wordLength + alignment) - (wordLength % alignment);
outBucket.spacing = spacing;
outBucket.chars.resize(sizeBytes);
assert(sizeBytes % alignment == 0);
if (sizeBytes > 0) {
if (!stream->seek(inBucket.startAddress, SEEK_SET))
return false;
stream->read(&outBucket.chars[0], sizeBytes);
}
uint numWords = sizeBytes / spacing;
outBucket.wordIndexes.resize(numWords);
if (backwards) {
for (size_t wordIndex = 0; wordIndex < numWords / 2; wordIndex++) {
char *swapA = &outBucket.chars[wordIndex * spacing];
char *swapB = &outBucket.chars[(numWords - 1 - wordIndex) * spacing];
for (size_t chIndex = 0; chIndex < wordLength; chIndex++) {
char temp = swapA[chIndex];
swapA[chIndex] = swapB[chIndex];
swapB[chIndex] = temp;
}
}
}
totalWords += numWords;
}
_sortedWords.resize(totalWords);
Common::Array<size_t> currentWordIndexes;
currentWordIndexes.resize(numBuckets);
for (size_t i = 0; i < numBuckets; i++)
currentWordIndexes[i] = 0;
for (size_t wordIndex = 0; wordIndex < totalWords; wordIndex++) {
size_t bestBucket = numBuckets;
const char *bestChars = nullptr;
for (size_t bucketIndex = 0; bucketIndex < numBuckets; bucketIndex++) {
size_t wordOffset = currentWordIndexes[bucketIndex] * _buckets[bucketIndex].spacing;
if (wordOffset < _buckets[bucketIndex].chars.size()) {
const char *candidate = &_buckets[bucketIndex].chars[wordOffset];
bool isWorse = true;
if (bestChars == nullptr)
isWorse = false;
else {
// The best bucket will always be shorter if it's set, so this is only better if it precedes it alphabetically
for (size_t i = 0; i < bestBucket; i++) {
if (candidate[i] > bestChars[i]) {
break;
} else if (candidate[i] < bestChars[i]) {
isWorse = false;
break;
}
}
}
if (!isWorse) {
bestBucket = bucketIndex;
bestChars = candidate;
}
}
}
assert(bestChars != nullptr);
const size_t bucketWordIndex = currentWordIndexes[bestBucket];
_buckets[bestBucket].wordIndexes[bucketWordIndex] = wordIndex;
currentWordIndexes[bestBucket]++;
_sortedWords[wordIndex].chars = bestChars;
_sortedWords[wordIndex].length = bestBucket;
}
return !stream->err();
}
const Common::Array<WordGameData::WordBucket> &WordGameData::getWordBuckets() const {
return _buckets;
}
const Common::Array<WordGameData::SortedWord>& WordGameData::getSortedWords() const {
return _sortedWords;
}
} // End of namespace ObsidianPlugIn
namespace PlugIns {
Common::SharedPtr<PlugIn> createObsidian() {
return Common::SharedPtr<PlugIn>(new Obsidian::ObsidianPlugIn());
Common::SharedPtr<PlugIn> createObsidian(const Common::SharedPtr<Obsidian::WordGameData> &wgData) {
return Common::SharedPtr<PlugIn>(new Obsidian::ObsidianPlugIn(wgData));
}
} // End of namespace PlugIns

View File

@ -31,6 +31,9 @@ namespace MTropolis {
namespace Obsidian {
class ObsidianPlugIn;
class WordGameData;
class MovementModifier : public Modifier {
public:
bool load(const PlugInModifierLoaderContext &context, const Data::Obsidian::MovementModifier &data);
@ -79,16 +82,91 @@ private:
int32 _lastChar;
};
class DictionaryModifier : public Modifier {
public:
DictionaryModifier();
bool load(const PlugInModifierLoaderContext &context, const Data::Obsidian::DictionaryModifier &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 "Dictionary Modifier"; }
#endif
private:
void resolveStringIndex();
MiniscriptInstructionOutcome scriptSetString(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome scriptSetIndex(MiniscriptThread *thread, const DynamicValue &value);
Common::SharedPtr<Modifier> shallowClone() const override;
Common::String _str;
const ObsidianPlugIn *_plugIn;
int32 _index;
bool _isIndexResolved;
};
class WordMixerModifier : public Modifier {
public:
WordMixerModifier();
bool load(const PlugInModifierLoaderContext &context, const Data::Obsidian::WordMixerModifier &data);
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "WordMixer Modifier"; }
#endif
private:
Common::SharedPtr<Modifier> shallowClone() const override;
};
class ObsidianPlugIn : public MTropolis::PlugIn {
public:
ObsidianPlugIn();
ObsidianPlugIn(const Common::SharedPtr<WordGameData> &wgData);
void registerModifiers(IPlugInModifierRegistrar *registrar) const override;
const Common::SharedPtr<WordGameData> &getWordGameData() const;
private:
PlugInModifierFactory<MovementModifier, Data::Obsidian::MovementModifier> _movementModifierFactory;
PlugInModifierFactory<RectShiftModifier, Data::Obsidian::RectShiftModifier> _rectShiftModifierFactory;
PlugInModifierFactory<TextWorkModifier, Data::Obsidian::TextWorkModifier> _textWorkModifierFactory;
PlugInModifierFactory<WordMixerModifier, Data::Obsidian::WordMixerModifier> _wordMixerModifierFactory;
PlugInModifierFactory<DictionaryModifier, Data::Obsidian::DictionaryModifier> _dictionaryModifierFactory;
Common::SharedPtr<WordGameData> _wgData;
};
struct WordGameLoadBucket {
uint32 startAddress;
uint32 endAddress;
};
class WordGameData {
public:
struct WordBucket {
Common::Array<char> chars;
Common::Array<uint16> wordIndexes;
uint32 spacing;
};
struct SortedWord {
const char *chars;
uint length;
};
bool load(Common::SeekableReadStream *stream, const WordGameLoadBucket *buckets, uint numBuckets, uint alignment, bool backwards);
const Common::Array<WordBucket> &getWordBuckets() const;
const Common::Array<SortedWord> &getSortedWords() const;
private:
Common::Array<WordBucket> _buckets;
Common::Array<SortedWord> _sortedWords;
};
} // End of namespace Obsidian

View File

@ -65,6 +65,23 @@ DataReadErrorCode TextWorkModifier::load(PlugIn& plugIn, const PlugInModifier& p
return kDataReadErrorNone;
}
DataReadErrorCode WordMixerModifier::load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) {
if (prefix.plugInRevision != 1)
return kDataReadErrorUnsupportedRevision;
return kDataReadErrorNone;
}
DataReadErrorCode DictionaryModifier::load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) {
if (prefix.plugInRevision != 1)
return kDataReadErrorUnsupportedRevision;
if (!str.load(reader) || !index.load(reader))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
}
} // End of namespace Obsidian
} // End of namespace Data

View File

@ -71,6 +71,19 @@ protected:
DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
};
struct DictionaryModifier : public PlugInModifierData {
PlugInTypeTaggedValue str;
PlugInTypeTaggedValue index;
protected:
DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
};
struct WordMixerModifier : public PlugInModifierData {
protected:
DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;
};
} // End of namespace Obsidian
} // End of namespace Data

View File

@ -812,6 +812,12 @@ VThreadState MidiModifier::consumeMessage(Runtime *runtime, const Common::Shared
}
}
}
if (_terminateWhen.respondsTo(msg->getEvent())) {
#ifdef MTROPOLIS_DEBUG_ENABLE
if (Debugger *debugger = runtime->debugGetDebugger())
debugger->notify(kDebugSeverityWarning, "MIDI player ordered to terminate, which isn't supported yet");
#endif
}
return kVThreadReturn;
}

View File

@ -50,6 +50,7 @@ public:
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Cursor Modifier"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
#endif
private:
@ -183,7 +184,7 @@ public:
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "MIDI Modifier"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
#endif
private:

View File

@ -28,12 +28,18 @@ class MidiDriver;
namespace MTropolis {
namespace Obsidian {
class WordGameData;
} // End of namespace Obsidian
class PlugIn;
namespace PlugIns {
Common::SharedPtr<PlugIn> createStandard();
Common::SharedPtr<PlugIn> createObsidian();
Common::SharedPtr<PlugIn> createObsidian(const Common::SharedPtr<Obsidian::WordGameData> &wgData);
} // End of namespace PlugIns

View File

@ -2136,7 +2136,10 @@ MiniscriptInstructionOutcome WorldManagerInterface::writeRefAttribute(Miniscript
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setCurrentScene>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
if (attrib == "refreshcursor") {
DynamicValueWriteFuncHelper<WorldManagerInterface, &WorldManagerInterface::setRefreshCursor>::create(this, result);
return kMiniscriptInstructionOutcomeContinue;
}
return RuntimeObject::writeRefAttribute(thread, result, attrib);
}
@ -2167,6 +2170,16 @@ MiniscriptInstructionOutcome WorldManagerInterface::setCurrentScene(MiniscriptTh
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome WorldManagerInterface::setRefreshCursor(MiniscriptThread *thread, const DynamicValue &value) {
if (value.getType() != DynamicValueTypes::kBoolean)
return kMiniscriptInstructionOutcomeFailed;
if (value.getBool())
thread->getRuntime()->forceCursorRefreshOnce();
return kMiniscriptInstructionOutcomeContinue;
}
SystemInterface::SystemInterface() : _masterVolume(kFullVolume) {
}
@ -3206,7 +3219,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, ISaveUIProvider *saveProv
_nextRuntimeGUID(1), _realDisplayMode(kColorDepthModeInvalid), _fakeDisplayMode(kColorDepthModeInvalid),
_displayWidth(1024), _displayHeight(768), _realTimeBase(0), _playTimeBase(0), _sceneTransitionState(kSceneTransitionStateNotTransitioning),
_lastFrameCursor(nullptr), _defaultCursor(new DefaultCursorGraphic()), _platform(kProjectPlatformUnknown),
_cachedMousePosition(Point16::create(0, 0)), _realMousePosition(Point16::create(0, 0)), _trackedMouseOutside(false), _haveModifierOverrideCursor(false) {
_cachedMousePosition(Point16::create(0, 0)), _realMousePosition(Point16::create(0, 0)), _trackedMouseOutside(false),
_forceCursorRefreshOnce(true), _haveModifierOverrideCursor(false) {
_random.reset(new Common::RandomSource("mtropolis"));
_vthread.reset(new VThread());
@ -3301,6 +3315,13 @@ bool Runtime::runFrame() {
continue;
}
if (_forceCursorRefreshOnce) {
_forceCursorRefreshOnce = false;
UpdateMousePositionTaskData *taskData = _vthread->pushTask("Runtime::updateMousePositionTask", this, &Runtime::updateMousePositionTask);
taskData->x = _cachedMousePosition.x;
taskData->y = _cachedMousePosition.y;
}
if (_queuedProjectDesc) {
Common::SharedPtr<ProjectDescription> desc = _queuedProjectDesc;
_queuedProjectDesc.reset();
@ -4573,6 +4594,10 @@ void Runtime::clearModifierCursorOverride() {
}
}
void Runtime::forceCursorRefreshOnce() {
_forceCursorRefreshOnce = true;
}
Common::RandomSource *Runtime::getRandom() const {
return _random.get();
}

View File

@ -1489,6 +1489,7 @@ public:
const Point16 &getCachedMousePosition() const;
void setModifierCursorOverride(uint32 cursorID);
void clearModifierCursorOverride();
void forceCursorRefreshOnce();
Common::RandomSource *getRandom() const;
WorldManagerInterface *getWorldManagerInterface() const;
@ -1670,6 +1671,7 @@ private:
Common::WeakPtr<Structural> _mouseOverObject;
Common::WeakPtr<Structural> _mouseTrackingObject;
bool _trackedMouseOutside;
bool _forceCursorRefreshOnce;
uint32 _modifierOverrideCursorID;
bool _haveModifierOverrideCursor;
@ -1768,6 +1770,7 @@ public:
private:
MiniscriptInstructionOutcome setCurrentScene(MiniscriptThread *thread, const DynamicValue &value);
MiniscriptInstructionOutcome setRefreshCursor(MiniscriptThread *thread, const DynamicValue &value);
};
class AssetManagerInterface : public RuntimeObject {