mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-06 02:46:49 +00:00
df838f50eb
This mimics the behavior of the original engine. Note that for hires2, this patch adds some glitches that are also present in the original, and removes some glitches that are not.
376 lines
10 KiB
C++
376 lines
10 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.
|
|
*
|
|
*/
|
|
|
|
#include "common/system.h"
|
|
#include "common/debug.h"
|
|
#include "common/error.h"
|
|
#include "common/file.h"
|
|
#include "common/stream.h"
|
|
#include "common/ptr.h"
|
|
|
|
#include "adl/hires1.h"
|
|
#include "adl/display.h"
|
|
|
|
namespace Adl {
|
|
|
|
void HiRes1Engine::runIntro() const {
|
|
StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
|
|
|
|
stream->seek(IDI_HR1_OFS_LOGO_0);
|
|
_display->setMode(DISPLAY_MODE_HIRES);
|
|
_display->loadFrameBuffer(*stream);
|
|
_display->updateHiResScreen();
|
|
delay(4000);
|
|
|
|
if (shouldQuit())
|
|
return;
|
|
|
|
_display->setMode(DISPLAY_MODE_TEXT);
|
|
|
|
StreamPtr basic(_files->createReadStream(IDS_HR1_LOADER));
|
|
Common::String str;
|
|
|
|
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"');
|
|
_display->printAsciiString(str + '\r');
|
|
|
|
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"');
|
|
_display->printAsciiString(str + "\r\r");
|
|
|
|
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"');
|
|
_display->printAsciiString(str + "\r\r");
|
|
|
|
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"');
|
|
_display->printAsciiString(str + '\r');
|
|
|
|
inputKey();
|
|
if (g_engine->shouldQuit())
|
|
return;
|
|
|
|
_display->setMode(DISPLAY_MODE_MIXED);
|
|
|
|
str = readStringAt(*stream, IDI_HR1_OFS_GAME_OR_HELP);
|
|
|
|
bool instructions = false;
|
|
|
|
while (1) {
|
|
_display->printString(str);
|
|
Common::String s = inputString();
|
|
|
|
if (g_engine->shouldQuit())
|
|
break;
|
|
|
|
if (s.empty())
|
|
continue;
|
|
|
|
if (s[0] == APPLECHAR('I')) {
|
|
instructions = true;
|
|
break;
|
|
} else if (s[0] == APPLECHAR('G')) {
|
|
break;
|
|
}
|
|
};
|
|
|
|
if (instructions) {
|
|
_display->setMode(DISPLAY_MODE_TEXT);
|
|
stream->seek(IDI_HR1_OFS_INTRO_TEXT);
|
|
|
|
const uint pages[] = { 6, 6, 4, 5, 8, 7, 0 };
|
|
|
|
uint page = 0;
|
|
while (pages[page] != 0) {
|
|
_display->home();
|
|
|
|
uint count = pages[page++];
|
|
for (uint i = 0; i < count; ++i) {
|
|
str = readString(*stream);
|
|
_display->printString(str);
|
|
stream->seek(3, SEEK_CUR);
|
|
}
|
|
|
|
inputString();
|
|
|
|
if (g_engine->shouldQuit())
|
|
return;
|
|
|
|
stream->seek(6, SEEK_CUR);
|
|
}
|
|
}
|
|
|
|
_display->printAsciiString("\r");
|
|
|
|
_display->setMode(DISPLAY_MODE_MIXED);
|
|
|
|
// Title screen shown during loading
|
|
stream.reset(_files->createReadStream(IDS_HR1_EXE_1));
|
|
stream->seek(IDI_HR1_OFS_LOGO_1);
|
|
_display->loadFrameBuffer(*stream);
|
|
_display->updateHiResScreen();
|
|
delay(2000);
|
|
}
|
|
|
|
void HiRes1Engine::init() {
|
|
if (Common::File::exists("MYSTHOUS.DSK")) {
|
|
_files = new Files_DOS33();
|
|
if (!static_cast<Files_DOS33 *>(_files)->open("MYSTHOUS.DSK"))
|
|
error("Failed to open MYSTHOUS.DSK");
|
|
} else
|
|
_files = new Files_Plain();
|
|
|
|
_graphics = new Graphics_v1(*_display);
|
|
|
|
StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
|
|
|
|
// Some messages have overrides inside the executable
|
|
_gameStrings.cantGoThere = readStringAt(*stream, IDI_HR1_OFS_STR_CANT_GO_THERE);
|
|
_gameStrings.dontHaveIt = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_HAVE_IT);
|
|
_gameStrings.dontUnderstand = readStringAt(*stream, IDI_HR1_OFS_STR_DONT_UNDERSTAND);
|
|
_gameStrings.gettingDark = readStringAt(*stream, IDI_HR1_OFS_STR_GETTING_DARK);
|
|
|
|
// Load other strings from executable
|
|
_strings.enterCommand = readStringAt(*stream, IDI_HR1_OFS_STR_ENTER_COMMAND);
|
|
_strings.verbError = readStringAt(*stream, IDI_HR1_OFS_STR_VERB_ERROR);
|
|
_strings.nounError = readStringAt(*stream, IDI_HR1_OFS_STR_NOUN_ERROR);
|
|
_strings.playAgain = readStringAt(*stream, IDI_HR1_OFS_STR_PLAY_AGAIN);
|
|
_strings.pressReturn = readStringAt(*stream, IDI_HR1_OFS_STR_PRESS_RETURN);
|
|
_strings.lineFeeds = readStringAt(*stream, IDI_HR1_OFS_STR_LINE_FEEDS);
|
|
|
|
// Set message IDs
|
|
_messageIds.cantGoThere = IDI_HR1_MSG_CANT_GO_THERE;
|
|
_messageIds.dontUnderstand = IDI_HR1_MSG_DONT_UNDERSTAND;
|
|
_messageIds.itemDoesntMove = IDI_HR1_MSG_ITEM_DOESNT_MOVE;
|
|
_messageIds.itemNotHere = IDI_HR1_MSG_ITEM_NOT_HERE;
|
|
_messageIds.thanksForPlaying = IDI_HR1_MSG_THANKS_FOR_PLAYING;
|
|
|
|
// Load message offsets
|
|
stream->seek(IDI_HR1_OFS_MSGS);
|
|
for (uint i = 0; i < IDI_HR1_NUM_MESSAGES; ++i)
|
|
_messages.push_back(_files->getDataBlock(IDS_HR1_MESSAGES, stream->readUint16LE()));
|
|
|
|
// Load picture data from executable
|
|
stream->seek(IDI_HR1_OFS_PICS);
|
|
for (uint i = 1; i <= IDI_HR1_NUM_PICS; ++i) {
|
|
byte block = stream->readByte();
|
|
Common::String name = Common::String::format("BLOCK%i", block);
|
|
uint16 offset = stream->readUint16LE();
|
|
_pictures[i] = _files->getDataBlock(name, offset);
|
|
}
|
|
|
|
// Load commands from executable
|
|
stream->seek(IDI_HR1_OFS_CMDS_1);
|
|
readCommands(*stream, _roomCommands);
|
|
|
|
stream->seek(IDI_HR1_OFS_CMDS_0);
|
|
readCommands(*stream, _globalCommands);
|
|
|
|
// Load dropped item offsets
|
|
stream->seek(IDI_HR1_OFS_ITEM_OFFSETS);
|
|
for (uint i = 0; i < IDI_HR1_NUM_ITEM_OFFSETS; ++i) {
|
|
Common::Point p;
|
|
p.x = stream->readByte();
|
|
p.y = stream->readByte();
|
|
_itemOffsets.push_back(p);
|
|
}
|
|
|
|
// Load right-angle line art
|
|
stream->seek(IDI_HR1_OFS_CORNERS);
|
|
uint16 cornersCount = stream->readUint16LE();
|
|
for (uint i = 0; i < cornersCount; ++i)
|
|
_corners.push_back(_files->getDataBlock(IDS_HR1_EXE_1, IDI_HR1_OFS_CORNERS + stream->readUint16LE()));
|
|
|
|
if (stream->eos() || stream->err())
|
|
error("Failed to read game data from '" IDS_HR1_EXE_1 "'");
|
|
|
|
stream->seek(IDI_HR1_OFS_VERBS);
|
|
loadWords(*stream, _verbs, _priVerbs);
|
|
|
|
stream->seek(IDI_HR1_OFS_NOUNS);
|
|
loadWords(*stream, _nouns, _priNouns);
|
|
}
|
|
|
|
void HiRes1Engine::initGameState() {
|
|
_state.vars.resize(IDI_HR1_NUM_VARS);
|
|
|
|
StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_1));
|
|
|
|
// Load room data from executable
|
|
_roomDesc.clear();
|
|
stream->seek(IDI_HR1_OFS_ROOMS);
|
|
for (uint i = 0; i < IDI_HR1_NUM_ROOMS; ++i) {
|
|
Room room;
|
|
stream->readByte();
|
|
_roomDesc.push_back(stream->readByte());
|
|
for (uint j = 0; j < 6; ++j)
|
|
room.connections[j] = stream->readByte();
|
|
room.picture = stream->readByte();
|
|
room.curPicture = stream->readByte();
|
|
_state.rooms.push_back(room);
|
|
}
|
|
|
|
// Load item data from executable
|
|
stream->seek(IDI_HR1_OFS_ITEMS);
|
|
byte id;
|
|
while ((id = stream->readByte()) != 0xff) {
|
|
Item item = Item();
|
|
item.id = id;
|
|
item.noun = stream->readByte();
|
|
item.room = stream->readByte();
|
|
item.picture = stream->readByte();
|
|
item.isLineArt = stream->readByte();
|
|
item.position.x = stream->readByte();
|
|
item.position.y = stream->readByte();
|
|
item.state = stream->readByte();
|
|
item.description = stream->readByte();
|
|
|
|
stream->readByte();
|
|
|
|
byte size = stream->readByte();
|
|
|
|
for (uint i = 0; i < size; ++i)
|
|
item.roomPictures.push_back(stream->readByte());
|
|
|
|
_state.items.push_back(item);
|
|
}
|
|
}
|
|
|
|
void HiRes1Engine::restartGame() {
|
|
_display->printString(_strings.pressReturn);
|
|
initState();
|
|
_display->printAsciiString(_strings.lineFeeds);
|
|
}
|
|
|
|
void HiRes1Engine::printString(const Common::String &str) {
|
|
Common::String wrap = str;
|
|
wordWrap(wrap);
|
|
_display->printString(wrap);
|
|
|
|
if (_messageDelay)
|
|
delay(14 * 166018 / 1000);
|
|
}
|
|
|
|
Common::String HiRes1Engine::loadMessage(uint idx) const {
|
|
StreamPtr stream(_messages[idx]->createReadStream());
|
|
return readString(*stream, APPLECHAR('\r')) + APPLECHAR('\r');
|
|
}
|
|
|
|
void HiRes1Engine::printMessage(uint idx) {
|
|
// Messages with hardcoded overrides don't delay after printing.
|
|
// It's unclear if this is a bug or not. In some cases the result
|
|
// is that these strings will scroll past the four-line text window
|
|
// before the user gets a chance to read them.
|
|
// NOTE: later games seem to wait for a key when the text window
|
|
// overflows and don't use delays. It might be better to use
|
|
// that system for this game as well.
|
|
switch (idx) {
|
|
case IDI_HR1_MSG_CANT_GO_THERE:
|
|
_display->printString(_gameStrings.cantGoThere);
|
|
return;
|
|
case IDI_HR1_MSG_DONT_HAVE_IT:
|
|
_display->printString(_gameStrings.dontHaveIt);
|
|
return;
|
|
case IDI_HR1_MSG_DONT_UNDERSTAND:
|
|
_display->printString(_gameStrings.dontUnderstand);
|
|
return;
|
|
case IDI_HR1_MSG_GETTING_DARK:
|
|
_display->printString(_gameStrings.gettingDark);
|
|
return;
|
|
default:
|
|
printString(loadMessage(idx));
|
|
}
|
|
}
|
|
|
|
void HiRes1Engine::drawItems() {
|
|
Common::List<Item>::iterator item;
|
|
|
|
uint dropped = 0;
|
|
|
|
for (item = _state.items.begin(); item != _state.items.end(); ++item) {
|
|
// Skip items not in this room
|
|
if (item->room != _state.room)
|
|
continue;
|
|
|
|
if (item->state == IDI_ITEM_DROPPED) {
|
|
// Draw dropped item if in normal view
|
|
if (getCurRoom().picture == getCurRoom().curPicture)
|
|
drawItem(*item, _itemOffsets[dropped++]);
|
|
} else {
|
|
// Draw fixed item if current view is in the pic list
|
|
Common::Array<byte>::const_iterator pic;
|
|
|
|
for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
|
|
if (*pic == getCurRoom().curPicture) {
|
|
drawItem(*item, item->position);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HiRes1Engine::drawItem(Item &item, const Common::Point &pos) {
|
|
if (item.isLineArt) {
|
|
StreamPtr stream(_corners[item.picture - 1]->createReadStream());
|
|
static_cast<Graphics_v1 *>(_graphics)->drawCorners(*stream, pos);
|
|
} else
|
|
drawPic(item.picture, pos);
|
|
}
|
|
|
|
void HiRes1Engine::loadRoom(byte roomNr) {
|
|
_roomData.description = loadMessage(_roomDesc[_state.room - 1]);
|
|
}
|
|
|
|
void HiRes1Engine::showRoom() {
|
|
_state.curPicture = getCurRoom().curPicture;
|
|
clearScreen();
|
|
loadRoom(_state.room);
|
|
|
|
if (!_state.isDark) {
|
|
drawPic(getCurRoom().curPicture);
|
|
drawItems();
|
|
}
|
|
|
|
_display->updateHiResScreen();
|
|
_messageDelay = false;
|
|
printString(_roomData.description);
|
|
_messageDelay = true;
|
|
}
|
|
|
|
void HiRes1Engine::wordWrap(Common::String &str) const {
|
|
uint end = 39;
|
|
|
|
while (1) {
|
|
if (str.size() <= end)
|
|
return;
|
|
|
|
while (str[end] != APPLECHAR(' '))
|
|
--end;
|
|
|
|
str.setChar(APPLECHAR('\r'), end);
|
|
end += 40;
|
|
}
|
|
}
|
|
|
|
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd) {
|
|
return new HiRes1Engine(syst, gd);
|
|
}
|
|
|
|
} // End of namespace Adl
|