Files
2026-02-02 04:50:13 +01:00

444 lines
15 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 "mohawk/riven_stacks/tspit.h"
#include "mohawk/cursors.h"
#include "mohawk/riven.h"
#include "mohawk/riven_card.h"
#include "mohawk/riven_graphics.h"
#include "mohawk/riven_inventory.h"
#include "mohawk/riven_video.h"
#include "common/events.h"
namespace Mohawk {
namespace RivenStacks {
TSpit::TSpit(MohawkEngine_Riven *vm) :
DomeSpit(vm, kStackTspit, "tsliders.190", "tsliderbg.190") {
REGISTER_COMMAND(TSpit, xtexterior300_telescopedown);
REGISTER_COMMAND(TSpit, xtexterior300_telescopeup);
REGISTER_COMMAND(TSpit, xtisland390_covercombo);
REGISTER_COMMAND(TSpit, xtatrusgivesbooks);
REGISTER_COMMAND(TSpit, xtchotakesbook);
REGISTER_COMMAND(TSpit, xthideinventory);
REGISTER_COMMAND(TSpit, xt7500_checkmarbles);
REGISTER_COMMAND(TSpit, xt7600_setupmarbles);
REGISTER_COMMAND(TSpit, xt7800_setup);
REGISTER_COMMAND(TSpit, xdrawmarbles);
REGISTER_COMMAND(TSpit, xtakeit);
REGISTER_COMMAND(TSpit, xtscpbtn);
REGISTER_COMMAND(TSpit, xtisland4990_domecheck);
REGISTER_COMMAND(TSpit, xtisland5056_opencard);
REGISTER_COMMAND(TSpit, xtisland5056_resetsliders);
REGISTER_COMMAND(TSpit, xtisland5056_slidermd);
REGISTER_COMMAND(TSpit, xtisland5056_slidermw);
REGISTER_COMMAND(TSpit, xtatboundary);
}
void TSpit::xtexterior300_telescopedown(const ArgumentArray &args) {
// First, show the button movie
RivenVideo *buttonVideo = _vm->_video->openSlot(3);
buttonVideo->seek(0);
buttonVideo->enable();
buttonVideo->playBlocking();
// Don't do anything else if the telescope power is off
if (_vm->_vars["ttelevalve"] == 0)
return;
uint32 &telescopePos = _vm->_vars["ttelescope"];
uint32 &telescopeCover = _vm->_vars["ttelecover"];
if (telescopePos != 1) {
// We're not at the bottom, and we can move down again
// Play a piece of the moving down movie
static const uint32 timeIntervals[] = { 4320, 3440, 2660, 1760, 880, 0 };
uint16 movieCode = telescopeCover ? 1 : 2;
RivenVideo *video = _vm->_video->openSlot(movieCode);
video->enable();
video->seek(timeIntervals[telescopePos]);
_vm->_sound->playCardSound("tTeleMove"); // Play the moving sound
video->playBlocking(timeIntervals[telescopePos - 1]);
video->stop();
// Now move the telescope down a position and refresh
telescopePos--;
_vm->getCard()->enter(false);
return;
}
// We're at the bottom, which means one of two things can happen...
if (telescopeCover == 1 && _vm->_vars["ttelepin"] == 1) {
// ...if the cover is open and the pin is up, the game is now over.
xtopenfissure();
} else {
// ...the telescope can't move down anymore.
// Play the sound of not being able to move
_vm->_sound->playCardSound("tTelDnMore");
}
}
void TSpit::xtexterior300_telescopeup(const ArgumentArray &args) {
// First, show the button movie
RivenVideo *buttonVideo = _vm->_video->openSlot(3);
buttonVideo->seek(0);
buttonVideo->enable();
buttonVideo->playBlocking();
// Don't do anything else if the telescope power is off
if (_vm->_vars["ttelevalve"] == 0)
return;
uint32 &telescopePos = _vm->_vars["ttelescope"];
// Check if we can't move up anymore
if (telescopePos == 5) {
// Play the sound of not being able to move
_vm->_sound->playCardSound("tTelDnMore");
return;
}
// Play a piece of the moving up movie
static const uint32 timeIntervals[] = { 0, 800, 1680, 2560, 3440, 4320 };
uint16 movieCode = _vm->_vars["ttelecover"] ? 4 : 5;
RivenVideo *video = _vm->_video->openSlot(movieCode);
video->enable();
video->seek(timeIntervals[telescopePos - 1]);
_vm->_sound->playCardSound("tTeleMove"); // Play the moving sound
video->playBlocking(timeIntervals[telescopePos]);
video->stop();
// Now move the telescope up a position and refresh
telescopePos++;
_vm->getCard()->enter(false);
}
void TSpit::xtopenfissure() {
if (_vm->_vars["pcage"] == 2) {
// The best ending: Catherine is free, Gehn is trapped, Atrus comes to rescue you.
// And now we fall back to Earth... all the way...
_vm->getCard()->playMovie(8);
runEndGame(8, 5000, 2640);
} else if (_vm->_vars["agehn"] == 4) {
// The ok ending: Catherine is still trapped, Gehn is trapped, Atrus comes to rescue you.
// Nice going! Catherine and the islanders are all dead now! Just go back to your home...
_vm->getCard()->playMovie(9);
runEndGame(9, 5000, 2088);
} else if (_vm->_vars["atrapbook"] == 1) {
// The bad ending: Catherine is trapped, Gehn is free, Atrus gets shot by Gehn,
// And then you get shot by Cho. Nice going! Catherine and the islanders are dead
// and you have just set Gehn free from Riven, not to mention you're dead.
_vm->getCard()->playMovie(10);
runEndGame(10, 5000, 1703);
} else {
// The impossible ending: You don't have Catherine's journal and yet you were somehow
// able to open the hatch on the telescope. The game provides an ending for those who
// cheat, load a saved game with the combo, or just guess the telescope combo. Atrus
// doesn't come and you just fall into the fissure.
_vm->getCard()->playMovie(11);
runEndGame(11, 5000, 0);
}
}
void TSpit::xtisland390_covercombo(const ArgumentArray &args) {
// Called when clicking the telescope cover buttons. args[0] is the button number (1...5).
uint32 &correctDigits = _vm->_vars["tcovercombo"];
if (correctDigits < 5 && args[0] == getComboDigit(_vm->_vars["tcorrectorder"], correctDigits))
correctDigits++;
else
correctDigits = 0;
// If we have hit the correct 5 buttons in a row, activate the hotspot to open up the
// telescope cover.
RivenHotspot *openCover = _vm->getCard()->getHotspotByName("openCover");
openCover->enable(correctDigits == 5);
}
// Atrus' Journal and Trap Book are added to inventory
void TSpit::xtatrusgivesbooks(const ArgumentArray &args) {
// Give the player Atrus' Journal and the Trap book
}
// Trap Book is removed from inventory
void TSpit::xtchotakesbook(const ArgumentArray &args) {
// And now Cho takes the trap book
}
void TSpit::xthideinventory(const ArgumentArray &args) {
}
// Marble Puzzle related constants
static const uint32 kMarbleCount = 6;
static const int kSmallMarbleWidth = 4;
static const int kSmallMarbleHeight = 2;
//static const int kLargeMarbleSize = 8;
static const int kMarbleHotspotSize = 13;
static const char *s_marbleNames[] = { "tred", "torange", "tyellow", "tgreen", "tblue", "tviolet" };
// Marble Puzzle helper functions
// The y portion takes the upper 16 bits, while the x portion takes the lower 16 bits
static void setMarbleX(uint32 &var, byte x) {
var = (var & 0xff00) | (x + 1);
}
static void setMarbleY(uint32 &var, byte y) {
var = ((y + 1) << 16) | (var & 0xff);
}
static byte getMarbleX(uint32 var) {
return (var & 0xff) - 1;
}
static byte getMarbleY(uint32 var) { // Give that that Y you old hag! </bad Seinfeld reference>
return ((var >> 16) & 0xff) - 1;
}
static Common::Rect generateMarbleGridRect(uint16 x, uint16 y) {
// x/y in terms of 0!
static const int marbleGridOffsetX[] = { 134, 202, 270, 338, 406 };
static const int marbleGridOffsetY[] = { 24, 92, 159, 227, 295 };
uint16 offsetX = marbleGridOffsetX[x / 5] + (x % 5) * kMarbleHotspotSize;
uint16 offsetY = marbleGridOffsetY[y / 5] + (y % 5) * kMarbleHotspotSize;
return Common::Rect(offsetX, offsetY, offsetX + kMarbleHotspotSize, offsetY + kMarbleHotspotSize);
}
void TSpit::xt7500_checkmarbles(const ArgumentArray &args) {
// Set apower if the marbles are in their correct spot.
bool valid = true;
static const uint32 marbleFinalValues[] = { 1114121, 1441798, 0, 65552, 65558, 262146 };
for (uint16 i = 0; i < kMarbleCount; i++)
if (_vm->_vars[s_marbleNames[i]] != marbleFinalValues[i]) {
valid = false;
break;
}
// If we have the correct combo, activate the power and reset the marble positions
// Otherwise, make sure the power is off
if (valid) {
_vm->_vars["apower"] = 1;
for (uint16 i = 0; i < kMarbleCount; i++)
_vm->_vars[s_marbleNames[i]] = 0;
} else
_vm->_vars["apower"] = 0;
}
void TSpit::xt7600_setupmarbles(const ArgumentArray &args) {
// Draw the small marbles when we're a step away from the waffle
// Convert from marble X coordinate to screen X coordinate
static const uint16 xPosOffsets[] = {
246, 245, 244, 243, 243, 241, 240, 240, 239, 238, 237, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 226, 225
};
// Convert from marble Y coordinate to screen Y coordinate
static const uint16 yPosOffsets[] = {
261, 263, 265, 267, 268, 270, 272, 274, 276, 278, 281, 284, 285, 288, 290, 293, 295, 298, 300, 303, 306, 309, 311, 314, 316
};
// Handle spacing for y coordinates due to the angle
static const double yAdjusts[] = {
4.56, 4.68, 4.76, 4.84, 4.84, 4.96, 5.04, 5.04, 5.12, 5.2, 5.28, 5.28, 5.36, 5.44, 5.4, 5.6, 5.72, 5.8, 5.88, 5.96, 6.04, 6.12, 6.2, 6.2, 6.28
};
// Waffle state of 0 is up, 1 down
bool waffleDown = _vm->_vars["twaffle"] != 0;
// Note that each of the small marble images is exactly 4x2
// The original seems to scale the marble images from extras.mhk, but
// we're using the pre-scaled images in the stack.
uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, buildCardResourceName("tsmallred"));
for (uint16 i = 0; i < kMarbleCount; i++) {
uint32 var = _vm->_vars[s_marbleNames[i]];
if (var == 0) {
// The marble is still in its initial place
// (Note that this is still drawn even if the waffle is down)
static const uint16 defaultX[] = { 375, 377, 379, 381, 383, 385 };
static const uint16 defaultY[] = { 253, 257, 261, 265, 268, 273 };
_vm->_gfx->copyImageToScreen(baseBitmapId + i, defaultX[i], defaultY[i], defaultX[i] + kSmallMarbleWidth, defaultY[i] + kSmallMarbleHeight);
} else if (waffleDown) {
// The marble is on the grid and the waffle is down
// (Nothing to draw here)
} else {
// The marble is on the grid and the waffle is up
int marbleX = (int)floor(getMarbleX(var) * yAdjusts[getMarbleY(var)] + xPosOffsets[getMarbleY(var)] + 0.5);
int marbleY = yPosOffsets[getMarbleY(var)];
_vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight);
}
}
}
void TSpit::setMarbleHotspots() {
// Set the hotspots
for (uint16 i = 0; i < kMarbleCount; i++) {
uint32 marblePos = _vm->_vars[s_marbleNames[i]];
RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
if (marblePos == 0) // In the receptacle
marbleHotspot->setRect(_marbleBaseHotspots[i]);
else // On the grid
marbleHotspot->setRect(generateMarbleGridRect(getMarbleX(marblePos), getMarbleY(marblePos)));
}
}
void TSpit::xt7800_setup(const ArgumentArray &args) {
// First, let's store the base receptacle hotspots for the marbles
if (_marbleBaseHotspots.empty())
for (uint16 i = 0; i < kMarbleCount; i++) {
RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
_marbleBaseHotspots.push_back(marbleHotspot->getRect());
}
// Move the marble hotspots based on their position variables
setMarbleHotspots();
_vm->_vars["themarble"] = 0;
}
void TSpit::drawMarbles() {
_vm->_gfx->beginScreenUpdate();
for (uint32 i = 0; i < kMarbleCount; i++) {
// Don't draw the marble if we're holding it
if (_vm->_vars["themarble"] - 1 == i)
continue;
RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
Common::Rect rect = marbleHotspot->getRect();
// Trim the rect down a bit
rect.left += 3;
rect.top += 3;
rect.right -= 2;
rect.bottom -= 2;
_vm->_gfx->drawExtrasImage(i + 200, rect);
}
_vm->_gfx->applyScreenUpdate();
}
void TSpit::xdrawmarbles(const ArgumentArray &args) {
// Draw marbles in the closeup
drawMarbles();
}
void TSpit::xtakeit(const ArgumentArray &args) {
// Pick up and move a marble
// First, let's figure out what marble we're now holding
uint32 &marble = _vm->_vars["themarble"];
marble = 0;
for (uint32 i = 0; i < kMarbleCount; i++) {
RivenHotspot *marbleHotspot = _vm->getCard()->getHotspotByName(s_marbleNames[i]);
if (marbleHotspot->containsPoint(getMousePosition())) {
marble = i + 1;
break;
}
}
if (marble == 0) {
// xtakeit() shouldn't be called if we're not on a marble hotspot,
// but maybe another mouse moved event was received between the moment
// this script was queued and the moment it was executed.
return;
}
// Redraw the background
_vm->getCard()->drawPicture(1);
// Loop until the player lets go (or quits)
while (mouseIsDown() && !_vm->hasGameEnded()) {
_vm->doFrame();
}
// Check if we landed in a valid location and no other marble has that location
uint32 &marblePos = _vm->_vars[s_marbleNames[marble - 1]];
bool foundMatch = false;
for (int y = 0; y < 25 && !foundMatch; y++) {
for (int x = 0; x < 25 && !foundMatch; x++) {
Common::Rect testHotspot = generateMarbleGridRect(x, y);
// Let's try to place the marble!
if (testHotspot.contains(getMousePosition())) {
// Set this as the position
setMarbleX(marblePos, x);
setMarbleY(marblePos, y);
// Let's make sure no other marble is in this spot...
for (uint16 i = 0; i < kMarbleCount; i++)
if (i != marble - 1 && _vm->_vars[s_marbleNames[i]] == marblePos)
marblePos = 0;
// We have a match
foundMatch = true;
}
}
}
// If we still don't have a match, reset it to the original location
if (!foundMatch)
marblePos = 0;
// Check the new hotspots and refresh everything
marble = 0;
setMarbleHotspots();
drawMarbles();
}
void TSpit::xtscpbtn(const ArgumentArray &args) {
runDomeButtonMovie();
}
void TSpit::xtisland4990_domecheck(const ArgumentArray &args) {
runDomeCheck();
}
void TSpit::xtisland5056_opencard(const ArgumentArray &args) {
checkDomeSliders();
}
void TSpit::xtisland5056_resetsliders(const ArgumentArray &args) {
resetDomeSliders(24);
}
void TSpit::xtisland5056_slidermd(const ArgumentArray &args) {
dragDomeSlider(24);
}
void TSpit::xtisland5056_slidermw(const ArgumentArray &args) {
checkSliderCursorChange(24);
}
void TSpit::xtatboundary(const ArgumentArray &args) {
runDemoBoundaryDialog();
}
} // End of namespace RivenStacks
} // End of namespace Mohawk