Initial commit
This commit is contained in:
576
engines/hadesch/rooms/minotaur.cpp
Normal file
576
engines/hadesch/rooms/minotaur.cpp
Normal file
@@ -0,0 +1,576 @@
|
||||
/* 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/>.
|
||||
*
|
||||
* Copyright 2020 Google
|
||||
*
|
||||
*/
|
||||
#include "hadesch/hadesch.h"
|
||||
#include "hadesch/video.h"
|
||||
#include "hadesch/ambient.h"
|
||||
|
||||
namespace Hadesch {
|
||||
static const char *kHighlightImage = "r6010ol0";
|
||||
static const char *kMaterialsImage = "r6010ok0";
|
||||
static const char *kMaterialsMoveImage = "r6020ba0";
|
||||
static const char *kMinotaurImage = "r6040ba0";
|
||||
static const char *kDigits = "0123456789";
|
||||
static const int numSquares = 25;
|
||||
static const char *minotaurStates[] = {
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"inactive",
|
||||
"encourage",
|
||||
"trapped",
|
||||
"escaped",
|
||||
"critical",
|
||||
"level",
|
||||
"idle"
|
||||
};
|
||||
|
||||
enum DaedalusDialogState {
|
||||
kMinotaur0 = 0,
|
||||
kMinotaur1 = 1,
|
||||
kMinotaur2 = 2,
|
||||
kMinotaurInactive = 3,
|
||||
kMinotaurEncourage = 4,
|
||||
kMinotaurTrapped = 5,
|
||||
kMinotaurEscaped = 6,
|
||||
kMinotaurCritical = 7,
|
||||
kMinotaurLevel = 8,
|
||||
kMinotaurIdle = 9
|
||||
};
|
||||
|
||||
enum Strength {
|
||||
kStrengthStraw = 1,
|
||||
kStrengthWood = 2,
|
||||
kStrengthBricks = 3,
|
||||
kStrengthStone = 4
|
||||
};
|
||||
|
||||
enum Position {
|
||||
kDown = 0,
|
||||
kLeft = 1,
|
||||
kUp = 2,
|
||||
kRight = 3
|
||||
};
|
||||
|
||||
static const char *dirnames[] = {
|
||||
"down",
|
||||
"left",
|
||||
"up",
|
||||
"right"
|
||||
};
|
||||
|
||||
enum {
|
||||
kRerenderLabyrinth = 1017001
|
||||
};
|
||||
|
||||
static const char *daedalusSoundSMK[] = {
|
||||
"R6100nA0",
|
||||
"R6100wA0",
|
||||
"R6100nB0",
|
||||
"R6100wC0",
|
||||
"R6100nD0",
|
||||
};
|
||||
|
||||
static const TranscribedSound daedalusSoundsAIF[] = {
|
||||
{"R6100nH0", _hs("Help us to move the walls so that they are strong enough to stop the minotaur")},
|
||||
{"R6100nL0", _hs("Click on a square to rotate the walls")},
|
||||
{"R6100nG0", _hs("Some walls are already locked in place and won't rotate")},
|
||||
{"R6100nK0", _hs("If you need help, refer to workman's equations")},
|
||||
{"R6170nA0", _hs("Careful, my friend. Some of the walls are not strong enough")},
|
||||
{"R6150nA0", _hs("You're a brave bullfighter, my friend")},
|
||||
{"R6150nB0", _hs("Keep it up. It looks like he's tiring")},
|
||||
{"R6150nC0", _hs("That's taking the bull by the horns")},
|
||||
{"R6150nD0", _hs("Don't give up. You can't beat him")},
|
||||
{"R6180nA0", _hs("You have beaten the Minotaur. You have the makings of a hero")},
|
||||
{"R6180nC0", _hs("You have beaten the beast at last")},
|
||||
{"R6180nD0", _hs("You have done it. The people of Crete are once again safe")},
|
||||
{"R6170nC0", _hs("Let's try again")},
|
||||
{"R6170nD0", _hs("Warn the people of Crete: the Minotaur has escaped. Workers, keep the Minotaur back in the labyrinth")},
|
||||
{"R6170nE0", _hs("I believe you and the Minotaur have not seen the last of one another")},
|
||||
{"R6170nF0", _hs("Ah that was a nobble effort, my friend")},
|
||||
{"R6160nA0", _hs("The Minotaur has broken though a critical wall. Workers, calm on the beast")},
|
||||
{"R6090eA0", _hs("Eh. Hm")},
|
||||
{"R6190nA0", _hs("Ok. Onto level two")},
|
||||
{"R6190nB0", _hs("Onto level three")},
|
||||
};
|
||||
|
||||
struct Wall {
|
||||
int _id;
|
||||
bool _isCritical;
|
||||
int _inTransit;
|
||||
Strength _strength;
|
||||
Position _position;
|
||||
|
||||
Wall() {
|
||||
_id = -1;
|
||||
_isCritical = false;
|
||||
_strength = kStrengthStraw;
|
||||
_position = kDown;
|
||||
_inTransit = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Cell {
|
||||
Common::Array<Wall> _movableWalls;
|
||||
Common::Array<Wall> _immovableWalls;
|
||||
bool _isRotatable;
|
||||
|
||||
Cell() {
|
||||
_isRotatable = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct Labyrinth {
|
||||
Cell _cells[25];
|
||||
};
|
||||
|
||||
class MinotaurHandler : public Handler {
|
||||
public:
|
||||
MinotaurHandler() {
|
||||
_highlight = -1;
|
||||
|
||||
_dialogState = kMinotaur0;
|
||||
_minotaurX = 1;
|
||||
_minotaurY = 2;
|
||||
_minotaurTileId = 7;
|
||||
_soundCounter = 0;
|
||||
_soundMax = 5;
|
||||
|
||||
_lastChargeSound = -1;
|
||||
_lastTrappedSound = -1;
|
||||
_lastEscapedSound = -1;
|
||||
_levelId = 0;
|
||||
|
||||
// consts
|
||||
xVector = Common::Point(-55, -33);
|
||||
yVector = Common::Point(+55, -33);
|
||||
}
|
||||
|
||||
void handleClick(const Common::String &name) override {
|
||||
if (name.firstChar() >= '0' && name.firstChar() <= '9') {
|
||||
rotate(name.asUint64());
|
||||
renderLabyrinth();
|
||||
return;
|
||||
}
|
||||
/* TODO: MNSH: Daedalus */
|
||||
}
|
||||
|
||||
void handleEvent(int eventId) override {
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
switch (eventId) {
|
||||
case kRerenderLabyrinth:
|
||||
renderLabyrinth();
|
||||
break;
|
||||
case 17953:
|
||||
g_vm->addTimer(17954, 300);
|
||||
break;
|
||||
case 17954:
|
||||
treatDialogState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void handleMouseOver(const Common::String &name) override {
|
||||
if (name.firstChar() >= '0' && name.firstChar() <= '9')
|
||||
_highlight = name.asUint64();
|
||||
else
|
||||
_highlight = -1;
|
||||
renderLabyrinth();
|
||||
}
|
||||
void handleMouseOut(const Common::String &name) override {
|
||||
_highlight = -1;
|
||||
renderLabyrinth();
|
||||
}
|
||||
|
||||
void prepareRoom() override {
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
room->loadHotZones("minotaur.hot", true);
|
||||
// TODO: load other puzzles
|
||||
loadPuzzle("3x3j");
|
||||
room->addStaticLayer("r6010pA0", 10000);
|
||||
room->addStaticLayer("r6010tA0", 6400);
|
||||
room->addStaticLayer("r6010oA0", 5500);
|
||||
room->addStaticLayer("r6010oB0", 4000);
|
||||
renderLabyrinth();
|
||||
g_vm->getHeroBelt()->setColour(HeroBelt::kCool);
|
||||
setDialogState(kMinotaur1);
|
||||
}
|
||||
|
||||
bool handleCheat(const Common::String &cheat) override {
|
||||
for (unsigned i = 0; i < ARRAYSIZE(minotaurStates); i++)
|
||||
if (minotaurStates[i][0] && minotaurStates[i] == cheat) {
|
||||
setDialogState(DaedalusDialogState(i));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void setDialogState(DaedalusDialogState state) {
|
||||
if (_dialogState) {
|
||||
// TODO
|
||||
} else {
|
||||
_dialogState = state;
|
||||
}
|
||||
}
|
||||
|
||||
void playDaedalusSound(int index) {
|
||||
// TODO: balance
|
||||
_currentSound = index;
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
if (index < ARRAYSIZE(daedalusSoundSMK))
|
||||
room->playVideo(daedalusSoundSMK[index], 17953);
|
||||
else
|
||||
room->playSpeech(daedalusSoundsAIF[index-ARRAYSIZE(daedalusSoundSMK)], 17953);
|
||||
}
|
||||
|
||||
void playDaedalusSoundWrap() {
|
||||
Persistent *persistent = g_vm->getPersistent();
|
||||
int index = 0;
|
||||
switch (_dialogState) {
|
||||
case kMinotaur0:
|
||||
treatDialogState();
|
||||
return;
|
||||
case kMinotaur1:
|
||||
index = _soundCounter;
|
||||
break;
|
||||
case kMinotaur2:
|
||||
index = _soundCounter + 5;
|
||||
break;
|
||||
case kMinotaurInactive:
|
||||
index = 9;
|
||||
break;
|
||||
case kMinotaurEncourage:
|
||||
index = randomExcept(10, 13, _lastChargeSound);
|
||||
_lastChargeSound = index;
|
||||
break;
|
||||
case kMinotaurTrapped:
|
||||
if (persistent->_quest == kCreteQuest)
|
||||
index = 14;
|
||||
else
|
||||
index = randomExcept(14, 16, _lastTrappedSound);
|
||||
_lastTrappedSound = index;
|
||||
break;
|
||||
case kMinotaurEscaped:
|
||||
index = randomExcept(17, 20, _lastEscapedSound);
|
||||
_lastEscapedSound = index;
|
||||
break;
|
||||
case kMinotaurCritical:
|
||||
index = 21;
|
||||
break;
|
||||
case kMinotaurLevel:
|
||||
index = 23 + _levelId / 15;
|
||||
break;
|
||||
case kMinotaurIdle:
|
||||
index = 22;
|
||||
break;
|
||||
}
|
||||
|
||||
playDaedalusSound(index);
|
||||
}
|
||||
|
||||
int randomExcept(int from, int to, int except) {
|
||||
Common::RandomSource &r = g_vm->getRnd();
|
||||
if (except < from || except > to)
|
||||
return r.getRandomNumberRng(from, to);
|
||||
int x = r.getRandomNumberRng(from, to - 1);
|
||||
if (x >= except)
|
||||
x++;
|
||||
return x;
|
||||
}
|
||||
|
||||
void treatDialogState() {
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
switch(_dialogState) {
|
||||
case kMinotaur1:
|
||||
if (_soundCounter < _soundMax) {
|
||||
playDaedalusSoundWrap();
|
||||
_soundCounter++;
|
||||
return;
|
||||
}
|
||||
setDialogState(kMinotaur2);
|
||||
return;
|
||||
case kMinotaur2:
|
||||
if (_soundCounter < _soundMax) {
|
||||
playDaedalusSoundWrap();
|
||||
_soundCounter++;
|
||||
return;
|
||||
}
|
||||
room->enableMouse();
|
||||
setDialogState(kMinotaur0);
|
||||
return;
|
||||
default:
|
||||
// TODO: implement this;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void renderLabyrinth() {
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
if (_highlight >= 0)
|
||||
room->selectFrame(kHighlightImage, 9990, 0,
|
||||
getTileBase(_highlight)
|
||||
- Common::Point(34, 52));
|
||||
else
|
||||
room->stopAnim(kHighlightImage);
|
||||
|
||||
for (int cell = 0; cell < numSquares; cell++) {
|
||||
for (int j = 0; j < ARRAYSIZE(dirnames); j++) {
|
||||
room->stopAnim(
|
||||
LayerId(kMaterialsImage, cell, Common::String(dirnames[j]) + "outer"));
|
||||
room->stopAnim(
|
||||
LayerId(kMaterialsImage, cell, Common::String(dirnames[j]) + "inner"));
|
||||
room->stopAnim(
|
||||
LayerId(kMaterialsMoveImage, cell, "to-" + Common::String(dirnames[j])));
|
||||
}
|
||||
|
||||
for (int j = 0; j < (int) _current._cells[cell]._movableWalls.size(); j++) {
|
||||
renderWall(cell, _current._cells[cell]._movableWalls[j], false);
|
||||
}
|
||||
|
||||
// Both original engine and us we're not able to handle 3
|
||||
// walls in the same place. The only way to avoid this from every hapenning
|
||||
// is to never put an immovable wall between 2 cells with movable walls.
|
||||
// Hence if we have any movable walls then we can make immovable walls outer
|
||||
// and they will not conflict with existing labyrinths.
|
||||
// If we have no movable walls we can easily put all immovable walls as inner
|
||||
|
||||
bool immovableAreOuter = !_current._cells[cell]._movableWalls.empty();
|
||||
|
||||
for (int j = 0; j < (int) _current._cells[cell]._immovableWalls.size(); j++) {
|
||||
renderWall(cell, _current._cells[cell]._immovableWalls[j], immovableAreOuter);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: which frame?
|
||||
room->selectFrame(kMinotaurImage, getMinotaurZ(), 30,
|
||||
getTileBase(_minotaurX, _minotaurY) - Common::Point(/*114, 117*/30+82, 33*2+52));
|
||||
}
|
||||
|
||||
int getMinotaurZ() {
|
||||
if (_minotaurX >= 5) {
|
||||
return 6500;
|
||||
}
|
||||
|
||||
if (_minotaurX < 0) {
|
||||
return 4500;
|
||||
}
|
||||
|
||||
if (_minotaurY >= 5) {
|
||||
return 5960;
|
||||
}
|
||||
|
||||
if (_minotaurY < 0) {
|
||||
return 4500;
|
||||
}
|
||||
|
||||
return 5000 + 150 * (_minotaurX + _minotaurY) + 60;
|
||||
}
|
||||
|
||||
void renderWall(int cell, Wall &wall, bool outer) {
|
||||
Common::Point delta;
|
||||
if (wall._inTransit) {
|
||||
wall._inTransit--;
|
||||
g_vm->getVideoRoom()->selectFrame(
|
||||
LayerId(kMaterialsMoveImage, cell, Common::String("to-") + dirnames[wall._position]),
|
||||
getWallZ(cell, wall._position, outer),
|
||||
(wall._strength - kStrengthStraw) * 4 + (wall._position + 1) % 4,
|
||||
getTileBase(cell) + Common::Point(-40, -88));
|
||||
g_vm->addTimer(kRerenderLabyrinth, kDefaultSpeed);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (wall._position) {
|
||||
case kUp:
|
||||
delta = xVector + xVector + yVector + Common::Point(-8, -3) +
|
||||
(outer ? Common::Point(0, 0) : Common::Point(+7, +5));
|
||||
break;
|
||||
case kDown:
|
||||
delta = xVector + yVector + Common::Point(-8, -3) +
|
||||
(outer ? Common::Point(+7, +5) : Common::Point(0, 0));
|
||||
break;
|
||||
case kLeft:
|
||||
delta = xVector + Common::Point(0, -33)
|
||||
+ (outer ? Common::Point(-7, +5) : Common::Point(0, 0));
|
||||
break;
|
||||
case kRight:
|
||||
delta = xVector + yVector + Common::Point(0, -33)
|
||||
+ (outer ? Common::Point(0, 0) : Common::Point(-7, +5));
|
||||
break;
|
||||
}
|
||||
|
||||
Common::Point pos = getTileBase(cell) + delta;
|
||||
|
||||
LayerId layer(kMaterialsImage, cell,
|
||||
Common::String(dirnames[wall._position]) +
|
||||
(outer ? "outer" : "inner"));
|
||||
int frame = (wall._strength - kStrengthStraw) * 2 + (wall._position % 2);
|
||||
g_vm->getVideoRoom()->selectFrame(layer, getWallZ(cell, wall._position, outer), frame, pos);
|
||||
}
|
||||
|
||||
int getWallZ(int cell, Position pos, bool outer) {
|
||||
int zValue = 150 * (cell / 5 + cell % 5) + 5000;
|
||||
switch (pos) {
|
||||
case kUp:
|
||||
zValue += outer? 110 : 100;
|
||||
break;
|
||||
case kDown:
|
||||
zValue += outer ? -10 : 0;
|
||||
break;
|
||||
case kLeft:
|
||||
zValue += outer ? 40 : 50;
|
||||
break;
|
||||
case kRight:
|
||||
zValue += outer ? 80 : 70;
|
||||
break;
|
||||
}
|
||||
return zValue;
|
||||
}
|
||||
|
||||
Common::Point getTileBase(int x, int y) {
|
||||
return Common::Point(320, 456) + xVector * x + yVector * y;
|
||||
}
|
||||
|
||||
Common::Point getTileBase(int id) {
|
||||
return getTileBase(id / 5, id % 5);
|
||||
}
|
||||
|
||||
void rotate(int cell) {
|
||||
for (int j = 0;
|
||||
j < (int) _current._cells[cell]._movableWalls.size();
|
||||
j++) {
|
||||
_current._cells[cell]._movableWalls[j]._position
|
||||
= (Position) ((_current._cells[cell]._movableWalls[j]._position + 1) % 4);
|
||||
_current._cells[cell]._movableWalls[j]._inTransit = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void loadPuzzle(const Common::String &name) {
|
||||
Common::SharedPtr<VideoRoom> room = g_vm->getVideoRoom();
|
||||
Common::SharedPtr<Common::SeekableReadStream> gameStream(room->openFile(name + ".mcf"));
|
||||
Common::SharedPtr<Common::SeekableReadStream> solStream(room->openFile(name + ".sol"));
|
||||
Common::SharedPtr<Common::SeekableReadStream> cwStream(room->openFile(name + ".cw"));
|
||||
readLabStream(_current, gameStream);
|
||||
readLabStream(_solution, solStream);
|
||||
for (int cell = 0; cell < numSquares; cell++) {
|
||||
room->setHotzoneEnabled(Common::String::format("%d", cell),
|
||||
!_current._cells[cell]._movableWalls.empty());
|
||||
}
|
||||
}
|
||||
|
||||
static void readLabStream(Labyrinth &lab, Common::SharedPtr<Common::SeekableReadStream> stream) {
|
||||
stream->readLine(); // Level number
|
||||
int gridSize = stream->readLine().asUint64();
|
||||
|
||||
if (gridSize == 0)
|
||||
gridSize = 1;
|
||||
|
||||
stream->readLine(); // ?
|
||||
stream->readLine(); // ?
|
||||
int numLines = stream->readLine().asUint64();
|
||||
// Extra walls
|
||||
if (gridSize == 3 || gridSize == 4) {
|
||||
Wall w;
|
||||
w._isCritical = false;
|
||||
w._id = -1;
|
||||
w._strength = kStrengthStone;
|
||||
w._position = kRight;
|
||||
if (gridSize == 3)
|
||||
lab._cells[4]._immovableWalls.push_back(w);
|
||||
lab._cells[24]._immovableWalls.push_back(w);
|
||||
}
|
||||
for (int i = 0; i < numLines; i++) {
|
||||
Common::String line = stream->readLine();
|
||||
size_t cur = 0;
|
||||
int rawcellid = line.asUint64();
|
||||
int transformedcellid =
|
||||
5 * (rawcellid / gridSize) +
|
||||
(rawcellid % gridSize) + (5 - gridSize)
|
||||
+ 5 * ((5 - gridSize) / 2);
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
int numWalls = line.substr(cur).asUint64();
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
/*int rotatable =*/ line.substr(cur).asUint64();
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
for (int j = 0; j < numWalls; j++) {
|
||||
Wall w;
|
||||
w._isCritical = false;
|
||||
w._id = line.substr(cur).asUint64();
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
int pos = line.substr(cur).asUint64();
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
w._strength = (Strength) line.substr(cur).asUint64();
|
||||
cur = line.findFirstNotOf(kDigits, cur);
|
||||
cur = line.findFirstOf(kDigits, cur);
|
||||
switch (pos % 4) {
|
||||
case 0:
|
||||
w._position = kLeft;
|
||||
break;
|
||||
case 1:
|
||||
w._position = kUp;
|
||||
break;
|
||||
case 2:
|
||||
w._position = kRight;
|
||||
break;
|
||||
case 3:
|
||||
w._position = kDown;
|
||||
break;
|
||||
}
|
||||
if (pos >= 4) {
|
||||
lab._cells[transformedcellid]._immovableWalls.push_back(w);
|
||||
} else {
|
||||
lab._cells[transformedcellid]._movableWalls.push_back(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Common::Point xVector;
|
||||
Common::Point yVector;
|
||||
int _highlight;
|
||||
|
||||
DaedalusDialogState _dialogState;
|
||||
int _minotaurX;
|
||||
int _minotaurY;
|
||||
int _minotaurTileId;
|
||||
int _lastChargeSound;
|
||||
int _lastTrappedSound;
|
||||
int _lastEscapedSound;
|
||||
int _levelId;
|
||||
int _currentSound;
|
||||
int _soundCounter;
|
||||
int _soundMax;
|
||||
|
||||
Labyrinth _current;
|
||||
Labyrinth _solution;
|
||||
};
|
||||
|
||||
Common::SharedPtr<Hadesch::Handler> makeMinotaurHandler() {
|
||||
return Common::SharedPtr<Hadesch::Handler>(new MinotaurHandler());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user