mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
2614 lines
74 KiB
C++
2614 lines
74 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/random.h"
|
|
|
|
#include "vcruise/ad2044_items.h"
|
|
#include "vcruise/audio_player.h"
|
|
#include "vcruise/circuitpuzzle.h"
|
|
#include "vcruise/runtime.h"
|
|
#include "vcruise/script.h"
|
|
|
|
|
|
namespace VCruise {
|
|
|
|
struct AD2044UnusualAnimationRules {
|
|
enum Type {
|
|
kTypeLoop, // Loop the animation
|
|
kTypePlayFirstFrameOnly,
|
|
kTypeSkip,
|
|
};
|
|
|
|
uint roomNumber;
|
|
uint screenNumber;
|
|
uint interactionID;
|
|
uint8 animLookupID;
|
|
Type ruleType;
|
|
};
|
|
|
|
AD2044UnusualAnimationRules g_unusualAnimationRules[] = {
|
|
// Room, screen, interaction, animation lookup ID, rule
|
|
{87, 0x24, 0xa4, 0xa7, AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly}, // Taking cigarette, don't play box spin
|
|
{87, 0x68, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypeLoop}, // Loop lit cigarette animation
|
|
{87, 0x69, 0xa3, 0xa5, AD2044UnusualAnimationRules::kTypeSkip}, // Taking lit cigarette, don't play cycling animation
|
|
};
|
|
|
|
#ifdef PEEK_STACK
|
|
#error "PEEK_STACK is already defined"
|
|
#endif
|
|
|
|
#ifdef TAKE_STACK
|
|
#error "TAKE_STACK is already defined"
|
|
#endif
|
|
|
|
#ifdef OPCODE_STUB
|
|
#error "OPCODE_STUB is already defined"
|
|
#endif
|
|
|
|
#define PEEK_STACK(n) \
|
|
if (!requireAvailableStack(n)) \
|
|
return; \
|
|
const StackValue *stackArgs = &this->_scriptStack[this->_scriptStack.size() - (n)]
|
|
|
|
|
|
#define TAKE_STACK_INT_NAMED(n, arrayName) \
|
|
StackInt_t arrayName[n]; \
|
|
do { \
|
|
if (!requireAvailableStack(n)) \
|
|
return; \
|
|
const uint stackSize = _scriptStack.size(); \
|
|
const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
|
|
for (uint i = 0; i < (n); i++) { \
|
|
if (stackArgsPtr[i].type != StackValue::kNumber) \
|
|
error("Expected op argument %u to be a number", i); \
|
|
arrayName[i] = stackArgsPtr[i].value.i; \
|
|
} \
|
|
this->_scriptStack.resize(stackSize - (n)); \
|
|
} while (false)
|
|
|
|
#define TAKE_STACK_INT(n) TAKE_STACK_INT_NAMED(n, stackArgs)
|
|
|
|
#define TAKE_STACK_STR_NAMED(n, arrayName) \
|
|
Common::String arrayName[n]; \
|
|
do { \
|
|
if (!requireAvailableStack(n)) \
|
|
return; \
|
|
const uint stackSize = _scriptStack.size(); \
|
|
const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
|
|
for (uint i = 0; i < (n); i++) { \
|
|
if (stackArgsPtr[i].type != StackValue::kString) \
|
|
error("Expected op argument %u to be a string", i); \
|
|
arrayName[i] = Common::move(stackArgsPtr[i].value.s); \
|
|
} \
|
|
this->_scriptStack.resize(stackSize - (n)); \
|
|
} while (false)
|
|
|
|
#define TAKE_STACK_STR(n) TAKE_STACK_STR_NAMED(n, stackArgs)
|
|
|
|
#define TAKE_STACK_VAR_NAMED(n, arrayName) \
|
|
StackValue arrayName[n]; \
|
|
do { \
|
|
if (!requireAvailableStack(n)) \
|
|
return; \
|
|
const uint stackSize = _scriptStack.size(); \
|
|
const StackValue *stackArgsPtr = &this->_scriptStack[stackSize - (n)]; \
|
|
for (uint i = 0; i < (n); i++) \
|
|
arrayName[i] = Common::move(stackArgsPtr[i]); \
|
|
this->_scriptStack.resize(stackSize - (n)); \
|
|
} while (false)
|
|
|
|
#define TAKE_STACK_VAR(n) TAKE_STACK_VAR_NAMED(n, stackArgs)
|
|
|
|
#define OPCODE_STUB(op) \
|
|
void Runtime::scriptOp##op(ScriptArg_t arg) { \
|
|
error("Unimplemented opcode '" #op "'"); \
|
|
}
|
|
|
|
void Runtime::scriptOpNumber(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(arg));
|
|
}
|
|
|
|
void Runtime::scriptOpRotate(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs + kAnimDefStackArgs);
|
|
|
|
_panLeftAnimationDef = stackArgsToAnimDef(stackArgs + 0);
|
|
_panRightAnimationDef = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
|
|
_haveHorizPanAnimations = true;
|
|
}
|
|
|
|
void Runtime::scriptOpAngle(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] == static_cast<StackInt_t>(_direction)) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpAngleGGet(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] < 0 || stackArgs[0] >= static_cast<StackInt_t>(GyroState::kNumGyros))
|
|
error("Invalid gyro index in angleGGet op");
|
|
|
|
_scriptStack.push_back(StackValue(_gyros.gyros[stackArgs[0]].currentState));
|
|
}
|
|
|
|
void Runtime::scriptOpSpeed(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_scriptEnv.fpsOverride = stackArgs[0];
|
|
}
|
|
|
|
void Runtime::scriptOpSAnimL(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs + 2);
|
|
|
|
if (stackArgs[kAnimDefStackArgs] != 0)
|
|
warning("sanimL second operand wasn't zero (what does that do?)");
|
|
|
|
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
|
|
uint direction = stackArgs[kAnimDefStackArgs + 1];
|
|
|
|
if (direction >= kNumDirections)
|
|
error("sanimL invalid direction");
|
|
|
|
_haveIdleAnimations[direction] = true;
|
|
|
|
StaticAnimation &outAnim = _idleAnimations[direction];
|
|
|
|
outAnim = StaticAnimation();
|
|
outAnim.animDefs[0] = animDef;
|
|
outAnim.animDefs[1] = animDef;
|
|
}
|
|
|
|
void Runtime::scriptOpChangeL(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
// ChangeL changes the screen number.
|
|
//
|
|
// If this isn't an entry script, then this must also re-trigger the entry script.
|
|
//
|
|
// In Reah, it also forces screen entry scripts to replay, which is needed for things like the fountain.
|
|
//
|
|
// In Schizm, it's needed for the preset buttons in the airship navigation coordinates to work correctly
|
|
// (Room 41 screen 0c2h)
|
|
//
|
|
// The check is required because otherwise, this causes an infinite loop in the temple when approaching the
|
|
// bells puzzle (Room 65 screen 0b2h) due to fnMlynekZerowanie -> 1 fnMlynkiLokacja -> changeL to MLYNKIZLEWEJ1
|
|
_screenNumber = stackArgs[0];
|
|
_havePendingScreenChange = true;
|
|
|
|
if (!_scriptEnv.isEntryScript)
|
|
_forceScreenChange = true;
|
|
}
|
|
|
|
void Runtime::scriptOpAnimR(ScriptArg_t arg) {
|
|
bool isRight = false;
|
|
|
|
if (_scriptEnv.panInteractionID == kPanLeftInteraction) {
|
|
debug(1, "Pan-left interaction from direction %u", _direction);
|
|
|
|
uint reverseDirectionSlice = (kNumDirections - _direction);
|
|
if (reverseDirectionSlice == kNumDirections)
|
|
reverseDirectionSlice = 0;
|
|
|
|
uint initialFrame = reverseDirectionSlice * (_panLeftAnimationDef.lastFrame - _panLeftAnimationDef.firstFrame) / kNumDirections + _panLeftAnimationDef.firstFrame;
|
|
|
|
AnimationDef trimmedAnimation = _panLeftAnimationDef;
|
|
trimmedAnimation.lastFrame--;
|
|
|
|
debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
|
|
|
|
changeAnimation(trimmedAnimation, initialFrame, false, _animSpeedRotation);
|
|
_gameState = kGameStatePanLeft;
|
|
} else if (_scriptEnv.panInteractionID == kPanRightInteraction) {
|
|
debug(1, "Pan-right interaction from direction %u", _direction);
|
|
|
|
uint initialFrame = _direction * (_panRightAnimationDef.lastFrame - _panRightAnimationDef.firstFrame) / kNumDirections + _panRightAnimationDef.firstFrame;
|
|
|
|
AnimationDef trimmedAnimation = _panRightAnimationDef;
|
|
trimmedAnimation.lastFrame--;
|
|
|
|
debug(1, "Running frame loop of %u - %u from frame %u", trimmedAnimation.firstFrame, trimmedAnimation.lastFrame, initialFrame);
|
|
|
|
changeAnimation(trimmedAnimation, initialFrame, false, _animSpeedRotation);
|
|
_gameState = kGameStatePanRight;
|
|
|
|
isRight = true;
|
|
}
|
|
|
|
uint cursorID = 0;
|
|
if (_haveHorizPanAnimations) {
|
|
uint panCursor = kPanCursorDraggableHoriz;
|
|
|
|
if (isRight)
|
|
panCursor |= kPanCursorDirectionRight;
|
|
else
|
|
panCursor |= kPanCursorDirectionLeft;
|
|
|
|
cursorID = _panCursors[panCursor];
|
|
}
|
|
|
|
changeToCursor(_cursors[cursorID]);
|
|
drawCompass();
|
|
}
|
|
|
|
void Runtime::scriptOpAnimF(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs + 3);
|
|
|
|
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
|
|
|
|
const AnimationDef *faceDirectionAnimDef = nullptr;
|
|
uint initialFrame = 0;
|
|
uint stopFrame = 0;
|
|
if (computeFaceDirectionAnimation(stackArgs[kAnimDefStackArgs + 2], faceDirectionAnimDef, initialFrame, stopFrame)) {
|
|
_postFacingAnimDef = animDef;
|
|
_animStopFrame = stopFrame;
|
|
changeAnimation(*faceDirectionAnimDef, initialFrame, false, _animSpeedRotation);
|
|
_gameState = kGameStateWaitingForFacingToAnim;
|
|
} else {
|
|
consumeAnimChangeAndAdjustAnim(animDef); // Needed for Schizm when entering the statue after finishing the temple.
|
|
|
|
changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
}
|
|
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
|
|
_direction = stackArgs[kAnimDefStackArgs + 1];
|
|
_havePendingScreenChange = true;
|
|
clearIdleAnimations();
|
|
|
|
uint cursorID = kCursorArrow;
|
|
if (_scriptEnv.panInteractionID == kPanUpInteraction)
|
|
cursorID = _panCursors[kPanCursorDraggableUp | kPanCursorDirectionUp];
|
|
else if (_scriptEnv.panInteractionID == kPanDownInteraction)
|
|
cursorID = _panCursors[kPanCursorDraggableDown | kPanCursorDirectionDown];
|
|
|
|
changeToCursor(_cursors[cursorID]);
|
|
}
|
|
|
|
void Runtime::scriptOpAnimN(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
const AnimationDef *faceDirectionAnimDef = nullptr;
|
|
uint initialFrame = 0;
|
|
uint stopFrame = 0;
|
|
if (computeFaceDirectionAnimation(stackArgs[0], faceDirectionAnimDef, initialFrame, stopFrame)) {
|
|
_animStopFrame = stopFrame;
|
|
changeAnimation(*faceDirectionAnimDef, initialFrame, false, _animSpeedRotation);
|
|
_gameState = kGameStateWaitingForFacing;
|
|
}
|
|
|
|
_direction = stackArgs[0];
|
|
_havePendingScreenChange = true;
|
|
|
|
changeToCursor(_cursors[kCursorArrow]);
|
|
}
|
|
|
|
void Runtime::scriptOpAnimG(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs * 2 + 1);
|
|
|
|
_gyros.posAnim = stackArgsToAnimDef(stackArgs + 0);
|
|
_gyros.negAnim = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
|
|
_gyros.isVertical = (stackArgs[kAnimDefStackArgs * 2 + 0] != 0);
|
|
|
|
if (_gyros.isVertical)
|
|
changeToCursor(_cursors[_panCursors[kPanCursorDraggableUp | kPanCursorDraggableDown]]);
|
|
else
|
|
changeToCursor(_cursors[_panCursors[kPanCursorDraggableHoriz]]);
|
|
|
|
_gyros.dragBasePoint = _mousePos;
|
|
_gyros.dragBaseState = _gyros.gyros[_gyros.activeGyro].currentState;
|
|
_gyros.dragCurrentState = _gyros.dragBaseState;
|
|
|
|
_gameState = kGameStateGyroIdle;
|
|
}
|
|
|
|
void Runtime::scriptOpAnimS(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs + 2);
|
|
|
|
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
|
|
|
|
consumeAnimChangeAndAdjustAnim(animDef);
|
|
|
|
// Static animations start on the last frame
|
|
changeAnimation(animDef, animDef.lastFrame, false);
|
|
|
|
// We have a choice of when to terminate animations: At the start of the final frame, or at the end of the final frame.
|
|
// Terminating at the start of the final frame means many frames can play in a single gameplay frame.
|
|
//
|
|
// In Reah, we terminate at the start because it doesn't really cause problems anywhere and helps some things like
|
|
// the basket weight puzzle in the bathhouse.
|
|
//
|
|
// In Schizm, several things like the mechanical computer and balloon gas puzzle pressure meter don't behave
|
|
// well when doing this, so we terminate at the end of the frame instead there.
|
|
_animTerminateAtStartOfFrame = (_gameID == GID_SCHIZM);
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
|
|
_direction = stackArgs[kAnimDefStackArgs + 1];
|
|
_havePendingScreenChange = true;
|
|
|
|
changeToCursor(_cursors[kCursorArrow]);
|
|
}
|
|
|
|
void Runtime::scriptOpAnim(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs + 2);
|
|
|
|
AnimationDef animDef = stackArgsToAnimDef(stackArgs + 0);
|
|
|
|
consumeAnimChangeAndAdjustAnim(animDef);
|
|
|
|
changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
_screenNumber = stackArgs[kAnimDefStackArgs + 0];
|
|
_direction = stackArgs[kAnimDefStackArgs + 1];
|
|
_havePendingScreenChange = true;
|
|
|
|
clearIdleAnimations();
|
|
|
|
if (_loadedAnimationHasSound)
|
|
changeToCursor(nullptr);
|
|
else {
|
|
uint cursorID = kCursorArrow;
|
|
if (_scriptEnv.panInteractionID == kPanUpInteraction)
|
|
cursorID = _panCursors[kPanCursorDraggableUp | kPanCursorDirectionUp];
|
|
else if (_scriptEnv.panInteractionID == kPanDownInteraction)
|
|
cursorID = _panCursors[kPanCursorDraggableDown | kPanCursorDirectionDown];
|
|
|
|
changeToCursor(_cursors[cursorID]);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpStatic(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs);
|
|
|
|
debug(10, "scriptOpStatic() kAnimDefStackArgs: %d", kAnimDefStackArgs);
|
|
for (uint i = 0; i < kAnimDefStackArgs; i++) {
|
|
debug(10, "\tstackArgs[%d]: %d", i, stackArgs[i]);
|
|
}
|
|
|
|
// FIXME: What does this actually do?
|
|
// It looks like this sets the last frame of an animation as the current scene graphic, but
|
|
// in some cases that's wrong. For instance, after solving the temple puzzle in Reah, viewing
|
|
// the rock on the left (screen 0c4 in room 20) runs ":PLANAS_SKALA static" after the rock
|
|
// symbol displays. However, :PLANAS_SKALA shows the rock with no symbol.
|
|
//
|
|
// Another problem occurs when viewing the rotor puzzle in the citadel, described below for now.
|
|
#if 0
|
|
// QUIRK/BUG WORKAROUND: Static animations don't override other static animations!
|
|
//
|
|
// In Reah Room05, the script for 0b8 (NGONG) sets the static animation to :NNAWA_NGONG and then
|
|
// to :NSWIT_SGONG, but NNAWA_NGONG is the correct one, so we must ignore the second static animation
|
|
if (_haveIdleStaticAnimation)
|
|
return;
|
|
|
|
AnimationDef animDef = stackArgsToAnimDef(stackArgs);
|
|
|
|
// QUIRK: In the Reah citadel rotor puzzle, all of the "BKOLO" screens execute :DKOLO1_BKOLO1 static but
|
|
// doing that would replace the transition animation's last frame with the new static animation frame,
|
|
// blanking out the puzzle, so we must detect if the new static animation is the same as the existing
|
|
// one and if so, ignore it.
|
|
if (animDef.animName == _idleCurrentStaticAnimation)
|
|
return;
|
|
|
|
// FIXME: _idleCurrentStaticAnimation must be cleared sometime! Maybe on loading a save.
|
|
|
|
changeAnimation(animDef, animDef.lastFrame, false, _animSpeedStaticAnim);
|
|
|
|
_havePendingPreIdleActions = true;
|
|
_haveHorizPanAnimations = false;
|
|
_haveIdleStaticAnimation = true;
|
|
_idleCurrentStaticAnimation = animDef.animName;
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
#endif
|
|
}
|
|
|
|
void Runtime::scriptOpVarLoad(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[0]);
|
|
|
|
Common::HashMap<uint32, int32>::const_iterator it = _variables.find(varID);
|
|
if (it == _variables.end())
|
|
_scriptStack.push_back(StackValue(0));
|
|
else
|
|
_scriptStack.push_back(StackValue(it->_value));
|
|
}
|
|
|
|
void Runtime::scriptOpVarStore(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[1]);
|
|
|
|
_variables[varID] = stackArgs[0];
|
|
}
|
|
|
|
void Runtime::scriptOpVarAddAndStore(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
uint32 varID = (static_cast<uint32>(_roomNumber) << 16) | static_cast<uint32>(stackArgs[0]);
|
|
|
|
Common::HashMap<uint32, int32>::iterator it = _variables.find(varID);
|
|
if (it == _variables.end())
|
|
_variables[varID] = stackArgs[1];
|
|
else
|
|
it->_value += stackArgs[1];
|
|
}
|
|
|
|
void Runtime::scriptOpVarGlobalLoad(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
uint32 varID = static_cast<uint32>(stackArgs[0]);
|
|
|
|
Common::HashMap<uint32, int32>::const_iterator it = _variables.find(varID);
|
|
if (it == _variables.end())
|
|
_scriptStack.push_back(StackValue(0));
|
|
else
|
|
_scriptStack.push_back(StackValue(it->_value));
|
|
}
|
|
|
|
void Runtime::scriptOpVarGlobalStore(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
uint32 varID = static_cast<uint32>(stackArgs[1]);
|
|
|
|
_variables[varID] = stackArgs[0];
|
|
}
|
|
|
|
void Runtime::scriptOpItemCheck(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
for (const InventoryItem &item : _inventory) {
|
|
if (item.itemID == static_cast<uint>(stackArgs[0])) {
|
|
_scriptEnv.lastHighlightedItem = item.itemID;
|
|
_scriptStack.push_back(StackValue(1));
|
|
return;
|
|
}
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(0));
|
|
}
|
|
|
|
void Runtime::scriptOpItemRemove(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
inventoryRemoveItem(stackArgs[0]);
|
|
}
|
|
|
|
void Runtime::scriptOpItemHighlightSet(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
bool isHighlighted = (stackArgs[1] != 0);
|
|
|
|
for (uint slot = 0; slot < kNumInventorySlots; slot++) {
|
|
InventoryItem &item = _inventory[slot];
|
|
|
|
if (item.itemID == static_cast<uint>(stackArgs[0])) {
|
|
item.highlighted = isHighlighted;
|
|
drawInventory(slot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpItemHighlightSetTrue(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
for (uint slot = 0; slot < kNumInventorySlots; slot++) {
|
|
InventoryItem &item = _inventory[slot];
|
|
|
|
if (item.itemID == static_cast<uint>(stackArgs[0])) {
|
|
item.highlighted = true;
|
|
drawInventory(slot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpItemAdd(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] == 0) {
|
|
// Weird special case, happens in Reah when breaking the glass barrier, this is called with 0 as the parameter.
|
|
// This can't be an inventory clear because it will not clear the crutch, but it does take away the gong beater,
|
|
// so the only explanation I can think of is that it clears the previously-checked inventory item.
|
|
inventoryRemoveItem(_scriptEnv.lastHighlightedItem);
|
|
} else
|
|
inventoryAddItem(stackArgs[0]);
|
|
}
|
|
|
|
void Runtime::scriptOpItemClear(ScriptArg_t arg) {
|
|
for (uint slot = 0; slot < kNumInventorySlots; slot++) {
|
|
InventoryItem &item = _inventory[slot];
|
|
|
|
if (item.itemID != 0) {
|
|
item.highlighted = false;
|
|
item.itemID = 0;
|
|
item.graphic.reset();
|
|
item.mask.reset();
|
|
drawInventory(slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpItemHaveSpace(ScriptArg_t arg) {
|
|
for (const InventoryItem &item : _inventory) {
|
|
if (item.itemID == 0) {
|
|
_scriptStack.push_back(StackValue(1));
|
|
return;
|
|
}
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(0));
|
|
}
|
|
|
|
void Runtime::scriptOpSetCursor(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= _cursors.size())
|
|
error("Invalid cursor ID");
|
|
|
|
uint resolvedCursorID = stackArgs[0];
|
|
|
|
Common::HashMap<StackInt_t, uint>::const_iterator overrideIt = _scriptCursorIDToResourceIDOverride.find(resolvedCursorID);
|
|
if (overrideIt != _scriptCursorIDToResourceIDOverride.end())
|
|
resolvedCursorID = overrideIt->_value;
|
|
|
|
changeToCursor(_cursors[resolvedCursorID]);
|
|
}
|
|
|
|
void Runtime::scriptOpSetRoom(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_roomNumber = stackArgs[0];
|
|
}
|
|
|
|
void Runtime::scriptOpLMB(ScriptArg_t arg) {
|
|
if (!_scriptEnv.lmb) {
|
|
_idleHaveClickInteraction = true;
|
|
terminateScript();
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpLMB1(ScriptArg_t arg) {
|
|
if (!_scriptEnv.lmbDrag) {
|
|
_idleHaveDragInteraction = true;
|
|
terminateScript();
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSoundS1(ScriptArg_t arg) {
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSoundS2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(1, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, sndParamArgs[0], 0, false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSoundS3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorYes, *cachedSound, getDefaultSoundVolume(), 0, false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(1, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorYes, *cachedSound, sndParamArgs[0], 0, false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSoundL3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorYes, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
|
|
}
|
|
|
|
void Runtime::scriptOp3DSoundL2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(3, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
|
|
triggerSound(kSoundLoopBehaviorYes, *cachedSound, sndParamArgs[0], 0, true, false);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOp3DSoundL3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(4, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
setSound3DParameters(*cachedSound, sndParamArgs[2], sndParamArgs[3], _pendingSoundParams3D);
|
|
triggerSound(kSoundLoopBehaviorYes, *cachedSound, sndParamArgs[0], sndParamArgs[1], true, false);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOp3DSoundS2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(3, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
setSound3DParameters(*cachedSound, sndParamArgs[1], sndParamArgs[2], _pendingSoundParams3D);
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, sndParamArgs[0], 0, true, false);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpStopAL(ScriptArg_t arg) {
|
|
warning("stopaL not implemented yet");
|
|
}
|
|
|
|
void Runtime::scriptOpAddXSound(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(3, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
RandomAmbientSound sound;
|
|
sound.name = sndNameArgs[0];
|
|
sound.volume = sndParamArgs[0];
|
|
sound.balance = sndParamArgs[1];
|
|
sound.frequency = sndParamArgs[2];
|
|
|
|
_randomAmbientSounds.push_back(sound);
|
|
}
|
|
|
|
void Runtime::scriptOpClrXSound(ScriptArg_t arg) {
|
|
_randomAmbientSounds.clear();
|
|
}
|
|
|
|
void Runtime::scriptOpStopSndLA(ScriptArg_t arg) {
|
|
warning("StopSndLA not implemented yet");
|
|
}
|
|
|
|
void Runtime::scriptOpStopSndLO(ScriptArg_t arg) {
|
|
TAKE_STACK_VAR(1);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByNameOrID(stackArgs[0], false, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
stopSound(*cachedSound);
|
|
}
|
|
|
|
void Runtime::scriptOpRange(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
_pendingSoundParams3D.minRange = stackArgs[0];
|
|
_pendingSoundParams3D.maxRange = stackArgs[1];
|
|
_pendingSoundParams3D.unknownRange = stackArgs[2];
|
|
}
|
|
|
|
void Runtime::scriptOpMusic(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
changeMusicTrack(stackArgs[0]);
|
|
}
|
|
|
|
void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
uint32 duration = static_cast<uint32>(stackArgs[0]) * 100u;
|
|
int32 newVolume = stackArgs[1];
|
|
|
|
_musicVolumeRampRatePerMSec = 0;
|
|
|
|
if (duration == 0) {
|
|
_musicVolume = newVolume;
|
|
if (_musicWavePlayer)
|
|
_musicWavePlayer->setVolume(newVolume);
|
|
} else {
|
|
if (newVolume != _musicVolume) {
|
|
uint32 timestamp = g_system->getMillis();
|
|
|
|
_musicVolumeRampRatePerMSec = (newVolume - _musicVolume) * 65536 / static_cast<int32>(duration);
|
|
_musicVolumeRampStartTime = timestamp;
|
|
_musicVolumeRampStartVolume = _musicVolume;
|
|
_musicVolumeRampEnd = newVolume;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpParm0(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(4);
|
|
|
|
if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
|
|
error("Invalid gyro index for Parm0");
|
|
|
|
uint gyroIndex = stackArgs[0];
|
|
|
|
Gyro &gyro = _gyros.gyros[gyroIndex];
|
|
gyro.numPreviousStatesRequired = 3;
|
|
for (uint i = 0; i < 3; i++)
|
|
gyro.requiredPreviousStates[i] = stackArgs[i + 1];
|
|
}
|
|
|
|
void Runtime::scriptOpParm1(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
|
|
error("Invalid gyro index for Parm1");
|
|
|
|
uint gyroIndex = stackArgs[0];
|
|
|
|
Gyro &gyro = _gyros.gyros[gyroIndex];
|
|
gyro.currentState = stackArgs[1];
|
|
gyro.requiredState = stackArgs[2];
|
|
|
|
gyro.requireState = true;
|
|
}
|
|
|
|
void Runtime::scriptOpParm2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
_gyros.completeInteraction = stackArgs[0];
|
|
_gyros.failureInteraction = stackArgs[1];
|
|
_gyros.frameSeparation = stackArgs[2];
|
|
|
|
if (_gyros.frameSeparation <= 0)
|
|
error("Invalid gyro frame separation");
|
|
}
|
|
|
|
void Runtime::scriptOpParm3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] < 0 || static_cast<uint>(stackArgs[0]) >= GyroState::kNumGyros)
|
|
error("Invalid gyro index for Parm3");
|
|
|
|
uint gyroIndex = stackArgs[0];
|
|
|
|
Gyro &gyro = _gyros.gyros[gyroIndex];
|
|
gyro.wrapAround = true;
|
|
}
|
|
|
|
void Runtime::scriptOpParmG(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
int32 gyroSlot = stackArgs[0];
|
|
int32 dragMargin = stackArgs[1];
|
|
int32 maxValue = stackArgs[2];
|
|
|
|
if (gyroSlot < 0 || static_cast<uint>(gyroSlot) >= GyroState::kNumGyros)
|
|
error("Invalid gyro slot from ParmG op");
|
|
|
|
_gyros.activeGyro = gyroSlot;
|
|
_gyros.dragMargin = dragMargin;
|
|
_gyros.maxValue = maxValue;
|
|
}
|
|
|
|
void Runtime::scriptOpSParmX(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
_pendingStaticAnimParams.initialDelay = stackArgs[0];
|
|
_pendingStaticAnimParams.repeatDelay = stackArgs[1];
|
|
_pendingStaticAnimParams.lockInteractions = (stackArgs[2] != 0);
|
|
}
|
|
|
|
void Runtime::scriptOpSAnimX(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs * 2 + 1);
|
|
|
|
AnimationDef animDef1 = stackArgsToAnimDef(stackArgs + 0);
|
|
AnimationDef animDef2 = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
|
|
|
|
uint direction = stackArgs[kAnimDefStackArgs * 2 + 0];
|
|
|
|
if (direction >= kNumDirections)
|
|
error("sanimX invalid direction");
|
|
|
|
_haveIdleAnimations[direction] = true;
|
|
|
|
StaticAnimation &outAnim = _idleAnimations[direction];
|
|
|
|
outAnim = StaticAnimation();
|
|
outAnim.animDefs[0] = animDef1;
|
|
outAnim.animDefs[1] = animDef2;
|
|
outAnim.params = _pendingStaticAnimParams;
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeUp3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_VAR_NAMED(1, sndIDArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByNameOrID(sndIDArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], false);
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeDn2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(1, sndParamArgs);
|
|
TAKE_STACK_VAR_NAMED(1, sndIDArgs);
|
|
|
|
uint32 durationMSec = static_cast<uint>(sndParamArgs[0]) * 100u;
|
|
|
|
if (sndIDArgs[0].type == StackValue::kNumber && sndIDArgs[0].value.i == 0) {
|
|
// Apply to all sounds
|
|
for (const Common::SharedPtr<SoundInstance> &sndPtr : _activeSounds)
|
|
triggerSoundRamp(*sndPtr, durationMSec, 0, true);
|
|
} else {
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByNameOrID(sndIDArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, durationMSec, getSilentSoundVolume(), true);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeDn3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_VAR_NAMED(1, sndIDArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByNameOrID(sndIDArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], false);
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeDn4(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(3, sndParamArgs);
|
|
TAKE_STACK_VAR_NAMED(1, sndIDArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByNameOrID(sndIDArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, sndParamArgs[0] * 100, sndParamArgs[1], sndParamArgs[2] != 0);
|
|
}
|
|
|
|
void Runtime::scriptOpRandom(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] == 0)
|
|
_scriptStack.push_back(StackValue(0));
|
|
else
|
|
_scriptStack.push_back(StackValue(_rng->getRandomNumber(stackArgs[0] - 1)));
|
|
}
|
|
|
|
void Runtime::scriptOpDrop(ScriptArg_t arg) {
|
|
TAKE_STACK_VAR(1);
|
|
(void)stackArgs;
|
|
}
|
|
|
|
void Runtime::scriptOpDup(ScriptArg_t arg) {
|
|
TAKE_STACK_VAR(1);
|
|
|
|
_scriptStack.push_back(stackArgs[0]);
|
|
_scriptStack.push_back(stackArgs[0]);
|
|
}
|
|
|
|
void Runtime::scriptOpSwap(ScriptArg_t arg) {
|
|
TAKE_STACK_VAR(2);
|
|
|
|
_scriptStack.push_back(Common::move(stackArgs[1]));
|
|
_scriptStack.push_back(Common::move(stackArgs[0]));
|
|
}
|
|
|
|
void Runtime::scriptOpSay1(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
// uint unk = sndParamArgs[0];
|
|
uint cycleLength = sndParamArgs[1];
|
|
debug(5, "Say1 cycle length: %u", cycleLength);
|
|
|
|
Common::String soundIDStr = sndNameArgs[0];
|
|
|
|
if (soundIDStr.size() < 4)
|
|
error("Say1 sound name was invalid");
|
|
|
|
uint32 cycleID = 0;
|
|
|
|
for (uint i = 0; i < 4; i++) {
|
|
char d = soundIDStr[i];
|
|
if (d < '0' || d > '9')
|
|
error("Invalid sound ID for say1");
|
|
|
|
cycleID = cycleID * 10 + (d - '0');
|
|
}
|
|
|
|
uint &cyclePosRef = _sayCycles[static_cast<uint32>(cycleID)];
|
|
|
|
uint32 cycledSoundID = (cyclePosRef + cycleID);
|
|
cyclePosRef++;
|
|
|
|
if (cyclePosRef == cycleLength)
|
|
cyclePosRef = 0;
|
|
|
|
soundIDStr = soundIDStr.substr(4);
|
|
for (uint i = 0; i < 4; i++) {
|
|
soundIDStr.insertChar(static_cast<char>((cycledSoundID % 10) + '0'), 0);
|
|
cycledSoundID /= 10;
|
|
}
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(soundIDStr, true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
triggerWaveSubtitles(*cachedSound, soundIDStr);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSay2(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
|
|
if (sndParamArgs[1] != 1)
|
|
error("Invalid interrupt arg for say2, only 1 is supported.");
|
|
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
triggerWaveSubtitles(*cachedSound, sndNameArgs[0]);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSay3(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
TriggeredOneShot oneShot;
|
|
oneShot.soundID = soundID;
|
|
oneShot.uniqueSlot = sndParamArgs[0];
|
|
|
|
// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
|
|
if (sndParamArgs[1] != 1)
|
|
error("Invalid interrupt arg for say3, only 1 is supported.");
|
|
|
|
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
_triggeredOneShots.push_back(oneShot);
|
|
|
|
triggerWaveSubtitles(*cachedSound, sndNameArgs[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSay3Get(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
TriggeredOneShot oneShot;
|
|
oneShot.soundID = soundID;
|
|
oneShot.uniqueSlot = sndParamArgs[0];
|
|
|
|
// The third param seems to control sound interruption, but say3 is a Reah-only op and it's only ever 1.
|
|
if (sndParamArgs[1] != 1)
|
|
error("Invalid interrupt arg for say3, only 1 is supported.");
|
|
|
|
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
_triggeredOneShots.push_back(oneShot);
|
|
_scriptStack.push_back(StackValue(soundID));
|
|
} else
|
|
_scriptStack.push_back(StackValue(0));
|
|
} else
|
|
_scriptStack.push_back(StackValue(0));
|
|
}
|
|
|
|
void Runtime::scriptOpSetTimer(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_timers[static_cast<uint>(stackArgs[0])] = g_system->getMillis() + static_cast<uint32>(stackArgs[1]) * 1000u;
|
|
}
|
|
|
|
void Runtime::scriptOpGetTimer(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
bool isCompleted = true;
|
|
|
|
Common::HashMap<uint, uint32>::const_iterator timerIt = _timers.find(stackArgs[0]);
|
|
if (timerIt != _timers.end())
|
|
isCompleted = (g_system->getMillis() >= timerIt->_value);
|
|
|
|
_scriptStack.push_back(StackValue(isCompleted ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpDelay(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_gameState = kGameStateDelay;
|
|
_delayCompletionTime = g_system->getMillis() + stackArgs[0];
|
|
}
|
|
|
|
void Runtime::scriptOpLoSet(ScriptArg_t arg) {
|
|
scriptOpVerticalPanSet(_havePanDownFromDirection);
|
|
}
|
|
|
|
void Runtime::scriptOpLoGet(ScriptArg_t arg) {
|
|
scriptOpVerticalPanGet();
|
|
}
|
|
|
|
void Runtime::scriptOpHiSet(ScriptArg_t arg) {
|
|
scriptOpVerticalPanSet(_havePanUpFromDirection);
|
|
}
|
|
|
|
void Runtime::scriptOpHiGet(ScriptArg_t arg) {
|
|
scriptOpVerticalPanGet();
|
|
}
|
|
|
|
void Runtime::scriptOpVerticalPanSet(bool *flags) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
uint baseDirection = static_cast<uint>(stackArgs[0]) % kNumDirections;
|
|
uint radius = stackArgs[1];
|
|
|
|
flags[baseDirection] = true;
|
|
|
|
uint rDir = baseDirection;
|
|
uint lDir = baseDirection;
|
|
for (uint i = 1; i <= radius; i++) {
|
|
rDir++;
|
|
if (rDir == kNumDirections)
|
|
rDir = 0;
|
|
|
|
if (lDir == 0)
|
|
lDir = kNumDirections;
|
|
lDir--;
|
|
|
|
flags[lDir] = true;
|
|
flags[rDir] = true;
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpVerticalPanGet() {
|
|
TAKE_STACK_INT(2);
|
|
|
|
// In any scenario where this is used, there is a corresponding hi/lo set and this only ever triggers off of interactions,
|
|
// so don't really even need to check anything other than the facing direction?
|
|
uint baseDirection = static_cast<uint>(stackArgs[0]) % kNumDirections;
|
|
uint radius = stackArgs[1];
|
|
|
|
uint rtDirection = (baseDirection + kNumDirections - _direction) % kNumDirections;
|
|
uint lfDirection = (_direction + kNumDirections - baseDirection) % kNumDirections;
|
|
|
|
bool isInRadius = (rtDirection <= radius || lfDirection <= radius);
|
|
|
|
_scriptStack.push_back(StackValue(isInRadius ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCallFunction(ScriptArg_t arg) {
|
|
Common::SharedPtr<Script> function = _scriptSet->functions[arg];
|
|
if (function) {
|
|
CallStackFrame newFrame;
|
|
newFrame._script = function;
|
|
newFrame._nextInstruction = 0;
|
|
|
|
_scriptCallStack.push_back(newFrame);
|
|
|
|
_gameState = kGameStateScriptReset;
|
|
} else {
|
|
error("Unknown function '%s'", _scriptSet->functionNames[arg].c_str());
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpReturn(ScriptArg_t arg) {
|
|
_scriptCallStack.pop_back();
|
|
_gameState = kGameStateScriptReset;
|
|
}
|
|
|
|
void Runtime::scriptOpSaveAs(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(4);
|
|
|
|
// Just ignore this op, it looks like it's for save room remapping of some sort but we allow
|
|
// saves at any idle screen.
|
|
(void)stackArgs;
|
|
}
|
|
|
|
void Runtime::scriptOpSave0(ScriptArg_t arg) {
|
|
warning("save0 op not implemented");
|
|
}
|
|
|
|
void Runtime::scriptOpExit(ScriptArg_t arg) {
|
|
_isInGame = false;
|
|
_mostRecentlyRecordedSaveState.reset();
|
|
_mostRecentValidSaveState.reset();
|
|
|
|
if (_gameID == GID_REAH) {
|
|
_havePendingScreenChange = true;
|
|
_forceScreenChange = true;
|
|
|
|
_roomNumber = 40;
|
|
_screenNumber = 0xa1;
|
|
|
|
terminateScript();
|
|
|
|
changeMusicTrack(0);
|
|
if (_musicWavePlayer)
|
|
_musicWavePlayer->setVolumeAndBalance(applyVolumeScale(getDefaultSoundVolume()), 0);
|
|
} else {
|
|
error("Don't know what screen to go to on exit");
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpNot(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] == 0) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpAnd(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] != 0 && stackArgs[1] != 0) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpOr(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] != 0 || stackArgs[1] != 0) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpAdd(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] + stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpSub(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] - stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpNegate(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_scriptStack.push_back(StackValue(-stackArgs[0]));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpEq(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] == stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpNE(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] != stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpLt(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] < stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpLE(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] <= stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpGt(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] > stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpCmpGE(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] >= stackArgs[1]) ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpBitLoad(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
|
|
_scriptStack.push_back(StackValue((stackArgs[0] >> stackArgs[1]) & 1));
|
|
}
|
|
|
|
void Runtime::scriptOpBitSet0(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
ScriptArg_t bitMask = static_cast<ScriptArg_t>(1) << stackArgs[1];
|
|
_scriptStack.push_back(StackValue(stackArgs[0] & ~bitMask));
|
|
}
|
|
|
|
void Runtime::scriptOpBitSet1(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
ScriptArg_t bitMask = static_cast<ScriptArg_t>(1) << stackArgs[1];
|
|
_scriptStack.push_back(StackValue(stackArgs[0] | bitMask));
|
|
}
|
|
|
|
void Runtime::scriptOpDisc1(ScriptArg_t arg) {
|
|
// Disc check, always pass
|
|
TAKE_STACK_INT(1);
|
|
(void)stackArgs;
|
|
_scriptStack.push_back(StackValue(1));
|
|
}
|
|
|
|
void Runtime::scriptOpDisc2(ScriptArg_t arg) {
|
|
// Disc check, always pass
|
|
TAKE_STACK_INT(2);
|
|
(void)stackArgs;
|
|
_scriptStack.push_back(StackValue(1));
|
|
}
|
|
|
|
void Runtime::scriptOpDisc3(ScriptArg_t arg) {
|
|
// Disc check, always pass
|
|
TAKE_STACK_INT(3);
|
|
(void)stackArgs;
|
|
_scriptStack.push_back(StackValue(1));
|
|
}
|
|
|
|
void Runtime::scriptOpGoto(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
uint newInteraction = static_cast<uint>(stackArgs[0]);
|
|
|
|
Common::SharedPtr<Script> newScript = nullptr;
|
|
|
|
if (_scriptSet) {
|
|
RoomScriptSet *roomScriptSet = getRoomScriptSetForCurrentRoom();
|
|
|
|
if (roomScriptSet) {
|
|
const ScreenScriptSetMap_t &screenScriptsMap = roomScriptSet->screenScripts;
|
|
ScreenScriptSetMap_t::const_iterator screenScriptIt = screenScriptsMap.find(_screenNumber);
|
|
if (screenScriptIt != screenScriptsMap.end()) {
|
|
const ScreenScriptSet &screenScriptSet = *screenScriptIt->_value;
|
|
|
|
ScriptMap_t::const_iterator interactionScriptIt = screenScriptSet.interactionScripts.find(newInteraction);
|
|
if (interactionScriptIt != screenScriptSet.interactionScripts.end())
|
|
newScript = interactionScriptIt->_value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newScript) {
|
|
// This only happens in Reah so we don't have to worry about what to do about frames on the callstack in Schizm
|
|
_gameState = kGameStateScriptReset;
|
|
|
|
CallStackFrame frame;
|
|
frame._script = newScript;
|
|
frame._nextInstruction = 0;
|
|
|
|
_scriptCallStack.resize(1);
|
|
_scriptCallStack[0] = frame;
|
|
} else {
|
|
error("Goto target %u couldn't be resolved", newInteraction);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpEscOn(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_escOn = (stackArgs[0] != 0);
|
|
}
|
|
|
|
void Runtime::scriptOpEscOff(ScriptArg_t arg) {
|
|
_escOn = false;
|
|
}
|
|
|
|
void Runtime::scriptOpEscGet(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_scriptEnv.esc ? 1 : 0));
|
|
_scriptEnv.esc = false;
|
|
}
|
|
|
|
void Runtime::scriptOpBackStart(ScriptArg_t arg) {
|
|
_scriptEnv.exitToMenu = true;
|
|
}
|
|
|
|
void Runtime::scriptOpAllowSaves(ScriptArg_t arg) {
|
|
_forceAllowSaves = true;
|
|
}
|
|
|
|
void Runtime::scriptOpAnimName(ScriptArg_t arg) {
|
|
if (_roomNumber >= _roomDefs.size())
|
|
error("Can't resolve animation for room, room number was invalid");
|
|
|
|
Common::String &animName = _scriptSet->strings[arg];
|
|
|
|
// In Reah, animations are mapped to rooms.
|
|
//
|
|
// In Schizm this can get very complicated: It supports overlapping room logics which in some cases
|
|
// have animation ranges mapped to a different animation.
|
|
//
|
|
// For example, in Schizm, rooms 25-28 all share one logic file and their corresponding animations are
|
|
// largely duplicates of each other with different skies.
|
|
//
|
|
// It appears that the animation to select is based on the remapped room if the animation can't be
|
|
// found in its primary room.
|
|
//
|
|
// For example, PRZYCUMIE_KRZESELKO is mapped to an animation range in room 25 and another range in
|
|
// room 26, and there is no mapping for room 28. In this case, the animation frame range from room 25
|
|
// is used, but it is remapped to animation 28.
|
|
Common::SharedPtr<RoomDef> roomDef = _roomDefs[_roomNumber];
|
|
if (roomDef) {
|
|
Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(animName);
|
|
if (it != roomDef->animations.end()) {
|
|
pushAnimDef(it->_value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (_roomNumber < _roomDuplicationOffsets.size() && _roomDuplicationOffsets[_roomNumber] != 0) {
|
|
int roomToUse = _roomNumber - _roomDuplicationOffsets[_roomNumber];
|
|
|
|
roomDef = _roomDefs[roomToUse];
|
|
|
|
Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(animName);
|
|
if (it != roomDef->animations.end()) {
|
|
AnimationDef animDef = it->_value;
|
|
|
|
if (animDef.animNum == roomToUse)
|
|
animDef.animNum = _roomNumber;
|
|
else if (animDef.animNum == -roomToUse)
|
|
animDef.animNum = -static_cast<int>(_roomNumber);
|
|
|
|
pushAnimDef(animDef);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
error("Can't resolve animation for room, couldn't find animation '%s'", animName.c_str());
|
|
}
|
|
|
|
void Runtime::scriptOpValueName(ScriptArg_t arg) {
|
|
if (_roomNumber >= _roomDefs.size())
|
|
error("Invalid room number for var name op");
|
|
|
|
const RoomDef *roomDef = _roomDefs[_roomNumber].get();
|
|
if (!roomDef)
|
|
error("Room def doesn't exist");
|
|
|
|
const Common::String &varName = _scriptSet->strings[arg];
|
|
|
|
Common::HashMap<Common::String, int>::const_iterator it = roomDef->values.find(varName);
|
|
if (it == roomDef->values.end())
|
|
error("Value '%s' doesn't exist in room %i", varName.c_str(), static_cast<int>(_roomNumber));
|
|
|
|
_scriptStack.push_back(StackValue(it->_value));
|
|
}
|
|
|
|
void Runtime::scriptOpVarName(ScriptArg_t arg) {
|
|
if (_roomNumber >= _roomDefs.size())
|
|
error("Invalid room number for var name op");
|
|
|
|
const RoomDef *roomDef = _roomDefs[_roomNumber].get();
|
|
if (!roomDef)
|
|
error("Room def doesn't exist");
|
|
|
|
const Common::String &varName = _scriptSet->strings[arg];
|
|
|
|
Common::HashMap<Common::String, uint>::const_iterator it = roomDef->vars.find(varName);
|
|
if (it == roomDef->vars.end())
|
|
error("Var '%s' doesn't exist in room %i", varName.c_str(), static_cast<int>(_roomNumber));
|
|
|
|
_scriptStack.push_back(StackValue(it->_value));
|
|
}
|
|
|
|
void Runtime::scriptOpSoundName(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
|
|
}
|
|
|
|
void Runtime::scriptOpCursorName(ScriptArg_t arg) {
|
|
const Common::String &cursorName = _scriptSet->strings[arg];
|
|
|
|
Common::HashMap<Common::String, StackInt_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
|
|
if (namedCursorIt == _namedCursors.end()) {
|
|
error("Unimplemented cursor name '%s'", cursorName.c_str());
|
|
return;
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(namedCursorIt->_value));
|
|
}
|
|
|
|
void Runtime::scriptOpDubbing(ScriptArg_t arg) {
|
|
warning("Dubbing op not implemented");
|
|
}
|
|
|
|
void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
|
|
PEEK_STACK(1);
|
|
|
|
if (stackArgs[0].type == StackValue::kNumber && stackArgs[0].value.i == arg)
|
|
_scriptStack.pop_back();
|
|
else
|
|
_scriptCallStack.back()._nextInstruction++;
|
|
}
|
|
|
|
void Runtime::scriptOpJump(ScriptArg_t arg) {
|
|
_scriptCallStack.back()._nextInstruction = arg;
|
|
}
|
|
|
|
void Runtime::scriptOpMusicStop(ScriptArg_t arg) {
|
|
_musicWavePlayer.reset();
|
|
if (_musicMidiPlayer) {
|
|
Common::StackLock lock(_midiPlayerMutex);
|
|
_musicMidiPlayer.reset();
|
|
}
|
|
_musicActive = false;
|
|
}
|
|
|
|
void Runtime::scriptOpMusicPlayScore(ScriptArg_t arg) {
|
|
TAKE_STACK_STR(2);
|
|
|
|
_scoreTrack = stackArgs[0];
|
|
_scoreSection = stackArgs[1];
|
|
_musicActive = true;
|
|
|
|
startScoreSection();
|
|
}
|
|
|
|
void Runtime::scriptOpScoreAlways(ScriptArg_t arg) {
|
|
assert(_gameID == GID_SCHIZM);
|
|
|
|
_musicMuteDisabled = true;
|
|
|
|
// We don't call startScoreSection here because ScoreAlways is always followed by a PlayScore
|
|
// that triggers the actual music, and we don't want to play any amount of the score that's about
|
|
// to be disabled. PlayScore will call startScoreSection after changing to the correct section.
|
|
}
|
|
|
|
void Runtime::scriptOpScoreNormal(ScriptArg_t arg) {
|
|
_musicMuteDisabled = false;
|
|
|
|
if (_musicMute) {
|
|
_musicWavePlayer.reset();
|
|
if (_musicMidiPlayer) {
|
|
Common::StackLock lock(_midiPlayerMutex);
|
|
_musicMidiPlayer.reset();
|
|
}
|
|
_scoreSectionEndTime = 0;
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSndPlay(ScriptArg_t arg) {
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorAuto, *cachedSound, getSilentSoundVolume(), 0, false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSndPlayEx(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_VAR_NAMED(1, sndNameArgs);
|
|
|
|
Common::String soundName;
|
|
if (sndNameArgs[0].type == StackValue::kString)
|
|
soundName = sndNameArgs[0].value.s;
|
|
else if (sndNameArgs[0].type == StackValue::kNumber) {
|
|
// Sometimes the name is a string, such as the bell puzzle in the temple.
|
|
// In this case the number is the name, with no suffix.
|
|
soundName = Common::String::format("%i", static_cast<int>(sndNameArgs[0].value.i));
|
|
} else
|
|
error("Invalid sound name type for SndPlayEx");
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(soundName, true, soundID, cachedSound);
|
|
|
|
if (cachedSound)
|
|
triggerSound(kSoundLoopBehaviorAuto, *cachedSound, sndParamArgs[0], sndParamArgs[1], false, false);
|
|
}
|
|
|
|
void Runtime::scriptOpSndPlay3D(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(5, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
SoundParams3D sndParams;
|
|
sndParams.minRange = sndParamArgs[2];
|
|
sndParams.maxRange = sndParamArgs[3];
|
|
sndParams.unknownRange = sndParamArgs[4]; // Doesn't appear to be the same thing as Reah. Usually 1000, sometimes 2000 or 3000.
|
|
|
|
if (cachedSound) {
|
|
setSound3DParameters(*cachedSound, sndParamArgs[0], sndParamArgs[1], sndParams);
|
|
triggerSound(kSoundLoopBehaviorAuto, *cachedSound, getSilentSoundVolume(), 0, true, false);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSndPlaying(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
SoundInstance *snd = resolveSoundByID(stackArgs[0]);
|
|
if (!snd || !snd->cache) {
|
|
_scriptStack.push_back(StackValue(0));
|
|
return;
|
|
}
|
|
|
|
if (snd->cache->isLoopActive) {
|
|
_scriptStack.push_back(StackValue(1));
|
|
return;
|
|
}
|
|
|
|
bool hasEnded = (snd->endTime < g_system->getMillis());
|
|
|
|
_scriptStack.push_back(StackValue(hasEnded ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpSndWait(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
SoundInstance *snd = resolveSoundByID(stackArgs[0]);
|
|
if (snd) {
|
|
_delayCompletionTime = snd->endTime;
|
|
_gameState = kGameStateDelay;
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSndHalt(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
SoundInstance *snd = resolveSoundByID(stackArgs[0]);
|
|
if (snd) {
|
|
convertLoopingSoundToNonLooping(*snd);
|
|
|
|
_delayCompletionTime = snd->endTime;
|
|
_gameState = kGameStateDelay;
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSndToBack(ScriptArg_t arg) {
|
|
recordSounds(*_altState);
|
|
}
|
|
|
|
void Runtime::scriptOpSndStop(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
SoundInstance *cachedSound = resolveSoundByID(stackArgs[0]);
|
|
|
|
if (cachedSound)
|
|
stopSound(*cachedSound);
|
|
}
|
|
|
|
void Runtime::scriptOpSndStopAll(ScriptArg_t arg) {
|
|
for (const Common::SharedPtr<SoundInstance> &snd : _activeSounds)
|
|
stopSound(*snd);
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeAdd(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
SoundInstance *cachedSound = resolveSoundByID(static_cast<uint>(stackArgs[0]));
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, stackArgs[1] * 100, cachedSound->volume + stackArgs[2], false);
|
|
}
|
|
|
|
void Runtime::scriptOpVolumeChange(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
SoundInstance *cachedSound = resolveSoundByID(static_cast<uint>(stackArgs[0]));
|
|
|
|
if (cachedSound)
|
|
triggerSoundRamp(*cachedSound, stackArgs[1] * 100, stackArgs[2], false);
|
|
}
|
|
|
|
void Runtime::scriptOpAnimVolume(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
_animVolume = stackArgs[0];
|
|
|
|
applyAnimationVolume();
|
|
}
|
|
|
|
void Runtime::scriptOpAnimChange(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
if (stackArgs[1] == 0)
|
|
error("animChange frame count shouldn't be zero");
|
|
|
|
_scriptEnv.animChangeSet = true;
|
|
_scriptEnv.animChangeFrameOffset = stackArgs[0];
|
|
_scriptEnv.animChangeNumFrames = stackArgs[1] - 1;
|
|
}
|
|
|
|
void Runtime::scriptOpScreenName(ScriptArg_t arg) {
|
|
const Common::String &scrName = _scriptSet->strings[arg];
|
|
|
|
uint roomNumber = 0;
|
|
|
|
if (_gameID == GID_SCHIZM) {
|
|
error("Screen numbers should be preprocessed in Schizm");
|
|
|
|
// ... absent that error, we would do roomNumber = _loadedRoomNumber probably.
|
|
// See the comment in optimizeScriptSet for explanation.
|
|
}
|
|
|
|
if (roomNumber < _roomDuplicationOffsets.size())
|
|
roomNumber -= _roomDuplicationOffsets[roomNumber];
|
|
|
|
RoomToScreenNameToRoomMap_t::const_iterator roomIt = _globalRoomScreenNameToScreenIDs.find(roomNumber);
|
|
if (roomIt != _globalRoomScreenNameToScreenIDs.end()) {
|
|
ScreenNameToRoomMap_t::const_iterator screenIt = roomIt->_value.find(scrName);
|
|
|
|
if (screenIt != roomIt->_value.end()) {
|
|
_scriptStack.push_back(StackValue(static_cast<StackInt_t>(screenIt->_value)));
|
|
return;
|
|
}
|
|
}
|
|
|
|
error("Couldn't resolve screen name '%s'", scrName.c_str());
|
|
}
|
|
|
|
void Runtime::scriptOpExtractByte(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(static_cast<StackInt_t>((stackArgs[0] >> (stackArgs[1] * 8) & 0xff))));
|
|
}
|
|
|
|
void Runtime::scriptOpInsertByte(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
StackInt_t value = stackArgs[0];
|
|
StackInt_t valueToInsert = (stackArgs[1] & 0xff);
|
|
int bytePos = stackArgs[2];
|
|
|
|
StackInt_t mask = static_cast<StackInt_t>(0xff) << (bytePos * 8);
|
|
|
|
value -= (value & mask);
|
|
value += (valueToInsert << (bytePos * 8));
|
|
|
|
_scriptStack.push_back(StackValue(value));
|
|
}
|
|
|
|
void Runtime::scriptOpString(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
|
|
}
|
|
|
|
void Runtime::scriptOpSpeechEx(ScriptArg_t arg) {
|
|
TAKE_STACK_INT_NAMED(2, sndParamArgs);
|
|
TAKE_STACK_STR_NAMED(1, sndNameArgs);
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
TriggeredOneShot oneShot;
|
|
oneShot.soundID = soundID;
|
|
oneShot.uniqueSlot = sndParamArgs[0];
|
|
|
|
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, sndParamArgs[1], 0, false, true);
|
|
_triggeredOneShots.push_back(oneShot);
|
|
|
|
triggerWaveSubtitles(*cachedSound, sndNameArgs[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSpeechTest(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
bool found = false;
|
|
|
|
for (const TriggeredOneShot &oneShot : _triggeredOneShots) {
|
|
if (oneShot.soundID == static_cast<uint>(stackArgs[0])) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(found ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpRandomInclusive(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
if (stackArgs[0] == 0)
|
|
_scriptStack.push_back(StackValue(0));
|
|
else
|
|
_scriptStack.push_back(StackValue(_rng->getRandomNumber(stackArgs[0])));
|
|
}
|
|
|
|
void Runtime::scriptOpHeroOut(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
_swapOutRoom = stackArgs[0];
|
|
_swapOutScreen = stackArgs[1];
|
|
_swapOutDirection = stackArgs[2];
|
|
}
|
|
|
|
void Runtime::scriptOpHeroGetPos(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
bool thisHero = false;
|
|
switch (stackArgs[0]) {
|
|
case 0:
|
|
thisHero = (_hero == 0);
|
|
break;
|
|
case 1:
|
|
thisHero = (_hero == 1);
|
|
break;
|
|
case 2:
|
|
thisHero = false;
|
|
break;
|
|
default:
|
|
error("Unhandled heroGetPos argument %i", static_cast<int>(stackArgs[0]));
|
|
return;
|
|
}
|
|
|
|
uint roomNumber = thisHero ? _roomNumber : _altState->roomNumber;
|
|
uint screenNumber = thisHero ? _screenNumber : _altState->screenNumber;
|
|
uint direction = thisHero ? _direction : _altState->direction;
|
|
|
|
uint combined = (roomNumber << 16) | (screenNumber << 8) | direction;
|
|
|
|
_scriptStack.push_back(StackValue(static_cast<StackInt_t>(combined)));
|
|
}
|
|
|
|
void Runtime::scriptOpHeroSetPos(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
bool thisHero = false;
|
|
switch (stackArgs[0]) {
|
|
case 0:
|
|
thisHero = (_hero == 0);
|
|
break;
|
|
case 1:
|
|
thisHero = (_hero == 1);
|
|
break;
|
|
case 2:
|
|
thisHero = false;
|
|
break;
|
|
default:
|
|
error("Unhandled heroSetPos argument %i", static_cast<int>(stackArgs[0]));
|
|
return;
|
|
}
|
|
|
|
if (thisHero) {
|
|
error("heroSetPos for the current hero isn't supported (and Schizm's game scripts shouldn't be doing it).");
|
|
return;
|
|
}
|
|
|
|
_altState->roomNumber = (stackArgs[1] >> 16) & 0xff;
|
|
_altState->screenNumber = (stackArgs[1] >> 8) & 0xff;
|
|
_altState->direction = stackArgs[1] & 0xff;
|
|
_altState->havePendingPostSwapScreenReset = true;
|
|
}
|
|
|
|
void Runtime::scriptOpHeroGet(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_hero));
|
|
}
|
|
|
|
void Runtime::scriptOpGetRoom(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_roomNumber));
|
|
}
|
|
|
|
void Runtime::scriptOpBitAnd(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] & stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpBitOr(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] | stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpAngleGet(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_direction));
|
|
}
|
|
|
|
void Runtime::scriptOpIsDVDVersion(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_isCDVariant ? 0 : 1));
|
|
}
|
|
|
|
void Runtime::scriptOpIsCDVersion(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_isCDVariant ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpDisc(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
(void)stackArgs;
|
|
|
|
// Always pass correct disc checks
|
|
_scriptStack.push_back(StackValue(1));
|
|
}
|
|
|
|
void Runtime::scriptOpHidePanel(ScriptArg_t arg) {
|
|
_isInGame = false;
|
|
|
|
clearTray();
|
|
}
|
|
|
|
void Runtime::scriptOpRotateUpdate(ScriptArg_t arg) {
|
|
warning("RotateUpdate op not implemented yet");
|
|
}
|
|
|
|
void Runtime::scriptOpMul(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] * stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpDiv(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
if (stackArgs[1] == 0) {
|
|
error("Division by zero");
|
|
return;
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] / stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpMod(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
if (stackArgs[1] == 0) {
|
|
error("Division by zero");
|
|
return;
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(stackArgs[0] % stackArgs[1]));
|
|
}
|
|
|
|
void Runtime::scriptOpGetDigit(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
StackInt_t digit = (stackArgs[0] >> (stackArgs[1] * 4)) & 0xf;
|
|
|
|
_scriptStack.push_back(StackValue(digit));
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleInit(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(kAnimDefStackArgs * 2 + 3);
|
|
|
|
AnimationDef animDef1 = stackArgsToAnimDef(stackArgs + 0);
|
|
AnimationDef animDef2 = stackArgsToAnimDef(stackArgs + kAnimDefStackArgs);
|
|
|
|
int firstMover = stackArgs[kAnimDefStackArgs * 2 + 0];
|
|
int firstMover2 = stackArgs[kAnimDefStackArgs * 2 + 1];
|
|
int unknownParam = stackArgs[kAnimDefStackArgs * 2 + 2];
|
|
|
|
if (firstMover != firstMover2 || unknownParam != 0)
|
|
error("PuzzleInit had a weird parameter");
|
|
|
|
clearCircuitPuzzle();
|
|
_circuitPuzzle.reset(new CircuitPuzzle(firstMover));
|
|
_circuitPuzzleConnectAnimation = animDef1;
|
|
_circuitPuzzleBlockAnimation = animDef2;
|
|
|
|
_idleIsOnOpenCircuitPuzzleLink = false;
|
|
|
|
_scriptEnv.puzzleWasSet = true;
|
|
|
|
if (firstMover == 2)
|
|
scriptOpPuzzleDoMove2(0);
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleWhoWon(ScriptArg_t arg) {
|
|
StackInt_t winner = 2;
|
|
if (_circuitPuzzle) {
|
|
switch (_circuitPuzzle->checkConclusion()) {
|
|
case CircuitPuzzle::kConclusionNone:
|
|
winner = 0;
|
|
break;
|
|
case CircuitPuzzle::kConclusionPlayerWon:
|
|
winner = 1;
|
|
break;
|
|
case CircuitPuzzle::kConclusionPlayerLost:
|
|
winner = 2;
|
|
|
|
// Clear puzzle so circuit highlights stop appearing
|
|
_circuitPuzzle.reset();
|
|
break;
|
|
default:
|
|
error("Unhandled puzzle conclusion");
|
|
break;
|
|
}
|
|
}
|
|
|
|
_scriptStack.push_back(StackValue(winner));
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleCanPress(ScriptArg_t arg) {
|
|
_scriptStack.push_back(StackValue(_idleIsOnOpenCircuitPuzzleLink ? 1 : 0));
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleDoMove1(ScriptArg_t arg) {
|
|
if (!_idleIsOnOpenCircuitPuzzleLink)
|
|
error("Attempted puzzleDoMove1 but don't have a circuit point");
|
|
|
|
if (!_circuitPuzzle)
|
|
error("Attempted puzzleDoMove1 but the circuit puzzle is gone");
|
|
|
|
_circuitPuzzle->addLink(_idleCircuitPuzzleCoord, _idleIsCircuitPuzzleLinkDown ? CircuitPuzzle::kCellDirectionDown : CircuitPuzzle::kCellDirectionRight);
|
|
|
|
SoundInstance *snd = nullptr;
|
|
StackInt_t soundID = 0;
|
|
resolveSoundByName("85_connect", true, soundID, snd);
|
|
|
|
if (snd)
|
|
triggerSound(kSoundLoopBehaviorNo, *snd, 0, 0, false, false);
|
|
|
|
const CircuitPuzzle::CellRectSpec *rectSpec = _circuitPuzzle->getCellRectSpec(_idleCircuitPuzzleCoord);
|
|
|
|
if (rectSpec) {
|
|
AnimationDef animDef = _circuitPuzzleConnectAnimation;
|
|
animDef.constraintRect = _idleIsCircuitPuzzleLinkDown ? rectSpec->_downLinkRect : rectSpec->_rightLinkRect;
|
|
|
|
changeAnimation(animDef, false);
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
}
|
|
|
|
clearCircuitHighlightRect(_idleCircuitPuzzleLinkHighlightRect);
|
|
_idleIsOnOpenCircuitPuzzleLink = false;
|
|
|
|
changeToCursor(_cursors[kCursorArrow]);
|
|
|
|
// Return to idle so the mouse changes to the correct cursor if it's moved over a new link while waiting for the animation
|
|
_havePendingReturnToIdleState = true;
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleDoMove2(ScriptArg_t arg) {
|
|
if (!_circuitPuzzle)
|
|
error("Attempted puzzleDoMove2 but the circuit puzzle is gone");
|
|
|
|
CircuitPuzzle::CellDirection actionDirection = CircuitPuzzle::kCellDirectionDown;
|
|
Common::Point actionCoord;
|
|
|
|
if (_circuitPuzzle->executeAIAction(*_rng, actionCoord, actionDirection)) {
|
|
SoundInstance *snd = nullptr;
|
|
StackInt_t soundID = 0;
|
|
resolveSoundByName("85_block", true, soundID, snd);
|
|
|
|
if (snd)
|
|
triggerSound(kSoundLoopBehaviorNo, *snd, 0, 0, false, false);
|
|
|
|
const CircuitPuzzle::CellRectSpec *rectSpec = _circuitPuzzle->getCellRectSpec(actionCoord);
|
|
|
|
if (rectSpec) {
|
|
AnimationDef animDef = _circuitPuzzleBlockAnimation;
|
|
animDef.constraintRect = (actionDirection == CircuitPuzzle::kCellDirectionDown) ? rectSpec->_downBarrierRect : rectSpec->_rightBarrierRect;
|
|
|
|
changeAnimation(animDef, false);
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpPuzzleDone(ScriptArg_t arg) {
|
|
_circuitPuzzle.reset();
|
|
}
|
|
|
|
// AD2044 ops
|
|
void Runtime::scriptOpAnimT(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
StackInt_t animationID = stackArgs[0];
|
|
|
|
Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _currentRoomAnimIDToFrameRange.find(animationID);
|
|
if (animRangeIt == _currentRoomAnimIDToFrameRange.end())
|
|
error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
|
|
|
|
AnimationDef animDef;
|
|
animDef.animNum = animRangeIt->_value.animationNum;
|
|
animDef.firstFrame = animRangeIt->_value.firstFrame;
|
|
animDef.lastFrame = animRangeIt->_value.lastFrame;
|
|
|
|
_keepStaticAnimationInIdle = true;
|
|
|
|
_haveIdleAnimations[0] = true;
|
|
|
|
StaticAnimation &outAnim = _idleAnimations[0];
|
|
|
|
outAnim = StaticAnimation();
|
|
outAnim.animDefs[0] = animDef;
|
|
outAnim.animDefs[1] = animDef;
|
|
}
|
|
|
|
void Runtime::scriptOpAnimAD2044(bool isForward) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
int16 animationID = 0;
|
|
|
|
bool found = false;
|
|
|
|
for (const AD2044AnimationDef &def : _ad2044AnimationDefs) {
|
|
if (def.roomID == _roomNumber && static_cast<StackInt_t>(def.lookupID) == stackArgs[0]) {
|
|
animationID = isForward ? def.fwdAnimationID : def.revAnimationID;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
error("Couldn't resolve animation lookup ID %i", static_cast<int>(stackArgs[0]));
|
|
|
|
Common::HashMap<int, AnimFrameRange>::const_iterator animRangeIt = _currentRoomAnimIDToFrameRange.find(animationID);
|
|
if (animRangeIt == _currentRoomAnimIDToFrameRange.end())
|
|
error("Couldn't resolve animation ID %i", static_cast<int>(animationID));
|
|
|
|
AnimationDef animDef;
|
|
animDef.animNum = animRangeIt->_value.animationNum;
|
|
animDef.firstFrame = animRangeIt->_value.firstFrame;
|
|
animDef.lastFrame = animRangeIt->_value.lastFrame;
|
|
|
|
for (const AD2044UnusualAnimationRules &unusualAnimRule : g_unusualAnimationRules) {
|
|
if (static_cast<StackInt_t>(unusualAnimRule.animLookupID) == stackArgs[0] && unusualAnimRule.interactionID == _scriptEnv.clickInteractionID && unusualAnimRule.roomNumber == _roomNumber && unusualAnimRule.screenNumber == _screenNumber) {
|
|
switch (unusualAnimRule.ruleType) {
|
|
case AD2044UnusualAnimationRules::kTypePlayFirstFrameOnly:
|
|
animDef.lastFrame = animDef.firstFrame;
|
|
break;
|
|
default:
|
|
error("Unknown unusual animation rule");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
changeAnimation(animDef, animDef.firstFrame, true, _animSpeedDefault);
|
|
|
|
_gameState = kGameStateWaitingForAnimation;
|
|
_screenNumber = stackArgs[1];
|
|
_havePendingScreenChange = true;
|
|
|
|
clearIdleAnimations();
|
|
|
|
if (_loadedAnimationHasSound)
|
|
changeToCursor(nullptr);
|
|
else {
|
|
changeToCursor(_cursors[kCursorWait]);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpAnimForward(ScriptArg_t arg) {
|
|
scriptOpAnimAD2044(true);
|
|
}
|
|
|
|
void Runtime::scriptOpAnimReverse(ScriptArg_t arg) {
|
|
scriptOpAnimAD2044(false);
|
|
}
|
|
|
|
OPCODE_STUB(AnimKForward)
|
|
|
|
void Runtime::scriptOpNoUpdate(ScriptArg_t arg) {
|
|
}
|
|
|
|
void Runtime::scriptOpNoClear(ScriptArg_t arg) {
|
|
}
|
|
|
|
void Runtime::scriptOpSayCycle_AD2044(const StackInt_t *values, uint numValues) {
|
|
// Checking the scripts, there don't appear to be any cycles that can't be tracked from
|
|
// the first value, so just use that.
|
|
uint &cyclePosRef = _sayCycles[static_cast<uint32>(values[0])];
|
|
|
|
Common::String soundName = Common::String::format("%02i-%08i", static_cast<int>(_disc * 10u + 1u), static_cast<int>(values[cyclePosRef]));
|
|
|
|
cyclePosRef = (cyclePosRef + 1u) % numValues;
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(soundName, true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
TriggeredOneShot oneShot;
|
|
oneShot.soundID = soundID;
|
|
oneShot.uniqueSlot = _disc;
|
|
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSay2K(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
scriptOpSayCycle_AD2044(stackArgs, 2);
|
|
}
|
|
|
|
void Runtime::scriptOpSay3K(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(3);
|
|
|
|
scriptOpSayCycle_AD2044(stackArgs, 3);
|
|
}
|
|
|
|
void Runtime::scriptOpSay1_AD2044(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
Common::String soundName = Common::String::format("%02i-%08i", static_cast<int>(_disc * 10u + 1u), static_cast<int>(stackArgs[0]));
|
|
|
|
StackInt_t soundID = 0;
|
|
SoundInstance *cachedSound = nullptr;
|
|
resolveSoundByName(soundName, true, soundID, cachedSound);
|
|
|
|
if (cachedSound) {
|
|
TriggeredOneShot oneShot;
|
|
oneShot.soundID = soundID;
|
|
oneShot.uniqueSlot = _disc;
|
|
|
|
if (Common::find(_triggeredOneShots.begin(), _triggeredOneShots.end(), oneShot) == _triggeredOneShots.end()) {
|
|
triggerSound(kSoundLoopBehaviorNo, *cachedSound, 100, 0, false, true);
|
|
_triggeredOneShots.push_back(oneShot);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Runtime::scriptOpSay1Rnd(ScriptArg_t arg) {
|
|
scriptOpSay1_AD2044(arg);
|
|
}
|
|
|
|
void Runtime::scriptOpSay2_AD2044(ScriptArg_t arg) {
|
|
scriptOpSay1_AD2044(arg);
|
|
}
|
|
|
|
void Runtime::scriptOpM(ScriptArg_t arg) {
|
|
// Looks like this is possibly support to present a mouse click prompt and end
|
|
// with the #EM instruction, but so far as best I can tell, it just stops
|
|
// execution.
|
|
scriptOpLMB(arg);
|
|
}
|
|
|
|
void Runtime::scriptOpEM(ScriptArg_t arg) {
|
|
}
|
|
|
|
void Runtime::scriptOpSE(ScriptArg_t arg) {
|
|
// English subtitle
|
|
if (_language == Common::PL_POL)
|
|
return;
|
|
|
|
_subtitleText = _scriptSet->strings[arg];
|
|
}
|
|
|
|
void Runtime::scriptOpSDot(ScriptArg_t arg) {
|
|
// Polish subtitle
|
|
if (_language == Common::PL_POL)
|
|
return;
|
|
|
|
_subtitleText = _scriptSet->strings[arg];
|
|
}
|
|
|
|
void Runtime::scriptOpE(ScriptArg_t arg) {
|
|
if (_language == Common::PL_POL)
|
|
return;
|
|
|
|
_tooltipText = _scriptSet->strings[arg];
|
|
redrawSubtitleSection();
|
|
}
|
|
|
|
void Runtime::scriptOpDot(ScriptArg_t arg) {
|
|
if (_language != Common::PL_POL)
|
|
return;
|
|
|
|
_tooltipText = _scriptSet->strings[arg];
|
|
redrawSubtitleSection();
|
|
}
|
|
|
|
void Runtime::scriptOpSound(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
debug(1, "TODO: %s() stackArgs[0]:%d stackArgs[1]:%d", __FUNCTION__, stackArgs[0], stackArgs[1]);
|
|
}
|
|
|
|
void Runtime::scriptOpISound(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(2);
|
|
|
|
debug(1, "TODO: %s() stackArgs[0]:%d stackArgs[1]:%d", __FUNCTION__, stackArgs[0], stackArgs[1]);
|
|
}
|
|
|
|
OPCODE_STUB(USound)
|
|
|
|
void Runtime::scriptOpRGet(ScriptArg_t arg) {
|
|
StackInt_t itemID = 0x2000;
|
|
|
|
if (_inventoryActiveItem.itemID < kNumAD2044Items) {
|
|
itemID = g_ad2044ItemInfos[_inventoryActiveItem.itemID].scriptItemID;
|
|
if (itemID == 0 && _inventoryActiveItem.itemID != 0) {
|
|
warning("No script item ID for item type %i", static_cast<int>(_inventoryActiveItem.itemID));
|
|
itemID = 0x2000;
|
|
}
|
|
} else
|
|
error("Invalid item ID");
|
|
|
|
_scriptStack.push_back(StackValue(itemID));
|
|
}
|
|
|
|
void Runtime::scriptOpRSet(ScriptArg_t arg) {
|
|
TAKE_STACK_INT(1);
|
|
|
|
for (uint itemID = 0; itemID < kNumAD2044Items; itemID++) {
|
|
if (static_cast<StackInt_t>(g_ad2044ItemInfos[itemID].scriptItemID) == stackArgs[0]) {
|
|
|
|
if (_inventoryActiveItem.itemID != itemID) {
|
|
Common::String itemFileName;
|
|
Common::String alphaFileName;
|
|
|
|
_inventoryActiveItem.itemID = itemID;
|
|
getFileNamesForItemGraphic(itemID, itemFileName, alphaFileName);
|
|
_inventoryActiveItem.graphic = loadGraphic(itemFileName, false);
|
|
_inventoryActiveItem.mask = loadGraphic(alphaFileName, false);
|
|
|
|
clearActiveItemGraphic();
|
|
drawActiveItemGraphic();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
error("Couldn't resolve item ID for script item 0x%x", static_cast<int>(stackArgs[0]));
|
|
}
|
|
|
|
void Runtime::scriptOpEndRSet(ScriptArg_t arg) {
|
|
scriptOpRSet(arg);
|
|
|
|
returnFromExaminingItem();
|
|
}
|
|
|
|
void Runtime::scriptOpStop(ScriptArg_t arg) {
|
|
terminateScript();
|
|
}
|
|
|
|
// Unused Schizm ops
|
|
// Only used in fnRandomBirds and fnRandomMachines in Room 60, both of which are unused
|
|
OPCODE_STUB(SndAddRandom)
|
|
OPCODE_STUB(SndClearRandom)
|
|
|
|
// Only used in Room 02 (cheat room, which isn't supported)
|
|
OPCODE_STUB(Speech)
|
|
OPCODE_STUB(Say)
|
|
OPCODE_STUB(Garbage)
|
|
|
|
// Referenced in Room 30 screen 0a4 interaction 0a0, however there is no interaction with that ID,
|
|
// so this is unreachable.
|
|
OPCODE_STUB(Fn)
|
|
|
|
#undef TAKE_STACK_STR
|
|
#undef TAKE_STACK_STR_NAMED
|
|
#undef TAKE_STACK_INT
|
|
#undef TAKE_STACK_INT_NAMED
|
|
#undef TAKE_STACK_VAR
|
|
#undef TAKE_STACK_VAR_NAMED
|
|
#undef PEEK_STACK
|
|
#undef OPCODE_STUB
|
|
|
|
|
|
#ifdef DISPATCH_OP
|
|
#error "DISPATCH_OP already defined"
|
|
#endif
|
|
|
|
#define DISPATCH_OP(op) \
|
|
case ScriptOps::k##op: \
|
|
this->scriptOp##op(arg); \
|
|
break
|
|
|
|
bool Runtime::runScript() {
|
|
if (_scriptCallStack.empty()) {
|
|
terminateScript();
|
|
return true;
|
|
}
|
|
|
|
CallStackFrame &frame = _scriptCallStack.back();
|
|
const Common::Array<Instruction> &instrs = frame._script->instrs;
|
|
|
|
while (_gameState == kGameStateScript) {
|
|
uint instrNum = frame._nextInstruction;
|
|
|
|
if (instrNum >= instrs.size()) {
|
|
_scriptCallStack.pop_back();
|
|
return true;
|
|
}
|
|
|
|
frame._nextInstruction = instrNum + 1u;
|
|
|
|
const Instruction &instr = instrs[instrNum];
|
|
int32 arg = instr.arg;
|
|
|
|
switch (instr.op) {
|
|
DISPATCH_OP(Number);
|
|
DISPATCH_OP(Rotate);
|
|
DISPATCH_OP(Angle);
|
|
DISPATCH_OP(AngleGGet);
|
|
DISPATCH_OP(Speed);
|
|
DISPATCH_OP(SAnimL);
|
|
DISPATCH_OP(ChangeL);
|
|
|
|
DISPATCH_OP(AnimR);
|
|
DISPATCH_OP(AnimF);
|
|
DISPATCH_OP(AnimN);
|
|
DISPATCH_OP(AnimG);
|
|
DISPATCH_OP(AnimS);
|
|
DISPATCH_OP(AnimT);
|
|
DISPATCH_OP(Anim);
|
|
|
|
DISPATCH_OP(Static);
|
|
DISPATCH_OP(VarLoad);
|
|
DISPATCH_OP(VarStore);
|
|
DISPATCH_OP(VarAddAndStore);
|
|
DISPATCH_OP(VarGlobalLoad);
|
|
DISPATCH_OP(VarGlobalStore);
|
|
DISPATCH_OP(ItemCheck);
|
|
DISPATCH_OP(ItemRemove);
|
|
DISPATCH_OP(ItemHighlightSet);
|
|
DISPATCH_OP(ItemAdd);
|
|
DISPATCH_OP(ItemHaveSpace);
|
|
DISPATCH_OP(ItemClear);
|
|
DISPATCH_OP(SetCursor);
|
|
DISPATCH_OP(SetRoom);
|
|
DISPATCH_OP(LMB);
|
|
DISPATCH_OP(LMB1);
|
|
DISPATCH_OP(SoundS1);
|
|
DISPATCH_OP(SoundS2);
|
|
DISPATCH_OP(SoundS3);
|
|
DISPATCH_OP(SoundL1);
|
|
DISPATCH_OP(SoundL2);
|
|
DISPATCH_OP(SoundL3);
|
|
DISPATCH_OP(3DSoundS2);
|
|
DISPATCH_OP(3DSoundL2);
|
|
DISPATCH_OP(3DSoundL3);
|
|
DISPATCH_OP(StopAL);
|
|
DISPATCH_OP(Range);
|
|
DISPATCH_OP(AddXSound);
|
|
DISPATCH_OP(ClrXSound);
|
|
DISPATCH_OP(StopSndLA);
|
|
DISPATCH_OP(StopSndLO);
|
|
|
|
DISPATCH_OP(Music);
|
|
DISPATCH_OP(MusicVolRamp);
|
|
DISPATCH_OP(Parm0);
|
|
DISPATCH_OP(Parm1);
|
|
DISPATCH_OP(Parm2);
|
|
DISPATCH_OP(Parm3);
|
|
DISPATCH_OP(ParmG);
|
|
DISPATCH_OP(SParmX);
|
|
DISPATCH_OP(SAnimX);
|
|
|
|
DISPATCH_OP(VolumeDn2);
|
|
DISPATCH_OP(VolumeDn3);
|
|
DISPATCH_OP(VolumeDn4);
|
|
DISPATCH_OP(VolumeUp3);
|
|
DISPATCH_OP(Random);
|
|
DISPATCH_OP(Drop);
|
|
DISPATCH_OP(Dup);
|
|
DISPATCH_OP(Swap);
|
|
DISPATCH_OP(Say1);
|
|
DISPATCH_OP(Say2);
|
|
DISPATCH_OP(Say3);
|
|
DISPATCH_OP(Say3Get);
|
|
DISPATCH_OP(SetTimer);
|
|
DISPATCH_OP(GetTimer);
|
|
DISPATCH_OP(Delay);
|
|
DISPATCH_OP(LoSet);
|
|
DISPATCH_OP(LoGet);
|
|
DISPATCH_OP(HiSet);
|
|
DISPATCH_OP(HiGet);
|
|
|
|
DISPATCH_OP(Not);
|
|
DISPATCH_OP(And);
|
|
DISPATCH_OP(Or);
|
|
DISPATCH_OP(Add);
|
|
DISPATCH_OP(Sub);
|
|
DISPATCH_OP(Negate);
|
|
DISPATCH_OP(CmpEq);
|
|
DISPATCH_OP(CmpGt);
|
|
DISPATCH_OP(CmpLt);
|
|
|
|
DISPATCH_OP(BitLoad);
|
|
DISPATCH_OP(BitSet0);
|
|
DISPATCH_OP(BitSet1);
|
|
|
|
DISPATCH_OP(Disc1);
|
|
DISPATCH_OP(Disc2);
|
|
DISPATCH_OP(Disc3);
|
|
|
|
DISPATCH_OP(Goto);
|
|
|
|
DISPATCH_OP(EscOn);
|
|
DISPATCH_OP(EscOff);
|
|
DISPATCH_OP(EscGet);
|
|
DISPATCH_OP(BackStart);
|
|
DISPATCH_OP(SaveAs);
|
|
DISPATCH_OP(Save0);
|
|
DISPATCH_OP(Exit);
|
|
DISPATCH_OP(AllowSaves);
|
|
|
|
DISPATCH_OP(AnimName);
|
|
DISPATCH_OP(ValueName);
|
|
DISPATCH_OP(VarName);
|
|
DISPATCH_OP(SoundName);
|
|
DISPATCH_OP(CursorName);
|
|
DISPATCH_OP(Dubbing);
|
|
|
|
DISPATCH_OP(CheckValue);
|
|
DISPATCH_OP(Jump);
|
|
|
|
// Schizm ops
|
|
DISPATCH_OP(CallFunction);
|
|
DISPATCH_OP(Return);
|
|
|
|
DISPATCH_OP(MusicStop);
|
|
DISPATCH_OP(MusicPlayScore);
|
|
DISPATCH_OP(ScoreAlways);
|
|
DISPATCH_OP(ScoreNormal);
|
|
DISPATCH_OP(SndPlay);
|
|
DISPATCH_OP(SndPlayEx);
|
|
DISPATCH_OP(SndPlay3D);
|
|
DISPATCH_OP(SndPlaying);
|
|
DISPATCH_OP(SndWait);
|
|
DISPATCH_OP(SndHalt);
|
|
DISPATCH_OP(SndToBack);
|
|
DISPATCH_OP(SndStop);
|
|
DISPATCH_OP(SndStopAll);
|
|
DISPATCH_OP(SndAddRandom);
|
|
DISPATCH_OP(SndClearRandom);
|
|
DISPATCH_OP(VolumeAdd);
|
|
DISPATCH_OP(VolumeChange);
|
|
DISPATCH_OP(AnimVolume);
|
|
DISPATCH_OP(AnimChange);
|
|
DISPATCH_OP(ScreenName);
|
|
DISPATCH_OP(ExtractByte);
|
|
DISPATCH_OP(InsertByte);
|
|
DISPATCH_OP(String);
|
|
DISPATCH_OP(CmpNE);
|
|
DISPATCH_OP(CmpLE);
|
|
DISPATCH_OP(CmpGE);
|
|
DISPATCH_OP(Speech);
|
|
DISPATCH_OP(SpeechEx);
|
|
DISPATCH_OP(SpeechTest);
|
|
DISPATCH_OP(Say);
|
|
DISPATCH_OP(RandomInclusive);
|
|
DISPATCH_OP(HeroOut);
|
|
DISPATCH_OP(HeroGetPos);
|
|
DISPATCH_OP(HeroSetPos);
|
|
DISPATCH_OP(HeroGet);
|
|
DISPATCH_OP(Garbage);
|
|
DISPATCH_OP(GetRoom);
|
|
DISPATCH_OP(BitAnd);
|
|
DISPATCH_OP(BitOr);
|
|
DISPATCH_OP(AngleGet);
|
|
DISPATCH_OP(IsCDVersion);
|
|
DISPATCH_OP(IsDVDVersion);
|
|
DISPATCH_OP(Disc);
|
|
DISPATCH_OP(HidePanel);
|
|
DISPATCH_OP(RotateUpdate);
|
|
DISPATCH_OP(Mul);
|
|
DISPATCH_OP(Div);
|
|
DISPATCH_OP(Mod);
|
|
DISPATCH_OP(GetDigit);
|
|
DISPATCH_OP(PuzzleInit);
|
|
DISPATCH_OP(PuzzleCanPress);
|
|
DISPATCH_OP(PuzzleDoMove1);
|
|
DISPATCH_OP(PuzzleDoMove2);
|
|
DISPATCH_OP(PuzzleDone);
|
|
DISPATCH_OP(PuzzleWhoWon);
|
|
DISPATCH_OP(Fn);
|
|
DISPATCH_OP(ItemHighlightSetTrue);
|
|
|
|
DISPATCH_OP(AnimForward);
|
|
DISPATCH_OP(AnimReverse);
|
|
DISPATCH_OP(AnimKForward);
|
|
DISPATCH_OP(NoUpdate);
|
|
DISPATCH_OP(NoClear);
|
|
|
|
DISPATCH_OP(Say1_AD2044);
|
|
DISPATCH_OP(Say2_AD2044);
|
|
DISPATCH_OP(Say1Rnd);
|
|
|
|
DISPATCH_OP(M);
|
|
DISPATCH_OP(EM);
|
|
DISPATCH_OP(SE);
|
|
DISPATCH_OP(SDot);
|
|
DISPATCH_OP(E);
|
|
DISPATCH_OP(Dot);
|
|
|
|
DISPATCH_OP(Sound);
|
|
DISPATCH_OP(ISound);
|
|
|
|
DISPATCH_OP(RGet);
|
|
DISPATCH_OP(RSet);
|
|
DISPATCH_OP(EndRSet);
|
|
|
|
DISPATCH_OP(Say2K);
|
|
DISPATCH_OP(Say3K);
|
|
DISPATCH_OP(Stop);
|
|
|
|
default:
|
|
error("Unimplemented opcode %i", static_cast<int>(instr.op));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#undef DISPATCH_OP
|
|
|
|
} // End of namespace VCruise
|