mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-03 17:33:05 +00:00
MTROPOLIS: Load data for Obsidian word games
This commit is contained in:
parent
790ed24635
commit
62accd5196
@ -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");
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user