diff --git a/engines/mtropolis/miniscript.cpp b/engines/mtropolis/miniscript.cpp index c565cbbc755..5925b0bdbc9 100644 --- a/engines/mtropolis/miniscript.cpp +++ b/engines/mtropolis/miniscript.cpp @@ -22,6 +22,7 @@ #include "mtropolis/miniscript.h" #include "common/config-manager.h" +#include "common/random.h" #include "common/memstream.h" namespace MTropolis { @@ -858,6 +859,245 @@ MiniscriptInstructionOutcome OrderedCompareInstruction::execute(MiniscriptThread BuiltinFunc::BuiltinFunc(BuiltinFunctionID bfid) : _funcID(bfid) { } +MiniscriptInstructionOutcome BuiltinFunc::execute(MiniscriptThread *thread) const { + size_t stackArgsNeeded = 1; + bool returnsValue = true; + + if (thread->getStackSize() < stackArgsNeeded) { + thread->error("Stack underflow"); + return kMiniscriptInstructionOutcomeFailed; + } + + for (size_t i = 0; i < stackArgsNeeded; i++) { + MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(i, false); + if (outcome != kMiniscriptInstructionOutcomeContinue) + return outcome; + } + + DynamicValue staticDest; + DynamicValue *dest = nullptr; + + if (returnsValue) { + if (stackArgsNeeded > 0) + dest = &thread->getStackValueFromTop(stackArgsNeeded - 1).value; + else + dest = &staticDest; + } + + MiniscriptInstructionOutcome outcome = executeFunction(thread, dest); + if (outcome != kMiniscriptInstructionOutcomeContinue) + return outcome; + + if (stackArgsNeeded > 0) { + size_t valuesToPop = stackArgsNeeded; + if (returnsValue) + valuesToPop--; + + if (valuesToPop > 0) + thread->popValues(valuesToPop); + } else { + if (returnsValue) + thread->pushValue(staticDest); + } + + return kMiniscriptInstructionOutcomeContinue; +} + +MiniscriptInstructionOutcome BuiltinFunc::executeFunction(MiniscriptThread *thread, DynamicValue *returnValue) const { + switch (_funcID) { + case kSin: + case kCos: + case kRandom: + case kSqrt: + case kTan: + case kAbs: + case kSign: + case kArctangent: + case kExp: + case kLn: + case kLog: + case kCosH: + case kSinH: + case kTanH: + case kTrunc: + case kRound: + return executeSimpleNumericInstruction(thread, returnValue); + case kRect2Polar: + return executeRectToPolar(thread, returnValue); + case kPolar2Rect: + return executePolarToRect(thread, returnValue); + case kNum2Str: + return executeNum2Str(thread, returnValue); + case kStr2Num: + return executeStr2Num(thread, returnValue); + default: + thread->error("Unimplemented built-in function"); + return kMiniscriptInstructionOutcomeFailed; + } +} + +MiniscriptInstructionOutcome BuiltinFunc::executeSimpleNumericInstruction(MiniscriptThread *thread, DynamicValue *returnValue) const { + double result = 0.0; + + double input = 0.0; + const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; + + switch (inputDynamicValue.getType()) { + case DynamicValueTypes::kInteger: + input = inputDynamicValue.getInt(); + break; + case DynamicValueTypes::kFloat: + input = inputDynamicValue.getFloat(); + break; + default: + thread->error("Invalid numeric function input type"); + return kMiniscriptInstructionOutcomeFailed; + } + + switch (_funcID) { + case kSin: + result = sin(input * (M_PI / 180.0)); + break; + case kCos: + result = cos(input * (M_PI / 180.0)); + break; + case kRandom: + if (input < 1.5) + result = 0.0; + else { + uint rngMax = static_cast(floor(input + 0.5)) - 1; + result = thread->getRuntime()->getRandom()->getRandomNumber(rngMax); + } + break; + case kSqrt: + result = sqrt(input); + break; + case kTan: + result = tan(input * (M_PI / 180.0)); + break; + case kAbs: + result = fabs(input); + break; + case kSign: + if (input < 0.0) + result = -1; + else if (input > 0.0) + result = 1; + else + result = 0; + break; + case kArctangent: + result = atan(input) * (180.0 / M_PI); + break; + case kExp: + result = exp(input); + break; + case kLn: + result = log(input); + break; + case kLog: + result = log10(input); + break; + case kCosH: + result = cosh(input * (M_PI / 180.0)); + break; + case kSinH: + result = sinh(input * (M_PI / 180.0)); + break; + case kTanH: + result = tanh(input * (M_PI / 180.0)); + break; + case kTrunc: + result = trunc(input); + break; + case kRound: + result = round(input); + break; + default: + thread->error("Unimplemented numeric function"); + return kMiniscriptInstructionOutcomeFailed; + } + + returnValue->setFloat(result); + + return kMiniscriptInstructionOutcomeContinue; +} + +MiniscriptInstructionOutcome BuiltinFunc::executeRectToPolar(MiniscriptThread *thread, DynamicValue *returnValue) const { + const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; + + if (inputDynamicValue.getType() != DynamicValueTypes::kPoint) { + thread->error("Polar to rect input must be a vector"); + return kMiniscriptInstructionOutcomeFailed; + } + + const Point16 &pt = inputDynamicValue.getPoint(); + + double angle = atan2(pt.x, pt.y); + double magnitude = sqrt(pt.x * pt.x + pt.y * pt.y); + + returnValue->setVector(AngleMagVector::create(angle, magnitude)); + + return kMiniscriptInstructionOutcomeContinue; +} + +MiniscriptInstructionOutcome BuiltinFunc::executePolarToRect(MiniscriptThread *thread, DynamicValue *returnValue) const { + const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; + + if (inputDynamicValue.getType() != DynamicValueTypes::kVector) { + thread->error("Polar to rect input must be a vector"); + return kMiniscriptInstructionOutcomeFailed; + } + + const AngleMagVector &vec = inputDynamicValue.getVector(); + + double x = cos(vec.angleRadians) * vec.magnitude; + double y = sin(vec.angleRadians) * vec.magnitude; + + returnValue->setPoint(Point16::create(static_cast(round(x)), static_cast(round(y)))); + + return kMiniscriptInstructionOutcomeContinue; +} + +MiniscriptInstructionOutcome BuiltinFunc::executeNum2Str(MiniscriptThread *thread, DynamicValue *returnValue) const { + Common::String result; + + const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; + switch (inputDynamicValue.getType()) { + case DynamicValueTypes::kInteger: + result.format("%i", static_cast(inputDynamicValue.getInt())); + break; + case DynamicValueTypes::kFloat: + result.format("%g", static_cast(inputDynamicValue.getFloat())); + break; + default: + thread->error("Invalid input value to num2str"); + return kMiniscriptInstructionOutcomeFailed; + } + + return kMiniscriptInstructionOutcomeContinue; +} + +MiniscriptInstructionOutcome BuiltinFunc::executeStr2Num(MiniscriptThread *thread, DynamicValue *returnValue) const { + double result = 0.0; + + const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value; + if (inputDynamicValue.getType() != DynamicValueTypes::kString) { + thread->error("Invalid input value to str2num"); + return kMiniscriptInstructionOutcomeFailed; + } + + const Common::String &str = inputDynamicValue.getString(); + if (str.size() == 0 || !sscanf(str.c_str(), "%lf", &result)) { + thread->error("Couldn't parse number"); + return kMiniscriptInstructionOutcomeFailed; + } + + returnValue->setFloat(result); + + return kMiniscriptInstructionOutcomeContinue; +} + MiniscriptInstructionOutcome StrConcat::execute(MiniscriptThread *thread) const { if (thread->getStackSize() < 2) { thread->error("Stack underflow"); diff --git a/engines/mtropolis/miniscript.h b/engines/mtropolis/miniscript.h index ee87264bd22..462bc271177 100644 --- a/engines/mtropolis/miniscript.h +++ b/engines/mtropolis/miniscript.h @@ -246,6 +246,15 @@ namespace MiniscriptInstructions { explicit BuiltinFunc(BuiltinFunctionID bfid); private: + MiniscriptInstructionOutcome execute(MiniscriptThread *thread) const override; + + MiniscriptInstructionOutcome executeFunction(MiniscriptThread *thread, DynamicValue *returnValue) const; + MiniscriptInstructionOutcome executeSimpleNumericInstruction(MiniscriptThread *thread, DynamicValue *returnValue) const; + MiniscriptInstructionOutcome executeRectToPolar(MiniscriptThread *thread, DynamicValue *returnValue) const; + MiniscriptInstructionOutcome executePolarToRect(MiniscriptThread *thread, DynamicValue *returnValue) const; + MiniscriptInstructionOutcome executeNum2Str(MiniscriptThread *thread, DynamicValue *returnValue) const; + MiniscriptInstructionOutcome executeStr2Num(MiniscriptThread *thread, DynamicValue *returnValue) const; + BuiltinFunctionID _funcID; }; diff --git a/engines/mtropolis/plugin/standard_data.cpp b/engines/mtropolis/plugin/standard_data.cpp index a70509800fc..c2b9c8ef2df 100644 --- a/engines/mtropolis/plugin/standard_data.cpp +++ b/engines/mtropolis/plugin/standard_data.cpp @@ -43,10 +43,9 @@ DataReadErrorCode STransCtModifier::load(PlugIn &plugIn, const PlugInModifier &p if (prefix.plugInRevision != 0) return kDataReadErrorUnsupportedRevision; - if (!reader.readU16(unknown1) || !unknown2.load(reader) || !reader.readU16(unknown3) || !unknown4.load(reader) - || !reader.readU16(unknown5) || !reader.readU32(unknown6) || !reader.readU16(unknown7) || !reader.readU32(unknown8) - || !reader.readU16(unknown9) || !reader.readU32(unknown10) || !reader.readU16(unknown11) || !reader.readU32(unknown12) - || !reader.readU16(unknown13) || !reader.readU32(unknown14) || !reader.readU16(unknown15) || !reader.readBytes(unknown16)) + if (!enableWhen.load(reader) || !disableWhen.load(reader) || !transitionType.load(reader) || + !transitionDirection.load(reader) || !unknown1.load(reader) || !steps.load(reader) || + !duration.load(reader) || !fullScreen.load(reader)) return kDataReadErrorReadFailed; return kDataReadErrorNone; diff --git a/engines/mtropolis/plugin/standard_data.h b/engines/mtropolis/plugin/standard_data.h index 9863f492d86..00a76be3a2b 100644 --- a/engines/mtropolis/plugin/standard_data.h +++ b/engines/mtropolis/plugin/standard_data.h @@ -44,22 +44,14 @@ protected: }; struct STransCtModifier : public PlugInModifierData { - uint16 unknown1; // Type tag? (0x17) - Event unknown2; // Probably "apply when" - uint16 unknown3; // Type tag? (0x17) - Event unknown4; // Probably "remove when" - uint16 unknown5; // Type tag? (1) - uint32 unknown6; - uint16 unknown7; // Type tag? (1) - uint32 unknown8; - uint16 unknown9; // Type tag? (1) - uint32 unknown10; - uint16 unknown11; // Type tag? (1) - uint32 unknown12; - uint16 unknown13; // Type tag? (1) - uint32 unknown14; - uint16 unknown15; // Type tag? (0x14) - uint8 unknown16[2]; + PlugInTypeTaggedValue enableWhen; // Event + PlugInTypeTaggedValue disableWhen; // Event + PlugInTypeTaggedValue transitionType; // int + PlugInTypeTaggedValue transitionDirection; // int + PlugInTypeTaggedValue unknown1; // int, seems to always be 1 + PlugInTypeTaggedValue steps; // int, seems to always be 32 + PlugInTypeTaggedValue duration; // int, always observed as 60000 + PlugInTypeTaggedValue fullScreen; // bool protected: DataReadErrorCode load(PlugIn &plugIn, const PlugInModifier &prefix, DataReader &reader) override; diff --git a/engines/mtropolis/runtime.cpp b/engines/mtropolis/runtime.cpp index 81c00fed80e..b49847d0bed 100644 --- a/engines/mtropolis/runtime.cpp +++ b/engines/mtropolis/runtime.cpp @@ -30,6 +30,7 @@ #include "common/debug.h" #include "common/file.h" +#include "common/random.h" #include "common/substream.h" #include "common/system.h" @@ -2483,6 +2484,8 @@ Runtime::SceneStackEntry::SceneStackEntry() { Runtime::Runtime(OSystem *system) : _nextRuntimeGUID(1), _realDisplayMode(kColorDepthModeInvalid), _fakeDisplayMode(kColorDepthModeInvalid), _displayWidth(1024), _displayHeight(768), _realTimeBase(0), _playTimeBase(0), _sceneTransitionState(kSceneTransitionStateNotTransitioning), _system(system), _lastFrameCursor(nullptr), _defaultCursor(new DefaultCursor()) { + _random.reset(new Common::RandomSource("mtropolis")); + _vthread.reset(new VThread()); for (int i = 0; i < kColorDepthModeCount; i++) { @@ -3187,7 +3190,9 @@ void Runtime::onMouseUp(int32 x, int32 y, Actions::MouseButton mButton) { _mouseFocusWindow.reset(); } - +Common::RandomSource* Runtime::getRandom() const { + return _random.get(); +} void Runtime::ensureMainWindowExists() { // Maybe there's a better spot for this @@ -4319,10 +4324,12 @@ bool VisualElement::readAttribute(MiniscriptThread *thread, DynamicValue &result if (attrib == "visible") { result.setBool(_visible); return true; - } - if (attrib == "direct") { + } else if (attrib == "direct") { result.setBool(_directToScreen); return true; + } else if (attrib == "position") { + result.setPoint(Point16::create(_rect.left, _rect.top)); + return true; } return Element::readAttribute(thread, result, attrib); @@ -4332,10 +4339,12 @@ bool VisualElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWrit if (attrib == "visible") { DynamicValueWriteFuncHelper::create(this, writeProxy); return true; - } - if (attrib == "direct") { + } else if (attrib == "direct") { DynamicValueWriteFuncHelper::create(this, writeProxy); return true; + } else if (attrib == "position") { + DynamicValueWriteFuncHelper::create(this, writeProxy); + return true; } return Element::writeRefAttribute(thread, writeProxy, attrib); @@ -4374,6 +4383,34 @@ bool VisualElement::scriptSetDirect(const DynamicValue &dest) { return false; } +bool VisualElement::scriptSetPosition(const DynamicValue &dest) { + if (dest.getType() == DynamicValueTypes::kPoint) { + const Point16 &destPoint = dest.getPoint(); + int32 xDelta = destPoint.x - _rect.left; + int32 yDelta = destPoint.y - _rect.right; + + offsetTranslate(xDelta, yDelta); + + return true; + } + return false; +} + +void VisualElement::offsetTranslate(int32 xDelta, int32 yDelta) { + _rect.left += xDelta; + _rect.right += xDelta; + _rect.top += yDelta; + _rect.bottom += yDelta; + + for (const Common::SharedPtr &child : _children) { + if (child->isElement()) { + Element *element = static_cast(child.get()); + if (element->isVisual()) + static_cast(element)->offsetTranslate(xDelta, yDelta); + } + } +} + VThreadState VisualElement::changeVisibilityTask(const ChangeFlagTaskData &taskData) { if (_visible != taskData.desiredFlag) { _visible = taskData.desiredFlag; diff --git a/engines/mtropolis/runtime.h b/engines/mtropolis/runtime.h index 51ccf30e9a7..22015f92336 100644 --- a/engines/mtropolis/runtime.h +++ b/engines/mtropolis/runtime.h @@ -39,6 +39,12 @@ class OSystem; +namespace Common { + +class RandomSource; + +} // End of namespace Common + namespace Graphics { struct WinCursorGroup; @@ -398,6 +404,13 @@ struct AngleMagVector { return !((*this) == other); } + inline static AngleMagVector create(double angleRadians, double magnitude) { + AngleMagVector result; + result.angleRadians = angleRadians; + result.magnitude = magnitude; + return result; + } + bool dynSetAngleDegrees(const DynamicValue &value); void dynGetAngleDegrees(DynamicValue &value) const; @@ -1267,6 +1280,8 @@ public: void onMouseMove(int32 x, int32 y); void onMouseUp(int32 x, int32 y, Actions::MouseButton mButton); + Common::RandomSource *getRandom() const; + #ifdef MTROPOLIS_DEBUG_ENABLE void debugSetEnabled(bool enabled); void debugBreak(); @@ -1360,6 +1375,8 @@ private: Common::SharedPtr _macFontMan; + Common::SharedPtr _random; + uint32 _nextRuntimeGUID; bool _displayModeSupported[kColorDepthModeCount]; @@ -1823,6 +1840,9 @@ protected: bool loadCommon(const Common::String &name, uint32 guid, const Data::Rect &rect, uint32 elementFlags, uint16 layer, uint32 streamLocator, uint16 sectionID); bool scriptSetDirect(const DynamicValue &dest); + bool scriptSetPosition(const DynamicValue &dest); + + void offsetTranslate(int32 xDelta, int32 yDelta); struct ChangeFlagTaskData { bool desiredFlag;