444 lines
15 KiB
C++
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
|