From 07726745d47b1a1468abf7befb1f04756c908f7f Mon Sep 17 00:00:00 2001 From: elasota Date: Mon, 12 Jun 2023 22:44:49 -0400 Subject: [PATCH] MTROPOLIS: Implement Miniscript global references and MTI Shanghai minigame --- engines/mtropolis/miniscript.cpp | 108 +++++++-- engines/mtropolis/miniscript.h | 18 +- engines/mtropolis/plugin/mti.cpp | 335 +++++++++++++++++++++++++- engines/mtropolis/plugin/mti.h | 39 +++ engines/mtropolis/plugin/mti_data.cpp | 2 +- engines/mtropolis/plugin/mti_data.h | 4 +- 6 files changed, 479 insertions(+), 27 deletions(-) diff --git a/engines/mtropolis/miniscript.cpp b/engines/mtropolis/miniscript.cpp index 122f719bdf0..50a2afa3dc5 100644 --- a/engines/mtropolis/miniscript.cpp +++ b/engines/mtropolis/miniscript.cpp @@ -28,6 +28,35 @@ namespace MTropolis { +class MiniscriptInstructionParserFeedback : public IMiniscriptInstructionParserFeedback { +public: + explicit MiniscriptInstructionParserFeedback(Common::Array *globalRefs); + + uint registerGlobalGUIDIndex(uint32 guid) override; + +private: + Common::Array *_globalRefs; +}; + +MiniscriptInstructionParserFeedback::MiniscriptInstructionParserFeedback(Common::Array *globalRefs) : _globalRefs(globalRefs) { +} + +uint MiniscriptInstructionParserFeedback::registerGlobalGUIDIndex(uint32 guid) { + for (uint i = 0; i < _globalRefs->size(); i++) { + if ((*_globalRefs)[i].guid == guid) + return i; + } + + uint newIndex = _globalRefs->size(); + + MiniscriptReferences::GlobalRef globalRef; + globalRef.guid = guid; + + _globalRefs->push_back(globalRef); + + return newIndex; +} + bool miniscriptEvaluateTruth(const DynamicValue &value) { // NOTE: Comparing equal to "true" only passes for 1 exactly, but for conditions, // any non-zero value is true. @@ -45,21 +74,29 @@ bool miniscriptEvaluateTruth(const DynamicValue &value) { } } +IMiniscriptInstructionParserFeedback::~IMiniscriptInstructionParserFeedback() { +} + MiniscriptInstruction::~MiniscriptInstruction() { } MiniscriptReferences::LocalRef::LocalRef() : guid(0) { } -MiniscriptReferences::MiniscriptReferences(const Common::Array &localRefs) : _localRefs(localRefs) { +MiniscriptReferences::GlobalRef::GlobalRef() : guid(0) { +} + +MiniscriptReferences::MiniscriptReferences(const Common::Array &localRefs, const Common::Array &globalRefs) : _localRefs(localRefs), _globalRefs(globalRefs) { } void MiniscriptReferences::linkInternalReferences(ObjectLinkingScope *scope) { // Resolve using name lookups since there are some known cases where the GUID is broken // e.g. "bArriveFromCutScene" in "Set bArriveFromCutScene on PE" in Obsidian - for (Common::Array::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it) { + for (Common::Array::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it) it->resolution = scope->resolve(it->guid, it->name, false); - } + + for (Common::Array::iterator it = _globalRefs.begin(), itEnd = _globalRefs.end(); it != itEnd; ++it) + it->resolution = scope->resolve(it->guid, "", true); } void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *visitor) { @@ -77,6 +114,21 @@ void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor * } } } + + for (GlobalRef &ref : _globalRefs) { + Common::SharedPtr obj = ref.resolution.lock(); + if (obj) { + if (obj->isModifier()) { + Common::WeakPtr mod = obj.staticCast(); + visitor->visitWeakModifierRef(mod); + ref.resolution = mod; + } else if (obj->isStructural()) { + Common::WeakPtr struc = obj.staticCast(); + visitor->visitWeakStructuralRef(struc); + ref.resolution = struc; + } + } + } } Common::WeakPtr MiniscriptReferences::getRefByIndex(uint index) const { @@ -85,6 +137,13 @@ Common::WeakPtr MiniscriptReferences::getRefByIndex(uint index) c return _localRefs[index].resolution; } +Common::WeakPtr MiniscriptReferences::getGlobalRefByIndex(uint index) const { + if (index >= _globalRefs.size()) + return Common::WeakPtr(); + return _globalRefs[index].resolution; + +} + MiniscriptProgram::MiniscriptProgram(const Common::SharedPtr > &programData, const Common::Array &instructions, const Common::Array &attributes) : _programData(programData), _instructions(instructions), _attributes(attributes) { } @@ -105,18 +164,18 @@ const Common::Array &MiniscriptProgram::getAttribu template struct MiniscriptInstructionLoader { - static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader); + static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback); }; template -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { // Default loader for simple instructions with no private data new (dest) T(); return true; } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { Data::Event dataEvent; if (!dataEvent.load(instrDataReader)) return false; @@ -135,7 +194,7 @@ bool MiniscriptInstructionLoader::loadInstruction( } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint32 functionID; if (!instrDataReader.readU32(functionID)) return false; @@ -148,7 +207,7 @@ bool MiniscriptInstructionLoader::loadInstr } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint32 childAttribute; if (!instrDataReader.readU32(childAttribute)) return false; @@ -158,7 +217,7 @@ bool MiniscriptInstructionLoader::loadInstruct } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint32 globalID; if (!instrDataReader.readU32(globalID)) return false; @@ -168,7 +227,7 @@ bool MiniscriptInstructionLoader::loadInstru } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint32 jumpFlags, unknown, instrOffset; if (!instrDataReader.readU32(jumpFlags) || !instrDataReader.readU32(unknown) || !instrDataReader.readU32(instrOffset)) return false; @@ -185,7 +244,7 @@ bool MiniscriptInstructionLoader::loadInstruction( } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint16 dataType; if (!instrDataReader.readU16(dataType)) return false; @@ -217,7 +276,9 @@ bool MiniscriptInstructionLoader::loadInstruc if (!instrDataReader.readU32(refValue)) return false; - new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &refValue, (instrFlags & 1) != 0); + uint32 indexedRef = feedback.registerGlobalGUIDIndex(refValue); + + new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &indexedRef, (instrFlags & 1) != 0); } else if (dataType == 0x1d) { MiniscriptInstructions::PushValue::Label label; if (!instrDataReader.readU32(label.superGroup) || !instrDataReader.readU32(label.id)) @@ -231,7 +292,7 @@ bool MiniscriptInstructionLoader::loadInstruc } template<> -bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader) { +bool MiniscriptInstructionLoader::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) { uint16 strLength; if (!instrDataReader.readU16(strLength)) return false; @@ -247,14 +308,14 @@ bool MiniscriptInstructionLoader::loadInstru } struct SIMiniscriptInstructionFactory { - bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr); + bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback); void (*getSizeAndAlignment)(size_t &outSize, size_t &outAlignment); }; template class MiniscriptInstructionFactory { public: - static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr); + static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback); static void getSizeAndAlignment(size_t &outSize, size_t &outAlignment); static SIMiniscriptInstructionFactory *getInstance(); @@ -264,8 +325,8 @@ private: }; template -bool MiniscriptInstructionFactory::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr) { - if (!MiniscriptInstructionLoader::loadInstruction(dest, instrFlags, instrDataReader)) +bool MiniscriptInstructionFactory::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback) { + if (!MiniscriptInstructionLoader::loadInstruction(dest, instrFlags, instrDataReader, feedback)) return false; outMiniscriptInstructionPtr = static_cast(static_cast(dest)); @@ -295,6 +356,7 @@ MiniscriptParser::InstructionData::InstructionData() bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::SharedPtr &outProgram, Common::SharedPtr &outReferences) { Common::Array localRefs; + Common::Array globalRefs; Common::Array attributes; Common::SharedPtr > programDataPtr; Common::Array miniscriptInstructions; @@ -302,7 +364,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha // If the program is empty then just return an empty program if (program.bytecode.size() == 0 || program.numOfInstructions == 0) { outProgram = Common::SharedPtr(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes)); - outReferences = Common::SharedPtr(new MiniscriptReferences(localRefs)); + outReferences = Common::SharedPtr(new MiniscriptReferences(localRefs, globalRefs)); return true; } @@ -377,6 +439,8 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha miniscriptInstructions.resize(program.numOfInstructions); + MiniscriptInstructionParserFeedback parserFeedback(&globalRefs); + // Create instructions for (size_t i = 0; i < program.numOfInstructions; i++) { const InstructionData &rawInstruction = rawInstructions[i]; @@ -388,7 +452,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha Common::MemoryReadStreamEndian instrContentsStream(static_cast(dataLoc), rawInstruction.contents.size(), reader.isBigEndian()); Data::DataReader instrContentsReader(0, instrContentsStream, reader.getProjectFormat()); - if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i])) { + if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i], parserFeedback)) { // Destroy any already-created instructions for (size_t di = 0; di < i; di++) { miniscriptInstructions[i - 1 - di]->~MiniscriptInstruction(); @@ -400,7 +464,7 @@ bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::Sha // Done outProgram = Common::SharedPtr(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes)); - outReferences = Common::SharedPtr(new MiniscriptReferences(localRefs)); + outReferences = Common::SharedPtr(new MiniscriptReferences(localRefs, globalRefs)); return true; } @@ -1662,8 +1726,8 @@ MiniscriptInstructionOutcome PushValue::execute(MiniscriptThread *thread) const value.setObject(ObjectReference(thread->getRefs()->getRefByIndex(_value.ref))); break; case DataType::kDataTypeGlobalRef: - thread->error("Global references are not implemented"); - return kMiniscriptInstructionOutcomeFailed; + value.setObject(ObjectReference(thread->getRefs()->getGlobalRefByIndex(_value.ref))); + break; case DataType::kDataTypeLabel: { MTropolis::Label label; label.id = _value.lbl.id; diff --git a/engines/mtropolis/miniscript.h b/engines/mtropolis/miniscript.h index 7ff7c1a0193..49c06fddfea 100644 --- a/engines/mtropolis/miniscript.h +++ b/engines/mtropolis/miniscript.h @@ -40,6 +40,13 @@ public: virtual MiniscriptInstructionOutcome execute(MiniscriptThread *thread) const = 0; }; +class IMiniscriptInstructionParserFeedback { +public: + virtual ~IMiniscriptInstructionParserFeedback(); + + virtual uint registerGlobalGUIDIndex(uint32 guid) = 0; +}; + class MiniscriptReferences { public: struct LocalRef { @@ -50,15 +57,24 @@ public: Common::WeakPtr resolution; }; - explicit MiniscriptReferences(const Common::Array &localRefs); + struct GlobalRef { + GlobalRef(); + + uint32 guid; + Common::WeakPtr resolution; + }; + + explicit MiniscriptReferences(const Common::Array &localRefs, const Common::Array &globalRefs); void linkInternalReferences(ObjectLinkingScope *scope); void visitInternalReferences(IStructuralReferenceVisitor *visitor); Common::WeakPtr getRefByIndex(uint index) const; + Common::WeakPtr getGlobalRefByIndex(uint index) const; private: Common::Array _localRefs; + Common::Array _globalRefs; }; diff --git a/engines/mtropolis/plugin/mti.cpp b/engines/mtropolis/plugin/mti.cpp index 674442c563f..3315f4e9f12 100644 --- a/engines/mtropolis/plugin/mti.cpp +++ b/engines/mtropolis/plugin/mti.cpp @@ -26,21 +26,147 @@ #include "mtropolis/miniscript.h" +#include "common/random.h" + namespace MTropolis { namespace MTI { + + +/* +Board layout: + +Layer 0: + 0 1 2 3 4 5 6 7 8 9 10 11 12 + +-----+-----+-----+-----+-----+-----+ +0 + 0 | 1 | 2 | 3 | 4 | 5 | +1 +-----+-----+-----+-----+-----+-----+ +2 +-----+ 7 | 8 | 9 | 10 +-----+-----+ +3 | 6 +-----+-----+-----+-----+ 11 | 12 | +4 +-----+ 13 | 14 | 15 | 16 +-----+-----+ +5 +-----+-----+-----+-----+-----+-----+ +6 + 17 | 18 | 19 | 20 | 21 | 22 | + +-----+-----+-----+-----+-----+-----+ + +Layer 1: + 0 1 2 3 4 5 6 7 8 9 10 11 12 + +0 +1 +-----+-----+ +2 | 23 | 24 | +3 +-----+-----+ +4 | 25 | 26 | +5 +-----+-----+ +6 + +Layer 2: + 0 1 2 3 4 5 6 7 8 9 10 11 12 + +0 +1 +2 +-----+ +3 | 27 | +4 +-----+ +5 +6 + +*/ + +ShanghaiModifier::TileCoordinate ShanghaiModifier::_tileCoordinates[ShanghaiModifier::kNumTiles] = { + {0, 0, 0}, + {2, 0, 0}, + {4, 0, 0}, + {6, 0, 0}, + {8, 0, 0}, + {10, 0, 0}, + + {0, 3, 0}, + {2, 2, 0}, + {4, 2, 0}, + {6, 2, 0}, + {8, 2, 0}, + {10, 3, 0}, + {12, 3, 0}, + + {2, 4, 0}, + {4, 4, 0}, + {6, 4, 0}, + {8, 4, 0}, + + {0, 6, 0}, + {2, 6, 0}, + {4, 6, 0}, + {6, 6, 0}, + {8, 6, 0}, + {10, 6, 0}, + + {4, 2, 0}, + {6, 2, 0}, + + {4, 4, 1}, + {6, 4, 1}, + + {5, 3, 2}, +}; ShanghaiModifier::ShanghaiModifier() { + for (uint x = 0; x < kBoardSizeX; x++) + for (uint y = 0; y < kBoardSizeY; y++) + for (uint z = 0; z < kBoardSizeZ; z++) + _tileAtCoordinate[x][y][z] = -1; + + for (uint i = 0; i < kNumTiles; i++) { + const TileCoordinate &coord = _tileCoordinates[i]; + assert(coord.x < kBoardSizeX); + assert(coord.y < kBoardSizeY); + assert(coord.z < kBoardSizeZ); + _tileAtCoordinate[coord.x][coord.y][coord.z] = i; + } } ShanghaiModifier::~ShanghaiModifier() { } bool ShanghaiModifier::respondsToEvent(const Event &evt) const { + if (_resetTileSetWhen.respondsTo(evt)) + return true; + return false; } VThreadState ShanghaiModifier::consumeMessage(Runtime *runtime, const Common::SharedPtr &msg) { + if (_resetTileSetWhen.respondsTo(msg->getEvent())) { + uint tileFaces[kNumTiles]; + + resetTiles(*runtime->getRandom(), tileFaces); + + Modifier *varMod = this->_tileSetRef.resolution.lock().get(); + + if (varMod == nullptr || !varMod->isVariable()) { + warning("Shanghai reset var ref was unavailable"); + return kVThreadError; + } + + VariableModifier *var = static_cast(varMod); + + Common::SharedPtr list(new DynamicList()); + + for (uint i = 0; i < kNumTiles; i++) { + DynamicValue tileValue; + tileValue.setInt(tileFaces[i]); + + list->setAtIndex(i, tileValue); + } + + DynamicValue listValue; + listValue.setList(list); + + MiniscriptThread thread(runtime, nullptr, nullptr, nullptr, this); + var->varSetValue(&thread, listValue); + + return kVThreadReturn; + } + return kVThreadReturn; } @@ -48,9 +174,217 @@ void ShanghaiModifier::disable(Runtime *runtime) { } bool ShanghaiModifier::load(const PlugInModifierLoaderContext &context, const Data::MTI::ShanghaiModifier &data) { + if (data.resetWhen.type != Data::PlugInTypeTaggedValue::kEvent) + return false; + + if (!_resetTileSetWhen.load(data.resetWhen.value.asEvent)) + return false; + + if (data.tileSetVar.type != Data::PlugInTypeTaggedValue::kVariableReference) + return false; + + _tileSetRef = VarReference(data.tileSetVar.value.asVarRefGUID, ""); + return true; } +void ShanghaiModifier::linkInternalReferences(ObjectLinkingScope *scope) { + _tileSetRef.linkInternalReferences(scope); +} + +void ShanghaiModifier::visitInternalReferences(IStructuralReferenceVisitor *visitor) { + _tileSetRef.visitInternalReferences(visitor); +} + +void ShanghaiModifier::resetTiles(Common::RandomSource &rng, uint (&tileFaces)[kNumTiles]) const { + uint possibleFaces[kNumFaces]; + uint numPossibleFaces = kNumFaces; + + for (uint i = 0; i < kNumFaces; i++) + possibleFaces[i] = i + 1; + + uint facesToInsert[kNumTiles / 2]; + uint numFacesToInsert = kNumTiles / 2; + + // Pick random faces, each one gets inserted twice + for (uint i = 0; i < kNumTiles / 4u; i++) { + uint faceToInsert = selectAndRemoveOne(rng, possibleFaces, numPossibleFaces); + facesToInsert[i * 2 + 0] = faceToInsert; + facesToInsert[i * 2 + 1] = faceToInsert; + } + + // We build the board by adding all tiles and then randomly picking 2 exposed tiles and + // assigning them a matching pair. A pair is only valid if the resulting board state has + // valid moves. + BoardState_t boardState = emptyBoardState(); + for (uint i = 0; i < kNumTiles; i++) + boardState = boardState | boardStateBit(i); + + for (uint pair = 0; pair < kNumTiles / 2u; pair++) { + uint exposedTiles[kNumTiles]; + uint numExposedTiles = 0; + + for (uint i = 0; i < kNumTiles; i++) { + if (boardState & boardStateBit(i)) { + if (tileIsExposed(boardState, i)) + exposedTiles[numExposedTiles++] = i; + } + } + + uint firstExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles); + + BoardState_t withFirstRemoved = boardState ^ boardStateBit(firstExposedTile); + + uint secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles); + BoardState_t withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile); + + if (numExposedTiles > 0) { + // If this isn't the last move, validate that this won't result in a stuck board state (e.g. only one tile exposed) + // If it would result in such a state, pick a different move. + for (;;) { + if (boardStateHasValidMove(withBothRemoved)) + break; + + if (numExposedTiles == 0) { + error("Shanghai board creation failed, board state was %x, removed %u to produce board state %x", static_cast(boardState), firstExposedTile, static_cast(withFirstRemoved)); + break; + } + + secondExposedTile = selectAndRemoveOne(rng, exposedTiles, numExposedTiles); + withBothRemoved = withFirstRemoved ^ boardStateBit(secondExposedTile); + } + } + + boardState = withBothRemoved; + + uint faceToInsert = selectAndRemoveOne(rng, facesToInsert, numFacesToInsert); + tileFaces[firstExposedTile] = faceToInsert; + tileFaces[secondExposedTile] = faceToInsert; + } +} + +uint ShanghaiModifier::selectAndRemoveOne(Common::RandomSource &rng, uint *valuesList, uint &listSize) { + if (listSize == 0) { + error("Internal error: selectAndRemoveOne ran out of values"); + return 0; + } + + if (listSize == 1) { + listSize = 0; + return valuesList[0]; + } + + uint selectedIndex = rng.getRandomNumber(listSize - 1); + uint selectedValue = valuesList[selectedIndex]; + + valuesList[selectedIndex] = valuesList[listSize - 1]; + listSize--; + + return selectedValue; +} + +bool ShanghaiModifier::boardStateHasValidMove(BoardState_t boardState) const { + uint numExposedTiles = 0; + for (uint i = 0; i < kNumTiles; i++) { + if (boardState & boardStateBit(i)) { + if (tileIsExposed(boardState, i)) { + numExposedTiles++; + if (numExposedTiles == 2) + return true; + } + } + } + + return false; +} + +bool ShanghaiModifier::tileIsExposed(BoardState_t boardState, uint tile) const { + uint tileX = _tileCoordinates[tile].x; + uint tileY = _tileCoordinates[tile].y; + uint tileZ = _tileCoordinates[tile].z; + + uint blockMinY = tileY; + uint blockMaxY = tileY; + if (blockMinY > 0) + blockMinY--; + if (blockMaxY < kBoardSizeY - 1u) + blockMaxY++; + + bool blockedOnLeft = false; + + if (tileX >= 2) { + // Check for left-side blocks + for (uint y = blockMinY; y <= blockMaxY; y++) { + if (tileExistsAtCoordinate(boardState, tileX - 2, y, tileZ)) { + blockedOnLeft = true; + break; + } + } + } + + if (blockedOnLeft) { + bool blockedOnRight = false; + + // Check for right-side blocks + if (tileX < kBoardSizeX - 2u) { + for (uint y = blockMinY; y <= blockMaxY; y++) { + if (tileExistsAtCoordinate(boardState, tileX + 2, y, tileZ)) { + blockedOnRight = true; + break; + } + } + } + + // Tile is blocked on left and right + if (blockedOnRight) + return false; + } + + // Check upper blocks + uint blockMinX = tileX; + uint blockMaxX = tileX; + if (blockMinX > 0) + blockMinX--; + if (blockMaxX < kBoardSizeX - 1u) + blockMaxX++; + + for (uint z = tileZ + 1; z < kBoardSizeZ; z++) { + for (uint x = blockMinX; x <= blockMaxX; x++) { + for (uint y = blockMinY; y <= blockMaxY; y++) { + if (tileExistsAtCoordinate(boardState, x, y, z)) + return false; + } + } + } + + return true; +} + +bool ShanghaiModifier::tileExistsAtCoordinate(BoardState_t boardState, uint x, uint y, uint z) const { + assert(x < kBoardSizeX); + assert(y < kBoardSizeY); + assert(z < kBoardSizeZ); + + int8 tile = _tileAtCoordinate[x][y][z]; + + if (tile < 0) + return false; + + if (boardState & boardStateBit(static_cast(tile))) + return true; + + return false; +} + +ShanghaiModifier::BoardState_t ShanghaiModifier::boardStateBit(uint bit) { + return static_cast(1) << bit; +} + +ShanghaiModifier::BoardState_t ShanghaiModifier::emptyBoardState() { + return 0; +} + + #ifdef MTROPOLIS_DEBUG_ENABLE void ShanghaiModifier::debugInspect(IDebugInspectionReport *report) const { } @@ -64,7 +398,6 @@ const char *ShanghaiModifier::getDefaultName() const { return "Shanghai Modifier"; // ??? } - PrintModifier::PrintModifier() { } diff --git a/engines/mtropolis/plugin/mti.h b/engines/mtropolis/plugin/mti.h index f48a65df086..1b49e60451a 100644 --- a/engines/mtropolis/plugin/mti.h +++ b/engines/mtropolis/plugin/mti.h @@ -27,6 +27,12 @@ #include "mtropolis/plugin/mti_data.h" #include "mtropolis/runtime.h" +namespace Common { + +class RandomSource; + +} // End of namespace Common + namespace MTropolis { namespace MTI { @@ -44,14 +50,47 @@ public: bool load(const PlugInModifierLoaderContext &context, const Data::MTI::ShanghaiModifier &data); + void linkInternalReferences(ObjectLinkingScope *scope) override; + void visitInternalReferences(IStructuralReferenceVisitor *visitor) override; + #ifdef MTROPOLIS_DEBUG_ENABLE const char *debugGetTypeName() const override { return "Shanghai Modifier"; } void debugInspect(IDebugInspectionReport *report) const override; #endif private: + static const uint kNumTiles = 28; + static const uint kNumFaces = 26; + + typedef uint32 BoardState_t; + + static const uint kBoardSizeX = 13; + static const uint kBoardSizeY = 7; + static const uint kBoardSizeZ = 3; + + struct TileCoordinate { + uint x; + uint y; + uint z; + }; + Common::SharedPtr shallowClone() const override; const char *getDefaultName() const override; + + void resetTiles(Common::RandomSource &rng, uint (&tileFaces)[kNumTiles]) const; + static uint selectAndRemoveOne(Common::RandomSource &rng, uint *valuesList, uint &listSize); + bool boardStateHasValidMove(BoardState_t boardState) const; + bool tileIsExposed(BoardState_t boardState, uint tile) const; + bool tileExistsAtCoordinate(BoardState_t boardState, uint x, uint y, uint z) const; + + static BoardState_t boardStateBit(uint bit); + static BoardState_t emptyBoardState(); + + Event _resetTileSetWhen; + VarReference _tileSetRef; + + static TileCoordinate _tileCoordinates[kNumTiles]; + int8 _tileAtCoordinate[kBoardSizeX][kBoardSizeY][kBoardSizeZ]; }; class PrintModifier : public Modifier { diff --git a/engines/mtropolis/plugin/mti_data.cpp b/engines/mtropolis/plugin/mti_data.cpp index ddfbef9cf35..2c2f9bf0751 100644 --- a/engines/mtropolis/plugin/mti_data.cpp +++ b/engines/mtropolis/plugin/mti_data.cpp @@ -31,7 +31,7 @@ DataReadErrorCode ShanghaiModifier::load(PlugIn &plugIn, const PlugInModifier &p if (prefix.plugInRevision != 0) return kDataReadErrorUnsupportedRevision; - if (!unknown1Event.load(reader) || !unknown2VarRef.load(reader)) + if (!resetWhen.load(reader) || !tileSetVar.load(reader)) return kDataReadErrorReadFailed; return kDataReadErrorNone; diff --git a/engines/mtropolis/plugin/mti_data.h b/engines/mtropolis/plugin/mti_data.h index f1f94188b52..dc7912da69c 100644 --- a/engines/mtropolis/plugin/mti_data.h +++ b/engines/mtropolis/plugin/mti_data.h @@ -34,8 +34,8 @@ namespace MTI { // Shanghai - ??? struct ShanghaiModifier : public PlugInModifierData { - PlugInTypeTaggedValue unknown1Event; // Probably "Enable When" - PlugInTypeTaggedValue unknown2VarRef; // VarRef (Probably tile set) + PlugInTypeTaggedValue resetWhen; // Reset When + PlugInTypeTaggedValue tileSetVar; // VarRef protected: DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override;