scummvm/engines/avalanche/avalot.cpp

1677 lines
42 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* This code is based on the original source code of Lord Avalot d'Argent version 1.3.
* Copyright (c) 1994-1995 Mike: Mark and Thomas Thurman.
*/
/* AVALOT The kernel of the program. */
#include "avalanche/avalanche.h"
#include "common/random.h"
#include "common/system.h"
#include "common/config-manager.h"
#include "graphics/palette.h"
namespace Avalanche {
// vv Stairs trap.
/* Explanation: $NSEW.
Nibble N: North.
0 = no connection,
2 = (left,) middle(, right) door with left-hand handle,
5 = (left,) middle(, right) door with right-hand handle,
7 = arch,
8 = arch and 1 north of it,
9 = arch and 2 north of it,
D = no connection + WINDOW,
E = no connection + TORCH,
F = recessed door (to Geida's room.)
Nibble S: South.
0 = no connection,
1,2,3 = left, middle, right door.
Nibble E: East.
0 = no connection (wall),
1 = no connection (wall + window),
2 = wall with door,
3 = wall with door and window,
6 = wall with candles,
7 = wall with door and candles,
F = straight-through corridor.
Nibble W: West.
0 = no connection (wall),
1 = no connection (wall + shield),
2 = wall with door,
3 = wall with door and shield,
4 = no connection (window),
5 = wall with door and window,
6 = wall with candles,
7 = wall with door and candles,
F = straight-through corridor. */
const char AvalancheEngine::kSpludwicksOrder[3] = {kObjectOnion, kObjectInk, kObjectMushroom};
const uint16 AvalancheEngine::kNotes[12] = {196, 220, 247, 262, 294, 330, 350, 392, 440, 494, 523, 587};
Room AvalancheEngine::_whereIs[29] = {
// The Lads
kRoomYours, // Avvy
kRoomSpludwicks, // Spludwick
kRoomOutsideYours, // Crapulus
kRoomDucks, // Duck - r__DucksRoom's not defined yet.
kRoomArgentPub, // Malagauche
kRoomRobins, // Friar Tuck.
kRoomDummy, // Robin Hood - can't meet him at the start.
kRoomBrummieRoad, // Cwytalot
kRoomLustiesRoom, // Baron du Lustie.
kRoomOutsideCardiffCastle, // The Duke of Cardiff.
kRoomArgentPub, // Dogfood
kRoomOutsideDucks, // Trader
kRoomArgentPub, // Ibythneth
kRoomAylesOffice, // Ayles
kRoomNottsPub, // Port
kRoomNottsPub, // Spurge
kRoomMusicRoom, // Jacques
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
kRoomNowhere,
// The Lasses
kRoomYours, // Arkata
kRoomGeidas, // Geida
kRoomDummy, // nobody allocated here!
kRoomWiseWomans // The Wise Woman.
};
void AvalancheEngine::handleKeyDown(Common::Event &event) {
_sound->click();
if ((Common::KEYCODE_F1 <= event.kbd.keycode) && (event.kbd.keycode <= Common::KEYCODE_F15))
_parser->handleFunctionKey(event);
else if ((32 <= event.kbd.ascii) && (event.kbd.ascii <= 128) && (event.kbd.ascii != 47))
_parser->handleInputText(event);
else
switch (event.kbd.keycode) { // We can control Avvy with the numpad as well.
case Common::KEYCODE_KP8:
event.kbd.keycode = Common::KEYCODE_UP;
break;
case Common::KEYCODE_KP2:
event.kbd.keycode = Common::KEYCODE_DOWN;
break;
case Common::KEYCODE_KP6:
event.kbd.keycode = Common::KEYCODE_RIGHT;
break;
case Common::KEYCODE_KP4:
event.kbd.keycode = Common::KEYCODE_LEFT;
break;
case Common::KEYCODE_KP9:
event.kbd.keycode = Common::KEYCODE_PAGEUP;
break;
case Common::KEYCODE_KP3:
event.kbd.keycode = Common::KEYCODE_PAGEDOWN;
break;
case Common::KEYCODE_KP7:
event.kbd.keycode = Common::KEYCODE_HOME;
break;
case Common::KEYCODE_KP1:
event.kbd.keycode = Common::KEYCODE_END;
break;
default:
break;
}
switch (event.kbd.keycode) {
case Common::KEYCODE_UP:
case Common::KEYCODE_DOWN:
case Common::KEYCODE_RIGHT:
case Common::KEYCODE_LEFT:
case Common::KEYCODE_PAGEUP:
case Common::KEYCODE_PAGEDOWN:
case Common::KEYCODE_HOME:
case Common::KEYCODE_END:
case Common::KEYCODE_KP5:
if (_alive && _avvyIsAwake) {
_animation->handleMoveKey(event); // Fallthroughs are intended.
drawDirection();
return;
}
case Common::KEYCODE_BACKSPACE:
_parser->handleBackspace();
break;
case Common::KEYCODE_RETURN:
_parser->handleReturn();
break;
default:
break;
}
drawDirection();
}
void AvalancheEngine::setup() {
init();
_dialogs->reset();
fadeOut();
_graphics->loadDigits();
_parser->_inputTextPos = 0;
_parser->_quote = true;
_animation->resetAnims();
_dialogs->setReadyLight(2);
fadeIn();
_parser->_cursorState = false;
_parser->cursorOn();
_animation->_sprites[0]->_speedX = kWalk;
_animation->updateSpeed();
_dropdown->init();
_graphics->drawSoundLight(_sound->_soundFl);
drawToolbar();
int16 loadSlot = ConfMan.instance().getInt("save_slot");
if (loadSlot >= 0) {
_thinks = 2; // You always have money.
thinkAbout(kObjectMoney, kThing);
loadGame(loadSlot);
} else {
// We don't need the MainMenu during the whole game, only at the beginning of it.
MainMenu *mainmenu = new MainMenu(this);
mainmenu->run();
delete mainmenu;
if (_letMeOut)
return;
newGame();
thinkAbout(kObjectMoney, kThing);
_dialogs->displayScrollChain('Q', 83); // Info on the game, etc.
}
}
void AvalancheEngine::runAvalot() {
setup();
while (!_letMeOut && !shouldQuit()) {
uint32 beginLoop = _system->getMillis();
updateEvents(); // The event handler.
_clock->update();
_dropdown->update();
_background->update();
_animation->animLink();
checkClick();
_timer->updateTimer();
_graphics->drawDebugLines();
_graphics->refreshScreen();
uint32 delay = _system->getMillis() - beginLoop;
if (delay <= 55)
_system->delayMillis(55 - delay); // Replaces slowdown(); 55 comes from 18.2 Hz (B Flight).
};
_closing->exitGame();
}
void AvalancheEngine::init() {
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 2; j++)
_also[i][j] = nullptr;
}
_letMeOut = false;
_currentMouse = 177;
_dropsOk = true;
_mouseText = "";
_cheat = false;
_cp = 0;
_ledStatus = 177;
for (int i = 0; i < 3; i++)
_scoreToDisplay[i] = -1; // Impossible digits.
_holdTheDawn = false;
_graphics->loadMouse(kCurWait);
CursorMan.showMouse(true);
}
/**
* Call a given Verb
* @remarks Originally called 'callverb'
*/
void AvalancheEngine::callVerb(VerbCode id) {
if (id == _parser->kPardon) {
Common::String tmpStr = Common::String::format("The f5 key lets you do a particular action in certain " \
"situations. However, at the moment there is nothing assigned to it. You may press alt-A to see " \
"what the current setting of this key is.");
_dialogs->displayText(tmpStr);
} else
_parser->doVerb(id);
}
/**
* Check is it's possible to give something to Spludwick
* @remarks Originally called 'nextstring'
*/
Common::String AvalancheEngine::readAlsoStringFromFile(Common::File &file) {
Common::String str;
byte length = file.readByte();
for (int i = 0; i < length; i++)
str += file.readByte();
return str;
}
void AvalancheEngine::scram(Common::String &str) {
for (uint i = 0; i < str.size(); i++)
str.setChar(str[i] ^ 177, i);
}
void AvalancheEngine::unScramble() {
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 2; j++) {
if (_also[i][j] != nullptr)
scram(*_also[i][j]);
}
}
scram(_listen);
scram(_flags);
}
void AvalancheEngine::loadAlso(byte num) {
for (int i = 0; i < 31; i++) {
for (int j = 0; j < 2; j++) {
if (_also[i][j] != nullptr) {
delete _also[i][j];
_also[i][j] = nullptr;
}
}
}
Common::String filename;
filename = Common::String::format("also%d.avd", num);
Common::File file;
if (!file.open(filename))
error("AVALANCHE: File not found: %s", filename.c_str());
file.seek(128);
byte alsoNum = file.readByte();
Common::String tmpStr;
for (int i = 0; i <= alsoNum; i++) {
for (int j = 0; j < 2; j++) {
_also[i][j] = new Common::String;
*_also[i][j] = readAlsoStringFromFile(file);
}
tmpStr = Common::String::format("\x9D%s\x9D", _also[i][0]->c_str());
*_also[i][0] = tmpStr;
}
memset(_lines, 0xFF, sizeof(_lines));
_lineNum = file.readByte();
for (int i = 0; i < _lineNum; i++) {
LineType *curLine = &_lines[i];
curLine->_x1 = file.readSint16LE();
curLine->_y1 = file.readSint16LE();
curLine->_x2 = file.readSint16LE();
curLine->_y2 = file.readSint16LE();
curLine->_color = (Color)file.readByte();
}
memset(_peds, 177, sizeof(_peds));
byte pedNum = file.readByte();
for (int i = 0; i < pedNum; i++) {
PedType *curPed = &_peds[i];
curPed->_x = file.readSint16LE();
curPed->_y = file.readSint16LE();
curPed->_direction = (Direction)file.readByte();
}
_fieldNum = file.readByte();
for (int i = 0; i < _fieldNum; i++) {
FieldType *curField = &_fields[i];
curField->_x1 = file.readSint16LE();
curField->_y1 = file.readSint16LE();
curField->_x2 = file.readSint16LE();
curField->_y2 = file.readSint16LE();
}
for (int i = 0; i < 15; i++) {
MagicType *magic = &_magics[i];
magic->_operation = file.readByte();
magic->_data = file.readUint16LE();
}
for (int i = 0; i < 7; i++) {
MagicType *portal = &_portals[i];
portal->_operation = file.readByte();
portal->_data = file.readUint16LE();
}
_flags.clear();
for (int i = 0; i < 26; i++)
_flags += file.readByte();
int16 size = file.readByte();
_listen.clear();
for (int i = 0; i < size; i++)
_listen += file.readByte();
_graphics->clearAlso();
CursorMan.showMouse(false);
for (int i = 0; i < _lineNum; i++) {
// We had to check if the lines are within the borders of the screen.
if ((_lines[i]._x1 >= 0) && (_lines[i]._x1 < kScreenWidth) && (_lines[i]._y1 >= 0) && (_lines[i]._y1 < kScreenHeight)
&& (_lines[i]._x2 >= 0) && (_lines[i]._x2 < kScreenWidth) && (_lines[i]._y2 >= 0) && (_lines[i]._y2 < kScreenHeight))
_graphics->setAlsoLine(_lines[i]._x1, _lines[i]._y1, _lines[i]._x2, _lines[i]._y2, _lines[i]._color);
}
CursorMan.showMouse(true);
file.close();
unScramble();
for (int i = 0; i <= alsoNum; i++) {
tmpStr = Common::String::format(",%s,", _also[i][0]->c_str());
*_also[i][0] = tmpStr;
}
}
void AvalancheEngine::loadBackground(byte num) {
Common::String filename = Common::String::format("place%d.avd", num);
Common::File file;
if (!file.open(filename))
error("AVALANCHE: File not found: %s", filename.c_str());
file.seek(146);
if (!_roomnName.empty())
_roomnName.clear();
for (int i = 0; i < 30; i++) {
char actChar = file.readByte();
if ((32 <= actChar) && (actChar <= 126))
_roomnName += actChar;
}
// Compression method byte follows this...
file.seek(177);
_graphics->loadBackground(file);
_graphics->refreshBackground();
file.close();
}
void AvalancheEngine::loadRoom(byte num) {
CursorMan.showMouse(false);
loadBackground(num);
loadAlso(num);
_background->loadSprites(num);
CursorMan.showMouse(true);
}
void AvalancheEngine::findPeople(byte room) {
for (int i = 1; i < 29; i++) {
if (_whereIs[i] == room) {
if (i < 25)
_him = (People)(150 + i);
else
_her = (People)(150 + i);
}
}
}
void AvalancheEngine::exitRoom(byte x) {
_sound->stopSound();
_background->release();
_animationsEnabled = false;
switch (x) {
case kRoomSpludwicks:
_timer->loseTimer(Timer::kReasonAvariciusTalks);
_avariciusTalk = 0;
// He doesn't HAVE to be talking for this to work. It just deletes it IF it exists.
break;
case kRoomBridge:
if (_drawbridgeOpen > 0) {
_drawbridgeOpen = 4; // Fully open.
_timer->loseTimer(Timer::kReasonDrawbridgeFalls);
}
break;
case kRoomOutsideCardiffCastle:
_timer->loseTimer(Timer::kReasonCardiffsurvey);
break;
case kRoomRobins:
_timer->loseTimer(Timer::kReasonGettingTiedUp);
break;
}
_interrogation = 0; // Leaving the room cancels all the questions automatically.
_animationsEnabled = true;
_lastRoom = _room;
if (_room != kRoomMap)
_lastRoomNotMap = _room;
}
/**
* Only when entering a NEW town! Not returning to the last one,
* but choosing another from the map.
* @remarks Originally called 'new_town'
*/
void AvalancheEngine::enterNewTown() {
_dropdown->setup();
switch (_room) {
case kRoomOutsideNottsPub: // Entry into Nottingham.
if ((_roomCount[kRoomRobins] > 0) && (_beenTiedUp) && (!_takenMushroom))
_mushroomGrowing = true;
break;
case kRoomWiseWomans: // Entry into Argent.
if (_talkedToCrapulus && (!_lustieIsAsleep)) {
_spludwickAtHome = !((_roomCount[kRoomWiseWomans] % 3) == 1);
_crapulusWillTell = !_spludwickAtHome;
} else {
_spludwickAtHome = true;
_crapulusWillTell = false;
}
if (_boxContent == kObjectWine)
_wineState = 3; // Vinegar
break;
default:
break;
}
if ((_room != kRoomOutsideDucks) && (_objects[kObjectOnion - 1]) && !(_onionInVinegar))
_rottenOnion = true; // You're holding the onion
}
void AvalancheEngine::putGeidaAt(byte whichPed, byte ped) {
if (ped == 0)
return;
AnimationType *spr1 = _animation->_sprites[1];
spr1->init(5, false); // load Geida
_animation->appearPed(1, whichPed);
spr1->_callEachStepFl = true;
spr1->_eachStepProc = Animation::kProcFollowAvvy;
}
void AvalancheEngine::enterRoom(Room roomId, byte ped) {
_animationsEnabled = false;
findPeople(roomId);
_room = roomId;
if (ped != 0)
_roomCount[roomId]++;
loadRoom(roomId);
if ((_roomCount[roomId] == 0) && (!getFlag('S')))
incScore(1);
_whereIs[kPeopleAvalot - 150] = _room;
if (_geidaFollows)
_whereIs[kPeopleGeida - 150] = roomId;
_roomCycles = 0;
if ((_lastRoom == kRoomMap) && (_lastRoomNotMap != _room))
enterNewTown();
_animation->updateSpeed();
switch (roomId) {
case kRoomYours:
if (_avvyInBed) {
_background->draw(-1, -1, 2);
_graphics->refreshBackground();
_timer->addTimer(100, Timer::kProcArkataShouts, Timer::kReasonArkataShouts);
}
break;
case kRoomOutsideYours:
if (ped > 0) {
AnimationType *spr1 = _animation->_sprites[1];
if (!_talkedToCrapulus) {
_whereIs[kPeopleCrapulus - 150] = kRoomOutsideYours;
spr1->init(8, false); // load Crapulus
if (_roomCount[kRoomOutsideYours] == 1) {
_animation->appearPed(1, 3); // Start on the right-hand side of the screen.
spr1->walkTo(4); // Walks up to greet you.
} else {
_animation->appearPed(1, 4); // Starts where he was before.
spr1->_facingDir = kDirLeft;
}
spr1->_callEachStepFl = true;
spr1->_eachStepProc = Animation::kProcFaceAvvy; // He always faces Avvy.
} else
_whereIs[kPeopleCrapulus - 150] = kRoomNowhere;
if (_crapulusWillTell) {
spr1->init(8, false);
_animation->appearPed(1, 1);
spr1->walkTo(3);
_timer->addTimer(20, Timer::kProcCrapulusSpludOut, Timer::kReasonCrapulusSaysSpludwickOut);
_crapulusWillTell = false;
}
}
break;
case kRoomOutsideSpludwicks:
if ((_roomCount[kRoomOutsideSpludwicks] == 1) && (ped == 1)) {
_timer->addTimer(20, Timer::kProcBang, Timer::kReasonExplosion);
_spludwickAtHome = true;
}
break;
case kRoomSpludwicks:
if (_spludwickAtHome) {
AnimationType *spr1 = _animation->_sprites[1];
if (ped > 0) {
spr1->init(2, false); // load Spludwick
_animation->appearPed(1, 1);
_whereIs[kPeopleSpludwick - 150] = kRoomSpludwicks;
}
spr1->_callEachStepFl = true;
spr1->_eachStepProc = Animation::kProcFollowAvvy;
} else
_whereIs[kPeopleSpludwick - 150] = kRoomNowhere;
break;
case kRoomBrummieRoad:
if (_geidaFollows)
putGeidaAt(4, ped);
if (_cwytalotGone) {
_magics[kColorLightred - 1]._operation = kMagicNothing;
_whereIs[kPeopleCwytalot - 150] = kRoomNowhere;
} else if (ped > 0) {
AnimationType *spr1 = _animation->_sprites[1];
spr1->init(4, false); // 4 = Cwytalot
spr1->_callEachStepFl = true;
spr1->_eachStepProc = Animation::kProcFollowAvvyY;
_whereIs[kPeopleCwytalot - 150] = kRoomBrummieRoad;
if (_roomCount[kRoomBrummieRoad] == 1) { // First time here...
_animation->appearPed(1, 1); // He appears on the right of the screen...
spr1->walkTo(3); // ...and he walks up...
} else {
// You've been here before.
_animation->appearPed(1, 3); // He's standing in your way straight away...
spr1->_facingDir = kDirLeft;
}
}
break;
case kRoomArgentRoad:
if ((_cwytalotGone) && (!_passedCwytalotInHerts) && (ped == 2) && (_roomCount[kRoomArgentRoad] > 3)) {
AnimationType *spr1 = _animation->_sprites[1];
spr1->init(4, false); // 4 = Cwytalot again
_animation->appearPed(1, 0);
spr1->walkTo(1);
spr1->_vanishIfStill = true;
_passedCwytalotInHerts = true;
// whereis[#157] = r__Nowhere; // can we fit this in?
_timer->addTimer(20, Timer::kProcCwytalotInHerts, Timer::kReasonCwytalotInHerts);
}
break;
case kRoomBridge:
if (_drawbridgeOpen == 4) { // open
_background->draw(-1, -1, 2); // Position of drawbridge
_graphics->refreshBackground();
_magics[kColorGreen - 1]._operation = kMagicNothing; // You may enter the drawbridge.
}
if (_geidaFollows)
putGeidaAt(ped + 2, ped); // load Geida
break;
case kRoomRobins:
if ((ped > 0) && (!_beenTiedUp)) {
// A welcome party... or maybe not...
AnimationType *spr1 = _animation->_sprites[1];
spr1->init(6, false);
_animation->appearPed(1, 1);
spr1->walkTo(2);
_timer->addTimer(36, Timer::kProcGetTiedUp, Timer::kReasonGettingTiedUp);
}
if (_beenTiedUp) {
_whereIs[kPeopleRobinHood - 150] = kRoomNowhere;
_whereIs[kPeopleFriarTuck - 150] = kRoomNowhere;
}
if (_tiedUp)
_background->draw(-1, -1, 1);
if (!_mushroomGrowing)
_background->draw(-1, -1, 2);
_graphics->refreshBackground();
break;
case kRoomOutsideCardiffCastle:
if (ped > 0) {
AnimationType *spr1 = _animation->_sprites[1];
switch (_cardiffQuestionNum) {
case 0 : // You've answered NONE of his questions.
spr1->init(9, false);
_animation->appearPed(1, 1);
spr1->walkTo(2);
_timer->addTimer(47, Timer::kProcCardiffSurvey, Timer::kReasonCardiffsurvey);
break;
case 5 :
_magics[1]._operation = kMagicNothing;
break; // You've answered ALL his questions. => nothing happens.
default: // You've answered SOME of his questions.
spr1->init(9, false);
_animation->appearPed(1, 2);
spr1->_facingDir = kDirRight;
_timer->addTimer(3, Timer::kProcCardiffReturn, Timer::kReasonCardiffsurvey);
}
}
if (_cardiffQuestionNum < 5)
_interrogation = _cardiffQuestionNum;
else
_interrogation = 0;
break;
case kRoomMap:
// You're entering the map.
fadeIn();
if (ped > 0)
_graphics->zoomOut(_peds[ped - 1]._x, _peds[ped - 1]._y);
if ((_objects[kObjectWine - 1]) && (_wineState != 3)) {
_dialogs->displayScrollChain('Q', 9); // Don't want to waste the wine!
_objects[kObjectWine - 1] = false;
refreshObjectList();
}
_dialogs->displayScrollChain('Q', 69);
break;
case kRoomCatacombs:
if ((ped == 0) || (ped == 3) || (ped == 5) || (ped == 6)) {
switch (ped) {
case 3: // Enter from oubliette
_catacombX = 8;
_catacombY = 4;
break;
case 5: // Enter from du Lustie's
_catacombX = 8;
_catacombY = 7;
break;
case 6: // Enter from Geida's
_catacombX = 4;
_catacombY = 1;
break;
}
_enterCatacombsFromLustiesRoom = true;
_animation->catacombMove(ped);
_enterCatacombsFromLustiesRoom = false;
}
break;
case kRoomArgentPub:
if (_wonNim)
_background->draw(-1, -1, 0); // No lute by the settle.
_malagauche = 0; // Ready to boot Malagauche
if (_givenBadgeToIby) {
_background->draw(-1, -1, 7);
_background->draw(-1, -1, 8);
}
_graphics->refreshBackground();
break;
case kRoomLustiesRoom:
_npcFacing = 1; // du Lustie.
if (_animation->getAvvyClothes() == 0) // Avvy in his normal clothes
_timer->addTimer(3, Timer::kProcCallsGuards, Timer::kReasonDuLustieTalks);
else if (!_enteredLustiesRoomAsMonk) // already
// Presumably, Avvy dressed as a monk.
_timer->addTimer(3, Timer::kProcGreetsMonk, Timer::kReasonDuLustieTalks);
if (_geidaFollows) {
putGeidaAt(4, ped);
if (_lustieIsAsleep) {
_background->draw(-1, -1, 4);
_graphics->refreshBackground();
}
}
break;
case kRoomMusicRoom:
if (_jacquesState > 0) {
_jacquesState = 5;
_background->draw(-1, -1, 1);
_graphics->refreshBackground();
_background->draw(-1, -1, 3);
_magics[kColorBrown - 1]._operation = kMagicNothing;
_whereIs[kPeopleJacques - 150] = kRoomNowhere;
}
if (ped != 0) {
_background->draw(-1, -1, 5);
_graphics->refreshBackground();
_sequence->startMusicRoomSeq();
}
break;
case kRoomOutsideNottsPub:
if (ped == 2) {
_background->draw(-1, -1, 2);
_graphics->refreshBackground();
_sequence->startDuckSeq();
}
break;
case kRoomOutsideArgentPub:
if (ped == 2) {
_background->draw(-1, -1, 5);
_graphics->refreshBackground();
_sequence->startMusicRoomSeq();
}
break;
case kRoomWiseWomans: {
AnimationType *spr1 = _animation->_sprites[1];
spr1->init(11, false);
if ((_roomCount[kRoomWiseWomans] == 1) && (ped > 0)) {
_animation->appearPed(1, 1); // Start on the right-hand side of the screen.
spr1->walkTo(3); // Walks up to greet you.
} else {
_animation->appearPed(1, 3); // Starts where she was before.
spr1->_facingDir = kDirLeft;
}
spr1->_callEachStepFl = true;
spr1->_eachStepProc = Animation::kProcFaceAvvy; // She always faces Avvy.
}
break;
case kRoomInsideCardiffCastle:
if (ped > 0) {
_animation->_sprites[1]->init(10, false); // Define the dart.
_background->draw(-1, -1, 0);
_graphics->refreshBackground();
_sequence->startCardiffSeq2();
} else {
_background->draw(-1, -1, 0);
if (_arrowInTheDoor)
_background->draw(-1, -1, 2);
else
_background->draw(-1, -1, 1);
_graphics->refreshBackground();
}
break;
case kRoomAvvysGarden:
if (ped == 1) {
_background->draw(-1, -1, 1);
_graphics->refreshBackground();
_sequence->startGardenSeq();
}
break;
case kRoomEntranceHall:
case kRoomInsideAbbey:
case kRoomYourHall:
if (ped == 2) {
#if 0
// It was the original:
_celer->show_one(-1, -1, 2);
_sequence->first_show(1);
_sequence->then_show(3);
_sequence->start_to_close();
#endif
_background->draw(-1, -1, 1);
_graphics->refreshBackground();
_sequence->startGardenSeq();
}
break;
case kRoomAylesOffice:
if (_aylesIsAwake)
_background->draw(-1, -1, 1);
_graphics->refreshBackground();
break; // Ayles awake.
case kRoomGeidas:
putGeidaAt(1, ped);
break; // load Geida
case kRoomEastHall:
case kRoomWestHall:
if (_geidaFollows)
putGeidaAt(ped + 1, ped);
break;
case kRoomLusties:
if (_geidaFollows)
putGeidaAt(ped + 5, ped);
break;
case kRoomNottsPub:
if (_sittingInPub)
_background->draw(-1, -1, 2);
_npcFacing = 1; // Port.
break;
case kRoomOutsideDucks:
if (ped == 2) {
// Shut the door
_background->draw(-1, -1, 2);
_graphics->refreshBackground();
_sequence->startDuckSeq();
}
break;
case kRoomDucks:
_npcFacing = 1; // Duck.
break;
default:
break;
}
_animationsEnabled = true;
}
void AvalancheEngine::thinkAbout(byte object, bool type) {
_thinks = object;
object--;
Common::String filename;
if (type == kThing) {
filename = "thinks.avd";
} else { // kPerson
filename = "folk.avd";
object -= 149;
if (object >= 25)
object -= 8;
if (object == 20)
object--; // Last time...
}
_graphics->loadMouse(kCurWait);
CursorMan.showMouse(false);
_graphics->drawThinkPic(filename, object);
CursorMan.showMouse(true);
_thinkThing = type;
}
void AvalancheEngine::drawToolbar() {
_graphics->drawToolbar();
_animation->setOldDirection(kDirNone);
drawDirection();
}
void AvalancheEngine::drawScore() {
uint16 score = _score;
int8 numbers[3] = {0, 0, 0};
for (int i = 0; i < 2; i++) {
byte divisor = 1;
for (int j = 0; j < (2 - i); j++)
divisor *= 10;
numbers[i] = score / divisor;
score -= numbers[i] * divisor;
}
numbers[2] = score;
CursorMan.showMouse(false);
for (int i = 0; i < 3; i++) {
if (_scoreToDisplay[i] != numbers[i])
_graphics->drawDigit(numbers[i], 250 + (i + 1) * 15, 177);
}
CursorMan.showMouse(true);
for (int i = 0; i < 3; i++)
_scoreToDisplay[i] = numbers[i];
}
void AvalancheEngine::incScore(byte num) {
for (int i = 1; i <= num; i++) {
_score++;
if (_soundFx) {
for (int j = 1; j <= 97; j++)
// Length of 2 is a guess, the original doesn't have a delay specified
_sound->playNote(177 + _score * 3, 2);
}
}
drawScore();
}
void AvalancheEngine::useCompass(const Common::Point &cursorPos) {
byte color = _graphics->getScreenColor(cursorPos);
switch (color) {
case kColorGreen:
_animation->setDirection(kDirUp);
_animation->setMoveSpeed(0, kDirUp);
drawDirection();
break;
case kColorBrown:
_animation->setDirection(kDirDown);
_animation->setMoveSpeed(0, kDirDown);
drawDirection();
break;
case kColorCyan:
_animation->setDirection(kDirLeft);
_animation->setMoveSpeed(0, kDirLeft);
drawDirection();
break;
case kColorLightmagenta:
_animation->setDirection(kDirRight);
_animation->setMoveSpeed(0, kDirRight);
drawDirection();
break;
case kColorRed:
case kColorWhite:
case kColorLightcyan:
case kColorYellow: // Fall-throughs are intended.
_animation->stopWalking();
drawDirection();
break;
}
}
void AvalancheEngine::fxToggle() {
warning("STUB: fxtoggle()");
}
void AvalancheEngine::refreshObjectList() {
_carryNum = 0;
if (_thinkThing && !_objects[_thinks - 1])
thinkAbout(kObjectMoney, kThing); // you always have money
for (int i = 0; i < kObjectNum; i++) {
if (_objects[i]) {
_objectList[_carryNum] = i + 1;
_carryNum++;
}
}
}
/**
* @remarks Originally called 'verte'
*/
void AvalancheEngine::guideAvvy(Common::Point cursorPos) {
if (!_userMovesAvvy)
return;
cursorPos.y /= 2;
byte what;
// _animation->tr[0] is Avalot.)
AnimationType *avvy = _animation->_sprites[0];
if (cursorPos.x < avvy->_x)
what = 1;
else if (cursorPos.x > (avvy->_x + avvy->_xLength))
what = 2;
else
what = 0; // On top
if (cursorPos.y < avvy->_y)
what += 3;
else if (cursorPos.y > (avvy->_y + avvy->_yLength))
what += 6;
switch (what) {
case 0:
_animation->stopWalking();
break; // Clicked on Avvy: no movement.
case 1:
_animation->setMoveSpeed(0, kDirLeft);
break;
case 2:
_animation->setMoveSpeed(0, kDirRight);
break;
case 3:
_animation->setMoveSpeed(0, kDirUp);
break;
case 4:
_animation->setMoveSpeed(0, kDirUpLeft);
break;
case 5:
_animation->setMoveSpeed(0, kDirUpRight);
break;
case 6:
_animation->setMoveSpeed(0, kDirDown);
break;
case 7:
_animation->setMoveSpeed(0, kDirDownLeft);
break;
case 8:
_animation->setMoveSpeed(0, kDirDownRight);
break;
} // No other values are possible.
drawDirection();
}
void AvalancheEngine::checkClick() {
Common::Point cursorPos = getMousePos();
/*if (mrelease > 0)
after_the_scroll = false;*/
if ((0 <= cursorPos.y) && (cursorPos.y <= 21))
_graphics->loadMouse(kCurUpArrow); // up arrow
else if ((317 <= cursorPos.y) && (cursorPos.y <= 339))
_graphics->loadMouse(kCurIBeam); //I-beam
else if ((340 <= cursorPos.y) && (cursorPos.y <= 399))
_graphics->loadMouse(kCurScrewDriver); // screwdriver
else if (!_dropdown->isActive()) { // Dropdown can handle its own pointers.
if (_holdLeftMouse) {
_graphics->loadMouse(kCurCrosshair); // Mark's crosshairs
guideAvvy(cursorPos); // Normally, if you click on the picture, you're guiding Avvy around.
} else
_graphics->loadMouse(kCurFletch); // fletch
}
if (_holdLeftMouse) {
if ((0 <= cursorPos.y) && (cursorPos.y <= 21)) { // Click on the dropdown menu.
if (_dropsOk)
_dropdown->update();
} else if ((317 <= cursorPos.y) && (cursorPos.y <= 339)) { // Click on the command line.
_parser->_inputTextPos = (cursorPos.x - 23) / 8;
if (_parser->_inputTextPos > _parser->_inputText.size() + 1)
_parser->_inputTextPos = _parser->_inputText.size() + 1;
if (_parser->_inputTextPos < 1)
_parser->_inputTextPos = 1;
_parser->_inputTextPos--;
_parser->plotText();
} else if ((340 <= cursorPos.y) && (cursorPos.y <= 399)) { // Check the toolbar.
if ((137 <= cursorPos.x) && (cursorPos.x <= 207)) { // Control Avvy with the compass.
if (_alive && _avvyIsAwake)
useCompass(cursorPos);
} else if ((208 <= cursorPos.x) && (cursorPos.x <= 260)) { // Examine the _thing.
do {
updateEvents();
} while (_holdLeftMouse);
if (_thinkThing) {
_parser->_thing = _thinks;
_parser->_thing += 49;
_parser->_person = kPeoplePardon;
} else {
_parser->_person = (People)_thinks;
_parser->_thing = _parser->kPardon;
}
callVerb(kVerbCodeExam);
} else if ((261 <= cursorPos.x) && (cursorPos.x <= 319)) { // Display the score.
do {
updateEvents();
} while (_holdLeftMouse);
callVerb(kVerbCodeScore);
} else if ((320 <= cursorPos.x) && (cursorPos.x <= 357)) { // Change speed.
_animation->_sprites[0]->_speedX = kWalk;
_animation->updateSpeed();
} else if ((358 <= cursorPos.x) && (cursorPos.x <= 395)) { // Change speed.
_animation->_sprites[0]->_speedX = kRun;
_animation->updateSpeed();
} else if ((396 <= cursorPos.x) && (cursorPos.x <= 483))
_sound->toggleSound();
else if ((535 <= cursorPos.x) && (cursorPos.x <= 640))
_mouseText.insertChar(kControlNewLine, 0);
} else if (!_dropsOk)
_mouseText = Common::String(13) + _mouseText;
}
}
void AvalancheEngine::errorLed() {
_dialogs->setReadyLight(0);
_graphics->drawErrorLight(true);
for (int i = 177; i >= 1; i--) {
_sound->playNote(177 + (i * 177177) / 999, 1);
_system->delayMillis(1);
}
_graphics->drawErrorLight(false);
_dialogs->setReadyLight(2);
}
/**
* Displays a fade out, full screen.
* This version is different to the one in the original, which was fading in 3 steps.
* @remarks Originally called 'dusk'
*/
void AvalancheEngine::fadeOut() {
byte pal[3], tmpPal[3];
_graphics->setBackgroundColor(kColorBlack);
if (_fxHidden)
return;
_fxHidden = true;
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
g_system->getPaletteManager()->grabPalette((byte *)tmpPal, j, 1);
_fxPal[i][j][0] = tmpPal[0];
_fxPal[i][j][1] = tmpPal[1];
_fxPal[i][j][2] = tmpPal[2];
if (tmpPal[0] >= 16)
pal[0] = tmpPal[0] - 16;
else
pal[0] = 0;
if (tmpPal[1] >= 16)
pal[1] = tmpPal[1] - 16;
else
pal[1] = 0;
if (tmpPal[2] >= 16)
pal[2] = tmpPal[2] - 16;
else
pal[2] = 0;
g_system->getPaletteManager()->setPalette(pal, j, 1);
}
_system->delayMillis(10);
_graphics->refreshScreen();
}
}
/**
* Displays a fade in, full screen.
* This version is different to the one in the original, which was fading in 3 steps.
* @remarks Originally called 'dawn'
*/
void AvalancheEngine::fadeIn() {
if (_holdTheDawn || !_fxHidden)
return;
_fxHidden = false;
byte pal[3];
for (int i = 15; i >= 0; i--) {
for (int j = 0; j < 16; j++) {
pal[0] = _fxPal[i][j][0];
pal[1] = _fxPal[i][j][1];
pal[2] = _fxPal[i][j][2];
g_system->getPaletteManager()->setPalette(pal, j, 1);
}
_system->delayMillis(10);
_graphics->refreshScreen();
}
if ((_room == kRoomYours) && _avvyInBed && _teetotal)
_graphics->setBackgroundColor(kColorYellow);
}
void AvalancheEngine::drawDirection() { // It's data is loaded in load_digits().
if (_animation->getOldDirection() == _animation->getDirection())
return;
_animation->setOldDirection(_animation->getDirection());
CursorMan.showMouse(false);
_graphics->drawDirection(_animation->getDirection(), 0, 161);
CursorMan.showMouse(true);
}
void AvalancheEngine::gameOver() {
_userMovesAvvy = false;
AnimationType *avvy = _animation->_sprites[0];
int16 sx = avvy->_x;
int16 sy = avvy->_y;
avvy->remove();
avvy->init(12, true); // 12 = Avalot falls
avvy->_stepNum = 0;
avvy->appear(sx, sy, kDirUp);
_timer->addTimer(3, Timer::kProcAvalotFalls, Timer::kReasonFallingOver);
_alive = false;
}
void AvalancheEngine::minorRedraw() {
fadeOut();
enterRoom(_room, 0); // Ped unknown or non-existant.
for (int i = 0; i < 3; i++)
_scoreToDisplay[i] = -1; // impossible digits
drawScore();
fadeIn();
}
uint16 AvalancheEngine::bearing(byte whichPed) {
AnimationType *avvy = _animation->_sprites[0];
PedType *curPed = &_peds[whichPed];
if (avvy->_x == curPed->_x)
return 0;
int16 deltaX = avvy->_x - curPed->_x;
int16 deltaY = avvy->_y - curPed->_y;
uint16 result = (uint16)(atan((float)(deltaY / deltaX)) * 180 / M_PI);
if (avvy->_x < curPed->_x) {
return result + 90;
} else {
return result + 270;
}
}
/**
* @remarks Originally called 'sprite_run'
*/
void AvalancheEngine::spriteRun() {
_doingSpriteRun = true;
_animation->animLink();
_doingSpriteRun = false;
}
// CHECKME: Unused function
void AvalancheEngine::fixFlashers() {
_ledStatus = 177;
_animation->setOldDirection(kDirNone);
_dialogs->setReadyLight(2);
drawDirection();
}
Common::String AvalancheEngine::intToStr(int32 num) {
return Common::String::format("%d", num);
}
void AvalancheEngine::resetVariables() {
_carryNum = 0;
for (int i = 0; i < kObjectNum; i++)
_objects[i] = false;
_score = 0;
_money = 0;
_room = kRoomNowhere;
_saveNum = 0;
for (int i = 0; i < 100; i++)
_roomCount[i] = 0;
_wonNim = false;
_wineState = 0;
_cwytalotGone = false;
_passwordNum = 0;
_aylesIsAwake = false;
_drawbridgeOpen = 0;
_avariciusTalk = 0;
_rottenOnion = false;
_onionInVinegar = false;
_givenToSpludwick = 0;
_brummieStairs = 0;
_cardiffQuestionNum = 0;
_passedCwytalotInHerts = false;
_avvyIsAwake = false;
_avvyInBed = false;
_userMovesAvvy = false;
_npcFacing = 0;
_givenBadgeToIby = false;
_friarWillTieYouUp = false;
_tiedUp = false;
_boxContent = 0;
_talkedToCrapulus = false;
_jacquesState = 0;
_bellsAreRinging = false;
_standingOnDais = false;
_takenPen = false;
_arrowInTheDoor = false;
_favoriteDrink = "";
_favoriteSong = "";
_worstPlaceOnEarth = "";
_spareEvening = "";
_totalTime = 0;
_jumpStatus = 0;
_mushroomGrowing = false;
_spludwickAtHome = false;
_lastRoom = kRoomDummy;
_lastRoomNotMap = kRoomDummy;
_crapulusWillTell = false;
_enterCatacombsFromLustiesRoom = false;
_teetotal = false;
_malagauche = 0;
_drinking = 0;
_enteredLustiesRoomAsMonk = false;
_catacombX = 0;
_catacombY = 0;
_avvysInTheCupboard = false;
_geidaFollows = false;
_givenPotionToGeida = false;
_lustieIsAsleep = false;
_beenTiedUp = false;
_sittingInPub = false;
_spurgeTalkCount = 0;
_metAvaroid = false;
_takenMushroom = false;
_givenPenToAyles = false;
_askedDogfoodAboutNim = false;
_startTime = getTimeInSeconds();
}
void AvalancheEngine::resetAllVariables() {
resetVariables();
_parser->resetVariables();
_nim->resetVariables();
_animation->resetVariables();
_sequence->resetVariables();
_background->resetVariables();
_dropdown->resetVariables();
_timer->resetVariables();
}
void AvalancheEngine::newGame() {
for (int i = 0; i < kMaxSprites; i++) {
AnimationType *spr = _animation->_sprites[i];
if (spr->_quick)
spr->remove();
}
// Deallocate sprite. Sorry, beta testers!
AnimationType *avvy = _animation->_sprites[0];
avvy->init(0, true);
_alive = true;
resetAllVariables();
_dialogs->setBubbleStateNatural();
_spareEvening = "answer a questionnaire";
_favoriteDrink = "beer";
_money = 30; // 2/6
_animation->setDirection(kDirStopped);
_parser->_wearing = kObjectClothes;
_objects[kObjectMoney - 1] = true;
_objects[kObjectBodkin - 1] = true;
_objects[kObjectBell - 1] = true;
_objects[kObjectClothes - 1] = true;
_thinkThing = true;
_thinks = 2;
refreshObjectList();
_animationsEnabled = true;
avvy->appear(300, 117, kDirRight); // Needed to initialize Avalot.
//for (gd = 0; gd <= 30; gd++) for (gm = 0; gm <= 1; gm++) also[gd][gm] = nil;
// fillchar(previous^,sizeof(previous^),#0); { blank out array }
_him = kPeoplePardon;
_her = kPeoplePardon;
_it = Parser::kPardon;
_passwordNum = _rnd->getRandomNumber(29) + 1; //Random(30) + 1;
_userMovesAvvy = false;
_doingSpriteRun = false;
_avvyInBed = true;
_isLoaded = false;
enterRoom(kRoomYours, 1);
avvy->_visible = false;
drawScore();
_dropdown->setup();
_clock->update();
spriteRun();
}
bool AvalancheEngine::getFlag(char x) {
for (uint16 i = 0; i < _flags.size(); i++) {
if (_flags[i] == x)
return true;
}
return false;
}
bool AvalancheEngine::decreaseMoney(uint16 amount) {
_money -= amount;
if (_money < 0) {
_dialogs->displayScrollChain('Q', 2); // "You are now denariusless!"
gameOver();
return false;
} else
return true;
}
Common::String AvalancheEngine::getName(People whose) {
static const char lads[17][20] = {
"Avalot", "Spludwick", "Crapulus", "Dr. Duck", "Malagauche",
"Friar Tuck", "Robin Hood", "Cwytalot", "du Lustie", "the Duke of Cardiff",
"Dogfood", "A trader", "Ibythneth", "Ayles", "Port",
"Spurge", "Jacques"
};
static const char lasses[4][15] = {"Arkata", "Geida", "\0xB1", "the Wise Woman"};
if (whose <= kPeopleJacques)
return Common::String(lads[whose - kPeopleAvalot]);
else if ((whose >= kPeopleArkata) && (whose <= kPeopleWisewoman))
return Common::String(lasses[whose - kPeopleArkata]);
else
error("getName() - Unexpected character id %d", (byte) whose);
}
Common::String AvalancheEngine::getItem(byte which) {
static const char items[kObjectNum][18] = {
"some wine", "your money-bag", "your bodkin", "a potion", "a chastity belt",
"a crossbow bolt", "a crossbow", "a lute", "a pilgrim's badge", "a mushroom",
"a key", "a bell", "a scroll", "a pen", "some ink",
"your clothes", "a habit", "an onion"
};
Common::String result;
if (which > 150)
which -= 149;
switch (which) {
case kObjectWine:
switch (_wineState) {
case 0:
case 1:
case 4:
result = Common::String(items[which - 1]);
break;
case 3:
result = "some vinegar";
break;
}
break;
case kObjectOnion:
if (_rottenOnion)
result = "a rotten onion";
else if (_onionInVinegar)
result = "a pickled onion (in the vinegar)";
else
result = Common::String(items[which - 1]);
break;
default:
if ((which < kObjectNum) && (which > 0))
result = Common::String(items[which - 1]);
else
result = "";
}
return result;
}
Common::String AvalancheEngine::f5Does() {
switch (_room) {
case kRoomYours:
if (!_avvyIsAwake)
return Common::String::format("%cWWake up", kVerbCodeWake);
else if (_avvyInBed)
return Common::String::format("%cGGet up", kVerbCodeStand);
break;
case kRoomInsideCardiffCastle:
if (_standingOnDais)
return Common::String::format("%cCClimb down", kVerbCodeClimb);
else
return Common::String::format("%cCClimb up", kVerbCodeClimb);
break;
case kRoomNottsPub:
if (_sittingInPub)
return Common::String::format("%cSStand up", kVerbCodeStand);
else
return Common::String::format("%cSSit down", kVerbCodeSit);
break;
case kRoomMusicRoom:
if (_animation->inField(5))
return Common::String::format("%cPPlay the harp", kVerbCodePlay);
break;
default:
break;
}
return Common::String::format("%c", kVerbCodePardon); // If all else fails...
}
void AvalancheEngine::flipRoom(Room room, byte ped) {
assert((ped > 0) && (ped < 15));
if (!_alive) {
// You can't leave the room if you're dead.
_animation->_sprites[0]->_moveX = 0;
_animation->_sprites[0]->_moveY = 0; // Stop him from moving.
return;
}
if ((room == kRoomDummy) && (_room == kRoomLusties)) {
_animation->hideInCupboard();
return;
}
if ((_jumpStatus > 0) && (_room == kRoomInsideCardiffCastle)) {
// You can't *jump* out of Cardiff Castle!
_animation->_sprites[0]->_moveX = 0;
return;
}
exitRoom(_room);
fadeOut();
for (int16 i = 1; i < _animation->kSpriteNumbMax; i++) {
if (_animation->_sprites[i]->_quick)
_animation->_sprites[i]->remove();
} // Deallocate sprite
if (_room == kRoomLustiesRoom)
_enterCatacombsFromLustiesRoom = true;
if (room > kRoomMap)
return;
enterRoom(room, ped);
_animation->appearPed(0, ped - 1);
_enterCatacombsFromLustiesRoom = false;
_animation->setOldDirection(_animation->getDirection());
_animation->setDirection(_animation->_sprites[0]->_facingDir);
drawDirection();
fadeIn();
}
/**
* Open the Door.
* This slides the door open. The data really ought to be saved in
* the Also file, and will be next time. However, for now, they're
* here.
* @remarks Originally called 'open_the_door'
*/
void AvalancheEngine::openDoor(Room whither, byte ped, byte magicnum) {
switch (_room) {
case kRoomOutsideYours:
case kRoomOutsideNottsPub:
case kRoomOutsideDucks:
_sequence->startOutsideSeq(whither, ped);
break;
case kRoomInsideCardiffCastle:
_sequence->startCardiffSeq(whither, ped);
break;
case kRoomAvvysGarden:
case kRoomEntranceHall:
case kRoomInsideAbbey:
case kRoomYourHall:
_sequence->startHallSeq(whither, ped);
break;
case kRoomMusicRoom:
case kRoomOutsideArgentPub:
_sequence->startMusicRoomSeq2(whither, ped);
break;
case kRoomLusties:
switch (magicnum) {
case 14:
if (_avvysInTheCupboard) {
_animation->hideInCupboard();
_sequence->startCupboardSeq();
return;
} else {
_animation->appearPed(0, 5);
_animation->_sprites[0]->_facingDir = kDirRight;
_sequence->startLustiesSeq2(whither, ped);
}
break;
case 12:
_sequence->startLustiesSeq3(whither, ped);
break;
}
break;
default:
_sequence->startDummySeq(whither, ped);
}
}
void AvalancheEngine::setRoom(People persId, Room roomId) {
_whereIs[persId - kPeopleAvalot] = roomId;
}
Room AvalancheEngine::getRoom(People persId) {
return _whereIs[persId - kPeopleAvalot];
}
} // End of namespace Avalanche