Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

3
engines/hugo/POTFILES Normal file
View File

@@ -0,0 +1,3 @@
engines/hugo/hugo.cpp
engines/hugo/file.cpp
engines/hugo/metaengine.cpp

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine hugo "Hugo Trilogy" yes "" "" "" "midi"

151
engines/hugo/console.cpp Normal file
View File

@@ -0,0 +1,151 @@
/* 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 "hugo/console.h"
#include "hugo/hugo.h"
#include "hugo/object.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
#include "hugo/text.h"
namespace Hugo {
HugoConsole::HugoConsole(HugoEngine *vm) : GUI::Debugger(), _vm(vm) {
registerCmd("listscreens", WRAP_METHOD(HugoConsole, Cmd_listScreens));
registerCmd("listobjects", WRAP_METHOD(HugoConsole, Cmd_listObjects));
registerCmd("getobject", WRAP_METHOD(HugoConsole, Cmd_getObject));
registerCmd("getallobjects", WRAP_METHOD(HugoConsole, Cmd_getAllObjects));
registerCmd("gotoscreen", WRAP_METHOD(HugoConsole, Cmd_gotoScreen));
registerCmd("Boundaries", WRAP_METHOD(HugoConsole, Cmd_boundaries));
}
HugoConsole::~HugoConsole() {
}
static int strToInt(const char *s) {
if (!*s)
// No string at all
return 0;
else if (toupper(s[strlen(s) - 1]) != 'H')
// Standard decimal string
return atoi(s);
// Hexadecimal string
uint tmp = 0;
int read = sscanf(s, "%xh", &tmp);
if (read < 1)
error("strToInt failed on string \"%s\"", s);
return (int)tmp;
}
/**
* This command loads up the specified screen number
*/
bool HugoConsole::Cmd_gotoScreen(int argc, const char **argv) {
if ((argc != 2) || (strToInt(argv[1]) > _vm->_numScreens)){
debugPrintf("Usage: %s <screen number>\n", argv[0]);
return true;
}
_vm->_scheduler->newScreen(strToInt(argv[1]));
return false;
}
/**
* This command lists all the screens available
*/
bool HugoConsole::Cmd_listScreens(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
debugPrintf("Available screens for this game are:\n");
for (int i = 0; i < _vm->_numScreens; i++)
debugPrintf("%2d - %s\n", i, _vm->_text->getScreenNames(i));
return true;
}
/**
* This command lists all the objects available
*/
bool HugoConsole::Cmd_listObjects(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
debugPrintf("Available objects for this game are:\n");
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
debugPrintf("%2d - %s\n", i, _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 2));
}
return true;
}
/**
* This command puts an object in the inventory
*/
bool HugoConsole::Cmd_getObject(int argc, const char **argv) {
if ((argc != 2) || (strToInt(argv[1]) > _vm->_object->_numObj)) {
debugPrintf("Usage: %s <object number>\n", argv[0]);
return true;
}
if (_vm->_object->_objects[strToInt(argv[1])]._genericCmd & TAKE)
_vm->_parser->takeObject(&_vm->_object->_objects[strToInt(argv[1])]);
else
debugPrintf("Object not available\n");
return true;
}
/**
* This command puts all the available objects in the inventory
*/
bool HugoConsole::Cmd_getAllObjects(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
_vm->_parser->takeObject(&_vm->_object->_objects[i]);
}
return false;
}
/**
* This command shows and hides boundaries
*/
bool HugoConsole::Cmd_boundaries(int argc, const char **argv) {
if (argc != 1) {
debugPrintf("Usage: %s\n", argv[0]);
return true;
}
_vm->getGameStatus()._showBoundariesFl = !_vm->getGameStatus()._showBoundariesFl;
return false;
}
} // End of namespace Hugo

48
engines/hugo/console.h Normal file
View File

@@ -0,0 +1,48 @@
/* 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/>.
*
*/
#ifndef HUGO_CONSOLE_H
#define HUGO_CONSOLE_H
#include "gui/debugger.h"
namespace Hugo {
class HugoEngine;
class HugoConsole : public GUI::Debugger {
public:
HugoConsole(HugoEngine *vm);
~HugoConsole(void) override;
private:
HugoEngine *_vm;
bool Cmd_listScreens(int argc, const char **argv);
bool Cmd_listObjects(int argc, const char **argv);
bool Cmd_getObject(int argc, const char **argv);
bool Cmd_getAllObjects(int argc, const char **argv);
bool Cmd_gotoScreen(int argc, const char **argv);
bool Cmd_boundaries(int argc, const char **argv);
};
} // End of namespace Hugo
#endif

5
engines/hugo/credits.pl Normal file
View File

@@ -0,0 +1,5 @@
begin_section("Hugo");
add_person("Arnaud Boutonn&eacute;", "Strangerke", "");
add_person("Oystein Eftevaag", "vinterstum", "");
add_person("Eugene Sandulenko", "sev", "");
end_section();

154
engines/hugo/detection.cpp Normal file
View File

@@ -0,0 +1,154 @@
/* 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 "base/plugins.h"
#include "engines/advancedDetector.h"
#include "hugo/detection.h"
#include "hugo/hugo.h"
static const DebugChannelDef debugFlagList[] = {
{Hugo::kDebugSchedule, "Schedule", "Script Schedule debug level"},
{Hugo::kDebugEngine, "Engine", "Engine debug level"},
{Hugo::kDebugDisplay, "Display", "Display debug level"},
{Hugo::kDebugMouse, "Mouse", "Mouse debug level"},
{Hugo::kDebugParser, "Parser", "Parser debug level"},
{Hugo::kDebugFile, "File", "File IO debug level"},
{Hugo::kDebugRoute, "Route", "Route debug level"},
{Hugo::kDebugInventory, "Inventory", "Inventory debug level"},
{Hugo::kDebugObject, "Object", "Object debug level"},
{Hugo::kDebugMusic, "Music", "Music debug level"},
DEBUG_CHANNEL_END
};
namespace Hugo {
static const PlainGameDescriptor hugoGames[] = {
// Games
{"hugo1", "Hugo's House of Horrors"},
{"hugo2", "Hugo II: Whodunit?"},
{"hugo3", "Hugo III: Jungle of Doom"},
{nullptr, nullptr}
};
static const HugoGameDescription gameDescriptions[] = {
// Hugo1 DOS
{
{
"hugo1", nullptr,
AD_ENTRY1s("house.art", "c9403b2fe539185c9fd569b6cc4ff5ca", 14811),
Common::EN_ANY,
Common::kPlatformDOS,
ADGF_NO_FLAGS,
GUIO2(GAMEOPTION_TTS, GAMEOPTION_WINDOWS_INTERFACE)
},
kGameTypeHugo1
},
// Hugo1 Windows
{
{
"hugo1", nullptr,
AD_ENTRY1s("objects.dat", "3ba0f108f7690a05a34c56a02fbe644a", 126488),
Common::EN_ANY,
Common::kPlatformWindows,
GF_PACKED,
GUIO1(GAMEOPTION_TTS)
},
kGameTypeHugo1
},
// Hugo2 DOS
{
{
"hugo2", nullptr,
AD_ENTRY1s("objects.dat", "88a718cc0ff2b3b25d49aaaa69d6d52c", 155240),
Common::EN_ANY,
Common::kPlatformDOS,
GF_PACKED,
GUIO2(GAMEOPTION_TTS, GAMEOPTION_WINDOWS_INTERFACE)
},
kGameTypeHugo2
},
// Hugo2 Windows
{
{
"hugo2", nullptr,
AD_ENTRY1s("objects.dat", "5df4ffc851e66a544c0e95e4e084a806", 158480),
Common::EN_ANY,
Common::kPlatformWindows,
GF_PACKED,
GUIO1(GAMEOPTION_TTS)
},
kGameTypeHugo2
},
// Hugo3 DOS
{
{
"hugo3", nullptr,
AD_ENTRY1s("objects.dat", "bb1b061538a445f2eb99b682c0f506cc", 136419),
Common::EN_ANY,
Common::kPlatformDOS,
GF_PACKED,
GUIO2(GAMEOPTION_TTS, GAMEOPTION_WINDOWS_INTERFACE)
},
kGameTypeHugo3
},
// Hugo3 Windows
{
{
"hugo3", nullptr,
AD_ENTRY1s("objects.dat", "c9a8af7aa14cc907434eecee3ddd06d3", 136638),
Common::EN_ANY,
Common::kPlatformWindows,
GF_PACKED,
GUIO1(GAMEOPTION_TTS)
},
kGameTypeHugo3
},
{AD_TABLE_END_MARKER, kGameTypeNone}
};
class HugoMetaEngineDetection : public AdvancedMetaEngineDetection<HugoGameDescription> {
public:
HugoMetaEngineDetection() : AdvancedMetaEngineDetection(gameDescriptions, hugoGames) {
}
const char *getName() const override {
return "hugo";
}
const char *getEngineName() const override {
return "Hugo";
}
const char *getOriginalCopyright() const override {
return "Hugo Engine (C) 1989-1997 David P. Gray";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
};
} // End of namespace Hugo
REGISTER_PLUGIN_STATIC(HUGO_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Hugo::HugoMetaEngineDetection);

62
engines/hugo/detection.h Normal file
View File

@@ -0,0 +1,62 @@
/* 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/>.
*
*/
#ifndef HUGO_DETECTION_H
#define HUGO_DETECTION_H
#include "engines/advancedDetector.h"
namespace Hugo {
enum HugoGameFeatures {
GF_PACKED = (1 << 0) // Database
};
enum GameType {
kGameTypeNone = 0,
kGameTypeHugo1,
kGameTypeHugo2,
kGameTypeHugo3
};
enum GameVariant {
kGameVariantH1Win = 0,
kGameVariantH2Win,
kGameVariantH3Win,
kGameVariantH1Dos,
kGameVariantH2Dos,
kGameVariantH3Dos,
kGameVariantNone
};
struct HugoGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
GameType gameType;
};
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS1
#define GAMEOPTION_WINDOWS_INTERFACE GUIO_GAMEOPTIONS2
} // End of namespace Hugo
#endif // HUGO_DETECTION_H

305
engines/hugo/dialogs.cpp Normal file
View File

@@ -0,0 +1,305 @@
/* 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 "common/substream.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "image/bmp.h"
#include "hugo/hugo.h"
#include "hugo/dialogs.h"
#include "hugo/file.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
#include "hugo/sound.h"
#include "hugo/util.h"
namespace Hugo {
TopMenu::TopMenu(HugoEngine *vm) : Dialog(0, 0, kMenuWidth, kMenuHeight), _vm(vm) {
_arrayBmp = nullptr;
_arraySize = 0;
init();
}
TopMenu::~TopMenu() {
for (int i = 0; i < _arraySize; i++) {
_arrayBmp[i * 2]->free();
delete _arrayBmp[i * 2];
_arrayBmp[i * 2 + 1]->free();
delete _arrayBmp[i * 2 + 1];
}
delete[] _arrayBmp;
}
void TopMenu::init() {
int x = kMenuX;
int y = kMenuY;
_whatButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("What is it?"), kCmdWhat);
_musicButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Music"), kCmdMusic);
_soundFXButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Sound FX"), kCmdSoundFX);
_saveButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Save game"), kCmdSave);
_loadButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Load game"), kCmdLoad);
_recallButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Recall last command"), kCmdRecall);
_turboButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Turbo"), kCmdTurbo);
_lookButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Description of the scene"), kCmdLook);
_inventButton = new GUI::PicButtonWidget(this, x, y, kButtonWidth, kButtonHeight, Common::U32String("Inventory"), kCmdInvent);
}
void TopMenu::reflowLayout() {
_w = g_system->getOverlayWidth();
int scale = (_w > 320 ? 2 : 1);
_h = kMenuHeight * scale;
resize(_x, _y, _w, _h);
int x = kMenuX * scale;
int y = kMenuY * scale;
_whatButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
_musicButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
_soundFXButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
x += kButtonSpace;
_saveButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
_loadButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
x += kButtonSpace;
_recallButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
_turboButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
x += kButtonSpace;
_lookButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
x += kButtonWidth + kButtonPad;
_inventButton->resize(x * scale, y * scale, kButtonWidth * scale, kButtonHeight * scale);
// Set the graphics to the 'on' buttons, except for the variable ones
_whatButton->setGfx(_arrayBmp[4 * kMenuWhat + scale - 1]);
_musicButton->setGfx(_arrayBmp[4 * kMenuMusic + scale - 1 + ((_vm->_config._musicFl) ? 0 : 2)]);
_soundFXButton->setGfx(_arrayBmp[4 * kMenuSoundFX + scale - 1 + ((_vm->_config._soundFl) ? 0 : 2)]);
_saveButton->setGfx(_arrayBmp[4 * kMenuSave + scale - 1]);
_loadButton->setGfx(_arrayBmp[4 * kMenuLoad + scale - 1]);
_recallButton->setGfx(_arrayBmp[4 * kMenuRecall + scale - 1]);
_turboButton->setGfx(_arrayBmp[4 * kMenuTurbo + scale - 1 + ((_vm->_config._turboFl) ? 0 : 2)]);
_lookButton->setGfx(_arrayBmp[4 * kMenuLook + scale - 1]);
_inventButton->setGfx(_arrayBmp[4 * kMenuInventory + scale - 1]);
}
void TopMenu::loadBmpArr(Common::SeekableReadStream &in) {
_arraySize = in.readUint16BE();
delete[] _arrayBmp;
_arrayBmp = new Graphics::Surface *[_arraySize * 2];
for (int i = 0; i < _arraySize; i++) {
uint16 bmpSize = in.readUint16BE();
uint32 filPos = in.pos();
Common::SeekableSubReadStream stream(&in, filPos, filPos + bmpSize);
Image::BitmapDecoder bitmapDecoder;
if (!bitmapDecoder.loadStream(stream))
error("TopMenu::loadBmpArr(): Could not load bitmap");
const Graphics::Surface *bitmapSrc = bitmapDecoder.getSurface();
if (bitmapSrc->format.bytesPerPixel == 1)
error("TopMenu::loadBmpArr(): Unhandled paletted image");
_arrayBmp[i * 2] = bitmapSrc->convertTo(g_system->getOverlayFormat());
_arrayBmp[i * 2 + 1] = new Graphics::Surface();
_arrayBmp[i * 2 + 1]->create(_arrayBmp[i * 2]->w * 2, _arrayBmp[i * 2]->h * 2, g_system->getOverlayFormat());
for (int j = 0; j < _arrayBmp[i * 2]->h; j++) {
byte *src = (byte *)_arrayBmp[i * 2]->getBasePtr(0, j);
byte *dst = (byte *)_arrayBmp[i * 2 + 1]->getBasePtr(0, j * 2);
for (int k = _arrayBmp[i * 2]->w; k > 0; k--) {
for (int m = _arrayBmp[i * 2]->format.bytesPerPixel; m > 0; m--) {
*dst++ = *src++;
}
src -= _arrayBmp[i * 2]->format.bytesPerPixel;
for (int m = _arrayBmp[i * 2]->format.bytesPerPixel; m > 0; m--) {
*dst++ = *src++;
}
}
src = (byte *)_arrayBmp[i * 2 + 1]->getBasePtr(0, j * 2);
dst = (byte *)_arrayBmp[i * 2 + 1]->getBasePtr(0, j * 2 + 1);
for (int k = _arrayBmp[i * 2 + 1]->pitch; k > 0; k--) {
*dst++ = *src++;
}
}
in.seek(filPos + bmpSize);
}
}
void TopMenu::handleCommand(GUI::CommandSender *sender, uint32 command, uint32 data) {
switch (command) {
case kCmdWhat:
close();
_vm->getGameStatus()._helpFl = true;
break;
case kCmdMusic:
_vm->_sound->toggleMusic();
_musicButton->setGfx(_arrayBmp[4 * kMenuMusic + (g_system->getOverlayWidth() > 320 ? 2 : 1) - 1 + ((_vm->_config._musicFl) ? 0 : 2)]);
_musicButton->draw();
g_gui.theme()->updateScreen();
g_system->updateScreen();
g_system->delayMillis(500);
close();
break;
case kCmdSoundFX:
_vm->_sound->toggleSound();
reflowLayout();
_soundFXButton->draw();
g_gui.theme()->updateScreen();
g_system->updateScreen();
g_system->delayMillis(500);
close();
break;
case kCmdSave:
close();
if (_vm->getGameStatus()._viewState == kViewPlay) {
if (_vm->getGameStatus()._gameOverFl)
_vm->gameOverMsg();
else
_vm->_file->saveGame(-1, Common::String());
}
break;
case kCmdLoad:
close();
_vm->_file->restoreGame(-1);
break;
case kCmdRecall:
close();
_vm->getGameStatus()._recallFl = true;
break;
case kCmdTurbo:
_vm->_parser->switchTurbo();
reflowLayout();
_turboButton->draw();
g_gui.theme()->updateScreen();
g_system->updateScreen();
g_system->delayMillis(500);
close();
break;
case kCmdLook:
close();
_vm->_parser->command("look around");
break;
case kCmdInvent:
close();
_vm->_parser->showInventory();
break;
default:
Dialog::handleCommand(sender, command, data);
}
}
void TopMenu::handleMouseUp(int x, int y, int button, int clickCount) {
if (y > _h)
close();
else
Dialog::handleMouseUp(x, y, button, clickCount);
}
EntryDialog::EntryDialog(const Common::String &title, const Common::String &buttonLabel, const Common::String &defaultValue) : GUI::Dialog(20, 20, 100, 50) {
const int screenW = g_system->getOverlayWidth();
const int screenH = g_system->getOverlayHeight();
int buttonWidth = g_gui.xmlEval()->getVar("Globals.Button.Width", 0);
int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
// First, determine the size the dialog needs. For this we have to break
// down the string into lines, and taking the maximum of their widths.
// Using this, and accounting for the space the button(s) need, we can set
// the real size of the dialog
Common::Array<Common::String> lines;
int lineCount, buttonPos;
int maxlineWidth = g_gui.getFont().wordWrapText(title, screenW - 2 * 30, lines);
// Calculate the desired dialog size (maxing out at 300*180 for now)
_w = MAX(maxlineWidth, buttonWidth) + 20;
lineCount = lines.size();
_h = 16 + buttonHeight + 8;
// Limit the number of lines so that the dialog still fits on the screen.
if (lineCount > (screenH - 20 - _h) / kLineHeight) {
lineCount = (screenH - 20 - _h) / kLineHeight;
}
_h += lineCount * kLineHeight;
// Center the dialog
_x = (screenW - _w) / 2;
_y = (screenH - _h) / 2;
// Each line is represented by one static text item.
for (int i = 0; i < lineCount; i++) {
new GUI::StaticTextWidget(this, 10, 10 + i * kLineHeight, maxlineWidth, kLineHeight,
lines[i], Graphics::kTextAlignCenter);
}
_text = new GUI::EditTextWidget(this, 10, 10 + lineCount * (kLineHeight + 1), _w - 20, kLineHeight, Common::U32String(), Common::U32String(), 0, kCmdFinishEdit);
_text->setEditString(defaultValue);
_h += kLineHeight + 5;
buttonPos = (_w - buttonWidth) / 2;
new GUI::ButtonWidget(this, buttonPos, _h - buttonHeight - 8, buttonWidth, buttonHeight, buttonLabel, Common::U32String(), kCmdButton, Common::ASCII_RETURN); // Confirm dialog
}
EntryDialog::~EntryDialog() {
}
void EntryDialog::handleCommand(GUI::CommandSender *sender, uint32 command, uint32 data) {
switch (command) {
case kCmdButton:
case kCmdFinishEdit:
close();
break;
default:
Dialog::handleCommand(sender, command, data);
}
}
} // End of namespace Hugo

117
engines/hugo/dialogs.h Normal file
View File

@@ -0,0 +1,117 @@
/* 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/>.
*
*/
#ifndef HUGO_DIALOGS_H
#define HUGO_DIALOGS_H
#include "gui/dialog.h"
#include "gui/widgets/edittext.h"
namespace Hugo {
class HugoEngine;
enum MenuOption {
kMenuWhat = 0,
kMenuMusic,
kMenuSoundFX,
kMenuSave,
kMenuLoad,
kMenuRecall,
kMenuTurbo,
kMenuLook,
kMenuInventory
};
enum {
kMenuWidth = 320,
kMenuHeight = 24,
kMenuX = 5,
kMenuY = 1,
kButtonWidth = 20,
kButtonHeight = 20,
kButtonPad = 1,
kButtonSpace = 5
};
enum {
// TopMenu commands
kCmdWhat = 'WHAT',
kCmdMusic = 'MUZK',
kCmdSoundFX = 'SOUN',
kCmdSave = 'SAVE',
kCmdLoad = 'LOAD',
kCmdRecall = 'RECL',
kCmdTurbo = 'TURB',
kCmdLook = 'LOOK',
kCmdInvent = 'INVT',
// EntryDialog commands
kCmdButton = 'BTNP',
kCmdFinishEdit = 'FNSH'
};
class TopMenu : public GUI::Dialog {
public:
TopMenu(HugoEngine *vm);
~TopMenu() override;
void reflowLayout() override;
void handleCommand(GUI::CommandSender *sender, uint32 command, uint32 data) override;
void handleMouseUp(int x, int y, int button, int clickCount) override;
void loadBmpArr(Common::SeekableReadStream &in);
protected:
void init();
HugoEngine *_vm;
GUI::PicButtonWidget *_whatButton;
GUI::PicButtonWidget *_musicButton;
GUI::PicButtonWidget *_soundFXButton;
GUI::PicButtonWidget *_loadButton;
GUI::PicButtonWidget *_saveButton;
GUI::PicButtonWidget *_recallButton;
GUI::PicButtonWidget *_turboButton;
GUI::PicButtonWidget *_lookButton;
GUI::PicButtonWidget *_inventButton;
Graphics::Surface **_arrayBmp;
uint16 _arraySize;
};
class EntryDialog : public GUI::Dialog {
public:
EntryDialog(const Common::String &title, const Common::String &buttonLabel, const Common::String &defaultValue);
~EntryDialog() override;
void handleCommand(GUI::CommandSender *sender, uint32 command, uint32 data) override;
const Common::U32String &getEditString() const { return _text->getEditString(); }
protected:
GUI::EditTextWidget *_text;
};
}
#endif // HUGO_DIALOGS_H

1247
engines/hugo/display.cpp Normal file

File diff suppressed because it is too large Load Diff

197
engines/hugo/display.h Normal file
View File

@@ -0,0 +1,197 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_DISPLAY_H
#define HUGO_DISPLAY_H
#include "graphics/surface.h"
#include "graphics/fonts/dosfont.h"
namespace Common {
class ReadStream;
class SeekableReadStream;
class WriteStream;
}
namespace Hugo {
enum OverlayState {kOvlUndef, kOvlForeground, kOvlBackground}; // Overlay state
static const int kCenter = -1; // Used to center text in x
class Screen {
public:
struct Rect { // Rectangle used in Display list
int16 _x; // Position in dib
int16 _y; // Position in dib
int16 _dx; // width
int16 _dy; // height
};
Screen(HugoEngine *vm);
virtual ~Screen();
virtual void loadFont(int16 fontId) = 0;
virtual void loadFontArr(Common::ReadStream &in) = 0;
int16 fontHeight() const;
int16 stringLength(const char *s) const;
void displayBackground();
virtual void displayFrame(const int sx, const int sy, Seq *seq, const bool foreFl) = 0;
void displayList(int update, ...);
void displayRect(const int16 x, const int16 y, const int16 dx, const int16 dy);
void drawBoundaries();
void drawRectangle(const bool filledFl, const int16 x1, const int16 y1, const int16 x2, const int16 y2, const int color);
void drawShape(const int x, const int y, const int color1, const int color2);
void updateStatusText();
void updatePromptText(const char *command, char cursor);
void drawStatusText();
void displayStatusText();
void drawPromptText();
void displayPromptText();
void freeScreen();
void hideCursor();
void initDisplay();
void initNewScreenDisplay();
virtual void loadPalette(Common::SeekableReadStream &in) = 0;
void moveImage(ImagePtr srcImage, const int16 x1, const int16 y1, const int16 dx, int16 dy, const int16 width1, ImagePtr dstImage, const int16 x2, const int16 y2, const int16 width2);
void remapPal(uint16 oldIndex, uint16 newIndex);
void resetInventoryObjId();
void restorePal(Common::ReadStream *f);
void savePal(Common::WriteStream *f) const;
void setBackgroundColor(const uint16 color);
void setCursorPal();
void selectInventoryObjId(const int16 objId);
void shadowStr(int16 sx, const int16 sy, const char *s, const byte color);
void showCursor();
void userHelp() const;
void writeStr(int16 sx, const int16 sy, const char *s, const byte color);
Common::Rect drawDosText(byte x, byte y, const char *text, byte color);
byte getDosMessageBoxBorder() const;
Common::KeyState dosMessageBox(const Common::String &text, bool protect = false, TtsOptions ttsOptions = kTtsReplaceNewlines);
Common::String dosPromptBox(const Common::String &text);
Common::KeyState getKey();
Icondib &getIconBuffer();
Viewdib &getBackBuffer();
Viewdib &getBackBufferBackup();
Viewdib &getFrontBuffer();
Viewdib &getGUIBuffer();
protected:
HugoEngine *_vm;
static const int kRectListSize = 16; // Size of add/restore rect lists
static const int kBlitListSize = kRectListSize * 2; // Size of dirty rect blit list
static const int kShapeSize = 24;
static const int kFontLength = 128; // Number of chars in font
static const int kFontSize = 1200; // Max size of font data
static const int kNumFonts = 3; // Number of dib fonts
static const byte stdMouseCursorHeight = 20;
static const byte stdMouseCursorWidth = 12;
bool fontLoadedFl[kNumFonts];
// Fonts used in dib (non-GDI)
byte *_arrayFont[kNumFonts];
byte _fnt; // Current font number
byte _fontdata[kNumFonts][kFontSize]; // Font data
byte *_font[kNumFonts][kFontLength]; // Ptrs to each char
int16 _arrayFontSize[kNumFonts];
byte *_mainPalette;
byte *_curPalette;
byte _paletteSize;
Graphics::DosFont _dosFont;
Viewdib _frontBuffer;
Graphics::Surface _frontSurface;
inline bool isInX(const int16 x, const Rect *rect) const;
inline bool isInY(const int16 y, const Rect *rect) const;
inline bool isOverlapping(const Rect *rectA, const Rect *rectB) const;
private:
byte _iconImage[kInvDx * kInvDy];
Icondib _iconBuffer; // Inventory icon DIB
int16 mergeLists(Rect *list, Rect *blist, const int16 len, int16 blen);
int16 center(const char *s) const;
Viewdib _backBuffer;
Viewdib _GUIBuffer; // User interface images
Viewdib _backBufferBackup; // Backup _backBuffer during inventory
Viewdib _frontBufferBoxBackup; // Backup _frontBuffer during DOS message boxes
// Formerly static variables used by displayList()
int16 _dlAddIndex, _dlRestoreIndex; // Index into add/restore lists
Rect _dlRestoreList[kRectListSize]; // The restore list
Rect _dlAddList[kRectListSize]; // The add list
Rect _dlBlistList[kBlitListSize]; // The blit list
//
void createPal();
void merge(const Rect *rectA, Rect *rectB);
void writeChr(const int sx, const int sy, const byte color, const char *local_fontdata);
};
class Screen_v1d : public Screen {
public:
Screen_v1d(HugoEngine *vm);
~Screen_v1d() override;
void loadFont(int16 fontId) override;
void loadFontArr(Common::ReadStream &in) override;
void displayFrame(const int sx, const int sy, Seq *seq, const bool foreFl) override;
void loadPalette(Common::SeekableReadStream &in) override;
protected:
OverlayState findOvl(Seq *seqPtr, ImagePtr dstPtr, uint16 y);
};
class Screen_v1w : public Screen {
public:
Screen_v1w(HugoEngine *vm);
~Screen_v1w() override;
void loadFont(int16 fontId) override;
void loadFontArr(Common::ReadStream &in) override;
void displayFrame(const int sx, const int sy, Seq *seq, const bool foreFl) override;
void loadPalette(Common::SeekableReadStream &in) override;
protected:
OverlayState findOvl(Seq *seqPtr, ImagePtr dstPtr, uint16 y);
};
} // End of namespace Hugo
#endif //HUGO_DISPLAY_H

614
engines/hugo/file.cpp Normal file
View File

@@ -0,0 +1,614 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "graphics/surface.h"
#include "graphics/thumbnail.h"
#include "gui/saveload.h"
#include "image/pcx.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/util.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "hugo/mouse.h"
namespace Hugo {
namespace {
static const char s_bootCypher[] = "Copyright 1992, David P Gray, Gray Design Associates";
static const int s_bootCypherLen = sizeof(s_bootCypher) - 1;
}
FileManager::FileManager(HugoEngine *vm) : _vm(vm) {
_hasReadHeader = false;
_firstUIFFl = true;
_UIFHeader->_size = 0;
_UIFHeader->_offset = 0;
_soundHdr->_size = 0;
_soundHdr->_offset = 0;
}
FileManager::~FileManager() {
}
/**
* Name scenery and objects picture databases
*/
const char *FileManager::getBootFilename() const {
return "HUGO.BSF";
}
const char *FileManager::getObjectFilename() const {
return "objects.dat";
}
const char *FileManager::getSceneryFilename() const {
return "scenery.dat";
}
const char *FileManager::getSoundFilename() const {
return "sounds.dat";
}
const char *FileManager::getStringFilename() const {
return "strings.dat";
}
const char *FileManager::getUifFilename() const {
return "uif.dat";
}
/**
* Read a pcx file of length len. Use supplied seqPtr and image_p or
* allocate space if NULL. Name used for errors. Returns address of seqPtr
* Set first TRUE to initialize b_index (i.e. not reading a sequential image in file).
*/
Seq *FileManager::readPCX(Common::SeekableReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name) {
debugC(1, kDebugFile, "readPCX(..., %s)", name);
// Allocate memory for Seq if 0
if (seqPtr == nullptr) {
if ((seqPtr = (Seq *)malloc(sizeof(Seq))) == nullptr)
error("Insufficient memory to run game.");
}
Image::PCXDecoder pcx;
if (!pcx.loadStream(f))
error("Error while reading PCX image");
const Graphics::Surface *pcxSurface = pcx.getSurface();
if (pcxSurface->format.bytesPerPixel != 1)
error("Invalid bytes per pixel in PCX surface (%d)", pcxSurface->format.bytesPerPixel);
// Find size of image data in 8-bit DIB format
// Note save of x2 - marks end of valid data before garbage
seqPtr->_lines = pcxSurface->h;
seqPtr->_x2 = seqPtr->_bytesPerLine8 = pcxSurface->w;
// Size of the image
uint16 size = pcxSurface->w * pcxSurface->h;
// Allocate memory for image data if NULL
if (imagePtr == nullptr)
imagePtr = (byte *)malloc((size_t) size);
assert(imagePtr);
seqPtr->_imagePtr = imagePtr;
for (uint16 y = 0; y < pcxSurface->h; y++)
memcpy(imagePtr + y * pcxSurface->w, pcxSurface->getBasePtr(0, y), pcxSurface->w);
return seqPtr;
}
/**
* Read object file of PCC images into object supplied
*/
void FileManager::readImage(const int objNum, Object *objPtr) {
debugC(1, kDebugFile, "readImage(%d, Object *objPtr)", objNum);
/**
* Structure of object file lookup entry
*/
struct objBlock_t {
uint32 objOffset;
uint32 objLength;
};
if (!objPtr->_seqNumb) // This object has no images
return;
if (_vm->isPacked()) {
_objectsArchive.seek((uint32)objNum * sizeof(objBlock_t), SEEK_SET);
objBlock_t objBlock; // Info on file within database
objBlock.objOffset = _objectsArchive.readUint32LE();
objBlock.objLength = _objectsArchive.readUint32LE();
_objectsArchive.seek(objBlock.objOffset, SEEK_SET);
} else {
Common::Path buf;
buf = _vm->_picDir.appendComponent(Common::String(_vm->_text->getNoun(objPtr->_nounIndex, 0)) + ".PIX");
if (!_objectsArchive.open(buf)) {
buf = Common::String(_vm->_text->getNoun(objPtr->_nounIndex, 0)) + ".PIX";
if (!_objectsArchive.open(buf))
error("File not found: %s", buf.toString().c_str());
}
}
bool firstImgFl = true; // Initializes pcx read function
Seq *seqPtr = nullptr; // Ptr to sequence structure
// Now read the images into an images list
for (int j = 0; j < objPtr->_seqNumb; j++) { // for each sequence
for (int k = 0; k < objPtr->_seqList[j]._imageNbr; k++) { // each image
if (k == 0) { // First image
// Read this image - allocate both seq and image memory
seqPtr = readPCX(_objectsArchive, nullptr, nullptr, firstImgFl, _vm->_text->getNoun(objPtr->_nounIndex, 0));
objPtr->_seqList[j]._seqPtr = seqPtr;
firstImgFl = false;
} else { // Subsequent image
// Read this image - allocate both seq and image memory
seqPtr->_nextSeqPtr = readPCX(_objectsArchive, nullptr, nullptr, firstImgFl, _vm->_text->getNoun(objPtr->_nounIndex, 0));
seqPtr = seqPtr->_nextSeqPtr;
}
// Compute the bounding box - x1, x2, y1, y2
// Note use of x2 - marks end of valid data in row
uint16 x2 = seqPtr->_x2;
seqPtr->_x1 = seqPtr->_x2;
seqPtr->_x2 = 0;
seqPtr->_y1 = seqPtr->_lines;
seqPtr->_y2 = 0;
ImagePtr dibPtr = seqPtr->_imagePtr;
for (int y = 0; y < seqPtr->_lines; y++, dibPtr += seqPtr->_bytesPerLine8 - x2) {
for (int x = 0; x < x2; x++) {
if (*dibPtr++) { // Some data found
if (x < seqPtr->_x1)
seqPtr->_x1 = x;
if (x > seqPtr->_x2)
seqPtr->_x2 = x;
if (y < seqPtr->_y1)
seqPtr->_y1 = y;
if (y > seqPtr->_y2)
seqPtr->_y2 = y;
}
}
}
}
assert(seqPtr);
seqPtr->_nextSeqPtr = objPtr->_seqList[j]._seqPtr; // loop linked list to head
}
// Set the current image sequence to first or last
switch (objPtr->_cycling) {
case kCycleInvisible: // (May become visible later)
case kCycleAlmostInvisible:
case kCycleNotCycling:
case kCycleForward:
objPtr->_currImagePtr = objPtr->_seqList[0]._seqPtr;
break;
case kCycleBackward:
objPtr->_currImagePtr = seqPtr;
break;
default:
warning("Unexpected cycling: %d", objPtr->_cycling);
}
if (!_vm->isPacked())
_objectsArchive.close();
}
/**
* Read sound (or music) file data. Call with SILENCE to free-up
* any allocated memory. Also returns size of data
*/
SoundPtr FileManager::getSound(const int16 sound, uint16 *size) {
debugC(1, kDebugFile, "getSound(%d)", sound);
// No more to do if SILENCE (called for cleanup purposes)
if (sound == _vm->_soundSilence)
return nullptr;
// Open sounds file
Common::File fp; // Handle to SOUND_FILE
if (!fp.open(getSoundFilename())) {
warning("Hugo Error: File not found %s", getSoundFilename());
return nullptr;
}
if (!_hasReadHeader) {
for (int i = 0; i < kMaxSounds; i++) {
_soundHdr[i]._size = fp.readUint16LE();
_soundHdr[i]._offset = fp.readUint32LE();
}
if (fp.err())
error("Wrong sound file format");
_hasReadHeader = true;
}
*size = _soundHdr[sound]._size;
if (*size == 0)
error("Wrong sound file format or missing sound %d", sound);
// Allocate memory for sound or music, if possible
SoundPtr soundPtr = (byte *)malloc(_soundHdr[sound]._size); // Ptr to sound data
assert(soundPtr);
// Seek to data and read it
fp.seek(_soundHdr[sound]._offset, SEEK_SET);
if (fp.read(soundPtr, _soundHdr[sound]._size) != _soundHdr[sound]._size)
error("Wrong sound file format");
fp.close();
return soundPtr;
}
/**
* Save game to supplied slot
*/
bool FileManager::saveGame(const int16 slot, const Common::String &descrip) {
debugC(1, kDebugFile, "saveGame(%d, %s)", slot, descrip.c_str());
int16 savegameId;
Common::String savegameDescription;
if (slot == -1) {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
savegameId = dialog->runModalWithCurrentTarget();
savegameDescription = dialog->getResultString();
delete dialog;
} else {
savegameId = slot;
if (!descrip.empty()) {
savegameDescription = descrip;
} else {
savegameDescription = Common::String::format("Quick save #%d", slot);
}
}
if (savegameId < 0) // dialog aborted
return false;
Common::String savegameFile = _vm->getSaveStateName(savegameId);
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
Common::OutSaveFile *out = saveMan->openForSaving(savegameFile);
if (!out) {
warning("Can't create file '%s', game not saved", savegameFile.c_str());
return false;
}
// Write version. We can't restore from obsolete versions
out->writeByte(kSavegameVersion);
if (savegameDescription == "") {
savegameDescription = "Untitled savegame";
}
out->writeSint16BE(savegameDescription.size() + 1);
out->write(savegameDescription.c_str(), savegameDescription.size() + 1);
Graphics::saveThumbnail(*out);
TimeDate curTime;
_vm->_system->getTimeAndDate(curTime);
uint32 saveDate = (curTime.tm_mday & 0xFF) << 24 | ((curTime.tm_mon + 1) & 0xFF) << 16 | ((curTime.tm_year + 1900) & 0xFFFF);
uint16 saveTime = (curTime.tm_hour & 0xFF) << 8 | ((curTime.tm_min) & 0xFF);
out->writeUint32BE(saveDate);
out->writeUint16BE(saveTime);
_vm->_object->saveObjects(out);
const Status &gameStatus = _vm->getGameStatus();
// Save whether hero image is swapped
out->writeByte(_vm->_heroImage);
// Save score
out->writeSint16BE(_vm->getScore());
// Save story mode
out->writeByte((gameStatus._storyModeFl) ? 1 : 0);
// Save jumpexit mode
out->writeByte((_vm->_mouse->getJumpExitFl()) ? 1 : 0);
// Save gameover status
out->writeByte((gameStatus._gameOverFl) ? 1 : 0);
// Save screen states
for (int i = 0; i < _vm->_numStates; i++)
out->writeByte(_vm->_screenStates[i]);
_vm->_scheduler->saveSchedulerData(out);
// Save palette table
_vm->_screen->savePal(out);
// Save maze status
out->writeByte((_vm->_maze._enabledFl) ? 1 : 0);
out->writeByte(_vm->_maze._size);
out->writeSint16BE(_vm->_maze._x1);
out->writeSint16BE(_vm->_maze._y1);
out->writeSint16BE(_vm->_maze._x2);
out->writeSint16BE(_vm->_maze._y2);
out->writeSint16BE(_vm->_maze._x3);
out->writeSint16BE(_vm->_maze._x4);
out->writeByte(_vm->_maze._firstScreenIndex);
out->writeByte((byte)_vm->getGameStatus()._viewState);
out->finalize();
delete out;
return true;
}
/**
* Restore game from supplied slot number
*/
bool FileManager::restoreGame(const int16 slot) {
debugC(1, kDebugFile, "restoreGame(%d)", slot);
int16 savegameId;
if (slot == -1) {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
savegameId = dialog->runModalWithCurrentTarget();
delete dialog;
} else {
savegameId = slot;
}
if (savegameId < 0) // dialog aborted
return false;
Common::String savegameFile = _vm->getSaveStateName(savegameId);
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
Common::InSaveFile *in = saveMan->openForLoading(savegameFile);
if (!in)
return false;
// Initialize new-game status
_vm->initStatus();
// Check version, can't restore from different versions
int saveVersion = in->readByte();
if (saveVersion != kSavegameVersion) {
warning("Savegame of incompatible version");
delete in;
return false;
}
// Skip over description
int32 saveGameNameSize = in->readSint16BE();
in->skip(saveGameNameSize);
Graphics::skipThumbnail(*in);
in->skip(6); // Skip date & time
// If hero image is currently swapped, swap it back before restore
if (_vm->_heroImage != kHeroIndex)
_vm->_object->swapImages(kHeroIndex, _vm->_heroImage, true);
_vm->_object->restoreObjects(in);
_vm->_heroImage = in->readByte();
// Restore ptrs to currently loaded objects
_vm->_object->restoreAllSeq();
// If hero swapped in saved game, swap it
byte heroImg = _vm->_heroImage;
if (heroImg != kHeroIndex)
_vm->_object->swapImages(kHeroIndex, _vm->_heroImage, true);
_vm->_heroImage = heroImg;
Status &gameStatus = _vm->getGameStatus();
int score = in->readSint16BE();
_vm->setScore(score);
#ifdef USE_TTS
_vm->_previousScore = -1;
#endif
gameStatus._storyModeFl = (in->readByte() == 1);
_vm->_mouse->setJumpExitFl(in->readByte() == 1);
gameStatus._gameOverFl = (in->readByte() == 1);
for (int i = 0; i < _vm->_numStates; i++)
_vm->_screenStates[i] = in->readByte();
_vm->_scheduler->restoreSchedulerData(in);
// Restore palette and change it if necessary
_vm->_screen->restorePal(in);
// Restore maze status
_vm->_maze._enabledFl = (in->readByte() == 1);
_vm->_maze._size = in->readByte();
_vm->_maze._x1 = in->readSint16BE();
_vm->_maze._y1 = in->readSint16BE();
_vm->_maze._x2 = in->readSint16BE();
_vm->_maze._y2 = in->readSint16BE();
_vm->_maze._x3 = in->readSint16BE();
_vm->_maze._x4 = in->readSint16BE();
_vm->_maze._firstScreenIndex = in->readByte();
_vm->_scheduler->restoreScreen(*_vm->_screenPtr);
if ((_vm->getGameStatus()._viewState = (Vstate) in->readByte()) != kViewPlay)
_vm->_screen->hideCursor();
delete in;
return true;
}
/**
* Reads boot file for program environment. Fatal error if not there or
* file checksum is bad. De-crypts structure while checking checksum
*/
void FileManager::readBootFile() {
debugC(1, kDebugFile, "readBootFile()");
Common::File ofp;
if (!ofp.open(getBootFilename())) {
if (_vm->_gameVariant == kGameVariantH1Dos) {
//TODO initialize properly _boot structure
warning("readBootFile - Skipping as H1 Dos may be a freeware");
memset(_vm->_boot._distrib, '\0', sizeof(_vm->_boot._distrib));
_vm->_boot._registered = kRegFreeware;
return;
} else if (_vm->getPlatform() == Common::kPlatformDOS) {
warning("readBootFile - Skipping as H2 and H3 Dos may be shareware");
memset(_vm->_boot._distrib, '\0', sizeof(_vm->_boot._distrib));
_vm->_boot._registered = kRegShareware;
return;
} else {
_vm->notifyBox(Common::String::format("Missing startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
return;
}
}
if (ofp.size() < (int32)sizeof(_vm->_boot)) {
_vm->notifyBox(Common::String::format("Corrupted startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
return;
}
_vm->_boot._checksum = ofp.readByte();
_vm->_boot._registered = ofp.readByte();
ofp.read(_vm->_boot._pbswitch, sizeof(_vm->_boot._pbswitch));
ofp.read(_vm->_boot._distrib, sizeof(_vm->_boot._distrib));
_vm->_boot._exitLen = ofp.readUint16LE();
ofp.close();
byte *p = (byte *)&_vm->_boot;
byte checksum = 0;
for (uint32 i = 0; i < sizeof(_vm->_boot); i++) {
checksum ^= p[i];
p[i] ^= s_bootCypher[i % s_bootCypherLen];
}
if (checksum) {
_vm->notifyBox(Common::String::format("Corrupted startup file '%s'", getBootFilename()));
_vm->getGameStatus()._doQuitFl = true;
}
}
/**
* Returns address of uif_hdr[id], reading it in if first call
* This file contains, between others, the bitmaps of the fonts used in the application
* UIF means User interface database (Windows Only)
*/
UifHdr *FileManager::getUIFHeader(const Uif id) {
debugC(1, kDebugFile, "getUIFHeader(%d)", id);
// Initialize offset lookup if not read yet
if (_firstUIFFl) {
_firstUIFFl = false;
// Open unbuffered to do far read
Common::File ip; // Image data file
if (!ip.open(getUifFilename()))
error("File not found: %s", getUifFilename());
if (ip.size() < (int32)sizeof(_UIFHeader))
error("Wrong UIF file format");
for (int i = 0; i < kMaxUifs; ++i) {
_UIFHeader[i]._size = ip.readUint16LE();
_UIFHeader[i]._offset = ip.readUint32LE();
}
ip.close();
}
return &_UIFHeader[id];
}
/**
* Read uif item into supplied buffer.
*/
void FileManager::readUIFItem(const int16 id, byte *buf) {
debugC(1, kDebugFile, "readUIFItem(%d, ...)", id);
// Open uif file to read data
Common::File ip; // UIF_FILE handle
if (!ip.open(getUifFilename()))
error("File not found: %s", getUifFilename());
// Seek to data
UifHdr *_UIFHeaderPtr = getUIFHeader((Uif)id);
ip.seek(_UIFHeaderPtr->_offset, SEEK_SET);
// We support pcx images and straight data
Seq *dummySeq; // Dummy Seq for image data
switch (id) {
case UIF_IMAGES: // Read uif images file
dummySeq = readPCX(ip, nullptr, buf, true, getUifFilename());
free(dummySeq);
break;
default: // Read file data into supplied array
if (ip.read(buf, _UIFHeaderPtr->_size) != _UIFHeaderPtr->_size)
error("Wrong UIF file format");
break;
}
ip.close();
}
/**
* Read the uif image file (inventory icons)
*/
void FileManager::readUIFImages() {
debugC(1, kDebugFile, "readUIFImages()");
readUIFItem(UIF_IMAGES, _vm->_screen->getGUIBuffer()); // Read all uif images
}
} // End of namespace Hugo

183
engines/hugo/file.h Normal file
View File

@@ -0,0 +1,183 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_FILE_H
#define HUGO_FILE_H
#include "common/file.h"
namespace Hugo {
/**
* Enumerate overlay file types
*/
enum OvlType {kOvlBoundary, kOvlOverlay, kOvlBase};
struct UifHdr { // UIF font/image look up
uint16 _size; // Size of uif item
uint32 _offset; // Offset of item in file
};
class FileManager {
public:
FileManager(HugoEngine *vm);
virtual ~FileManager();
SoundPtr getSound(const int16 sound, uint16 *size);
void readBootFile();
void readImage(const int objNum, Object *objPtr);
void readUIFImages();
void readUIFItem(const int16 id, byte *buf);
bool restoreGame(const int16 slot);
bool saveGame(const int16 slot, const Common::String &descrip);
// Name scenery and objects picture databases
const char *getBootFilename() const;
const char *getObjectFilename() const;
const char *getSceneryFilename() const;
const char *getSoundFilename() const;
const char *getStringFilename() const;
const char *getUifFilename() const;
virtual void openDatabaseFiles() = 0;
virtual void closeDatabaseFiles() = 0;
virtual void instructions() const = 0;
virtual void readBackground(const int screenIndex) = 0;
virtual void readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) = 0;
virtual const char *fetchString(const int index) = 0;
protected:
HugoEngine *_vm;
static const int kMaxUifs = 32; // Max possible uif items in hdr
static const int kMaxSounds = 64; // Max number of sounds
static const int kRepeatMask = 0xC0; // Top 2 bits mean a repeat code
static const int kLengthMask = 0x3F; // Lower 6 bits are length
static const int kNumColors = 16; // Num colors to save in palette
/**
* Structure of scenery file lookup entry
*/
struct SceneBlock {
uint32 _sceneOffset;
uint32 _sceneLength;
uint32 _boundaryOffset;
uint32 _boundaryLength;
uint32 _overlayOffset;
uint32 _overlayLength;
uint32 _baseOffset;
uint32 _baseLength;
};
struct PCCHeader { // Structure of PCX file header
byte _mfctr, _vers, _enc, _bpx;
uint16 _x1, _y1, _x2, _y2; // bounding box
uint16 _xres, _yres;
byte _palette[3 * kNumColors]; // EGA color palette
byte _vmode, _planes;
uint16 _bytesPerLine; // Bytes per line
byte _fill2[60];
}; // Header of a PCC file
bool _firstUIFFl;
UifHdr _UIFHeader[kMaxUifs]; // Lookup for uif fonts/images
Common::File _stringArchive; // Handle for string file
Common::File _sceneryArchive1; // Handle for scenery file
Common::File _objectsArchive; // Handle for objects file
Seq *readPCX(Common::SeekableReadStream &f, Seq *seqPtr, byte *imagePtr, const bool firstFl, const char *name);
// If this is the first call, read the lookup table
bool _hasReadHeader;
SoundHdr _soundHdr[kMaxSounds]; // Sound lookup table
private:
UifHdr *getUIFHeader(const Uif id);
};
class FileManager_v1d : public FileManager {
public:
FileManager_v1d(HugoEngine *vm);
~FileManager_v1d() override;
void closeDatabaseFiles() override;
void instructions() const override;
void openDatabaseFiles() override;
void readBackground(const int screenIndex) override;
void readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) override;
const char *fetchString(const int index) override;
};
class FileManager_v2d : public FileManager_v1d {
public:
FileManager_v2d(HugoEngine *vm);
~FileManager_v2d() override;
void closeDatabaseFiles() override;
void openDatabaseFiles() override;
void readBackground(const int screenIndex) override;
void readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) override;
const char *fetchString(const int index) override;
private:
char *_fetchStringBuf;
};
class FileManager_v3d : public FileManager_v2d {
public:
FileManager_v3d(HugoEngine *vm);
~FileManager_v3d() override;
void closeDatabaseFiles() override;
void openDatabaseFiles() override;
void readBackground(const int screenIndex) override;
void readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) override;
private:
Common::File _sceneryArchive2; // Handle for scenery file
};
class FileManager_v2w : public FileManager_v2d {
public:
FileManager_v2w(HugoEngine *vm);
~FileManager_v2w() override;
void instructions() const override;
};
class FileManager_v1w : public FileManager_v2w {
public:
FileManager_v1w(HugoEngine *vm);
~FileManager_v1w() override;
void readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) override;
};
} // End of namespace Hugo
#endif //HUGO_FILE_H

145
engines/hugo/file_v1d.cpp Normal file
View File

@@ -0,0 +1,145 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/display.h"
#include "hugo/text.h"
#include "hugo/util.h"
namespace Hugo {
FileManager_v1d::FileManager_v1d(HugoEngine *vm) : FileManager(vm) {
}
FileManager_v1d::~FileManager_v1d() {
}
void FileManager_v1d::openDatabaseFiles() {
debugC(1, kDebugFile, "openDatabaseFiles");
}
void FileManager_v1d::closeDatabaseFiles() {
debugC(1, kDebugFile, "closeDatabaseFiles");
}
/**
* Open and read in an overlay file, close file
*/
void FileManager_v1d::readOverlay(const int screenNum, ImagePtr image, const OvlType overlayType) {
debugC(1, kDebugFile, "readOverlay(%d, ...)", screenNum);
const char *ovlExt[] = {".b", ".o", ".ob"};
Common::Path buf(_vm->_text->getScreenNames(screenNum));
buf.appendInPlace(ovlExt[overlayType]);
if (!Common::File::exists(buf)) {
memset(image, 0, kOvlSize);
warning("File not found: %s", buf.toString().c_str());
return;
}
if (!_sceneryArchive1.open(buf))
error("File not found: %s", buf.toString().c_str());
ImagePtr tmpImage = image; // temp ptr to overlay file
_sceneryArchive1.read(tmpImage, kOvlSize);
_sceneryArchive1.close();
}
/**
* Read a PCX image into dib_a
*/
void FileManager_v1d::readBackground(const int screenIndex) {
debugC(1, kDebugFile, "readBackground(%d)", screenIndex);
Common::Path buf(_vm->_text->getScreenNames(screenIndex));
buf.appendInPlace(".ART");
if (!_sceneryArchive1.open(buf))
error("File not found: %s", buf.toString().c_str());
// Read the image into dummy seq and static dib_a
Seq *dummySeq; // Image sequence structure for Read_pcx
dummySeq = readPCX(_sceneryArchive1, nullptr, _vm->_screen->getFrontBuffer(), true, _vm->_text->getScreenNames(screenIndex));
free(dummySeq);
_sceneryArchive1.close();
}
const char *FileManager_v1d::fetchString(const int index) {
debugC(1, kDebugFile, "fetchString(%d)", index);
return _vm->_text->getStringtData(index);
}
/**
* Simple instructions given when F1 pressed twice in a row
* Only in DOS versions
*/
void FileManager_v1d::instructions() const {
// Note: HELP.DAT uses CRLF line endings. The original used `open()` from
// Microsoft's CRT in non-binary mode, so `read()` translated these to LF.
// This is necessary because the DOS message boxes only expect LF characters.
// The original source code's comments call the surviving character "CR",
// but it is really LF. We have copied these comments, so when you see a
// comment refer to "CR", such as later in this function, it means LF.
Common::File f;
if (!f.open("help.dat")) {
warning("help.dat not found");
return;
}
char readBuf;
while (f.read(&readBuf, 1)) {
if (readBuf == '\r') {
continue; // skip '\r'
}
char line[1024], *wrkLine;
wrkLine = line;
wrkLine[0] = readBuf;
wrkLine++;
do {
f.read(wrkLine, 1);
if (*wrkLine == '\r') {
f.read(wrkLine, 1); // skip '\r'
}
} while (*wrkLine++ != '#'); // '#' is EOP
wrkLine[-2] = '\0'; // Remove EOP and previous CR
_vm->notifyBox(line);
wrkLine = line;
f.read(&readBuf, 1); // Remove CR after EOP
if (readBuf == '\r') {
f.read(&readBuf, 1); // skip '\r'
}
}
f.close();
}
} // End of namespace Hugo

88
engines/hugo/file_v1w.cpp Normal file
View File

@@ -0,0 +1,88 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/util.h"
namespace Hugo {
FileManager_v1w::FileManager_v1w(HugoEngine *vm) : FileManager_v2w(vm) {
}
FileManager_v1w::~FileManager_v1w() {
}
/**
* Open and read in an overlay file, close file
*/
void FileManager_v1w::readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) {
debugC(1, kDebugFile, "readOverlay(%d, ...)", screenNum);
ImagePtr tmpImage = image; // temp ptr to overlay file
_sceneryArchive1.seek((uint32)screenNum * sizeof(SceneBlock), SEEK_SET);
SceneBlock sceneBlock; // Database header entry
sceneBlock._sceneOffset = _sceneryArchive1.readUint32LE();
sceneBlock._sceneLength = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryOffset = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryLength = _sceneryArchive1.readUint32LE();
sceneBlock._overlayOffset = _sceneryArchive1.readUint32LE();
sceneBlock._overlayLength = _sceneryArchive1.readUint32LE();
sceneBlock._baseOffset = _sceneryArchive1.readUint32LE();
sceneBlock._baseLength = _sceneryArchive1.readUint32LE();
uint32 i = 0;
switch (overlayType) {
case kOvlBoundary:
_sceneryArchive1.seek(sceneBlock._boundaryOffset, SEEK_SET);
i = sceneBlock._boundaryLength;
break;
case kOvlOverlay:
_sceneryArchive1.seek(sceneBlock._overlayOffset, SEEK_SET);
i = sceneBlock._overlayLength;
break;
case kOvlBase:
_sceneryArchive1.seek(sceneBlock._baseOffset, SEEK_SET);
i = sceneBlock._baseLength;
break;
default:
error("Bad overlayType: %d", overlayType);
break;
}
if (i == 0) {
memset(image, 0, kOvlSize);
return;
}
_sceneryArchive1.read(tmpImage, kOvlSize);
}
} // End of namespace Hugo

188
engines/hugo/file_v2d.cpp Normal file
View File

@@ -0,0 +1,188 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/text.h"
#include "hugo/util.h"
namespace Hugo {
FileManager_v2d::FileManager_v2d(HugoEngine *vm) : FileManager_v1d(vm) {
_fetchStringBuf = (char *)malloc(kMaxBoxChar);
}
FileManager_v2d::~FileManager_v2d() {
free(_fetchStringBuf);
}
/**
* Open "database" file (packed files)
*/
void FileManager_v2d::openDatabaseFiles() {
debugC(1, kDebugFile, "openDatabaseFiles");
if (!_stringArchive.open(getStringFilename()))
error("File not found: %s", getStringFilename());
if (!_sceneryArchive1.open(getSceneryFilename()))
error("File not found: %s", getSceneryFilename());
if (!_objectsArchive.open(getObjectFilename()))
error("File not found: %s", getObjectFilename());
}
/**
* Close "Database" files
*/
void FileManager_v2d::closeDatabaseFiles() {
debugC(1, kDebugFile, "closeDatabaseFiles");
_stringArchive.close();
_sceneryArchive1.close();
_objectsArchive.close();
}
/**
* Read a PCX image into dib_a
*/
void FileManager_v2d::readBackground(const int screenIndex) {
debugC(1, kDebugFile, "readBackground(%d)", screenIndex);
_sceneryArchive1.seek((uint32) screenIndex * sizeof(SceneBlock), SEEK_SET);
SceneBlock sceneBlock; // Read a database header entry
sceneBlock._sceneOffset = _sceneryArchive1.readUint32LE();
sceneBlock._sceneLength = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryOffset = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryLength = _sceneryArchive1.readUint32LE();
sceneBlock._overlayOffset = _sceneryArchive1.readUint32LE();
sceneBlock._overlayLength = _sceneryArchive1.readUint32LE();
sceneBlock._baseOffset = _sceneryArchive1.readUint32LE();
sceneBlock._baseLength = _sceneryArchive1.readUint32LE();
_sceneryArchive1.seek(sceneBlock._sceneOffset, SEEK_SET);
// Read the image into dummy seq and static dib_a
Seq *dummySeq; // Image sequence structure for Read_pcx
dummySeq = readPCX(_sceneryArchive1, nullptr, _vm->_screen->getFrontBuffer(), true, _vm->_text->getScreenNames(screenIndex));
free(dummySeq);
}
/**
* Open and read in an overlay file, close file
*/
void FileManager_v2d::readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) {
debugC(1, kDebugFile, "readOverlay(%d, ...)", screenNum);
ImagePtr tmpImage = image; // temp ptr to overlay file
_sceneryArchive1.seek((uint32)screenNum * sizeof(SceneBlock), SEEK_SET);
SceneBlock sceneBlock; // Database header entry
sceneBlock._sceneOffset = _sceneryArchive1.readUint32LE();
sceneBlock._sceneLength = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryOffset = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryLength = _sceneryArchive1.readUint32LE();
sceneBlock._overlayOffset = _sceneryArchive1.readUint32LE();
sceneBlock._overlayLength = _sceneryArchive1.readUint32LE();
sceneBlock._baseOffset = _sceneryArchive1.readUint32LE();
sceneBlock._baseLength = _sceneryArchive1.readUint32LE();
uint32 i = 0;
switch (overlayType) {
case kOvlBoundary:
_sceneryArchive1.seek(sceneBlock._boundaryOffset, SEEK_SET);
i = sceneBlock._boundaryLength;
break;
case kOvlOverlay:
_sceneryArchive1.seek(sceneBlock._overlayOffset, SEEK_SET);
i = sceneBlock._overlayLength;
break;
case kOvlBase:
_sceneryArchive1.seek(sceneBlock._baseOffset, SEEK_SET);
i = sceneBlock._baseLength;
break;
default:
error("Bad overlayType: %d", overlayType);
break;
}
if (i == 0) {
memset(image, 0, kOvlSize);
return;
}
// Read in the overlay file using MAC Packbits. (We're not proud!)
int16 k = 0; // byte count
do {
int8 data = _sceneryArchive1.readByte(); // Read a code byte
if ((byte)data == 0x80) // Noop
;
else if (data >= 0) { // Copy next data+1 literally
for (i = 0; i <= (byte)data; i++, k++)
*tmpImage++ = _sceneryArchive1.readByte();
} else { // Repeat next byte -data+1 times
int16 j = _sceneryArchive1.readByte();
for (i = 0; i < (byte)(-data + 1); i++, k++)
*tmpImage++ = j;
}
} while (k < kOvlSize);
}
/**
* Fetch string from file, decode and return ptr to string in memory
*/
const char *FileManager_v2d::fetchString(const int index) {
debugC(1, kDebugFile, "fetchString(%d)", index);
// Get offset to string[index] (and next for length calculation)
_stringArchive.seek((uint32)index * sizeof(uint32), SEEK_SET);
uint32 off1 = _stringArchive.readUint32LE();
uint32 off2 = _stringArchive.readUint32LE();
if (!off1 || !off2)
error("An error has occurred: bad String offset");
// Check size of string
if ((off2 - off1) >= (uint32) kMaxBoxChar)
error("Fetched string too long!");
// Position to string and read it into gen purpose _textBoxBuffer
_stringArchive.seek(off1, SEEK_SET);
if (_stringArchive.read(_fetchStringBuf, (uint16)(off2 - off1)) == 0)
error("An error has occurred: fetchString");
// Null terminate, decode and return it
_fetchStringBuf[off2-off1] = '\0';
_vm->_scheduler->decodeString(_fetchStringBuf);
return _fetchStringBuf;
}
} // End of namespace Hugo

50
engines/hugo/file_v2w.cpp Normal file
View File

@@ -0,0 +1,50 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/util.h"
namespace Hugo {
FileManager_v2w::FileManager_v2w(HugoEngine *vm) : FileManager_v2d(vm) {
}
FileManager_v2w::~FileManager_v2w() {
}
/**
* Display a Windows help file
* Same comment than in SCI: maybe in the future we can implement this, but for now this message should suffice
*/
void FileManager_v2w::instructions() const {
_vm->notifyBox(Common::String::format("Please use an external viewer to open the game's help file: HUGOWIN%d.HLP", _vm->_gameVariant + 1));
}
} // End of namespace Hugo

206
engines/hugo/file_v3d.cpp Normal file
View File

@@ -0,0 +1,206 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "hugo/hugo.h"
#include "hugo/file.h"
#include "hugo/display.h"
#include "hugo/text.h"
#include "hugo/util.h"
namespace Hugo {
FileManager_v3d::FileManager_v3d(HugoEngine *vm) : FileManager_v2d(vm) {
}
FileManager_v3d::~FileManager_v3d() {
}
/**
* Read a PCX image into dib_a
*/
void FileManager_v3d::readBackground(const int screenIndex) {
debugC(1, kDebugFile, "readBackground(%d)", screenIndex);
_sceneryArchive1.seek((uint32) screenIndex * sizeof(SceneBlock), SEEK_SET);
SceneBlock sceneBlock; // Read a database header entry
sceneBlock._sceneOffset = _sceneryArchive1.readUint32LE();
sceneBlock._sceneLength = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryOffset = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryLength = _sceneryArchive1.readUint32LE();
sceneBlock._overlayOffset = _sceneryArchive1.readUint32LE();
sceneBlock._overlayLength = _sceneryArchive1.readUint32LE();
sceneBlock._baseOffset = _sceneryArchive1.readUint32LE();
sceneBlock._baseLength = _sceneryArchive1.readUint32LE();
Seq *dummySeq; // Image sequence structure for Read_pcx
if (screenIndex < 20) {
_sceneryArchive1.seek(sceneBlock._sceneOffset, SEEK_SET);
// Read the image into dummy seq and static dib_a
dummySeq = readPCX(_sceneryArchive1, nullptr, _vm->_screen->getFrontBuffer(), true, _vm->_text->getScreenNames(screenIndex));
} else {
_sceneryArchive2.seek(sceneBlock._sceneOffset, SEEK_SET);
// Read the image into dummy seq and static dib_a
dummySeq = readPCX(_sceneryArchive2, nullptr, _vm->_screen->getFrontBuffer(), true, _vm->_text->getScreenNames(screenIndex));
}
free(dummySeq);
}
/**
* Open "database" file (packed files)
*/
void FileManager_v3d::openDatabaseFiles() {
debugC(1, kDebugFile, "openDatabaseFiles");
if (!_stringArchive.open(getStringFilename()))
error("File not found: %s", getStringFilename());
if (!_sceneryArchive1.open("scenery1.dat"))
error("File not found: scenery1.dat");
if (!_sceneryArchive2.open("scenery2.dat"))
error("File not found: scenery2.dat");
if (!_objectsArchive.open(getObjectFilename()))
error("File not found: %s", getObjectFilename());
}
/**
* Close "Database" files
*/
void FileManager_v3d::closeDatabaseFiles() {
debugC(1, kDebugFile, "closeDatabaseFiles");
_stringArchive.close();
_sceneryArchive1.close();
_sceneryArchive2.close();
_objectsArchive.close();
}
/**
* Open and read in an overlay file, close file
*/
void FileManager_v3d::readOverlay(const int screenNum, ImagePtr image, OvlType overlayType) {
debugC(1, kDebugFile, "readOverlay(%d, ...)", screenNum);
ImagePtr tmpImage = image; // temp ptr to overlay file
_sceneryArchive1.seek((uint32)screenNum * sizeof(SceneBlock), SEEK_SET);
SceneBlock sceneBlock; // Database header entry
sceneBlock._sceneOffset = _sceneryArchive1.readUint32LE();
sceneBlock._sceneLength = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryOffset = _sceneryArchive1.readUint32LE();
sceneBlock._boundaryLength = _sceneryArchive1.readUint32LE();
sceneBlock._overlayOffset = _sceneryArchive1.readUint32LE();
sceneBlock._overlayLength = _sceneryArchive1.readUint32LE();
sceneBlock._baseOffset = _sceneryArchive1.readUint32LE();
sceneBlock._baseLength = _sceneryArchive1.readUint32LE();
uint32 i = 0;
if (screenNum < 20) {
switch (overlayType) {
case kOvlBoundary:
_sceneryArchive1.seek(sceneBlock._boundaryOffset, SEEK_SET);
i = sceneBlock._boundaryLength;
break;
case kOvlOverlay:
_sceneryArchive1.seek(sceneBlock._overlayOffset, SEEK_SET);
i = sceneBlock._overlayLength;
break;
case kOvlBase:
_sceneryArchive1.seek(sceneBlock._baseOffset, SEEK_SET);
i = sceneBlock._baseLength;
break;
default:
error("Bad overlayType: %d", overlayType);
break;
}
if (i == 0) {
memset(image, 0, kOvlSize);
return;
}
// Read in the overlay file using MAC Packbits. (We're not proud!)
int16 k = 0; // byte count
do {
int8 data = _sceneryArchive1.readByte();// Read a code byte
if ((byte)data == 0x80) // Noop
;
else if (data >= 0) { // Copy next data+1 literally
for (i = 0; i <= (byte)data; i++, k++)
*tmpImage++ = _sceneryArchive1.readByte();
} else { // Repeat next byte -data+1 times
int16 j = _sceneryArchive1.readByte();
for (i = 0; i < (byte)(-data + 1); i++, k++)
*tmpImage++ = j;
}
} while (k < kOvlSize);
} else {
switch (overlayType) {
case kOvlBoundary:
_sceneryArchive2.seek(sceneBlock._boundaryOffset, SEEK_SET);
i = sceneBlock._boundaryLength;
break;
case kOvlOverlay:
_sceneryArchive2.seek(sceneBlock._overlayOffset, SEEK_SET);
i = sceneBlock._overlayLength;
break;
case kOvlBase:
_sceneryArchive2.seek(sceneBlock._baseOffset, SEEK_SET);
i = sceneBlock._baseLength;
break;
default:
error("Bad overlayType: %d", overlayType);
break;
}
if (i == 0) {
memset(image, 0, kOvlSize);
return;
}
// Read in the overlay file using MAC Packbits. (We're not proud!)
int16 k = 0; // byte count
do {
int8 data = _sceneryArchive2.readByte();// Read a code byte
if ((byte)data == 0x80) // Noop
;
else if (data >= 0) { // Copy next data+1 literally
for (i = 0; i <= (byte)data; i++, k++)
*tmpImage++ = _sceneryArchive2.readByte();
} else { // Repeat next byte -data+1 times
int16 j = _sceneryArchive2.readByte();
for (i = 0; i < (byte)(-data + 1); i++, k++)
*tmpImage++ = j;
}
} while (k < kOvlSize);
}
}
} // End of namespace Hugo

178
engines/hugo/game.h Normal file
View File

@@ -0,0 +1,178 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_GAME_H
#define HUGO_GAME_H
#include "common/keyboard.h"
namespace Common {
class WriteStream;
class SeekableReadStream;
}
namespace Hugo {
// Game specific equates
#define TAKE_TEXT_DOS "Ok"
#define TAKE_TEXT_WINDOWS "Picked up the %s ok."
enum {LOOK_NAME = 1, TAKE_NAME}; // Index of name used in showing takeables and in confirming take
// Definitions of 'generic' commands: Max # depends on size of gencmd in
// the Object record since each requires 1 bit. Currently up to 16
enum {LOOK = 1, TAKE = 2, DROP = 4, LOOK_S = 8};
enum TEXTCOLORS {
_TBLACK, _TBLUE, _TGREEN, _TCYAN,
_TRED, _TMAGENTA, _TBROWN, _TWHITE,
_TGRAY, _TLIGHTBLUE, _TLIGHTGREEN, _TLIGHTCYAN,
_TLIGHTRED, _TLIGHTMAGENTA, _TLIGHTYELLOW, _TBRIGHTWHITE
};
enum Uif {U_FONT5, U_FONT6, U_FONT8, UIF_IMAGES, NUM_UIF_ITEMS};
static const int kFirstFont = U_FONT5;
/**
* Enumerate ways of cycling a sequence of frames
*/
enum Cycle {kCycleInvisible, kCycleAlmostInvisible, kCycleNotCycling, kCycleForward, kCycleBackward};
/**
* Enumerate sequence index matching direction of travel
*/
enum {SEQ_RIGHT, SEQ_LEFT, SEQ_DOWN, SEQ_UP};
enum Font {LARGE_ROMAN, MED_ROMAN, NUM_GDI_FONTS, INIT_FONTS, DEL_FONTS};
/**
* Enumerate the different path types for an object
*/
enum Path {
kPathUser, // User has control of object via cursor keys
kPathAuto, // Computer has control, controlled by action lists
kPathQuiet, // Computer has control and no commands allowed
kPathChase, // Computer has control, object is chasing hero
kPathChase2, // Same as CHASE, except keeps cycling when stationary
kPathWander, // Computer has control, object is wandering randomly
kPathWander2 // Same as WANDER, except keeps cycling when stationary
};
struct hugoBoot { // Common HUGO boot file
char _checksum; // Checksum for boot structure (not exit text)
char _registered; // TRUE if registered version, else FALSE
char _pbswitch[8]; // Playback switch string
char _distrib[32]; // Distributor branding string
uint16 _exitLen; // Length of exit text (next in file)
} PACKED_STRUCT;
/**
* Game specific type definitions
*/
typedef byte *ImagePtr; // ptr to an object image (sprite)
typedef byte *SoundPtr; // ptr to sound (or music) data
/**
* Structure for initializing maze processing
*/
struct Maze {
bool _enabledFl; // TRUE when maze processing enabled
byte _size; // Size of (square) maze matrix
int _x1, _y1, _x2, _y2; // maze hotspot bounding box
int _x3, _x4; // north, south x entry coordinates
byte _firstScreenIndex; // index of first screen in maze
};
/**
* The following is a linked list of images in an animation sequence
* The image data is in 8-bit DIB format, i.e. 1 byte = 1 pixel
*/
struct Seq { // Linked list of images
byte *_imagePtr; // ptr to image
uint16 _bytesPerLine8; // bytes per line (8bits)
uint16 _lines; // lines
uint16 _x1, _x2, _y1, _y2; // Offsets from x,y: data bounding box
Seq *_nextSeqPtr; // ptr to next record
};
/**
* The following is an array of structures of above sequences
*/
struct SeqList {
uint16 _imageNbr; // Number of images in sequence
Seq *_seqPtr; // Ptr to sequence structure
};
#include "common/pack-start.h" // START STRUCT PACKING
struct SoundHdr { // Sound file lookup entry
uint16 _size; // Size of sound data in bytes
uint32 _offset; // Offset of sound data in file
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
static const int kMaxSeqNumb = 4; // Number of sequences of images in object
/**
* Following is definition of object attributes
*/
struct Object {
uint16 _nounIndex; // String identifying object
uint16 _dataIndex; // String describing the object
uint16 *_stateDataIndex; // Added by Strangerke to handle the LOOK_S state-dependant descriptions
Path _pathType; // Describe path object follows
int _vxPath, _vyPath; // Delta velocities (e.g. for CHASE)
uint16 _actIndex; // Action list to do on collision with hero
byte _seqNumb; // Number of sequences in list
Seq *_currImagePtr; // Sequence image currently in use
SeqList _seqList[kMaxSeqNumb]; // Array of sequence structure ptrs and lengths
Cycle _cycling; // Whether cycling, forward or backward
byte _cycleNumb; // No. of times to cycle
byte _frameInterval; // Interval (in ticks) between frames
byte _frameTimer; // Decrementing timer for above
int8 _radius; // Defines sphere of influence by hero
byte _screenIndex; // Screen in which object resides
int _x, _y; // Current coordinates of object
int _oldx, _oldy; // Previous coordinates of object
int8 _vx, _vy; // Velocity
byte _objValue; // Value of object
int _genericCmd; // Bit mask of 'generic' commands for object
uint16 _cmdIndex; // ptr to list of cmd structures for verbs
bool _carriedFl; // TRUE if object being carried
byte _state; // state referenced in cmd list
bool _verbOnlyFl; // TRUE if verb-only cmds allowed e.g. sit,look
byte _priority; // Whether object fore, background or floating
int16 _viewx, _viewy; // Position to view object from (or 0 or -1)
int16 _direction; // Direction to view object from
byte _curSeqNum; // Save which seq number currently in use
byte _curImageNum; // Save which image of sequence currently in use
int8 _oldvx; // Previous vx (used in wandering)
int8 _oldvy; // Previous vy
};
} // End of namespace Hugo
#endif

840
engines/hugo/hugo.cpp Normal file
View File

@@ -0,0 +1,840 @@
/* 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 "common/system.h"
#include "common/random.h"
#include "common/error.h"
#include "common/events.h"
#include "common/debug-channels.h"
#include "common/config-manager.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "hugo/hugo.h"
#include "hugo/console.h"
#include "hugo/dialogs.h"
#include "hugo/file.h"
#include "hugo/game.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/mouse.h"
#include "hugo/inventory.h"
#include "hugo/parser.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/intro.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "engines/util.h"
namespace Hugo {
HugoEngine *HugoEngine::s_Engine = nullptr;
HugoEngine::HugoEngine(OSystem *syst, const HugoGameDescription *gd) : Engine(syst), _gameDescription(gd),
_hero(nullptr), _heroImage(0), _defltTunes(nullptr), _numScreens(0), _tunesNbr(0), _soundSilence(0), _soundTest(0),
_screenStates(nullptr), _numStates(0), _score(0), _maxscore(0), _lastTime(0), _curTime(0), _episode(nullptr)
{
_system = syst;
setDebugger(new HugoConsole(this));
_rnd = nullptr;
_screen = nullptr;
_mouse = nullptr;
_inventory = nullptr;
_parser = nullptr;
_route = nullptr;
_sound = nullptr;
_intro = nullptr;
_object = nullptr;
_text = nullptr;
_topMenu = nullptr;
_status._storyModeFl = false;
_status._gameOverFl = false;
_status._lookFl = false;
_status._recallFl = false;
_status._newScreenFl = false;
_status._godModeFl = false;
_status._showBoundariesFl = false;
_status._doQuitFl = false;
_status._skipIntroFl = false;
_status._helpFl = false;
_status._tick = 0;
_status._viewState = kViewIntroInit;
_status._song = 0;
_gameType = kGameTypeNone;
_platform = Common::kPlatformUnknown;
_packedFl = false;
_windowsInterfaceFl = (gd->desc.platform == Common::kPlatformWindows) ||
ConfMan.getBool("use_windows_interface");
_numVariant = 0;
_gameVariant = kGameVariantNone;
_normalTPS = 0;
_screenPtr = nullptr;
_config._musicFl = true;
_config._soundFl = true;
_config._turboFl = false;
_look = 0;
_take = 0;
_drop = 0;
_maze._enabledFl = false;
_maze._size = 0;
_maze._x1 = _maze._y1 = _maze._x2 = _maze._y2 = _maze._x3 = _maze._x4 = 0;
_maze._firstScreenIndex = 0;
_boot._checksum = 0;
_boot._registered = kRegShareware;
_boot._exitLen = 0;
_file = nullptr;
_scheduler = nullptr;
#ifdef USE_TTS
_queueAllVoicing = false;
_voiceScoreLine = true;
_voiceSoundSetting = false;
_previousScore = 0;
#endif
}
HugoEngine::~HugoEngine() {
_file->closeDatabaseFiles();
_intro->freeIntroData();
_inventory->freeInvent();
_mouse->freeHotspots();
_object->freeObjects();
_parser->freeParser();
_scheduler->freeScheduler();
_screen->freeScreen();
_text->freeAllTexts();
free(_defltTunes);
free(_screenStates);
delete _topMenu;
delete _object;
delete _sound;
delete _route;
delete _parser;
delete _inventory;
delete _mouse;
delete _screen;
delete _intro;
delete _scheduler;
delete _file;
delete _text;
delete _rnd;
}
Status &HugoEngine::getGameStatus() {
return _status;
}
int HugoEngine::getScore() const {
return _score;
}
void HugoEngine::setScore(const int newScore) {
_score = newScore;
}
void HugoEngine::adjustScore(const int adjustment) {
_score += adjustment;
}
int HugoEngine::getMaxScore() const {
return _maxscore;
}
void HugoEngine::setMaxScore(const int newScore) {
_maxscore = newScore;
}
Common::Error HugoEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
return (_file->saveGame(slot, desc) ? Common::kNoError : Common::kWritingFailed);
}
Common::Error HugoEngine::loadGameState(int slot) {
return (_file->restoreGame(slot) ? Common::kNoError : Common::kReadingFailed);
}
bool HugoEngine::hasFeature(EngineFeature f) const {
return (f == kSupportsReturnToLauncher) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime);
}
const char *HugoEngine::getCopyrightString1() const {
return "Copyright 1989-1997 David P Gray,";
}
const char *HugoEngine::getCopyrightString2() const {
return "All Rights Reserved.";
}
GameType HugoEngine::getGameType() const {
return _gameType;
}
Common::Platform HugoEngine::getPlatform() const {
return _platform;
}
bool HugoEngine::isPacked() const {
return _packedFl;
}
bool HugoEngine::useWindowsInterface() const {
return _windowsInterfaceFl;
}
/**
* Print options for user when dead
*/
void HugoEngine::gameOverMsg() {
notifyBox(_text->getTextUtil(kGameOver));
}
Common::Error HugoEngine::run() {
s_Engine = this;
initGraphics(320, 200);
_mouse = new MouseHandler(this);
_inventory = new InventoryHandler(this);
_route = new Route(this);
_sound = new SoundHandler(this);
// Setup mixer
syncSoundSettings();
_text = new TextHandler(this);
_topMenu = new TopMenu(this);
switch (_gameVariant) {
case kGameVariantH1Win: // H1 Win
_file = new FileManager_v1w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v1w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH2Win:
_file = new FileManager_v2w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v2w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH3Win:
_file = new FileManager_v2w(this);
_scheduler = new Scheduler_v1w(this);
_intro = new intro_v3w(this);
_screen = new Screen_v1w(this);
_parser = new Parser_v1w(this);
_object = new ObjectHandler_v1w(this);
_normalTPS = 9;
break;
case kGameVariantH1Dos: // H1 DOS
_file = new FileManager_v1d(this);
_scheduler = new Scheduler_v1d(this);
_intro = new intro_v1d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v1d(this);
_object = new ObjectHandler_v1d(this);
_normalTPS = 8;
break;
case kGameVariantH2Dos:
_file = new FileManager_v2d(this);
_scheduler = new Scheduler_v2d(this);
_intro = new intro_v2d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v2d(this);
_object = new ObjectHandler_v2d(this);
_normalTPS = 8;
break;
case kGameVariantH3Dos:
_file = new FileManager_v3d(this);
_scheduler = new Scheduler_v3d(this);
_intro = new intro_v3d(this);
_screen = new Screen_v1d(this);
_parser = new Parser_v3d(this);
_object = new ObjectHandler_v3d(this);
_normalTPS = 9;
break;
default:
break;
}
if (!loadHugoDat())
return Common::kUnknownError;
// Use Windows-looking mouse cursor
if (useWindowsInterface()) {
_screen->setCursorPal();
_screen->resetInventoryObjId();
}
_scheduler->initCypher();
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan) {
ttsMan->enable(ConfMan.getBool("tts_enabled"));
ttsMan->setLanguage(ConfMan.get("language"));
}
#endif
initStatus(); // Initialize game status
initConfig(); // Initialize user's config
if (!_status._doQuitFl) {
initialize();
resetConfig(); // Reset user's config
initMachine();
// Start the state machine
_status._viewState = kViewIntroInit;
int16 loadSlot = Common::ConfigManager::instance().getInt("save_slot");
if (loadSlot >= 0) {
_status._skipIntroFl = true;
_file->restoreGame(loadSlot);
} else {
_file->saveGame(99, "New Game [reserved]");
}
}
while (!_status._doQuitFl) {
_screen->drawBoundaries();
g_system->updateScreen();
runMachine();
// Handle input
Common::Event event;
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
_parser->actionHandler(event);
break;
case Common::EVENT_KEYDOWN:
_parser->keyHandler(event);
break;
case Common::EVENT_MOUSEMOVE:
_mouse->setMouseX(event.mouse.x);
_mouse->setMouseY(event.mouse.y);
break;
case Common::EVENT_LBUTTONUP:
_mouse->setLeftButton();
break;
case Common::EVENT_RBUTTONUP:
_mouse->setRightButton();
break;
case Common::EVENT_QUIT:
_status._doQuitFl = true;
break;
default:
break;
}
}
if (_status._helpFl) {
_status._helpFl = false;
_file->instructions();
}
if (useWindowsInterface()) {
_mouse->mouseHandler(); // Mouse activity - adds to display list
}
_screen->displayList(kDisplayDisplay); // Blit the display list to screen
_status._doQuitFl |= shouldQuit(); // update game quit flag
}
return Common::kNoError;
}
void HugoEngine::initMachine() {
if (_gameVariant == kGameVariantH1Dos)
readScreenFiles(0);
else
_file->readBackground(_numScreens - 1); // Splash screen
_object->readObjectImages(); // Read all object images
if (_platform == Common::kPlatformWindows)
_file->readUIFImages(); // Read all uif images (only in Win versions)
_sound->initPcspkrPlayer();
}
/**
* Hugo game state machine - called during onIdle
*/
void HugoEngine::runMachine() {
Status &gameStatus = getGameStatus();
_curTime = g_system->getMillis();
// Process machine once every tick
while (_curTime - _lastTime < (uint32)(1000 / getTPS())) {
g_system->delayMillis(5);
_curTime = g_system->getMillis();
}
_lastTime = _curTime;
switch (gameStatus._viewState) {
case kViewIdle: // Not processing state machine
_screen->hideCursor();
_intro->preNewGame(); // Any processing before New Game selected
break;
case kViewIntroInit: // Initialization before intro begins
_intro->introInit();
gameStatus._viewState = kViewIntro;
break;
case kViewIntro: // Do any game-dependant preamble
if (_intro->introPlay()) { // Process intro screen
_scheduler->newScreen(0); // Initialize first screen
gameStatus._viewState = kViewPlay;
}
break;
case kViewPlay: // Playing game
if (useWindowsInterface()) {
_screen->showCursor();
}
_parser->charHandler(); // Process user cmd input
if (!gameStatus._gameOverFl) {
_object->moveObjects(); // Process object movement
_scheduler->runScheduler(); // Process any actions
_screen->displayList(kDisplayRestore); // Restore previous background
_object->updateImages(); // Draw into _frontBuffer, compile display list
_screen->drawStatusText();
_screen->drawPromptText();
_screen->displayList(kDisplayDisplay); // Blit the display list to screen
} else {
// game over: update status and prompt
_screen->displayStatusText(); // Draw to _frontBuffer, copy to screen
_screen->displayPromptText(); // Draw to _frontBuffer, copy to screen
}
_sound->checkMusic();
break;
case kViewInvent: // Accessing inventory
_inventory->runInventory(); // Process Inventory state machine
break;
case kViewExit: // Game over or user exited
gameStatus._viewState = kViewIdle;
_status._doQuitFl = true;
break;
default:
break;
}
}
/**
* Loads Hugo.dat file, which contains all the hardcoded data in the original executables
*/
bool HugoEngine::loadHugoDat() {
Common::File in;
Common::String filename = "hugo.dat";
in.open(filename.c_str());
if (!in.isOpen()) {
const char *msg = _s("Unable to locate the '%s' engine data file.");
Common::U32String errorMessage = Common::U32String::format(_(msg), filename.c_str());
GUIErrorMessage(errorMessage);
warning(msg, filename.c_str());
return false;
}
// Read header
char buf[4];
in.read(buf, 4);
if (memcmp(buf, "HUGO", 4)) {
Common::U32String errorMessage = Common::U32String::format(_("The '%s' engine data file is corrupt."), filename.c_str());
GUIErrorMessage(errorMessage);
return false;
}
int majVer = in.readByte();
int minVer = in.readByte();
if ((majVer != HUGO_DAT_VER_MAJ) || (minVer != HUGO_DAT_VER_MIN)) {
Common::U32String errorMessage = Common::U32String::format(
_("Incorrect version of the '%s' engine data file found. Expected %d.%d but got %d.%d."),
filename.c_str(), HUGO_DAT_VER_MAJ, HUGO_DAT_VER_MIN, majVer, minVer);
GUIErrorMessage(errorMessage);
return false;
}
_numVariant = in.readUint16BE();
_screen->loadPalette(in);
_screen->loadFontArr(in);
_text->loadAllTexts(in);
_intro->loadIntroData(in);
_parser->loadArrayReqs(in);
_parser->loadCatchallList(in);
_parser->loadBackgroundObjects(in);
_parser->loadCmdList(in);
_mouse->loadHotspots(in);
_inventory->loadInvent(in);
_object->loadObjectUses(in);
_object->loadObjectArr(in);
_object->loadNumObj(in);
_scheduler->loadPoints(in);
_scheduler->loadScreenAct(in);
_scheduler->loadActListArr(in);
_scheduler->loadAlNewscrIndex(in);
_hero = &_object->_objects[kHeroIndex]; // This always points to hero
_screenPtr = &(_object->_objects[kHeroIndex]._screenIndex); // Current screen is hero's
_heroImage = kHeroIndex; // Current in use hero image
for (int varnt = 0; varnt < _numVariant; varnt++) {
if (varnt == _gameVariant) {
_tunesNbr = in.readSByte();
_soundSilence = in.readSByte();
_soundTest = in.readSByte();
} else {
in.readSByte();
in.readSByte();
in.readSByte();
}
}
int numElem;
//Read _defltTunes
for (int varnt = 0; varnt < _numVariant; varnt++) {
numElem = in.readUint16BE();
if (varnt == _gameVariant) {
_defltTunes = (int16 *)malloc(sizeof(int16) * numElem);
for (int i = 0; i < numElem; i++)
_defltTunes[i] = in.readSint16BE();
} else {
for (int i = 0; i < numElem; i++)
in.readSint16BE();
}
}
//Read _screenStates size
for (int varnt = 0; varnt < _numVariant; varnt++) {
numElem = in.readUint16BE();
if (varnt == _gameVariant) {
_numStates = numElem;
_screenStates = (byte *)malloc(sizeof(byte) * numElem);
memset(_screenStates, 0, sizeof(byte) * numElem);
}
}
//Read look, take and drop special verbs indexes
for (int varnt = 0; varnt < _numVariant; varnt++) {
if (varnt == _gameVariant) {
_look = in.readUint16BE();
_take = in.readUint16BE();
_drop = in.readUint16BE();
} else {
in.readUint16BE();
in.readUint16BE();
in.readUint16BE();
}
}
_sound->loadIntroSong(in);
_topMenu->loadBmpArr(in);
return true;
}
uint16 **HugoEngine::loadLongArray(Common::SeekableReadStream &in) {
uint16 **resArray = nullptr;
for (int varnt = 0; varnt < _numVariant; varnt++) {
uint16 numRows = in.readUint16BE();
if (varnt == _gameVariant) {
resArray = (uint16 **)malloc(sizeof(uint16 *) * (numRows + 1));
resArray[numRows] = nullptr;
}
for (int i = 0; i < numRows; i++) {
uint16 numElems = in.readUint16BE();
if (varnt == _gameVariant) {
uint16 *resRow = (uint16 *)malloc(sizeof(uint16) * numElems);
for (int j = 0; j < numElems; j++)
resRow[j] = in.readUint16BE();
resArray[i] = resRow;
} else {
in.skip(numElems * sizeof(uint16));
}
}
}
return resArray;
}
/**
* Sets the playlist to be the default tune selection
*/
void HugoEngine::initPlaylist(bool playlist[kMaxTunes]) {
debugC(1, kDebugEngine, "initPlaylist");
for (int16 i = 0; i < kMaxTunes; i++)
playlist[i] = false;
for (int16 i = 0; _defltTunes[i] != -1; i++)
playlist[_defltTunes[i]] = true;
}
/**
* Initialize the dynamic game status
*/
void HugoEngine::initStatus() {
debugC(1, kDebugEngine, "initStatus");
_status._storyModeFl = false; // Not in story mode
_status._gameOverFl = false; // Hero not knobbled yet
_status._lookFl = false; // Toolbar "look" button
_status._recallFl = false; // Toolbar "recall" button
_status._newScreenFl = false; // Screen not just loaded
_status._godModeFl = false; // No special cheats allowed
_status._showBoundariesFl = false; // Boundaries hidden by default
_status._doQuitFl = false;
_status._skipIntroFl = false;
_status._helpFl = false;
// Initialize every start of new game
_status._tick = 0; // Tick count
_status._viewState = kViewIdle; // View state
}
/**
* Initialize default config values. Must be done before Initialize().
*/
void HugoEngine::initConfig() {
debugC(1, kDebugEngine, "initConfig()");
_config._musicFl = true; // Music state initially on
_config._soundFl = true; // Sound state initially on
_config._turboFl = false; // Turbo state initially off
initPlaylist(_config._playlist); // Initialize default tune playlist
_file->readBootFile(); // Read startup structure
}
/**
* Reset config parts. Currently only reset music played based on playlist
*/
void HugoEngine::resetConfig() {
debugC(1, kDebugEngine, "resetConfig()");
// Find first tune and play it
for (int16 i = 0; i < kMaxTunes; i++) {
if (_config._playlist[i]) {
_sound->playMusic(i);
break;
}
}
}
void HugoEngine::initialize() {
debugC(1, kDebugEngine, "initialize");
_maze._enabledFl = false;
_line[0] = '\0';
_sound->initSound();
_scheduler->initEventQueue(); // Init scheduler stuff
_screen->initDisplay(); // Create Dibs and palette
_file->openDatabaseFiles(); // Open database files
calcMaxScore(); // Initialize maxscore
_rnd = new Common::RandomSource("hugo");
switch (_gameVariant) {
case kGameVariantH1Dos:
_episode = "\"Hugo's House of Horrors\"";
_picDir = "";
break;
case kGameVariantH2Dos:
_episode = "\"Hugo II: Whodunit?\"";
_picDir = "";
break;
case kGameVariantH3Dos:
_episode = "\"Hugo III: Jungle of Doom\"";
_picDir = "pictures/";
break;
case kGameVariantH1Win:
_episode = "\"Hugo's Horrific Adventure\"";
_picDir = "hugo1/";
break;
case kGameVariantH2Win:
_episode = "\"Hugo's Mystery Adventure\"";
_picDir = "hugo2/";
break;
case kGameVariantH3Win:
_episode = "\"Hugo's Amazon Adventure\"";
_picDir = "hugo3/";
break;
default:
error("Unknown game");
}
}
/**
* Read scenery, overlay files for given screen number
*/
void HugoEngine::readScreenFiles(const int screenNum) {
debugC(1, kDebugEngine, "readScreenFiles(%d)", screenNum);
_file->readBackground(screenNum); // Scenery file
memcpy(_screen->getBackBuffer(), _screen->getFrontBuffer(), sizeof(_screen->getFrontBuffer())); // Make a copy
// Workaround for graphic glitches in DOS versions. Cleaning the overlays fix the problem
memset(_object->_objBound, '\0', sizeof(Overlay));
memset(_object->_boundary, '\0', sizeof(Overlay));
memset(_object->_overlay, '\0', sizeof(Overlay));
memset(_object->_ovlBase, '\0', sizeof(Overlay));
_file->readOverlay(screenNum, _object->_boundary, kOvlBoundary); // Boundary file
_file->readOverlay(screenNum, _object->_overlay, kOvlOverlay); // Overlay file
_file->readOverlay(screenNum, _object->_ovlBase, kOvlBase); // Overlay base file
// Suppress a boundary used in H3 DOS in 'Crash' screen, which blocks
// pathfinding and is useless.
if ((screenNum == 0) && (_gameVariant == kGameVariantH3Dos))
_object->clearScreenBoundary(50, 311, 152);
}
/**
* Set the new screen number into the hero object and any carried objects
*/
void HugoEngine::setNewScreen(const int screenNum) {
debugC(1, kDebugEngine, "setNewScreen(%d)", screenNum);
*_screenPtr = screenNum; // HERO object
_object->setCarriedScreen(screenNum); // Carried objects
}
/**
* Add up all the object values and all the bonus points
*/
void HugoEngine::calcMaxScore() {
debugC(1, kDebugEngine, "calcMaxScore");
_maxscore = _object->calcMaxScore() + _scheduler->calcMaxPoints();
}
/**
* Exit game, advertise trilogy, show copyright
*/
void HugoEngine::endGame() {
debugC(1, kDebugEngine, "endGame");
if (_boot._registered != kRegRegistered)
notifyBox(_text->getTextEngine(kEsAdvertise));
Common::String text = Common::String::format("%s\n\n%s\n%s",
_episode, getCopyrightString1(), getCopyrightString2());
notifyBox(text, false, kTtsSpeech);
_status._viewState = kViewExit;
}
bool HugoEngine::canLoadGameStateCurrently(Common::U32String *msg) {
return true;
}
bool HugoEngine::canSaveGameStateCurrently(Common::U32String *msg) {
return (_status._viewState == kViewPlay);
}
int8 HugoEngine::getTPS() const {
return ((_config._turboFl) ? kTurboTps : _normalTPS);
}
void HugoEngine::syncSoundSettings() {
Engine::syncSoundSettings();
_sound->syncVolume();
}
Common::String HugoEngine::getSaveStateName(int slot) const {
return _targetName + Common::String::format("-%02d.SAV", slot);
}
Common::KeyCode HugoEngine::notifyBox(const Common::String &text, bool protect, TtsOptions ttsOptions) {
if (useWindowsInterface()) {
Utils::notifyBox(text, ttsOptions);
return Common::KEYCODE_INVALID;
} else {
Common::KeyState keyState = _screen->dosMessageBox(text, protect, ttsOptions);
return keyState.keycode;
}
}
Common::String HugoEngine::promptBox(const Common::String &text) {
if (useWindowsInterface()) {
return Utils::promptBox(text);
} else {
return _screen->dosPromptBox(text);
}
}
bool HugoEngine::yesNoBox(const Common::String &text, bool useFirstKey) {
if (useWindowsInterface()) {
return Utils::yesNoBox(text);
} else {
if (useFirstKey) {
Common::KeyState keyState = _screen->dosMessageBox(text);
return (keyState.keycode == Common::KEYCODE_y);
} else {
Common::String response = _screen->dosPromptBox(text);
response.trim();
return response.equalsIgnoreCase("yes");
}
}
}
void HugoEngine::takeObjectBox(const char *name) {
if (useWindowsInterface()) {
notifyBox(Common::String::format(TAKE_TEXT_WINDOWS, name));
} else {
notifyBox(TAKE_TEXT_DOS);
}
}
#ifdef USE_TTS
void HugoEngine::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) {
// _previousSaid is used to prevent the TTS from looping when sayText is called inside a loop,
// for example when the user hovers over an object. Without it when the text ends it would speak
// the same text again.
// _previousSaid is cleared when appropriate to allow for repeat requests
if (_previousSaid != text) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ttsMan->isSpeaking() && _queueAllVoicing) {
Utils::sayText(text, Common::TextToSpeechManager::QUEUE, false);
} else {
Utils::sayText(text, action, false);
_queueAllVoicing = false;
}
_previousSaid = text;
}
}
#endif
} // End of namespace Hugo

368
engines/hugo/hugo.h Normal file
View File

@@ -0,0 +1,368 @@
/* 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/>.
*
*/
#ifndef HUGO_HUGO_H
#define HUGO_HUGO_H
#include "common/text-to-speech.h"
#include "engines/engine.h"
// This include is here temporarily while the engine is being refactored.
#include "hugo/game.h"
#include "hugo/detection.h"
#define HUGO_DAT_VER_MAJ 0 // 1 byte
#define HUGO_DAT_VER_MIN 42 // 1 byte
#define DATAALIGNMENT 4
namespace Common {
class SeekableReadStream;
class RandomSource;
}
/**
* This is the namespace of the Hugo engine.
*
* Status of this engine: ???
*
* Games using this engine:
* - Hugo's House of Horror
* - Whodunit?
* - Jungle of Doom
* - Hugo's Horrific Adventure
* - Hugo's Mystery Adventure
* - Hugo's Amazon Adventure
*/
namespace Hugo {
static const int kSavegameVersion = 6;
static const int kInvDx = 32; // Width of an inventory icon
static const int kInvDy = 32; // Height of inventory icon
static const int kMaxTunes = 16; // Max number of tunes
static const int kStepDx = 5; // Num pixels moved in x by HERO per step
static const int kStepDy = 4; // Num pixels moved in y by HERO per step
static const int kXPix = 320; // Width of pcx background file
static const int kYPix = 200; // Height of pcx background file
static const int kViewSizeX = kXPix; // Width of window view
static const int kViewSizeY = 192; // Height of window view. In original game: 184
static const int kDibOffY = 0; // Offset into dib SrcY (old status line area). In original game: 8
static const int kCompLineSize = 40; // number of bytes in a compressed line
static const int kMaxTextCols = 40; // Number of text lines in display
static const int kMaxTextRows = 25; // Number of text lines in display
static const int kMaxLineSize = kMaxTextCols - 2; // Max length of user input line
static const int kMaxBoxChar = kMaxTextCols * kMaxTextRows; // Max chars on screen
static const int kOvlSize = kCompLineSize * kYPix; // Size of an overlay file
static const int kStateDontCare = 0xFF; // Any state allowed in command verb
static const int kHeroIndex = 0; // In all enums, HERO is the first element
static const int kArrowNumb = 2; // Number of arrows (left/right)
static const int kLeftArrow = -2; // Cursor over Left arrow in inventory icon bar
static const int kRightArrow = -3; // Cursor over Right arrow in inventory icon bar
static const int kMaxPath = 256; // Max length of a full path name
static const int kHeroMaxWidth = 24; // Maximum width of hero
static const int kHeroMinWidth = 16; // Minimum width of hero
typedef char Command[kMaxLineSize + 8]; // Command line (+spare for prompt,cursor)
struct Config { // User's config (saved)
bool _musicFl; // State of Music button/menu item
bool _soundFl; // State of Sound button/menu item
bool _turboFl; // State of Turbo button/menu item
bool _playlist[kMaxTunes]; // Tune playlist
};
typedef byte Icondib[kXPix * kInvDy]; // Icon bar dib
typedef byte Viewdib[(long)kXPix * kYPix]; // Viewport dib
typedef byte Overlay[kOvlSize]; // Overlay file
enum HUGOAction {
kActionNone,
kActionEscape,
kActionMoveTop,
kActionMoveBottom,
kActionMoveLeft,
kActionMoveRight,
kActionMoveTopRight,
kActionMoveTopLeft,
kActionMoveBottomRight,
kActionMoveBottomLeft,
kActionUserHelp,
kActionToggleSound,
kActionRepeatLine,
kActionSaveGame,
kActionRestoreGame,
kActionNewGame,
kActionInventory,
kActionToggleTurbo
};
enum HugoDebugChannels {
kDebugSchedule = 1,
kDebugEngine,
kDebugDisplay,
kDebugMouse,
kDebugParser,
kDebugFile,
kDebugRoute,
kDebugInventory,
kDebugObject,
kDebugMusic,
};
enum HugoRegistered {
kRegShareware = 0,
kRegRegistered,
kRegFreeware
};
/**
* Inventory icon bar states
*/
enum Istate {kInventoryOff, kInventoryUp, kInventoryDown, kInventoryActive};
/**
* Game view state machine
*/
enum Vstate {kViewIdle, kViewIntroInit, kViewIntro, kViewPlay, kViewInvent, kViewExit};
/**
* Enumerate whether object is foreground, background or 'floating'
* If floating, HERO can collide with it and fore/back ground is determined
* by relative y-coord of object base. This is the general case.
* If fore or background, no collisions can take place and object is either
* behind or in front of all others, although can still be hidden by the
* the overlay plane. OVEROVL means the object is FLOATING (to other
* objects) but is never hidden by the overlay plane
*/
enum {kPriorityForeground, kPriorityBackground, kPriorityFloating, kPriorityOverOverlay};
/**
* Display list functions
*/
enum Dupdate {kDisplayInit, kDisplayAdd, kDisplayDisplay, kDisplayRestore};
/**
* Priority for sound effect
*/
enum Priority {kSoundPriorityLow, kSoundPriorityMedium, kSoundPriorityHigh};
// Strings used by the engine
enum seqTextEngine {
kEsAdvertise = 0
};
struct Status { // Game status (not saved)
bool _storyModeFl; // Game is telling story - no commands
bool _gameOverFl; // Game is over - hero knobbled
bool _lookFl; // Toolbar "look" button pressed
bool _recallFl; // Toolbar "recall" button pressed
bool _newScreenFl; // New screen just loaded in dib_a
bool _godModeFl; // Allow DEBUG features in live version
bool _showBoundariesFl; // Flag used to show and hide boundaries,
// used by the console
bool _doQuitFl;
bool _skipIntroFl;
bool _helpFl;
uint32 _tick; // Current time in ticks
Vstate _viewState; // View state machine
int16 _song; // Current song
};
/**
* Structure to define an EXIT or other collision-activated hotspot
*/
struct Hotspot {
int _screenIndex; // Screen in which hotspot appears
int _x1, _y1, _x2, _y2; // Bounding box of hotspot
uint16 _actIndex; // Actions to carry out if a 'hit'
int16 _viewx, _viewy, _direction; // Used in auto-route mode
};
enum TtsOptions {
kTtsNoSpeech = 0,
kTtsSpeech = (1 << 0),
kTtsReplaceNewlines = ((1 << 1) | kTtsSpeech)
};
class FileManager;
class Scheduler;
class Screen;
class MouseHandler;
class InventoryHandler;
class Parser;
class Route;
class SoundHandler;
class IntroHandler;
class ObjectHandler;
class TextHandler;
class TopMenu;
class HugoConsole;
class HugoEngine : public Engine {
public:
HugoEngine(OSystem *syst, const HugoGameDescription *gd);
~HugoEngine() override;
OSystem *_system;
byte _numVariant;
byte _gameVariant;
int8 _soundSilence;
int8 _soundTest;
int8 _tunesNbr;
uint16 _numScreens;
uint16 _numStates;
int8 _normalTPS; // Number of ticks (frames) per second.
// 8 for Win versions, 9 for DOS versions
Object *_hero;
byte *_screenPtr;
byte _heroImage;
byte *_screenStates;
Command _line; // Line of user text input
Config _config; // User's config
int16 *_defltTunes;
uint16 _look;
uint16 _take;
uint16 _drop;
Maze _maze; // Maze control structure
hugoBoot _boot; // Boot info structure
Common::RandomSource *_rnd;
const char *_episode;
Common::Path _picDir;
Command _statusLine; // text at top row of screen
Command _promptLine; // text at bottom row of screen
#ifdef USE_TTS
bool _voiceScoreLine;
bool _voiceSoundSetting;
bool _queueAllVoicing;
int _previousScore;
Common::String _previousSaid;
#endif
const HugoGameDescription *_gameDescription;
uint32 getFeatures() const;
const char *getGameId() const;
GameType getGameType() const;
Common::Platform getPlatform() const;
bool isPacked() const;
bool useWindowsInterface() const;
// Used by the qsort function
static HugoEngine &get() {
assert(s_Engine != nullptr);
return *s_Engine;
}
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
bool loadHugoDat();
int8 getTPS() const;
void initGame(const HugoGameDescription *gd);
void initGamePart(const HugoGameDescription *gd);
void endGame();
void gameOverMsg();
void initStatus();
void readScreenFiles(const int screen);
void setNewScreen(const int screen);
void shutdown();
void syncSoundSettings() override;
Status &getGameStatus();
int getScore() const;
void setScore(const int newScore);
void adjustScore(const int adjustment);
int getMaxScore() const;
void setMaxScore(const int newScore);
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
Common::Error loadGameState(int slot) override;
bool hasFeature(EngineFeature f) const override;
const char *getCopyrightString1() const;
const char *getCopyrightString2() const;
Common::String getSaveStateName(int slot) const override;
uint16 **loadLongArray(Common::SeekableReadStream &in);
Common::KeyCode notifyBox(const Common::String &text, bool protect = false, TtsOptions ttsOptions = kTtsReplaceNewlines);
Common::String promptBox(const Common::String &text);
bool yesNoBox(const Common::String &text, bool useFirstKey);
void takeObjectBox(const char *name);
#ifdef USE_TTS
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT);
#endif
FileManager *_file;
Scheduler *_scheduler;
Screen *_screen;
MouseHandler *_mouse;
InventoryHandler *_inventory;
Parser *_parser;
Route *_route;
SoundHandler *_sound;
IntroHandler *_intro;
ObjectHandler *_object;
TextHandler *_text;
TopMenu *_topMenu;
protected:
// Engine APIs
Common::Error run() override;
private:
static const int kTurboTps = 16; // This many in turbo mode
Status _status; // Game status structure
uint32 _lastTime;
uint32 _curTime;
static HugoEngine *s_Engine;
GameType _gameType;
Common::Platform _platform;
bool _packedFl;
bool _windowsInterfaceFl;
int _score; // Holds current score
int _maxscore; // Holds maximum score
void initPlaylist(bool playlist[kMaxTunes]);
void initConfig();
void initialize();
void initMachine();
void calcMaxScore();
void resetConfig();
void runMachine();
};
} // End of namespace Hugo
#endif // HUGO_HUGO_H

577
engines/hugo/intro.cpp Normal file
View File

@@ -0,0 +1,577 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/system.h"
#include "common/events.h"
#include "common/textconsole.h"
#include "graphics/font.h"
#include "graphics/pixelformat.h"
#include "hugo/hugo.h"
#include "hugo/intro.h"
#include "hugo/file.h"
#include "hugo/game.h"
#include "hugo/util.h"
#include "hugo/display.h"
#include "hugo/sound.h"
#include "hugo/text.h"
namespace Hugo {
IntroHandler::IntroHandler(HugoEngine *vm) : _vm(vm) {
_introXSize = 0;
_introTicks = 0;
_introX = _introY = nullptr;
}
IntroHandler::~IntroHandler() {
}
byte IntroHandler::getIntroSize() const {
return _introXSize;
}
/**
* Read _introX and _introY from hugo.dat
*/
void IntroHandler::loadIntroData(Common::SeekableReadStream &in) {
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
int numRows = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_introXSize = numRows;
_introX = (byte *)malloc(sizeof(byte) * _introXSize);
_introY = (byte *)malloc(sizeof(byte) * _introXSize);
for (int i = 0; i < _introXSize; i++) {
_introX[i] = in.readByte();
_introY[i] = in.readByte();
}
} else {
in.skip(numRows * 2);
}
}
}
void IntroHandler::freeIntroData() {
free(_introX);
free(_introY);
_introX = _introY = nullptr;
}
/**
* Wait for a delay in milliseconds while processing events.
* This keeps the program window responsive during long delays.
* Returns false if interrupted by a quit event or the Escape key.
*/
bool IntroHandler::wait(uint32 delay) {
const uint32 startTime = _vm->_system->getMillis();
while (!_vm->shouldQuit()) {
Common::Event event;
while (_vm->getEventManager()->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
return false; // interrupted by quit
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if (event.customType == kActionEscape) {
return false; // interrupted by Escape
}
break;
default:
break;
}
}
if (_vm->_system->getMillis() - startTime >= delay) {
return true; // delay completed
}
_vm->_system->updateScreen();
_vm->_system->delayMillis(10);
}
return false; // interrupted by quit
}
intro_v1d::intro_v1d(HugoEngine *vm) : IntroHandler(vm) {
_introState = 0;
}
intro_v1d::~intro_v1d() {
}
void intro_v1d::preNewGame() {
}
void intro_v1d::introInit() {
_introState = 0;
_introTicks = 0;
_surf.init(320, 200, 320, _vm->_screen->getFrontBuffer(), Graphics::PixelFormat::createFormatCLUT8());
_vm->_screen->displayList(kDisplayInit);
}
bool intro_v1d::introPlay() {
byte introSize = getIntroSize();
if (_vm->getGameStatus()._skipIntroFl)
return true;
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ttsMan->isSpeaking()) {
return false;
}
Common::String ttsMessage;
#endif
Common::String copyright;
if (_introTicks < introSize) {
switch (_introState++) {
case 0:
_vm->_screen->drawRectangle(true, 0, 0, 319, 199, _TMAGENTA);
_vm->_screen->drawRectangle(true, 10, 10, 309, 189, _TBLACK);
break;
case 1:
_vm->_screen->drawShape(20, 92,_TLIGHTMAGENTA,_TMAGENTA);
_vm->_screen->drawShape(250,92,_TLIGHTMAGENTA,_TMAGENTA);
// TROMAN, size 10-5
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 8)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 8");
char buffer[80];
if (_vm->_boot._registered == kRegRegistered)
Common::strcpy_s(buffer, "Registered Version");
else if (_vm->_boot._registered == kRegShareware)
Common::strcpy_s(buffer, "Shareware Version");
else if (_vm->_boot._registered == kRegFreeware)
Common::strcpy_s(buffer, "Freeware Version");
else
error("Unknown registration flag in hugo.bsf: %d", _vm->_boot._registered);
_font.drawString(&_surf, buffer, 0, 163, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
copyright = Common::String::format("%s %s", _vm->getCopyrightString1(), _vm->getCopyrightString2());
_font.drawString(&_surf, copyright, 0, 176, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Hugo's House of Horrors\n\n";
ttsMessage += buffer;
ttsMessage += '\n';
ttsMessage += _vm->getCopyrightString1();
ttsMessage += ' ';
ttsMessage += _vm->getCopyrightString2();
#endif
if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
_font.drawString(&_surf, buffer, 0, 75, 320, _TMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
// Insert at second newline after "Hugo's House of Horrors" so that a newline remains between that line
// and this one
ttsMessage.insertString(buffer, ttsMessage.find('\n') + 1);
#endif
}
// SCRIPT, size 24-16
Common::strcpy_s(buffer, "Hugo's");
if (_font.loadFromFON("SCRIPT.FON")) {
_font.drawString(&_surf, buffer, 0, 20, 320, _TMAGENTA, Graphics::kTextAlignCenter);
} else {
// Workaround: SCRIPT.FON doesn't load properly at the moment
_vm->_screen->loadFont(2);
_vm->_screen->writeStr(kCenter, 20, buffer, _TMAGENTA);
}
// TROMAN, size 30-24
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 24)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 24");
Common::strcpy_s(buffer, "House of Horrors !");
_font.drawString(&_surf, buffer, 0, 50, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
break;
case 2:
_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
// TROMAN, size 16-9
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 14)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 14");
Common::strcpy_s(buffer, "S t a r r i n g :");
_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Starring:";
#endif
break;
case 3:
// TROMAN, size 20-9
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 18)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 18");
Common::strcpy_s(buffer, "Hugo !");
_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer;
#endif
break;
case 4:
_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
// TROMAN, size 16-9
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 14)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 14");
Common::strcpy_s(buffer, "P r o d u c e d b y :");
_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Produced by:";
#endif
break;
case 5:
// TROMAN size 16-9
Common::strcpy_s(buffer, "David P Gray !");
_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer;
#endif
break;
case 6:
_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
// TROMAN, size 16-9
Common::strcpy_s(buffer, "D i r e c t e d b y :");
_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Directed by:";
#endif
break;
case 7:
// TROMAN, size 16-9
Common::strcpy_s(buffer, "David P Gray !");
_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer;
#endif
break;
case 8:
_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
// TROMAN, size 16-9
Common::strcpy_s(buffer, "M u s i c b y :");
_font.drawString(&_surf, buffer, 0, 95, 320, _TMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Music by:";
#endif
break;
case 9:
// TROMAN, size 16-9
Common::strcpy_s(buffer, "David P Gray !");
_font.drawString(&_surf, buffer, 0, 115, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer;
#endif
break;
case 10:
_vm->_screen->drawRectangle(true, 82, 92, 237, 138, _TBLACK);
// TROMAN, size 20-14
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 18)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 18");
Common::strcpy_s(buffer, "E n j o y !");
_font.drawString(&_surf, buffer, 0, 100, 320, _TLIGHTMAGENTA, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = "Enjoy!";
#endif
break;
default:
break;
}
#ifdef USE_TTS
_vm->sayText(ttsMessage);
#endif
_vm->_screen->displayBackground();
if (!wait(1000)) {
// Wait was interrupted by quit event or Escape.
// Skip the rest of the introduction.
return true;
}
}
return (++_introTicks >= introSize);
}
intro_v2d::intro_v2d(HugoEngine *vm) : IntroHandler(vm) {
}
intro_v2d::~intro_v2d() {
}
void intro_v2d::preNewGame() {
}
void intro_v2d::introInit() {
_vm->_screen->displayList(kDisplayInit);
_vm->_file->readBackground(_vm->_numScreens - 1); // display splash screen
_surf.init(320, 200, 320, _vm->_screen->getFrontBuffer(), Graphics::PixelFormat::createFormatCLUT8());
char buffer[128];
// TROMAN, size 10-5
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 8)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 8");
if (_vm->_boot._registered)
Common::sprintf_s(buffer, "%s %s Registered Version", _vm->getCopyrightString1(), _vm->getCopyrightString2());
else
Common::sprintf_s(buffer, "%s %s Shareware Version", _vm->getCopyrightString1(), _vm->getCopyrightString2());
_font.drawString(&_surf, buffer, 0, 186, 320, _TLIGHTRED, Graphics::kTextAlignCenter);
#ifdef USE_TTS
Common::String ttsMessage = buffer;
#endif
if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
// TROMAN, size 10-5
Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
_font.drawString(&_surf, buffer, 0, 1, 320, _TLIGHTRED, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer + ttsMessage;
#endif
}
#ifdef USE_TTS
_vm->sayText(ttsMessage);
#endif
_vm->_screen->displayBackground();
wait(5000);
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
while (ttsMan && ttsMan->isSpeaking()) {
g_system->delayMillis(10);
}
#endif
}
bool intro_v2d::introPlay() {
return true;
}
intro_v3d::intro_v3d(HugoEngine *vm) : IntroHandler(vm) {
}
intro_v3d::~intro_v3d() {
}
void intro_v3d::preNewGame() {
}
void intro_v3d::introInit() {
_vm->_screen->displayList(kDisplayInit);
_vm->_file->readBackground(_vm->_numScreens - 1); // display splash screen
_surf.init(320, 200, 320, _vm->_screen->getFrontBuffer(), Graphics::PixelFormat::createFormatCLUT8());
char buffer[128];
if (_vm->_boot._registered)
Common::sprintf_s(buffer, "%s %s Registered Version", _vm->getCopyrightString1(), _vm->getCopyrightString2());
else
Common::sprintf_s(buffer,"%s %s Shareware Version", _vm->getCopyrightString1(), _vm->getCopyrightString2());
// TROMAN, size 10-5
if (!_font.loadFromFON("TMSRB.FON", Graphics::WinFontDirEntry("Tms Rmn", 8)))
error("Unable to load font TMSRB.FON, face 'Tms Rmn', size 8");
_font.drawString(&_surf, buffer, 0, 190, 320, _TBROWN, Graphics::kTextAlignCenter);
#ifdef USE_TTS
Common::String ttsMessage = buffer;
#endif
if ((*_vm->_boot._distrib != '\0') && (scumm_stricmp(_vm->_boot._distrib, "David P. Gray"))) {
Common::sprintf_s(buffer, "Distributed by %s.", _vm->_boot._distrib);
_font.drawString(&_surf, buffer, 0, 0, 320, _TBROWN, Graphics::kTextAlignCenter);
#ifdef USE_TTS
ttsMessage = buffer + ttsMessage;
#endif
}
#ifdef USE_TTS
_vm->sayText(ttsMessage);
#endif
_vm->_screen->displayBackground();
wait(5000);
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
while (ttsMan && ttsMan->isSpeaking()) {
g_system->delayMillis(10);
}
#endif
_vm->_file->readBackground(22); // display screen MAP_3d
_vm->_screen->displayBackground();
_introTicks = 0;
_vm->_sound->_DOSSongPtr = _vm->_sound->_DOSIntroSong;
}
/**
* Hugo 3 - Preamble screen before going into game. Draws path of Hugo's plane.
* Called every tick. Returns TRUE when complete
*/
bool intro_v3d::introPlay() {
if (_vm->getGameStatus()._skipIntroFl)
return true;
if (_introTicks < getIntroSize()) {
_surf.setPixel(_introX[_introTicks], _introY[_introTicks] - kDibOffY, _TBRIGHTWHITE);
_vm->_screen->displayBackground();
// Text boxes at various times
switch (_introTicks) {
case 4:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro1), true);
break;
case 9:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro2), true);
break;
case 35:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro3), true);
break;
default:
break;
}
}
return (++_introTicks >= getIntroSize());
}
intro_v1w::intro_v1w(HugoEngine *vm) : IntroHandler(vm) {
}
intro_v1w::~intro_v1w() {
}
void intro_v1w::preNewGame() {
_vm->getGameStatus()._viewState = kViewIntroInit;
}
void intro_v1w::introInit() {
}
bool intro_v1w::introPlay() {
return true;
}
intro_v2w::intro_v2w(HugoEngine *vm) : IntroHandler(vm) {
}
intro_v2w::~intro_v2w() {
}
void intro_v2w::preNewGame() {
}
void intro_v2w::introInit() {
_vm->_file->readBackground(_vm->_numScreens - 1); // display splash screen
_vm->_screen->displayBackground();
wait(3000);
}
bool intro_v2w::introPlay() {
return true;
}
intro_v3w::intro_v3w(HugoEngine *vm) : IntroHandler(vm) {
}
intro_v3w::~intro_v3w() {
}
void intro_v3w::preNewGame() {
}
/**
* Hugo 3 - show map and set up for introPlay()
*/
void intro_v3w::introInit() {
_vm->_screen->displayList(kDisplayInit);
_vm->_file->readBackground(_vm->_numScreens - 1); // display splash screen
_vm->_screen->displayBackground();
wait(3000);
_vm->_file->readBackground(22); // display screen MAP_3w
_vm->_screen->displayBackground();
_introTicks = 0;
_vm->_screen->loadFont(0);
}
/**
* Hugo 3 - Preamble screen before going into game. Draws path of Hugo's plane.
* Called every tick. Returns TRUE when complete
*/
bool intro_v3w::introPlay() {
if (_vm->getGameStatus()._skipIntroFl)
return true;
if (_introTicks < getIntroSize()) {
// Scale viewport x_intro,y_intro to screen (offsetting y)
_vm->_screen->writeStr(_introX[_introTicks], _introY[_introTicks] - kDibOffY, "x", _TBRIGHTWHITE);
_vm->_screen->displayBackground();
// Text boxes at various times
switch (_introTicks) {
case 4:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro1), true);
break;
case 9:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro2), true);
break;
case 35:
_vm->notifyBox(_vm->_text->getTextIntro(kIntro3), true);
break;
default:
break;
}
}
return (++_introTicks >= getIntroSize());
}
} // End of namespace Hugo

134
engines/hugo/intro.h Normal file
View File

@@ -0,0 +1,134 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef INTRO_H
#define INTRO_H
#include "graphics/surface.h"
#include "graphics/fonts/winfont.h"
namespace Hugo {
enum seqTextIntro {
kIntro1 = 0,
kIntro2 = 1,
kIntro3 = 2
};
class IntroHandler {
public:
IntroHandler(HugoEngine *vm);
Graphics::Surface _surf;
Graphics::WinFont _font;
virtual ~IntroHandler();
virtual void preNewGame() = 0;
virtual void introInit() = 0;
virtual bool introPlay() = 0;
void freeIntroData();
void loadIntroData(Common::SeekableReadStream &in);
byte getIntroSize() const;
protected:
HugoEngine *_vm;
byte *_introX;
byte *_introY;
byte _introXSize;
int16 _introTicks; // Count calls to introPlay()
bool wait(uint32 delay);
};
class intro_v1w : public IntroHandler {
public:
intro_v1w(HugoEngine *vm);
~intro_v1w() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
};
class intro_v1d : public IntroHandler {
public:
intro_v1d(HugoEngine *vm);
~intro_v1d() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
private:
int _introState;
};
class intro_v2w : public IntroHandler {
public:
intro_v2w(HugoEngine *vm);
~intro_v2w() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
};
class intro_v2d : public IntroHandler {
public:
intro_v2d(HugoEngine *vm);
~intro_v2d() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
};
class intro_v3w : public IntroHandler {
public:
intro_v3w(HugoEngine *vm);
~intro_v3w() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
};
class intro_v3d : public IntroHandler {
public:
intro_v3d(HugoEngine *vm);
~intro_v3d() override;
void preNewGame() override;
void introInit() override;
bool introPlay() override;
};
} // End of namespace Hugo
#endif

293
engines/hugo/inventory.cpp Normal file
View File

@@ -0,0 +1,293 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/mouse.h"
#include "hugo/inventory.h"
#include "hugo/parser.h"
#include "hugo/object.h"
namespace Hugo {
static const int kMaxDisp = (kXPix / kInvDx); // Max icons displayable
InventoryHandler::InventoryHandler(HugoEngine *vm) : _vm(vm) {
_invent = nullptr;
_firstIconId = 0;
_inventoryState = kInventoryOff; // Inventory icon bar state
_inventoryHeight = 0; // Inventory icon bar pos
_inventoryObjId = -1; // Inventory object selected (none)
_maxInvent = 0;
}
void InventoryHandler::setInventoryObjId(int16 objId) {
_inventoryObjId = objId;
}
void InventoryHandler::setInventoryState(Istate state) {
_inventoryState = state;
}
void InventoryHandler::freeInvent() {
free(_invent);
_invent = nullptr;
}
int16 InventoryHandler::getInventoryObjId() const {
return _inventoryObjId;
}
Istate InventoryHandler::getInventoryState() const {
return _inventoryState;
}
/**
* Read _invent from Hugo.dat
*/
void InventoryHandler::loadInvent(Common::SeekableReadStream &in) {
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
int16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_maxInvent = numElem;
_invent = (int16 *)malloc(sizeof(int16) * numElem);
for (int i = 0; i < numElem; i++)
_invent[i] = in.readSint16BE();
} else {
in.skip(numElem * sizeof(int16));
}
}
}
/**
* Construct the inventory scrollbar in dib_i
* imageTotNumb is total number of inventory icons
* displayNumb is number requested for display
* scrollFl is TRUE if scroll arrows required
* firstObjId is index of first (scrolled) inventory object to display
*/
void InventoryHandler::constructInventory(const int16 imageTotNumb, int displayNumb, const bool scrollFl, int16 firstObjId) {
debugC(1, kDebugInventory, "constructInventory(%d, %d, %d, %d)", imageTotNumb, displayNumb, (scrollFl) ? 0 : 1, firstObjId);
// Clear out icon buffer
memset(_vm->_screen->getIconBuffer(), 0, sizeof(_vm->_screen->getIconBuffer()));
// If needed, copy arrows - reduce number of icons displayable
if (scrollFl) { // Display at first and last icon positions
_vm->_screen->moveImage(_vm->_screen->getGUIBuffer(), 0, 0, kInvDx, kInvDy, kXPix, _vm->_screen->getIconBuffer(), 0, 0, kXPix);
_vm->_screen->moveImage(_vm->_screen->getGUIBuffer(), kInvDx, 0, kInvDx, kInvDy, kXPix, _vm->_screen->getIconBuffer(), kInvDx *(kMaxDisp - 1), 0, kXPix);
displayNumb = MIN(displayNumb, kMaxDisp - kArrowNumb);
} else // No, override first index - we can show 'em all!
firstObjId = 0;
// Copy inventory icons to remaining positions
int16 displayed = 0;
int16 carried = 0;
for (int16 i = 0; (i < imageTotNumb) && (displayed < displayNumb); i++) {
if (_vm->_object->isCarried(_invent[i])) {
// Check still room to display and past first scroll index
if (displayed < displayNumb && carried >= firstObjId) {
// Compute source coordinates in dib_u
int16 ux = (i + kArrowNumb) * kInvDx % kXPix;
int16 uy = (i + kArrowNumb) * kInvDx / kXPix * kInvDy;
// Compute dest coordinates in dib_i
int16 ix = ((scrollFl) ? displayed + 1 : displayed) * kInvDx;
displayed++; // Count number displayed
// Copy the icon
_vm->_screen->moveImage(_vm->_screen->getGUIBuffer(), ux, uy, kInvDx, kInvDy, kXPix, _vm->_screen->getIconBuffer(), ix, 0, kXPix);
}
carried++; // Count number carried
}
}
}
/**
* Process required action for inventory
* Returns objId under cursor (or -1) for INV_GET
*/
int16 InventoryHandler::processInventory(const int action, ...) {
debugC(1, kDebugInventory, "processInventory(int action, ...)");
int16 imageNumb; // Total number of inventory items
int displayNumb; // Total number displayed/carried
// Compute total number and number displayed, i.e. number carried
for (imageNumb = 0, displayNumb = 0; imageNumb < _maxInvent && _invent[imageNumb] != -1; imageNumb++) {
if (_vm->_object->isCarried(_invent[imageNumb]))
displayNumb++;
}
// Will we need the scroll arrows?
bool scrollFl = displayNumb > kMaxDisp;
va_list marker; // Args used for D_ADD operation
int16 cursorx, cursory; // Current cursor position
int16 objId = -1; // Return objid under cursor
switch (action) {
case kInventoryActionInit: // Initialize inventory display
constructInventory(imageNumb, displayNumb, scrollFl, _firstIconId);
break;
case kInventoryActionLeft: // Scroll left by one icon
_firstIconId = MAX(0, _firstIconId - 1);
constructInventory(imageNumb, displayNumb, scrollFl, _firstIconId);
break;
case kInventoryActionRight: // Scroll right by one icon
_firstIconId = MIN(displayNumb, _firstIconId + 1);
constructInventory(imageNumb, displayNumb, scrollFl, _firstIconId);
break;
case kInventoryActionGet: // Return object id under cursor
// Get cursor position from variable argument list
va_start(marker, action); // Initialize variable arguments
cursorx = va_arg(marker, int); // Cursor x
cursory = va_arg(marker, int); // Cursor y
va_end(marker); // Reset variable arguments
cursory -= kDibOffY; // Icon bar is at true zero
if (cursory > 0 && cursory < kInvDy) { // Within icon bar?
int16 i = cursorx / kInvDx; // Compute icon index
if (scrollFl) { // Scroll buttons displayed
if (i == 0) { // Left scroll button
objId = kLeftArrow;
} else {
if (i == kMaxDisp - 1) // Right scroll button
objId = kRightArrow;
else // Adjust for scroll
i += _firstIconId - 1; // i is icon index
}
}
// If not an arrow, find object id - limit to valid range
if (objId == -1 && i < displayNumb) {
// Find objid by counting # carried objects == i+1
int16 j;
for (j = 0, i++; i > 0 && j < _vm->_object->_numObj; j++) {
if (_vm->_object->isCarried(j)) {
if (--i == 0)
objId = j;
}
}
}
}
break;
default:
break;
}
return objId; // For the INV_GET action
}
/**
* Process inventory state machine
*/
void InventoryHandler::runInventory() {
Status &gameStatus = _vm->getGameStatus();
debugC(1, kDebugInventory, "runInventory");
switch (_inventoryState) {
default:
case kInventoryOff: // Icon bar off screen
break;
case kInventoryUp: // Icon bar moving up
_inventoryHeight -= kStepDy; // Move the icon bar up
if (_inventoryHeight <= 0) // Limit travel
_inventoryHeight = 0;
// Move visible portion to _frontBuffer, restore uncovered portion, display results
_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, _inventoryHeight, kXPix, _vm->_screen->getFrontBuffer(), 0, kDibOffY, kXPix);
_vm->_screen->moveImage(_vm->_screen->getBackBufferBackup(), 0, _inventoryHeight + kDibOffY, kXPix, kStepDy, kXPix, _vm->_screen->getFrontBuffer(), 0, _inventoryHeight + kDibOffY, kXPix);
_vm->_screen->displayRect(0, kDibOffY, kXPix, _inventoryHeight + kStepDy);
if (_inventoryHeight == 0) { // Finished moving up?
// Yes, restore dibs and exit back to game state machine
_vm->_screen->moveImage(_vm->_screen->getBackBufferBackup(), 0, 0, kXPix, kYPix, kXPix, _vm->_screen->getBackBuffer(), 0, 0, kXPix);
_vm->_screen->moveImage(_vm->_screen->getBackBuffer(), 0, 0, kXPix, kYPix, kXPix, _vm->_screen->getFrontBuffer(), 0, 0, kXPix);
_vm->_object->updateImages(); // Add objects back into display list for restore
_inventoryState = kInventoryOff;
gameStatus._viewState = kViewPlay;
}
break;
case kInventoryDown: // Icon bar moving down
// If this is the first step, initialize dib_i
// and get any icon/text out of _frontBuffer
if (_inventoryHeight == 0) {
processInventory(kInventoryActionInit); // Initialize dib_i
_vm->_screen->displayList(kDisplayRestore); // Restore _frontBuffer
_vm->_object->updateImages(); // Rebuild _frontBuffer without icons/text
_vm->_screen->displayList(kDisplayDisplay); // Blit display list to screen
}
_inventoryHeight += kStepDy; // Move the icon bar down
if (_inventoryHeight > kInvDy) // Limit travel
_inventoryHeight = kInvDy;
// Move visible portion to _frontBuffer, display results
_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, _inventoryHeight, kXPix, _vm->_screen->getFrontBuffer(), 0, kDibOffY, kXPix);
_vm->_screen->displayRect(0, kDibOffY, kXPix, _inventoryHeight);
if (_inventoryHeight == kInvDy) { // Finished moving down?
// Yes, prepare view dibs for special inventory display since
// we can't refresh objects while icon bar overlayed...
// 1. Save backing store _backBuffer in temporary dib_c
// 2. Make snapshot of _frontBuffer the new _backBuffer backing store
// 3. Reset the display list
_vm->_screen->moveImage(_vm->_screen->getBackBuffer(), 0, 0, kXPix, kYPix, kXPix, _vm->_screen->getBackBufferBackup(), 0, 0, kXPix);
_vm->_screen->moveImage(_vm->_screen->getFrontBuffer(), 0, 0, kXPix, kYPix, kXPix, _vm->_screen->getBackBuffer(), 0, 0, kXPix);
_vm->_screen->displayList(kDisplayInit);
_inventoryState = kInventoryActive;
}
break;
case kInventoryActive: // Inventory active
_vm->_parser->charHandler(); // Still allow commands
_vm->_screen->displayList(kDisplayRestore); // Restore previous background
_vm->_screen->displayList(kDisplayDisplay); // Blit the display list to screen
break;
}
}
/**
* Find index of dragged icon
*/
int16 InventoryHandler::findIconId(int16 objId) {
int16 iconId = 0;
for (; iconId < _maxInvent; iconId++) {
if (objId == _invent[iconId])
break;
}
return iconId;
}
} // End of namespace Hugo

71
engines/hugo/inventory.h Normal file
View File

@@ -0,0 +1,71 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_INVENTORY_H
#define HUGO_INVENTORY_H
namespace Hugo {
/**
* Actions for Process_inventory()
*/
enum InvAct {kInventoryActionInit, kInventoryActionLeft, kInventoryActionRight, kInventoryActionGet};
class InventoryHandler {
public:
InventoryHandler(HugoEngine *vm);
void setInventoryObjId(int16 objId);
void setInventoryState(Istate state);
void freeInvent();
int16 getInventoryObjId() const;
Istate getInventoryState() const;
int16 findIconId(int16 objId);
void loadInvent(Common::SeekableReadStream &in);
int16 processInventory(const int action, ...);
void runInventory();
private:
HugoEngine *_vm;
static const int kStepDy = 8; // Pixels per step movement
int16 _firstIconId; // Index of first icon to display
int16 *_invent;
Istate _inventoryState; // Inventory icon bar state
int16 _inventoryHeight; // Inventory icon bar height
int16 _inventoryObjId; // Inventory object selected, or -1
byte _maxInvent;
void constructInventory(const int16 imageTotNumb, int displayNumb, const bool scrollFl, int16 firstObjId);
};
} // End of namespace Hugo
#endif // HUGO_INVENTORY_H

396
engines/hugo/metaengine.cpp Normal file
View File

@@ -0,0 +1,396 @@
/* 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 "engines/advancedDetector.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "hugo/hugo.h"
#include "hugo/detection.h"
namespace Hugo {
#ifdef USE_TTS
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_WINDOWS_INTERFACE,
{
_s("Use Windows interface"),
_s("Use mouse and toolbar from Windows version"),
"use_windows_interface",
false,
0,
0
}
},
{
GAMEOPTION_TTS,
{
_s("Enable Text to Speech"),
_s("Use TTS to read text in the game (if TTS is available)"),
"tts_enabled",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
#endif
uint32 HugoEngine::getFeatures() const {
return _gameDescription->desc.flags;
}
const char *HugoEngine::getGameId() const {
return _gameDescription->desc.gameId;
}
class HugoMetaEngine : public AdvancedMetaEngine<HugoGameDescription> {
public:
const char *getName() const override {
return "hugo";
}
#ifdef USE_TTS
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return optionsList;
}
#endif
Common::Error createInstance(OSystem *syst, Engine **engine, const HugoGameDescription *gd) const override;
bool hasFeature(MetaEngineFeature f) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
int getMaximumSaveSlot() const override;
SaveStateList listSaves(const char *target) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
bool removeSaveState(const char *target, int slot) const override;
Common::String getSavegameFile(int saveGameIdx, const char *target) const override {
if (!target)
target = getName();
if (saveGameIdx == kSavegameFilePattern)
return Common::String::format("%s-##.SAV", target);
else
return Common::String::format("%s-%02d.SAV", target, saveGameIdx);
}
};
Common::Error HugoMetaEngine::createInstance(OSystem *syst, Engine **engine, const HugoGameDescription *gd) const {
*engine = new HugoEngine(syst,gd);
((HugoEngine *)*engine)->initGame(gd);
return Common::kNoError;
}
bool HugoMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate);
}
Common::KeymapArray HugoMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Hugo;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "hugo-default", _("Default keymappings"));
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, "game-shortcuts", _("Game keymappings"));
Action *act;
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
act->addDefaultInputMapping("KP_PLUS");
engineKeyMap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
act->addDefaultInputMapping("KP_MINUS");
engineKeyMap->addAction(act);
act = new Action("USERHELP", _("User help"));
act->setCustomEngineActionEvent(kActionUserHelp);
act->addDefaultInputMapping("F1");
act->addDefaultInputMapping("JOY_Y");
engineKeyMap->addAction(act);
act = new Action("TOGGLESOUND", _("Toggle sound"));
act->setCustomEngineActionEvent(kActionToggleSound);
act->addDefaultInputMapping("F2");
engineKeyMap->addAction(act);
act = new Action("REPEATLINE", _("Repeat last line"));
act->setCustomEngineActionEvent(kActionRepeatLine);
act->addDefaultInputMapping("F3");
act->addDefaultInputMapping("JOY_RIGHT_STICK");
engineKeyMap->addAction(act);
act = new Action("SAVEGAME", _("Save game"));
act->setCustomEngineActionEvent(kActionSaveGame);
act->addDefaultInputMapping("F4");
act->addDefaultInputMapping("C+s");
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
engineKeyMap->addAction(act);
act = new Action("RESTOREGAME", _("Restore game"));
act->setCustomEngineActionEvent(kActionRestoreGame);
act->addDefaultInputMapping("F5");
act->addDefaultInputMapping("C+l");
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
engineKeyMap->addAction(act);
act = new Action("NEWGAME", _("New game"));
act->setCustomEngineActionEvent(kActionNewGame);
act->addDefaultInputMapping("C+n");
act->addDefaultInputMapping("JOY_GUIDE");
engineKeyMap->addAction(act);
act = new Action("INVENTORY", _("Show inventory"));
act->setCustomEngineActionEvent(kActionInventory);
act->addDefaultInputMapping("F6");
act->addDefaultInputMapping("JOY_X");
engineKeyMap->addAction(act);
act = new Action("TURBOMODE", _("Turbo mode"));
act->setCustomEngineActionEvent(kActionToggleTurbo);
act->addDefaultInputMapping("F8");
act->addDefaultInputMapping("JOY_LEFT_STICK");
engineKeyMap->addAction(act);
act = new Action("ESC", _("Escape"));
act->setCustomEngineActionEvent(kActionEscape);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_BACK");
engineKeyMap->addAction(act);
// I18N: Move actor in the top direction
act = new Action("MOVETOP", _("Move to top"));
act->setCustomEngineActionEvent(kActionMoveTop);
act->addDefaultInputMapping("KP8");
act->addDefaultInputMapping("UP");
act->addDefaultInputMapping("JOY_UP");
gameKeyMap->addAction(act);
// I18N: Move actor in the bottom direction
act = new Action("MOVEBOTTOM", _("Move to bottom"));
act->setCustomEngineActionEvent(kActionMoveBottom);
act->addDefaultInputMapping("KP2");
act->addDefaultInputMapping("DOWN");
act->addDefaultInputMapping("JOY_DOWN");
gameKeyMap->addAction(act);
// I18N: Move actor in the left direction
act = new Action("MOVELEFT", _("Move to left"));
act->setCustomEngineActionEvent(kActionMoveLeft);
act->addDefaultInputMapping("KP4");
act->addDefaultInputMapping("LEFT");
act->addDefaultInputMapping("JOY_LEFT");
gameKeyMap->addAction(act);
// I18N: Move actor in the right direction
act = new Action("MOVERIGHT", _("Move to right"));
act->setCustomEngineActionEvent(kActionMoveRight);
act->addDefaultInputMapping("KP6");
act->addDefaultInputMapping("RIGHT");
act->addDefaultInputMapping("JOY_RIGHT");
gameKeyMap->addAction(act);
// I18N: Move actor in the top-left direction
act = new Action("MOVETOPLEFT", _("Move to top left"));
act->setCustomEngineActionEvent(kActionMoveTopLeft);
act->addDefaultInputMapping("KP7");
gameKeyMap->addAction(act);
// I18N: Move actor in the top-right direction
act = new Action("MOVETOPRIGHT", _("Move to top right"));
act->setCustomEngineActionEvent(kActionMoveTopRight);
act->addDefaultInputMapping("KP9");
gameKeyMap->addAction(act);
// I18N: Move actor in the bottom-left direction
act = new Action("MOVEBTMLEFT", _("Move to bottom left"));
act->setCustomEngineActionEvent(kActionMoveBottomLeft);
act->addDefaultInputMapping("KP1");
gameKeyMap->addAction(act);
// I18N: Move actor in the bottom-right direction
act = new Action("MOVEBTMRIGHT", _("Move to bottom right"));
act->setCustomEngineActionEvent(kActionMoveBottomRight);
act->addDefaultInputMapping("KP3");
gameKeyMap->addAction(act);
KeymapArray keymaps(2);
keymaps[0] = engineKeyMap;
keymaps[1] = gameKeyMap;
return keymaps;
}
int HugoMetaEngine::getMaximumSaveSlot() const { return 99; }
SaveStateList HugoMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
Common::String pattern = target;
pattern += "-##.SAV";
filenames = saveFileMan->listSavefiles(pattern);
SaveStateList saveList;
char slot[3];
for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) {
slot[0] = filename->c_str()[filename->size() - 6];
slot[1] = filename->c_str()[filename->size() - 5];
slot[2] = '\0';
// Obtain the last 2 digits of the filename (without extension), since they correspond to the save slot
int slotNum = atoi(slot);
if (slotNum >= 0 && slotNum <= getMaximumSaveSlot()) {
Common::InSaveFile *file = saveFileMan->openForLoading(*filename);
if (file) {
int saveVersion = file->readByte();
if (saveVersion != kSavegameVersion) {
warning("Savegame of incompatible version");
delete file;
continue;
}
// read name
uint16 nameSize = file->readUint16BE();
if (nameSize >= 255) {
delete file;
continue;
}
char name[256];
file->read(name, nameSize);
name[nameSize] = 0;
saveList.push_back(SaveStateDescriptor(this, slotNum, name));
delete file;
}
}
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
SaveStateDescriptor HugoMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(getSavegameFile(slot, target));
if (file) {
int saveVersion = file->readByte();
if (saveVersion != kSavegameVersion) {
warning("Savegame of incompatible version");
delete file;
return SaveStateDescriptor();
}
uint32 saveNameLength = file->readUint16BE();
char saveName[256];
file->read(saveName, saveNameLength);
saveName[saveNameLength] = 0;
SaveStateDescriptor desc(this, slot, saveName);
// Protect slot 99 (used for 'restart game')
if (slot == 99) {
desc.setDeletableFlag(false);
desc.setWriteProtectedFlag(true);
}
Graphics::Surface *thumbnail;
if (!Graphics::loadThumbnail(*file, thumbnail)) {
warning("Missing or broken savegame thumbnail");
delete file;
return SaveStateDescriptor();
}
desc.setThumbnail(thumbnail);
uint32 saveDate = file->readUint32BE();
uint16 saveTime = file->readUint16BE();
int day = (saveDate >> 24) & 0xFF;
int month = (saveDate >> 16) & 0xFF;
int year = saveDate & 0xFFFF;
desc.setSaveDate(year, month, day);
int hour = (saveTime >> 8) & 0xFF;
int minutes = saveTime & 0xFF;
desc.setSaveTime(hour, minutes);
delete file;
return desc;
}
SaveStateDescriptor desc(this, slot, Common::String());
// Protect slot 99 (used for 'restart game')
if (slot == 99)
desc.setWriteProtectedFlag(true);
return desc;
}
bool HugoMetaEngine::removeSaveState(const char *target, int slot) const {
return g_system->getSavefileManager()->removeSavefile(getSavegameFile(slot, target));
}
} // End of namespace Hugo
#if PLUGIN_ENABLED_DYNAMIC(HUGO)
REGISTER_PLUGIN_DYNAMIC(HUGO, PLUGIN_TYPE_ENGINE, Hugo::HugoMetaEngine);
#else
REGISTER_PLUGIN_STATIC(HUGO, PLUGIN_TYPE_ENGINE, Hugo::HugoMetaEngine);
#endif
namespace Hugo {
void HugoEngine::initGame(const HugoGameDescription *gd) {
_gameType = gd->gameType;
_platform = gd->desc.platform;
_packedFl = (getFeatures() & GF_PACKED);
_gameVariant = _gameType - 1 + ((_platform == Common::kPlatformWindows) ? 0 : 3);
}
} // End of namespace Hugo

43
engines/hugo/module.mk Normal file
View File

@@ -0,0 +1,43 @@
MODULE := engines/hugo
MODULE_OBJS := \
console.o \
dialogs.o \
display.o \
file.o \
file_v1d.o \
file_v2d.o \
file_v3d.o \
file_v1w.o \
file_v2w.o \
hugo.o \
intro.o \
inventory.o \
metaengine.o \
mouse.o \
object.o \
object_v1d.o \
object_v1w.o \
object_v2d.o \
object_v3d.o \
parser.o \
parser_v1w.o \
parser_v1d.o \
parser_v2d.o \
parser_v3d.o \
route.o \
schedule.o \
sound.o \
text.o \
util.o
# This module can be built as a plugin
ifeq ($(ENABLE_HUGO), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

418
engines/hugo/mouse.cpp Normal file
View File

@@ -0,0 +1,418 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// mouse.cpp : Handle all mouse activity
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/dialogs.h"
#include "hugo/game.h"
#include "hugo/mouse.h"
#include "hugo/schedule.h"
#include "hugo/display.h"
#include "hugo/inventory.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/object.h"
#include "hugo/text.h"
namespace Hugo {
MouseHandler::MouseHandler(HugoEngine *vm) : _vm(vm) {
_hotspots = nullptr;
_leftButtonFl = false;
_rightButtonFl = false;
_jumpExitFl = false; // Can't jump to a screen exit
_mouseX = kXPix / 2;
_mouseY = kYPix / 2;
}
void MouseHandler::resetLeftButton() {
_leftButtonFl = false;
}
void MouseHandler::resetRightButton() {
_rightButtonFl = false;
}
void MouseHandler::setLeftButton() {
_leftButtonFl = true;
}
void MouseHandler::setRightButton() {
_rightButtonFl = true;
}
void MouseHandler::setJumpExitFl(bool fl) {
_jumpExitFl = fl;
}
void MouseHandler::setMouseX(int x) {
_mouseX = x;
}
void MouseHandler::setMouseY(int y) {
_mouseY = y;
}
void MouseHandler::freeHotspots() {
free(_hotspots);
_hotspots = nullptr;
}
bool MouseHandler::getJumpExitFl() const {
return _jumpExitFl;
}
int MouseHandler::getMouseX() const {
return _mouseX;
}
int MouseHandler::getMouseY() const {
return _mouseY;
}
int16 MouseHandler::getDirection(const int16 hotspotId) const {
return _hotspots[hotspotId]._direction;
}
int16 MouseHandler::getHotspotActIndex(const int16 hotspotId) const {
return _hotspots[hotspotId]._actIndex;
}
/**
* Shadow-blit supplied string into dib_a at cx,cy and add to display list
*/
void MouseHandler::cursorText(const char *buffer, const int16 cx, const int16 cy, const Uif fontId, const int16 color) {
debugC(1, kDebugMouse, "cursorText(%s, %d, %d, %d, %d)", buffer, cx, cy, fontId, color);
_vm->_screen->loadFont(fontId);
// Find bounding rect for string
int16 sdx = _vm->_screen->stringLength(buffer);
int16 sdy = _vm->_screen->fontHeight() + 1; // + 1 for shadow
int16 sx, sy;
if (cx < kXPix / 2) {
sx = cx + kCursorNameOffX;
if (_vm->_inventory->getInventoryObjId() == -1) {
sy = cy + kCursorNameOffY;
} else {
sy = cy + kCursorNameOffY - (_vm->_screen->fontHeight() + 1);
if (sy < 0) {
sx = cx + kCursorNameOffX + 25;
sy = cy + kCursorNameOffY;
}
}
} else {
sx = cx - sdx - kCursorNameOffX / 2;
sy = cy + kCursorNameOffY;
}
if (sy < 0) {
sy = 0;
}
// Display the string and add rect to display list
_vm->_screen->shadowStr(sx, sy, buffer, _TBRIGHTWHITE);
_vm->_screen->displayList(kDisplayAdd, sx, sy, sdx, sdy);
#ifdef USE_TTS
_vm->sayText(buffer);
#endif
}
/**
* Find the exit hotspot containing cx, cy.
* Return hotspot index or -1 if not found.
*/
int16 MouseHandler::findExit(const int16 cx, const int16 cy, byte screenId) {
debugC(2, kDebugMouse, "findExit(%d, %d, %d)", cx, cy, screenId);
for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
if (_hotspots[i]._screenIndex == screenId) {
if (cx >= _hotspots[i]._x1 && cx <= _hotspots[i]._x2 && cy >= _hotspots[i]._y1 && cy <= _hotspots[i]._y2)
return i;
}
}
return -1;
}
/**
* Process a mouse right click at coord cx, cy over object objid
*/
void MouseHandler::processRightClick(const int16 objId, const int16 cx, const int16 cy) {
debugC(1, kDebugMouse, "ProcessRightClick(%d, %d, %d)", objId, cx, cy);
Status &gameStatus = _vm->getGameStatus();
if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
return;
int16 inventObjId = _vm->_inventory->getInventoryObjId();
// Check if this was over iconbar
if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) { // Clicked over iconbar object
if (inventObjId == -1)
_vm->_screen->selectInventoryObjId(objId);
else if (inventObjId == objId)
_vm->_screen->resetInventoryObjId();
else
_vm->_object->useObject(objId); // Use status.objid on object
} else { // Clicked over viewport object
Object *obj = &_vm->_object->_objects[objId];
int16 x, y;
switch (obj->_viewx) { // Where to walk to
case -1: { // Walk to object position
bool foundFl = false;
if (_vm->_object->findObjectSpace(obj, &x, &y))
foundFl = _vm->_route->startRoute(kRouteGet, objId, x, y); // TRUE if route found to object
if (!foundFl) // Can't get there, try to use from here
_vm->_object->useObject(objId);
}
break;
case 0: // Immediate use
_vm->_object->useObject(objId); // Pick up or use object
break;
default: // Walk to view point if possible
if (!_vm->_route->startRoute(kRouteGet, objId, obj->_viewx, obj->_viewy)) {
if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
_vm->_object->useObject(objId); // immediate use
else
Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
}
break;
}
}
}
/** Process a left mouse click over:
* 1. An icon - show description
* 2. An object - walk to and show description
* 3. An icon scroll arrow - scroll the iconbar
* 4. Nothing - attempt to walk there
* 5. Exit - walk to exit hotspot
*/
void MouseHandler::processLeftClick(const int16 objId, const int16 cx, const int16 cy) {
debugC(1, kDebugMouse, "ProcessLeftClick(%d, %d, %d)", objId, cx, cy);
int16 i, x, y;
Object *obj;
Status &gameStatus = _vm->getGameStatus();
if (gameStatus._storyModeFl || _vm->_hero->_pathType == kPathQuiet) // Make sure user has control
return;
switch (objId) {
case -1: // Empty space - attempt to walk there
_vm->_route->startRoute(kRouteSpace, 0, cx, cy);
break;
case kLeftArrow: // A scroll arrow - scroll the iconbar
case kRightArrow:
// Scroll the iconbar and display results
_vm->_inventory->processInventory((objId == kLeftArrow) ? kInventoryActionLeft : kInventoryActionRight);
_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getFrontBuffer(), 0, kDibOffY, kXPix);
_vm->_screen->moveImage(_vm->_screen->getIconBuffer(), 0, 0, kXPix, kInvDy, kXPix, _vm->_screen->getBackBuffer(), 0, kDibOffY, kXPix);
_vm->_screen->displayList(kDisplayAdd, 0, kDibOffY, kXPix, kInvDy);
break;
case kExitHotspot: // Walk to exit hotspot
i = findExit(cx, cy, *_vm->_screenPtr);
x = _hotspots[i]._viewx;
y = _hotspots[i]._viewy;
if (x >= 0) { // Hotspot refers to an exit
// Special case of immediate exit
if (_jumpExitFl) {
// Get rid of iconbar if necessary
if (_vm->_inventory->getInventoryState() != kInventoryOff)
_vm->_inventory->setInventoryState(kInventoryUp);
_vm->_scheduler->insertActionList(_hotspots[i]._actIndex);
} else { // Set up route to exit spot
if (_hotspots[i]._direction == Common::KEYCODE_RIGHT)
x -= kHeroMaxWidth;
else if (_hotspots[i]._direction == Common::KEYCODE_LEFT)
x += kHeroMaxWidth;
if (!_vm->_route->startRoute(kRouteExit, i, x, y))
Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
}
// Get rid of any attached icon
_vm->_screen->resetInventoryObjId();
}
break;
default: // Look at an icon or object
obj = &_vm->_object->_objects[objId];
// Over iconbar - immediate description
if ((_vm->_inventory->getInventoryState() == kInventoryActive) && (cy < kInvDy + kDibOffY)) {
_vm->_object->lookObject(obj);
} else {
bool foundFl = false; // TRUE if route found to object
switch (obj->_viewx) { // Clicked over viewport object
case -1: // Walk to object position
if (_vm->_object->findObjectSpace(obj, &x, &y))
foundFl = _vm->_route->startRoute(kRouteLook, objId, x, y);
if (!foundFl) // Can't get there, immediate description
_vm->_object->lookObject(obj);
break;
case 0: // Immediate description
_vm->_object->lookObject(obj);
break;
default: // Walk to view point if possible
if (!_vm->_route->startRoute(kRouteLook, objId, obj->_viewx, obj->_viewy)) {
if (_vm->_hero->_cycling == kCycleInvisible) // If invisible do
_vm->_object->lookObject(obj); // immediate decription
else
Utils::notifyBox(_vm->_text->getTextMouse(kMsNoWayText)); // Can't get there
}
break;
}
}
break;
}
}
/**
* Process mouse activity
*/
void MouseHandler::mouseHandler() {
debugC(2, kDebugMouse, "mouseHandler");
Status &gameStatus = _vm->getGameStatus();
Istate inventState = _vm->_inventory->getInventoryState();
if ((gameStatus._viewState != kViewPlay) && (inventState != kInventoryActive))
return;
int16 cx = getMouseX();
int16 cy = getMouseY();
// gameStatus._cx = cx; // Save cursor coords
// gameStatus._cy = cy;
// Don't process if outside client area
if ((cx < 0) || (cx > kXPix) || (cy < kDibOffY) || (cy > kViewSizeY + kDibOffY))
return;
int16 objId = -1; // Current source object
// Process cursor over an object or icon
if (inventState == kInventoryActive) { // Check inventory icon bar first
objId = _vm->_inventory->processInventory(kInventoryActionGet, cx, cy);
} else {
if (cy < 5 && cy > 0) {
_vm->_topMenu->runModal();
// When the top menu is shown, it eats all the events, including mouse move, which means the
// getMouseX() and getMouseY() have not been updated and the topMenu will be shown immediately
// again. We do not know where the cursor is currently, but move it outside of the ]0, 5[ range.
setMouseY(0);
}
}
if (!gameStatus._gameOverFl) {
if (objId == -1) // No match, check rest of view
objId = _vm->_object->findObject(cx, cy);
if (objId >= 0) { // Got a match
// Display object name next to cursor (unless CURSOR_NOCHAR)
// Note test for swapped hero name
const char *name = _vm->_text->getNoun(_vm->_object->_objects[(objId == kHeroIndex) ? _vm->_heroImage : objId]._nounIndex, kCursorNameIndex);
if (name[0] != kCursorNochar)
cursorText(name, cx, cy, U_FONT8, _TBRIGHTWHITE);
#ifdef USE_TTS
else {
_vm->_previousSaid.clear();
}
#endif
// Process right click over object in view or iconbar
if (_rightButtonFl)
processRightClick(objId, cx, cy);
}
// Process cursor over an exit hotspot
if (objId == -1) {
int i = findExit(cx, cy, *_vm->_screenPtr);
if (i != -1 && _hotspots[i]._viewx >= 0) {
objId = kExitHotspot;
cursorText(_vm->_text->getTextMouse(kMsExit), cx, cy, U_FONT8, _TBRIGHTWHITE);
}
#ifdef USE_TTS
else {
_vm->_previousSaid.clear();
}
#endif
}
}
// Left click over icon, object or to move somewhere
if (_leftButtonFl)
processLeftClick(objId, cx, cy);
// Clear mouse click states
resetLeftButton();
resetRightButton();
}
void MouseHandler::readHotspot(Common::ReadStream &in, Hotspot &hotspot) {
hotspot._screenIndex = in.readSint16BE();
hotspot._x1 = in.readSint16BE();
hotspot._y1 = in.readSint16BE();
hotspot._x2 = in.readSint16BE();
hotspot._y2 = in.readSint16BE();
hotspot._actIndex = in.readUint16BE();
hotspot._viewx = in.readSint16BE();
hotspot._viewy = in.readSint16BE();
hotspot._direction = in.readSint16BE();
}
/**
* Load hotspots data from hugo.dat
*/
void MouseHandler::loadHotspots(Common::ReadStream &in) {
Hotspot *wrkHotspots = nullptr;
Hotspot tmp;
memset(&tmp, 0, sizeof(tmp));
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
int numRows = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_hotspots = wrkHotspots = (Hotspot *)malloc(sizeof(Hotspot) * numRows);
for (int i = 0; i < numRows; i++)
readHotspot(in, (varnt == _vm->_gameVariant) ? wrkHotspots[i] : tmp);
}
}
/**
* Display hotspot boundaries for the current screen
*/
void MouseHandler::drawHotspots() const {
for (int i = 0; _hotspots[i]._screenIndex >= 0; i++) {
Hotspot *hotspot = &_hotspots[i];
if (hotspot->_screenIndex == _vm->_hero->_screenIndex)
_vm->_screen->drawRectangle(false, hotspot->_x1, hotspot->_y1, hotspot->_x2, hotspot->_y2, _TLIGHTRED);
}
}
} // End of namespace Hugo

94
engines/hugo/mouse.h Normal file
View File

@@ -0,0 +1,94 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_MOUSE_H
#define HUGO_MOUSE_H
#include "hugo/game.h"
namespace Common {
class ReadStream;
}
namespace Hugo {
class MouseHandler {
public:
MouseHandler(HugoEngine *vm);
void mouseHandler();
void resetLeftButton();
void resetRightButton();
void setLeftButton();
void setRightButton();
void setJumpExitFl(bool fl);
void setMouseX(int x);
void setMouseY(int y);
void freeHotspots();
bool getJumpExitFl() const;
int getMouseX() const;
int getMouseY() const;
int16 getDirection(const int16 hotspotId) const;
int16 getHotspotActIndex(const int16 hotspotId) const;
void drawHotspots() const;
int16 findExit(const int16 cx, const int16 cy, byte screenId);
void loadHotspots(Common::ReadStream &in);
private:
HugoEngine *_vm;
static const char kCursorNochar = '~'; // Don't show name of object under cursor
static const int kExitHotspot = -4; // Cursor over Exit hotspot
static const int kCursorNameIndex = 2; // Index of name used under cursor
static const int kCursorNameOffX = 10; // Cursor offset to name string
static const int kCursorNameOffY = -2; // Cursor offset to name string
enum seqTextMouse {
kMsNoWayText = 0,
kMsExit = 1
};
Hotspot *_hotspots;
bool _leftButtonFl; // Left mouse button pressed
bool _rightButtonFl; // Right button pressed
int _mouseX;
int _mouseY;
bool _jumpExitFl; // Allowed to jump to a screen exit
void cursorText(const char *buffer, const int16 cx, const int16 cy, const Uif fontId, const int16 color);
void processRightClick(const int16 objId, const int16 cx, const int16 cy);
void processLeftClick(const int16 objId, const int16 cx, const int16 cy);
void readHotspot(Common::ReadStream &in, Hotspot &hotspot);
};
} // End of namespace Hugo
#endif //HUGO_MOUSE_H

834
engines/hugo/object.cpp Normal file
View File

@@ -0,0 +1,834 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/object.h"
#include "hugo/display.h"
#include "hugo/file.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
#include "hugo/text.h"
#include "hugo/inventory.h"
#include "hugo/mouse.h"
namespace Hugo {
ObjectHandler::ObjectHandler(HugoEngine *vm) : _vm(vm) {
_uses = nullptr;
_objects = nullptr;
_numObj = 0;
_objCount = 0;
_usesSize = 0;
memset(_objBound, '\0', sizeof(Overlay));
memset(_boundary, '\0', sizeof(Overlay));
memset(_overlay, '\0', sizeof(Overlay));
memset(_ovlBase, '\0', sizeof(Overlay));
}
ObjectHandler::~ObjectHandler() {
}
byte ObjectHandler::getBoundaryOverlay(uint16 index) const {
return _boundary[index];
}
byte ObjectHandler::getObjectBoundary(uint16 index) const {
return _objBound[index];
}
byte ObjectHandler::getBaseBoundary(uint16 index) const {
return _ovlBase[index];
}
byte ObjectHandler::getFirstOverlay(uint16 index) const {
return _overlay[index];
}
bool ObjectHandler::isCarried(int objIndex) const {
return _objects[objIndex]._carriedFl;
}
void ObjectHandler::setCarry(int objIndex, bool val) {
_objects[objIndex]._carriedFl = val;
}
void ObjectHandler::setVelocity(int objIndex, int8 vx, int8 vy) {
_objects[objIndex]._vx = vx;
_objects[objIndex]._vy = vy;
}
void ObjectHandler::setPath(int objIndex, Path pathType, int16 vxPath, int16 vyPath) {
_objects[objIndex]._pathType = pathType;
_objects[objIndex]._vxPath = vxPath;
_objects[objIndex]._vyPath = vyPath;
}
/**
* Save sequence number and image number in given object
*/
void ObjectHandler::saveSeq(Object *obj) {
debugC(1, kDebugObject, "saveSeq");
bool found = false;
for (int i = 0; !found && (i < obj->_seqNumb); i++) {
Seq *q = obj->_seqList[i]._seqPtr;
for (int j = 0; !found && (j < obj->_seqList[i]._imageNbr); j++) {
if (obj->_currImagePtr == q) {
found = true;
obj->_curSeqNum = i;
obj->_curImageNum = j;
} else {
q = q->_nextSeqPtr;
}
}
}
}
/**
* Set up cur_seqPtr from stored sequence and image number in object
*/
void ObjectHandler::restoreSeq(Object *obj) {
debugC(1, kDebugObject, "restoreSeq");
Seq *q = obj->_seqList[obj->_curSeqNum]._seqPtr;
for (int j = 0; j < obj->_curImageNum; j++)
q = q->_nextSeqPtr;
obj->_currImagePtr = q;
}
/**
* If status.objid = -1, pick up objid, else use status.objid on objid,
* if objid can't be picked up, use it directly
*/
void ObjectHandler::useObject(int16 objId) {
debugC(1, kDebugObject, "useObject(%d)", objId);
int16 inventObjId = _vm->_inventory->getInventoryObjId();
Object *obj = &_objects[objId]; // Ptr to object
if (inventObjId == -1) {
const char *verb; // Background verb to use directly
// Get or use objid directly
if ((obj->_genericCmd & TAKE) || obj->_objValue) // Get collectible item
Common::sprintf_s(_vm->_line, "%s %s", _vm->_text->getVerb(_vm->_take, 0), _vm->_text->getNoun(obj->_nounIndex, 0));
else if (obj->_cmdIndex != 0) // Use non-collectible item if able
Common::sprintf_s(_vm->_line, "%s %s", _vm->_text->getVerb(_vm->_parser->getCmdDefaultVerbIdx(obj->_cmdIndex), 0), _vm->_text->getNoun(obj->_nounIndex, 0));
else if ((verb = _vm->_parser->useBG(_vm->_text->getNoun(obj->_nounIndex, 0))) != nullptr)
Common::sprintf_s(_vm->_line, "%s %s", verb, _vm->_text->getNoun(obj->_nounIndex, 0));
else
return; // Can't use object directly
} else {
// Use status.objid on objid
// Default to first cmd verb
Common::sprintf_s(_vm->_line, "%s %s %s", _vm->_text->getVerb(_vm->_parser->getCmdDefaultVerbIdx(_objects[inventObjId]._cmdIndex), 0),
_vm->_text->getNoun(_objects[inventObjId]._nounIndex, 0),
_vm->_text->getNoun(obj->_nounIndex, 0));
// Check valid use of objects and override verb if necessary
for (Uses *use = _uses; use->_objId != _numObj; use++) {
if (inventObjId == use->_objId) {
// Look for secondary object, if found use matching verb
bool foundFl = false;
for (Target *target = use->_targets; target->_nounIndex != 0; target++)
if (target->_nounIndex == obj->_nounIndex) {
foundFl = true;
Common::sprintf_s(_vm->_line, "%s %s %s", _vm->_text->getVerb(target->_verbIndex, 0),
_vm->_text->getNoun(_objects[inventObjId]._nounIndex, 0),
_vm->_text->getNoun(obj->_nounIndex, 0));
}
// No valid use of objects found, print failure string
if (!foundFl) {
// Deselect dragged icon if inventory not active
if (_vm->_inventory->getInventoryState() != kInventoryActive)
_vm->_screen->resetInventoryObjId();
Utils::notifyBox(_vm->_text->getTextData(use->_dataIndex));
return;
}
}
}
}
if (_vm->_inventory->getInventoryState() == kInventoryActive) // If inventory active, remove it
_vm->_inventory->setInventoryState(kInventoryUp);
_vm->_screen->resetInventoryObjId();
_vm->_parser->lineHandler(); // and process command
}
/**
* Return object index of the topmost object under the cursor, or -1 if none
* Objects are filtered if not "useful"
*/
int16 ObjectHandler::findObject(uint16 x, uint16 y) {
debugC(3, kDebugObject, "findObject(%d, %d)", x, y);
int16 objIndex = -1; // Index of found object
uint16 y2Max = 0; // Greatest y2
Object *obj = _objects;
// Check objects on screen
for (int i = 0; i < _numObj; i++, obj++) {
// Object must be in current screen and "useful"
if (obj->_screenIndex == *_vm->_screenPtr && (obj->_genericCmd || obj->_objValue || obj->_cmdIndex)) {
Seq *curImage = obj->_currImagePtr;
// Object must have a visible image...
if (curImage != nullptr && obj->_cycling != kCycleInvisible) {
// If cursor inside object
if (x >= (uint16)obj->_x && x <= obj->_x + curImage->_x2 && y >= (uint16)obj->_y && y <= obj->_y + curImage->_y2) {
// If object is closest so far
if (obj->_y + curImage->_y2 > y2Max) {
y2Max = obj->_y + curImage->_y2;
objIndex = i; // Found an object!
}
}
} else {
// ...or a dummy object that has a hotspot rectangle
if (curImage == nullptr && obj->_vxPath != 0 && !obj->_carriedFl) {
// If cursor inside special rectangle
if ((int16)x >= obj->_oldx && (int16)x < obj->_oldx + obj->_vxPath && (int16)y >= obj->_oldy && (int16)y < obj->_oldy + obj->_vyPath) {
// If object is closest so far
if (obj->_oldy + obj->_vyPath - 1 > (int16)y2Max) {
y2Max = obj->_oldy + obj->_vyPath - 1;
objIndex = i; // Found an object!
}
}
}
}
}
}
return objIndex;
}
/**
* Issue "Look at <object>" command
* Note special case of swapped hero image
*/
void ObjectHandler::lookObject(Object *obj) {
debugC(1, kDebugObject, "lookObject");
if (obj == _vm->_hero)
// Hero swapped - look at other
obj = &_objects[_vm->_heroImage];
_vm->_parser->command("%s %s", _vm->_text->getVerb(_vm->_look, 0), _vm->_text->getNoun(obj->_nounIndex, 0));
}
/**
* Free all object images, uses and ObjArr (before exiting)
*/
void ObjectHandler::freeObjects() {
debugC(1, kDebugObject, "freeObjects");
if (_vm->_hero != nullptr && _vm->_hero->_seqList[0]._seqPtr != nullptr) {
// Free all sequence lists and image data
for (int16 i = 0; i < _numObj; i++) {
Object *obj = &_objects[i];
for (int16 j = 0; j < obj->_seqNumb; j++) {
Seq *seq = obj->_seqList[j]._seqPtr;
Seq *next;
if (seq == nullptr) // Failure during database load
break;
if (seq->_imagePtr != nullptr) {
free(seq->_imagePtr);
seq->_imagePtr = nullptr;
}
seq = seq->_nextSeqPtr;
while (seq != obj->_seqList[j]._seqPtr) {
if (seq->_imagePtr != nullptr) {
free(seq->_imagePtr);
seq->_imagePtr = nullptr;
}
next = seq->_nextSeqPtr;
free(seq);
seq = next;
}
free(seq);
seq = nullptr;
}
}
}
if (_uses != nullptr) {
for (int16 i = 0; i < _usesSize; i++)
free(_uses[i]._targets);
free(_uses);
_uses = nullptr;
}
for (int16 i = 0; i < _objCount; i++) {
free(_objects[i]._stateDataIndex);
_objects[i]._stateDataIndex = nullptr;
}
free(_objects);
_objects = nullptr;
}
/**
* Compare function for the quicksort. The sort is to order the objects in
* increasing vertical position, using y+y2 as the baseline
* Returns -1 if ay2 < by2 else 1 if ay2 > by2 else 0
*/
int ObjectHandler::y2comp(const void *a, const void *b) {
debugC(6, kDebugObject, "y2comp");
byte index1 = *(const byte *)a;
byte index2 = *(const byte *)b;
const Object *p1 = &HugoEngine::get()._object->_objects[index1];
const Object *p2 = &HugoEngine::get()._object->_objects[index2];
if (p1 == p2)
// Why does qsort try the same indexes?
return 0;
// WORKAROUND: The original y2comp() is ambiguous when both objects have
// background priority or when both objects have foreground priority.
// The resulting sort order depends on the CRT's qsort implementation.
// The original only used Microsoft's qsort, which still happens to work,
// but our builds produce different results on other platforms and CRTs.
// The only affected objects are the parrot and secret passage in Hugo2,
// because they are the only ambiguously sorted objects that overlap.
// We work around this by keeping the sort order stable when the
// comparison would otherwise be ambiguous. This preserves the original
// result in the one scene that depends on it. Fixes the parrot when
// entering from the secret passage on Mac and AmigaOS. Bug #6054
if ((p1->_priority == kPriorityBackground && p2->_priority == kPriorityBackground) ||
(p1->_priority == kPriorityForeground && p2->_priority == kPriorityForeground)) {
return (index1 < index2) ? -1 : 1;
}
if (p1->_priority == kPriorityBackground)
return -1;
if (p2->_priority == kPriorityBackground)
return 1;
if (p1->_priority == kPriorityForeground)
return 1;
if (p2->_priority == kPriorityForeground)
return -1;
int ay2 = p1->_y + p1->_currImagePtr->_y2;
int by2 = p2->_y + p2->_currImagePtr->_y2;
return ay2 - by2;
}
/**
* Return TRUE if object being carried by hero
*/
bool ObjectHandler::isCarrying(uint16 wordIndex) {
debugC(1, kDebugObject, "isCarrying(%d)", wordIndex);
for (int i = 0; i < _numObj; i++) {
if ((wordIndex == _objects[i]._nounIndex) && _objects[i]._carriedFl)
return true;
}
return false;
}
/**
* Describe any takeable objects visible in this screen
*/
void ObjectHandler::showTakeables() {
debugC(1, kDebugObject, "showTakeables");
for (int j = 0; j < _numObj; j++) {
Object *obj = &_objects[j];
if ((obj->_cycling != kCycleInvisible) &&
(obj->_screenIndex == *_vm->_screenPtr) &&
(((TAKE & obj->_genericCmd) == TAKE) || obj->_objValue)) {
_vm->notifyBox(Common::String::format("You can also see:\n%s.", _vm->_text->getNoun(obj->_nounIndex, LOOK_NAME)));
}
}
}
/**
* Find a clear space around supplied object that hero can walk to
*/
bool ObjectHandler::findObjectSpace(Object *obj, int16 *destx, int16 *desty) {
debugC(1, kDebugObject, "findObjectSpace(...)");
Seq *curImage = obj->_currImagePtr;
int16 y = obj->_y + curImage->_y2 - 1;
bool foundFl = true;
// Try left rear corner
for (int16 x = *destx = obj->_x + curImage->_x1; x < *destx + kHeroMaxWidth; x++) {
if (checkBoundary(x, y))
foundFl = false;
}
if (!foundFl) { // Try right rear corner
foundFl = true;
for (int16 x = *destx = obj->_x + curImage->_x2 - kHeroMaxWidth + 1; x <= obj->_x + (int16)curImage->_x2; x++) {
if (checkBoundary(x, y))
foundFl = false;
}
}
if (!foundFl) { // Try left front corner
foundFl = true;
y += 2;
for (int16 x = *destx = obj->_x + curImage->_x1; x < *destx + kHeroMaxWidth; x++) {
if (checkBoundary(x, y))
foundFl = false;
}
}
if (!foundFl) { // Try right rear corner
foundFl = true;
for (int16 x = *destx = obj->_x + curImage->_x2 - kHeroMaxWidth + 1; x <= obj->_x + (int16)curImage->_x2; x++) {
if (checkBoundary(x, y))
foundFl = false;
}
}
*desty = y;
return foundFl;
}
void ObjectHandler::readUse(Common::ReadStream &in, Uses &curUse) {
curUse._objId = in.readSint16BE();
curUse._dataIndex = in.readUint16BE();
uint16 numSubElem = in.readUint16BE();
curUse._targets = (Target *)malloc(sizeof(Target) * numSubElem);
for (int j = 0; j < numSubElem; j++) {
curUse._targets[j]._nounIndex = in.readUint16BE();
curUse._targets[j]._verbIndex = in.readUint16BE();
}
}
/**
* Load _uses from Hugo.dat
*/
void ObjectHandler::loadObjectUses(Common::ReadStream &in) {
Uses tmpUse;
tmpUse._targets = nullptr;
//Read _uses
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_usesSize = numElem;
_uses = (Uses *)malloc(sizeof(Uses) * numElem);
}
for (int i = 0; i < numElem; i++) {
if (varnt == _vm->_gameVariant)
readUse(in, _uses[i]);
else {
readUse(in, tmpUse);
free(tmpUse._targets);
tmpUse._targets = nullptr;
}
}
}
}
void ObjectHandler::readObject(Common::ReadStream &in, Object &curObject) {
curObject._nounIndex = in.readUint16BE();
curObject._dataIndex = in.readUint16BE();
uint16 numSubElem = in.readUint16BE();
if (numSubElem == 0)
curObject._stateDataIndex = nullptr;
else
curObject._stateDataIndex = (uint16 *)malloc(sizeof(uint16) * numSubElem);
for (int j = 0; j < numSubElem; j++)
curObject._stateDataIndex[j] = in.readUint16BE();
curObject._pathType = (Path) in.readSint16BE();
curObject._vxPath = in.readSint16BE();
curObject._vyPath = in.readSint16BE();
curObject._actIndex = in.readUint16BE();
curObject._seqNumb = in.readByte();
curObject._currImagePtr = nullptr;
if (curObject._seqNumb == 0) {
curObject._seqList[0]._imageNbr = 0;
curObject._seqList[0]._seqPtr = nullptr;
}
for (int j = 0; j < curObject._seqNumb; j++) {
curObject._seqList[j]._imageNbr = in.readUint16BE();
curObject._seqList[j]._seqPtr = nullptr;
}
curObject._cycling = (Cycle)in.readByte();
curObject._cycleNumb = in.readByte();
curObject._frameInterval = in.readByte();
curObject._frameTimer = in.readByte();
curObject._radius = in.readByte();
curObject._screenIndex = in.readByte();
curObject._x = in.readSint16BE();
curObject._y = in.readSint16BE();
curObject._oldx = in.readSint16BE();
curObject._oldy = in.readSint16BE();
curObject._vx = in.readByte();
curObject._vy = in.readByte();
curObject._objValue = in.readByte();
curObject._genericCmd = in.readSint16BE();
curObject._cmdIndex = in.readUint16BE();
curObject._carriedFl = (in.readByte() != 0);
curObject._state = in.readByte();
curObject._verbOnlyFl = (in.readByte() != 0);
curObject._priority = in.readByte();
curObject._viewx = in.readSint16BE();
curObject._viewy = in.readSint16BE();
curObject._direction = in.readSint16BE();
curObject._curSeqNum = in.readByte();
curObject._curImageNum = in.readByte();
curObject._oldvx = in.readByte();
curObject._oldvy = in.readByte();
}
/**
* Load ObjectArr from Hugo.dat
*/
void ObjectHandler::loadObjectArr(Common::ReadStream &in) {
debugC(6, kDebugObject, "loadObject(&in)");
Object tmpObject;
tmpObject._stateDataIndex = nullptr;
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_objCount = numElem;
_objects = (Object *)malloc(sizeof(Object) * numElem);
}
for (int i = 0; i < numElem; i++) {
if (varnt == _vm->_gameVariant)
readObject(in, _objects[i]);
else {
// Skip over uneeded objects.
readObject(in, tmpObject);
free(tmpObject._stateDataIndex);
tmpObject._stateDataIndex = nullptr;
}
}
}
}
/**
* Set the screenindex property of the carried objets to the given screen
* number
*/
void ObjectHandler::setCarriedScreen(int screenNum) {
for (int i = kHeroIndex + 1; i < _numObj; i++) {// Any others
if (isCarried(i)) // being carried
_objects[i]._screenIndex = screenNum;
}
}
/**
* Load _numObj from Hugo.dat
*/
void ObjectHandler::loadNumObj(Common::ReadStream &in) {
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
int numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_numObj = numElem;
}
}
/**
* Restore all sequences
*/
void ObjectHandler::restoreAllSeq() {
// Restore ptrs to currently loaded objects
for (int i = 0; i < _numObj; i++)
restoreSeq(&_objects[i]);
}
/**
* Save objects
*/
void ObjectHandler::saveObjects(Common::WriteStream *out) {
for (int i = 0; i < _numObj; i++) {
// Save where curr_seqPtr is pointing to
saveSeq(&_objects[i]);
out->writeByte(_objects[i]._pathType);
out->writeSint16BE(_objects[i]._vxPath);
out->writeSint16BE(_objects[i]._vyPath);
out->writeByte(_objects[i]._cycling);
out->writeByte(_objects[i]._cycleNumb);
out->writeByte(_objects[i]._frameTimer);
out->writeByte(_objects[i]._screenIndex);
out->writeSint16BE(_objects[i]._x);
out->writeSint16BE(_objects[i]._y);
out->writeSint16BE(_objects[i]._oldx);
out->writeSint16BE(_objects[i]._oldy);
out->writeSByte(_objects[i]._vx);
out->writeSByte(_objects[i]._vy);
out->writeByte(_objects[i]._objValue);
out->writeByte((_objects[i]._carriedFl) ? 1 : 0);
out->writeByte(_objects[i]._state);
out->writeByte(_objects[i]._priority);
out->writeSint16BE(_objects[i]._viewx);
out->writeSint16BE(_objects[i]._viewy);
out->writeSint16BE(_objects[i]._direction);
out->writeByte(_objects[i]._curSeqNum);
out->writeByte(_objects[i]._curImageNum);
out->writeSByte(_objects[i]._oldvx);
out->writeSByte(_objects[i]._oldvy);
}
}
/**
* Restore objects
*/
void ObjectHandler::restoreObjects(Common::SeekableReadStream *in) {
for (int i = 0; i < _numObj; i++) {
_objects[i]._pathType = (Path) in->readByte();
_objects[i]._vxPath = in->readSint16BE();
_objects[i]._vyPath = in->readSint16BE();
_objects[i]._cycling = (Cycle) in->readByte();
_objects[i]._cycleNumb = in->readByte();
_objects[i]._frameTimer = in->readByte();
_objects[i]._screenIndex = in->readByte();
_objects[i]._x = in->readSint16BE();
_objects[i]._y = in->readSint16BE();
_objects[i]._oldx = in->readSint16BE();
_objects[i]._oldy = in->readSint16BE();
_objects[i]._vx = in->readSByte();
_objects[i]._vy = in->readSByte();
_objects[i]._objValue = in->readByte();
_objects[i]._carriedFl = (in->readByte() == 1);
_objects[i]._state = in->readByte();
_objects[i]._priority = in->readByte();
_objects[i]._viewx = in->readSint16BE();
_objects[i]._viewy = in->readSint16BE();
_objects[i]._direction = in->readSint16BE();
_objects[i]._curSeqNum = in->readByte();
_objects[i]._curImageNum = in->readByte();
_objects[i]._oldvx = in->readSByte();
_objects[i]._oldvy = in->readSByte();
}
}
/**
* Compute max object score
*/
int ObjectHandler::calcMaxScore() {
int score = 0;
for (int i = 0; i < _numObj; i++)
score += _objects[i]._objValue;
return score;
}
/**
* Read Object images
*/
void ObjectHandler::readObjectImages() {
debugC(1, kDebugObject, "readObjectImages");
for (int i = 0; i < _numObj; i++)
_vm->_file->readImage(i, &_objects[i]);
}
bool ObjectHandler::checkBoundary(int16 x, int16 y) {
// Check if Boundary bit set
return (_boundary[y * kCompLineSize + x / 8] & (0x80 >> x % 8)) != 0;
}
/**
* Return maximum allowed movement (from zero to vx) such that object does
* not cross a boundary (either background or another object)
*/
int ObjectHandler::deltaX(const int x1, const int x2, const int vx, int y) const {
// Explanation of algorithm: The boundaries are drawn as contiguous
// lines 1 pixel wide. Since DX,DY are not necessarily 1, we must
// detect boundary crossing. If vx positive, examine each pixel from
// x1 old to x2 new, else x2 old to x1 new, both at the y2 line.
// If vx zero, no need to check. If vy non-zero then examine each
// pixel on the line segment x1 to x2 from y old to y new.
// Fix from Hugo I v1.5:
// Note the diff is munged in the return statement to cater for a special
// cases arising from differences in image widths from one sequence to
// another. The problem occurs reversing direction at a wall where the
// new image intersects before the object can move away. This is cured
// by comparing the intersection with half the object width pos. If the
// intersection is in the other half wrt the intended direction, use the
// desired vx, else use the computed delta. i.e. believe the desired vx
debugC(3, kDebugEngine, "deltaX(%d, %d, %d, %d)", x1, x2, vx, y);
if (vx == 0)
return 0; // Object stationary
y *= kCompLineSize; // Offset into boundary file
if (vx > 0) {
// Moving to right
for (int i = x1 >> 3; i <= (x2 + vx) >> 3; i++) {// Search by byte
int b = Utils::firstBit((byte)(_boundary[y + i] | _objBound[y + i]));
if (b < 8) { // b is index or 8
// Compute x of boundary and test if intersection
b += i << 3;
if ((b >= x1) && (b <= x2 + vx))
return (b < x1 + ((x2 - x1) >> 1)) ? vx : b - x2 - 1; // return dx
}
}
} else {
// Moving to left
for (int i = x2 >> 3; i >= (x1 + vx) >> 3; i--) {// Search by byte
int b = Utils::lastBit((byte)(_boundary[y + i] | _objBound[y + i]));
if (b < 8) { // b is index or 8
// Compute x of boundary and test if intersection
b += i << 3;
if ((b >= x1 + vx) && (b <= x2))
return (b > x1 + ((x2 - x1) >> 1)) ? vx : b - x1 + 1; // return dx
}
}
}
return vx;
}
/**
* Similar to Delta_x, but for movement in y direction. Special case of
* bytes at end of line segment; must only count boundary bits falling on
* line segment.
*/
int ObjectHandler::deltaY(const int x1, const int x2, const int vy, const int y) const {
debugC(3, kDebugEngine, "deltaY(%d, %d, %d, %d)", x1, x2, vy, y);
if (vy == 0)
return 0; // Object stationary
int inc = (vy > 0) ? 1 : -1;
for (int j = y + inc; j != (y + vy + inc); j += inc) { //Search by byte
for (int i = x1 >> 3; i <= x2 >> 3; i++) {
int b = _boundary[j * kCompLineSize + i] | _objBound[j * kCompLineSize + i];
if (b != 0) { // Any bit set
// Make sure boundary bits fall on line segment
if (i == (x2 >> 3)) // Adjust right end
b &= 0xff << ((i << 3) + 7 - x2);
else if (i == (x1 >> 3)) // Adjust left end
b &= 0xff >> (x1 - (i << 3));
if (b)
return j - y - inc;
}
}
}
return vy;
}
/**
* Store a horizontal line segment in the object boundary file
*/
void ObjectHandler::storeBoundary(const int x1, const int x2, const int y) {
debugC(5, kDebugEngine, "storeBoundary(%d, %d, %d)", x1, x2, y);
for (int i = x1 >> 3; i <= x2 >> 3; i++) { // For each byte in line
byte *b = &_objBound[y * kCompLineSize + i];// get boundary byte
if (i == x2 >> 3) // Adjust right end
*b |= 0xff << ((i << 3) + 7 - x2);
else if (i == x1 >> 3) // Adjust left end
*b |= 0xff >> (x1 - (i << 3));
else
*b = 0xff;
}
}
/**
* Clear a horizontal line segment in the object boundary file
*/
void ObjectHandler::clearBoundary(const int x1, const int x2, const int y) {
debugC(5, kDebugEngine, "clearBoundary(%d, %d, %d)", x1, x2, y);
for (int i = x1 >> 3; i <= x2 >> 3; i++) { // For each byte in line
byte *b = &_objBound[y * kCompLineSize + i];// get boundary byte
if (i == x2 >> 3) // Adjust right end
*b &= ~(0xff << ((i << 3) + 7 - x2));
else if (i == x1 >> 3) // Adjust left end
*b &= ~(0xff >> (x1 - (i << 3)));
else
*b = 0;
}
}
/**
* Clear a horizontal line segment in the screen boundary file
* Used to fix some data issues
*/
void ObjectHandler::clearScreenBoundary(const int x1, const int x2, const int y) {
debugC(5, kDebugEngine, "clearScreenBoundary(%d, %d, %d)", x1, x2, y);
for (int i = x1 >> 3; i <= x2 >> 3; i++) { // For each byte in line
byte *b = &_boundary[y * kCompLineSize + i];// get boundary byte
if (i == x2 >> 3) // Adjust right end
*b &= ~(0xff << ((i << 3) + 7 - x2));
else if (i == x1 >> 3) // Adjust left end
*b &= ~(0xff >> (x1 - (i << 3)));
else
*b = 0;
}
}
/**
* An object has collided with a boundary. See if any actions are required
*/
void ObjectHandler::boundaryCollision(Object *obj) {
debugC(1, kDebugEngine, "boundaryCollision");
if (obj == _vm->_hero) {
// Hotspots only relevant to HERO
int x;
if (obj->_vx > 0)
x = obj->_x + obj->_currImagePtr->_x2;
else
x = obj->_x + obj->_currImagePtr->_x1;
int y = obj->_y + obj->_currImagePtr->_y2;
int16 index = _vm->_mouse->findExit(x, y, obj->_screenIndex);
if (index >= 0)
_vm->_scheduler->insertActionList(_vm->_mouse->getHotspotActIndex(index));
} else {
// Check whether an object collided with HERO
int dx = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1 - obj->_x - obj->_currImagePtr->_x1;
int dy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - obj->_y - obj->_currImagePtr->_y2;
// If object's radius is infinity, use a closer value
int8 radius = obj->_radius;
if (radius < 0)
radius = kStepDx * 2;
if ((abs(dx) <= radius) && (abs(dy) <= radius))
_vm->_scheduler->insertActionList(obj->_actIndex);
}
}
} // End of namespace Hugo

164
engines/hugo/object.h Normal file
View File

@@ -0,0 +1,164 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_OBJECT_H
#define HUGO_OBJECT_H
#include "common/file.h"
namespace Hugo {
struct Target { // Secondary target for action
uint16 _nounIndex; // Secondary object
uint16 _verbIndex; // Action on secondary object
};
struct Uses { // Define uses of certain objects
int16 _objId; // Primary object
uint16 _dataIndex; // String if no secondary object matches
Target *_targets; // List of secondary targets
};
class ObjectHandler {
public:
ObjectHandler(HugoEngine *vm);
virtual ~ObjectHandler();
Overlay _objBound;
Overlay _boundary; // Boundary overlay file
Overlay _overlay; // First overlay file
Overlay _ovlBase; // First overlay base file
Object *_objects;
uint16 _numObj;
byte getBoundaryOverlay(uint16 index) const;
byte getObjectBoundary(uint16 index) const;
byte getBaseBoundary(uint16 index) const;
byte getFirstOverlay(uint16 index) const;
int deltaX(const int x1, const int x2, const int vx, int y) const;
int deltaY(const int x1, const int x2, const int vy, const int y) const;
void boundaryCollision(Object *obj);
void clearBoundary(const int x1, const int x2, const int y);
void clearScreenBoundary(const int x1, const int x2, const int y);
void storeBoundary(const int x1, const int x2, const int y);
virtual void homeIn(const int objIndex1, const int objIndex2, const int8 objDx, const int8 objDy) = 0;
virtual void moveObjects() = 0;
virtual void updateImages() = 0;
virtual void swapImages(int objIndex1, int objIndex2, bool restoring) = 0;
bool isCarrying(uint16 wordIndex);
bool findObjectSpace(Object *obj, int16 *destx, int16 *desty);
int calcMaxScore();
int16 findObject(uint16 x, uint16 y);
void freeObjects();
void loadObjectArr(Common::ReadStream &in);
void loadObjectUses(Common::ReadStream &in);
void loadNumObj(Common::ReadStream &in);
void lookObject(Object *obj);
void readObjectImages();
void readObject(Common::ReadStream &in, Object &curObject);
void readUse(Common::ReadStream &in, Uses &curUse);
void restoreAllSeq();
void restoreObjects(Common::SeekableReadStream *in);
void saveObjects(Common::WriteStream *out);
void saveSeq(Object *obj);
void setCarriedScreen(int screenNum);
void showTakeables();
void useObject(int16 objId);
static int y2comp(const void *a, const void *b);
bool isCarried(int objIndex) const;
void setCarry(int objIndex, bool val);
void setVelocity(int objIndex, int8 vx, int8 vy);
void setPath(int objIndex, Path pathType, int16 vxPath, int16 vyPath);
protected:
HugoEngine *_vm;
static const int kEdge = 10; // Closest object can get to edge of screen
static const int kEdge2 = kEdge * 2; // Push object further back on edge collision
static const int kMaxObjNumb = 128; // Used in Update_images()
uint16 _objCount;
Uses *_uses;
uint16 _usesSize;
void restoreSeq(Object *obj);
inline bool checkBoundary(int16 x, int16 y);
static inline int sign(int a) { return (a < 0) ? -1 : 1; }
};
class ObjectHandler_v1d : public ObjectHandler {
public:
ObjectHandler_v1d(HugoEngine *vm);
~ObjectHandler_v1d() override;
void homeIn(const int objIndex1, const int objIndex2, const int8 objDx, const int8 objDy) override;
void moveObjects() override;
void updateImages() override;
void swapImages(int objIndex1, int objIndex2, bool restoring) override;
};
class ObjectHandler_v2d : public ObjectHandler_v1d {
public:
ObjectHandler_v2d(HugoEngine *vm);
~ObjectHandler_v2d() override;
void moveObjects() override;
void updateImages() override;
void homeIn(const int objIndex1, const int objIndex2, const int8 objDx, const int8 objDy) override;
};
class ObjectHandler_v3d : public ObjectHandler_v2d {
public:
ObjectHandler_v3d(HugoEngine *vm);
~ObjectHandler_v3d() override;
void moveObjects() override;
void swapImages(int objIndex1, int objIndex2, bool restoring) override;
};
class ObjectHandler_v1w : public ObjectHandler_v3d {
public:
ObjectHandler_v1w(HugoEngine *vm);
~ObjectHandler_v1w() override;
void moveObjects() override;
void updateImages() override;
void swapImages(int objIndex1, int objIndex2, bool restoring) override;
};
} // End of namespace Hugo
#endif //HUGO_OBJECT_H

386
engines/hugo/object_v1d.cpp Normal file
View File

@@ -0,0 +1,386 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/random.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/object.h"
#include "hugo/display.h"
#include "hugo/file.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
namespace Hugo {
ObjectHandler_v1d::ObjectHandler_v1d(HugoEngine *vm) : ObjectHandler(vm) {
}
ObjectHandler_v1d::~ObjectHandler_v1d() {
}
/**
* Draw all objects on screen as follows:
* 1. Sort 'FLOATING' objects in order of y2 (base of object)
* 2. Display new object frames/positions in dib
* Finally, cycle any animating objects to next frame
*/
void ObjectHandler_v1d::updateImages() {
debugC(5, kDebugObject, "updateImages");
// Initialize the index array to visible objects in current screen
int objNumb = 0;
byte objindex[kMaxObjNumb]; // Array of indeces to objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i];
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling >= kCycleAlmostInvisible))
objindex[objNumb++] = i;
}
// Sort the objects into increasing y+y2 (painter's algorithm)
qsort(objindex, objNumb, sizeof(objindex[0]), y2comp);
// Add each visible object to display list
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
// Count down inter-frame timer
if (obj->_frameTimer)
obj->_frameTimer--;
if (obj->_cycling > kCycleAlmostInvisible) { // Only if visible
switch (obj->_cycling) {
case kCycleNotCycling:
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, false);
break;
case kCycleForward:
if (obj->_frameTimer) // Not time to see next frame yet
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, false);
else
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr->_nextSeqPtr, false);
break;
case kCycleBackward: {
Seq *seqPtr = obj->_currImagePtr;
if (!obj->_frameTimer) { // Show next frame
while (seqPtr->_nextSeqPtr != obj->_currImagePtr)
seqPtr = seqPtr->_nextSeqPtr;
}
_vm->_screen->displayFrame(obj->_x, obj->_y, seqPtr, false);
break;
}
default:
break;
}
}
}
_vm->_scheduler->waitForRefresh();
// Cycle any animating objects
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
if (obj->_cycling != kCycleInvisible) {
// Only if it's visible
if (obj->_cycling == kCycleAlmostInvisible)
obj->_cycling = kCycleInvisible;
// Now Rotate to next picture in sequence
switch (obj->_cycling) {
case kCycleNotCycling:
break;
case kCycleForward:
if (!obj->_frameTimer) {
// Time to step to next frame
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is last frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr->_nextSeqPtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb) { // Decr cycleNumb if Non-continuous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
case kCycleBackward: {
if (!obj->_frameTimer) {
// Time to step to prev frame
Seq *seqPtr = obj->_currImagePtr;
while (obj->_currImagePtr->_nextSeqPtr != seqPtr)
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is first frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb){ // Decr cycleNumb if Non-contiunous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
}
default:
break;
}
obj->_oldx = obj->_x;
obj->_oldy = obj->_y;
}
}
}
/**
* Update all object positions. Process object 'local' events
* including boundary events and collisions
*/
void ObjectHandler_v1d::moveObjects() {
debugC(4, kDebugObject, "moveObjects");
// Added to DOS version in order to handle mouse properly
// Do special route processing
_vm->_route->processRoute();
// Perform any adjustments to velocity based on special path types
// and store all (visible) object baselines into the boundary file.
// Don't store foreground or background objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if (obj->_screenIndex == *_vm->_screenPtr) {
switch (obj->_pathType) {
case kPathChase: {
// Allowable motion wrt boundary
int dx = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1 - obj->_x - currImage->_x1;
int dy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - obj->_y - currImage->_y2 - 1;
if (abs(dx) <= 1)
obj->_vx = 0;
else
obj->_vx = (dx > 0) ? MIN(dx, obj->_vxPath) : MAX(dx, -obj->_vxPath);
if (abs(dy) <= 1)
obj->_vy = 0;
else
obj->_vy = (dy > 0) ? MIN(dy, obj->_vyPath) : MAX(dy, -obj->_vyPath);
// Set first image in sequence (if multi-seq object)
if (obj->_seqNumb == 4) {
if (!obj->_vx) { // Got 4 directions
if (obj->_vx != obj->_oldvx) {// vx just stopped
if (dy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (dx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
}
if (obj->_vx || obj->_vy) {
if (obj->_seqNumb > 1)
obj->_cycling = kCycleForward;
} else {
obj->_cycling = kCycleNotCycling;
boundaryCollision(obj); // Must have got hero!
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
break;
}
case kPathWander:
if (!_vm->_rnd->getRandomNumber(3 * _vm->_normalTPS)) { // Kick on random interval
obj->_vx = _vm->_rnd->getRandomNumber(obj->_vxPath << 1) - obj->_vxPath;
obj->_vy = _vm->_rnd->getRandomNumber(obj->_vyPath << 1) - obj->_vyPath;
// Set first image in sequence (if multi-seq object)
if (obj->_seqNumb > 1) {
if (!obj->_vx && (obj->_seqNumb > 2)) {
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (obj->_vy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (obj->_vx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
if (obj->_vx || obj->_vy)
obj->_cycling = kCycleForward;
else
obj->_cycling = kCycleNotCycling;
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
}
break;
default:
; // Really, nothing
}
// Store boundaries
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(obj->_x + currImage->_x1, obj->_x + currImage->_x2, obj->_y + currImage->_y2);
}
}
// Move objects, allowing for boundaries
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_vx || obj->_vy)) {
// Only process if it's moving
// Do object movement. Delta_x,y return allowed movement in x,y
// to move as close to a boundary as possible without crossing it.
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
// object coordinates
int x1 = obj->_x + currImage->_x1; // Left edge of object
int x2 = obj->_x + currImage->_x2; // Right edge
int y1 = obj->_y + currImage->_y1; // Top edge
int y2 = obj->_y + currImage->_y2; // Bottom edge
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(x1, x2, y2); // Clear our own boundary
// Allowable motion wrt boundary
int dx = deltaX(x1, x2, obj->_vx, y2);
if (dx != obj->_vx) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vx = 0;
}
int dy = deltaY(x1, x2, obj->_vy, y2);
if (dy != obj->_vy) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vy = 0;
}
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(x1, x2, y2); // Re-store our own boundary
obj->_x += dx; // Update object position
obj->_y += dy;
// Don't let object go outside screen
if (x1 < kEdge)
obj->_x = kEdge2;
if (x2 > (kXPix - kEdge))
obj->_x = kXPix - kEdge2 - (x2 - x1);
if (y1 < kEdge)
obj->_y = kEdge2;
if (y2 > (kYPix - kEdge))
obj->_y = kYPix - kEdge2 - (y2 - y1);
if ((obj->_vx == 0) && (obj->_vy == 0))
obj->_cycling = kCycleNotCycling;
}
}
// Clear all object baselines from the boundary file.
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(obj->_oldx + currImage->_x1, obj->_oldx + currImage->_x2, obj->_oldy + currImage->_y2);
}
}
/**
* Swap all the images of one object with another. Set hero_image (we make
* the assumption for now that the first obj is always the HERO) to the object
* number of the swapped image
*/
void ObjectHandler_v1d::swapImages(int objIndex1, int objIndex2, bool restoring) {
debugC(1, kDebugObject, "swapImages(%d, %d, %d)", objIndex1, objIndex2, restoring);
// WORKAROUND: If Hero's image is swapped then restoring a game sets Hero's
// direction to right. This bug is quite noticeable in Hugo2 as Penelope.
// This was fixed in Hugo3 and Windows. We backport the fix to this version,
// but only when restoring so that original in-game behavior is preserved.
if (restoring) {
saveSeq(&_objects[objIndex1]);
}
SeqList tmpSeqList[kMaxSeqNumb];
int seqListSize = sizeof(SeqList) * kMaxSeqNumb;
memmove(tmpSeqList, _objects[objIndex1]._seqList, seqListSize);
memmove(_objects[objIndex1]._seqList, _objects[objIndex2]._seqList, seqListSize);
memmove(_objects[objIndex2]._seqList, tmpSeqList, seqListSize);
if (restoring) {
restoreSeq(&_objects[objIndex1]);
} else {
_objects[objIndex1]._currImagePtr = _objects[objIndex1]._seqList[0]._seqPtr;
}
_objects[objIndex2]._currImagePtr = _objects[objIndex2]._seqList[0]._seqPtr;
_vm->_heroImage = (_vm->_heroImage == kHeroIndex) ? objIndex2 : kHeroIndex;
}
void ObjectHandler_v1d::homeIn(int objIndex1, const int objIndex2, const int8 objDx, const int8 objDy) {
// object obj1 will home in on object obj2
Object *obj1 = &_objects[objIndex1];
Object *obj2 = &_objects[objIndex2];
obj1->_pathType = kPathAuto;
int dx = obj1->_x + obj1->_currImagePtr->_x1 - obj2->_x - obj2->_currImagePtr->_x1;
int dy = obj1->_y + obj1->_currImagePtr->_y1 - obj2->_y - obj2->_currImagePtr->_y1;
if (dx == 0) // Don't EVER divide by zero!
dx = 1;
if (dy == 0)
dy = 1;
if (abs(dx) > abs(dy)) {
obj1->_vx = objDx * -sign(dx);
obj1->_vy = abs((objDy * dy) / dx) * -sign(dy);
} else {
obj1->_vy = objDy * sign(dy);
obj1->_vx = abs((objDx * dx) / dy) * sign(dx);
}
}
} // End of namespace Hugo

382
engines/hugo/object_v1w.cpp Normal file
View File

@@ -0,0 +1,382 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/random.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/object.h"
#include "hugo/display.h"
#include "hugo/file.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
namespace Hugo {
ObjectHandler_v1w::ObjectHandler_v1w(HugoEngine *vm) : ObjectHandler_v3d(vm) {
}
ObjectHandler_v1w::~ObjectHandler_v1w() {
}
/**
* Draw all objects on screen as follows:
* 1. Sort 'FLOATING' objects in order of y2 (base of object)
* 2. Display new object frames/positions in dib
* Finally, cycle any animating objects to next frame
*/
void ObjectHandler_v1w::updateImages() {
debugC(5, kDebugObject, "updateImages");
// Initialize the index array to visible objects in current screen
int objNumb = 0;
byte objindex[kMaxObjNumb]; // Array of indeces to objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i];
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling >= kCycleAlmostInvisible))
objindex[objNumb++] = i;
}
// Sort the objects into increasing y+y2 (painter's algorithm)
qsort(objindex, objNumb, sizeof(objindex[0]), y2comp);
// Add each visible object to display list
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
// Count down inter-frame timer
if (obj->_frameTimer)
obj->_frameTimer--;
if (obj->_cycling > kCycleAlmostInvisible) { // Only if visible
switch (obj->_cycling) {
case kCycleNotCycling:
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, obj->_priority == kPriorityOverOverlay);
break;
case kCycleForward:
if (obj->_frameTimer) // Not time to see next frame yet
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, obj->_priority == kPriorityOverOverlay);
else
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr->_nextSeqPtr, obj->_priority == kPriorityOverOverlay);
break;
case kCycleBackward: {
Seq *seqPtr = obj->_currImagePtr;
if (!obj->_frameTimer) { // Show next frame
while (seqPtr->_nextSeqPtr != obj->_currImagePtr)
seqPtr = seqPtr->_nextSeqPtr;
}
_vm->_screen->displayFrame(obj->_x, obj->_y, seqPtr, obj->_priority == kPriorityOverOverlay);
break;
}
default:
break;
}
}
}
// Cycle any animating objects
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
if (obj->_cycling != kCycleInvisible) {
// Only if it's visible
if (obj->_cycling == kCycleAlmostInvisible)
obj->_cycling = kCycleInvisible;
// Now Rotate to next picture in sequence
switch (obj->_cycling) {
case kCycleNotCycling:
break;
case kCycleForward:
if (!obj->_frameTimer) {
// Time to step to next frame
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is last frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr->_nextSeqPtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb) { // Decr cycleNumb if Non-continuous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
case kCycleBackward: {
if (!obj->_frameTimer) {
// Time to step to prev frame
Seq *seqPtr = obj->_currImagePtr;
while (obj->_currImagePtr->_nextSeqPtr != seqPtr)
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is first frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb){ // Decr cycleNumb if Non-continuous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
}
default:
break;
}
obj->_oldx = obj->_x;
obj->_oldy = obj->_y;
}
}
}
/**
* Update all object positions. Process object 'local' events
* including boundary events and collisions
*/
void ObjectHandler_v1w::moveObjects() {
debugC(4, kDebugObject, "moveObjects");
// Do special route processing
_vm->_route->processRoute();
// Perform any adjustments to velocity based on special path types
// and store all (visible) object baselines into the boundary file.
// Don't store foreground or background objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if (obj->_screenIndex == *_vm->_screenPtr) {
switch (obj->_pathType) {
case kPathChase:
case kPathChase2: {
int8 radius = obj->_radius; // Default to object's radius
if (radius < 0) // If radius infinity, use closer value
radius = kStepDx;
// Allowable motion wrt boundary
int dx = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1 - obj->_x - currImage->_x1;
int dy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - obj->_y - currImage->_y2 - 1;
if (abs(dx) <= radius)
obj->_vx = 0;
else
obj->_vx = (dx > 0) ? MIN(dx, obj->_vxPath) : MAX(dx, -obj->_vxPath);
if (abs(dy) <= radius)
obj->_vy = 0;
else
obj->_vy = (dy > 0) ? MIN(dy, obj->_vyPath) : MAX(dy, -obj->_vyPath);
// Set first image in sequence (if multi-seq object)
switch (obj->_seqNumb) {
case 4:
if (!obj->_vx) { // Got 4 directions
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dy >= 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (dx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
case 3:
case 2:
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dx > 0) // Left & right only
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
default:
break;
}
if (obj->_vx || obj->_vy) {
obj->_cycling = kCycleForward;
} else {
obj->_cycling = kCycleNotCycling;
boundaryCollision(obj); // Must have got hero!
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
break;
}
case kPathWander2:
case kPathWander:
if (!_vm->_rnd->getRandomNumber(3 * _vm->_normalTPS)) { // Kick on random interval
obj->_vx = _vm->_rnd->getRandomNumber(obj->_vxPath << 1) - obj->_vxPath;
obj->_vy = _vm->_rnd->getRandomNumber(obj->_vyPath << 1) - obj->_vyPath;
// Set first image in sequence (if multi-seq object)
if (obj->_seqNumb > 1) {
if (!obj->_vx && (obj->_seqNumb >= 4)) {
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (obj->_vy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (obj->_vx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
}
if (obj->_vx || obj->_vy)
obj->_cycling = kCycleForward;
break;
default:
; // Really, nothing
}
// Store boundaries
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(obj->_x + currImage->_x1, obj->_x + currImage->_x2, obj->_y + currImage->_y2);
}
}
// Move objects, allowing for boundaries
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_vx || obj->_vy)) {
// Only process if it's moving
// Do object movement. Delta_x,y return allowed movement in x,y
// to move as close to a boundary as possible without crossing it.
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
// object coordinates
int x1 = obj->_x + currImage->_x1; // Left edge of object
int x2 = obj->_x + currImage->_x2; // Right edge
int y1 = obj->_y + currImage->_y1; // Top edge
int y2 = obj->_y + currImage->_y2; // Bottom edge
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(x1, x2, y2); // Clear our own boundary
// Allowable motion wrt boundary
int dx = deltaX(x1, x2, obj->_vx, y2);
if (dx != obj->_vx) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vx = 0;
}
int dy = deltaY(x1, x2, obj->_vy, y2);
if (dy != obj->_vy) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vy = 0;
}
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(x1, x2, y2); // Re-store our own boundary
obj->_x += dx; // Update object position
obj->_y += dy;
// Don't let object go outside screen
if (x1 < kEdge)
obj->_x = kEdge2;
if (x2 > (kXPix - kEdge))
obj->_x = kXPix - kEdge2 - (x2 - x1);
if (y1 < kEdge)
obj->_y = kEdge2;
if (y2 > (kYPix - kEdge))
obj->_y = kYPix - kEdge2 - (y2 - y1);
if ((obj->_vx == 0) && (obj->_vy == 0) && (obj->_pathType != kPathWander2) && (obj->_pathType != kPathChase2))
obj->_cycling = kCycleNotCycling;
}
}
// Clear all object baselines from the boundary file.
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(obj->_oldx + currImage->_x1, obj->_oldx + currImage->_x2, obj->_oldy + currImage->_y2);
}
// If maze mode is enabled, do special maze processing
if (_vm->_maze._enabledFl) {
Seq *currImage = _vm->_hero->_currImagePtr; // Get ptr to current image
// hero coordinates
int x1 = _vm->_hero->_x + currImage->_x1; // Left edge of object
int x2 = _vm->_hero->_x + currImage->_x2; // Right edge
int y1 = _vm->_hero->_y + currImage->_y1; // Top edge
int y2 = _vm->_hero->_y + currImage->_y2; // Bottom edge
_vm->_scheduler->processMaze(x1, x2, y1, y2);
}
}
/**
* Swap all the images of one object with another. Set hero_image (we make
* the assumption for now that the first obj is always the HERO) to the object
* number of the swapped image
*/
void ObjectHandler_v1w::swapImages(int objIndex1, int objIndex2, bool restoring) {
debugC(1, kDebugObject, "swapImages(%d, %d, %d)", objIndex1, objIndex2, restoring);
saveSeq(&_objects[objIndex1]);
SeqList tmpSeqList[kMaxSeqNumb];
int seqListSize = sizeof(SeqList) * kMaxSeqNumb;
memmove(tmpSeqList, _objects[objIndex1]._seqList, seqListSize);
memmove(_objects[objIndex1]._seqList, _objects[objIndex2]._seqList, seqListSize);
memmove(_objects[objIndex2]._seqList, tmpSeqList, seqListSize);
restoreSeq(&_objects[objIndex1]);
_objects[objIndex2]._currImagePtr = _objects[objIndex2]._seqList[0]._seqPtr;
_vm->_heroImage = (_vm->_heroImage == kHeroIndex) ? objIndex2 : kHeroIndex;
// Make sure baseline stays constant
_objects[objIndex1]._y += _objects[objIndex2]._currImagePtr->_y2 - _objects[objIndex1]._currImagePtr->_y2;
}
} // End of namespace Hugo

382
engines/hugo/object_v2d.cpp Normal file
View File

@@ -0,0 +1,382 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/random.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/object.h"
#include "hugo/display.h"
#include "hugo/file.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
namespace Hugo {
ObjectHandler_v2d::ObjectHandler_v2d(HugoEngine *vm) : ObjectHandler_v1d(vm) {
}
ObjectHandler_v2d::~ObjectHandler_v2d() {
}
/**
* Draw all objects on screen as follows:
* 1. Sort 'FLOATING' objects in order of y2 (base of object)
* 2. Display new object frames/positions in dib
* Finally, cycle any animating objects to next frame
*/
void ObjectHandler_v2d::updateImages() {
debugC(5, kDebugObject, "updateImages");
// Initialize the index array to visible objects in current screen
int objNumb = 0;
byte objindex[kMaxObjNumb]; // Array of indices to objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i];
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling >= kCycleAlmostInvisible))
objindex[objNumb++] = i;
}
// Sort the objects into increasing y+y2 (painter's algorithm)
qsort(objindex, objNumb, sizeof(objindex[0]), y2comp);
// Add each visible object to display list
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
// Count down inter-frame timer
if (obj->_frameTimer)
obj->_frameTimer--;
if (obj->_cycling > kCycleAlmostInvisible) { // Only if visible
switch (obj->_cycling) {
case kCycleNotCycling:
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, obj->_priority == kPriorityOverOverlay);
break;
case kCycleForward:
if (obj->_frameTimer) // Not time to see next frame yet
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr, obj->_priority == kPriorityOverOverlay);
else
_vm->_screen->displayFrame(obj->_x, obj->_y, obj->_currImagePtr->_nextSeqPtr, obj->_priority == kPriorityOverOverlay);
break;
case kCycleBackward: {
Seq *seqPtr = obj->_currImagePtr;
if (!obj->_frameTimer) { // Show next frame
while (seqPtr->_nextSeqPtr != obj->_currImagePtr)
seqPtr = seqPtr->_nextSeqPtr;
}
_vm->_screen->displayFrame(obj->_x, obj->_y, seqPtr, obj->_priority == kPriorityOverOverlay);
break;
}
default:
break;
}
}
}
_vm->_scheduler->waitForRefresh();
// Cycle any animating objects
for (int i = 0; i < objNumb; i++) {
Object *obj = &_objects[objindex[i]];
if (obj->_cycling != kCycleInvisible) {
// Only if it's visible
if (obj->_cycling == kCycleAlmostInvisible)
obj->_cycling = kCycleInvisible;
// Now Rotate to next picture in sequence
switch (obj->_cycling) {
case kCycleNotCycling:
break;
case kCycleForward:
if (!obj->_frameTimer) {
// Time to step to next frame
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is last frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr->_nextSeqPtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb) { // Decr cycleNumb if Non-continuous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
case kCycleBackward: {
if (!obj->_frameTimer) {
// Time to step to prev frame
Seq *seqPtr = obj->_currImagePtr;
while (obj->_currImagePtr->_nextSeqPtr != seqPtr)
obj->_currImagePtr = obj->_currImagePtr->_nextSeqPtr;
// Find out if this is first frame of sequence
// If so, reset frame_timer and decrement n_cycle
if (obj->_frameInterval || obj->_cycleNumb) {
obj->_frameTimer = obj->_frameInterval;
for (int j = 0; j < obj->_seqNumb; j++) {
if (obj->_currImagePtr == obj->_seqList[j]._seqPtr) {
if (obj->_cycleNumb){ // Decr cycleNumb if Non-continuous
if (!--obj->_cycleNumb)
obj->_cycling = kCycleNotCycling;
}
}
}
}
}
break;
}
default:
break;
}
obj->_oldx = obj->_x;
obj->_oldy = obj->_y;
}
}
}
/**
* Update all object positions. Process object 'local' events
* including boundary events and collisions
*/
void ObjectHandler_v2d::moveObjects() {
debugC(4, kDebugObject, "moveObjects");
// Added to DOS version in order to handle mouse properly
// Do special route processing
_vm->_route->processRoute();
// Perform any adjustments to velocity based on special path types
// and store all (visible) object baselines into the boundary file.
// Don't store foreground or background objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if (obj->_screenIndex == *_vm->_screenPtr) {
switch (obj->_pathType) {
case kPathChase:
case kPathChase2: {
int8 radius = obj->_radius; // Default to object's radius
if (radius < 0) // If radius infinity, use closer value
radius = kStepDx;
// Allowable motion wrt boundary
int dx = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1 - obj->_x - currImage->_x1;
int dy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - obj->_y - currImage->_y2 - 1;
if (abs(dx) <= radius)
obj->_vx = 0;
else
obj->_vx = (dx > 0) ? MIN(dx, obj->_vxPath) : MAX(dx, -obj->_vxPath);
if (abs(dy) <= radius)
obj->_vy = 0;
else
obj->_vy = (dy > 0) ? MIN(dy, obj->_vyPath) : MAX(dy, -obj->_vyPath);
// Set first image in sequence (if multi-seq object)
switch (obj->_seqNumb) {
case 4:
if (!obj->_vx) { // Got 4 directions
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (dx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
case 3:
case 2:
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dx > 0) // Left & right only
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
default:
break;
}
if (obj->_vx || obj->_vy) {
obj->_cycling = kCycleForward;
} else {
obj->_cycling = kCycleNotCycling;
boundaryCollision(obj); // Must have got hero!
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
break;
}
case kPathWander2:
case kPathWander:
if (!_vm->_rnd->getRandomNumber(3 * _vm->_normalTPS)) { // Kick on random interval
obj->_vx = _vm->_rnd->getRandomNumber(obj->_vxPath << 1) - obj->_vxPath;
obj->_vy = _vm->_rnd->getRandomNumber(obj->_vyPath << 1) - obj->_vyPath;
// Set first image in sequence (if multi-seq object)
if (obj->_seqNumb > 1) {
if (!obj->_vx && (obj->_seqNumb >= 4)) {
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (obj->_vy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (obj->_vx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
}
if (obj->_vx || obj->_vy)
obj->_cycling = kCycleForward;
break;
default:
; // Really, nothing
}
// Store boundaries
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(obj->_x + currImage->_x1, obj->_x + currImage->_x2, obj->_y + currImage->_y2);
}
}
// Move objects, allowing for boundaries
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_vx || obj->_vy)) {
// Only process if it's moving
// Do object movement. Delta_x,y return allowed movement in x,y
// to move as close to a boundary as possible without crossing it.
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
// object coordinates
int x1 = obj->_x + currImage->_x1; // Left edge of object
int x2 = obj->_x + currImage->_x2; // Right edge
int y1 = obj->_y + currImage->_y1; // Top edge
int y2 = obj->_y + currImage->_y2; // Bottom edge
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(x1, x2, y2); // Clear our own boundary
// Allowable motion wrt boundary
int dx = deltaX(x1, x2, obj->_vx, y2);
if (dx != obj->_vx) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vx = 0;
}
int dy = deltaY(x1, x2, obj->_vy, y2);
if (dy != obj->_vy) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vy = 0;
}
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(x1, x2, y2); // Re-store our own boundary
obj->_x += dx; // Update object position
obj->_y += dy;
// Don't let object go outside screen
if (x1 < kEdge)
obj->_x = kEdge2;
if (x2 > (kXPix - kEdge))
obj->_x = kXPix - kEdge2 - (x2 - x1);
if (y1 < kEdge)
obj->_y = kEdge2;
if (y2 > (kYPix - kEdge))
obj->_y = kYPix - kEdge2 - (y2 - y1);
if ((obj->_vx == 0) && (obj->_vy == 0) && (obj->_pathType != kPathWander2) && (obj->_pathType != kPathChase2))
obj->_cycling = kCycleNotCycling;
}
}
// Clear all object baselines from the boundary file.
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(obj->_oldx + currImage->_x1, obj->_oldx + currImage->_x2, obj->_oldy + currImage->_y2);
}
// If maze mode is enabled, do special maze processing
if (_vm->_maze._enabledFl) {
Seq *currImage = _vm->_hero->_currImagePtr; // Get ptr to current image
// hero coordinates
int x1 = _vm->_hero->_x + currImage->_x1; // Left edge of object
int x2 = _vm->_hero->_x + currImage->_x2; // Right edge
int y1 = _vm->_hero->_y + currImage->_y1; // Top edge
int y2 = _vm->_hero->_y + currImage->_y2; // Bottom edge
_vm->_scheduler->processMaze(x1, x2, y1, y2);
}
}
void ObjectHandler_v2d::homeIn(const int objIndex1, const int objIndex2, const int8 objDx, const int8 objDy) {
// object obj1 will home in on object obj2
Object *obj1 = &_objects[objIndex1];
Object *obj2 = &_objects[objIndex2];
obj1->_pathType = kPathAuto;
int dx = obj1->_x + obj1->_currImagePtr->_x1 - obj2->_x - obj2->_currImagePtr->_x1;
int dy = obj1->_y + obj1->_currImagePtr->_y1 - obj2->_y - obj2->_currImagePtr->_y1;
if (dx == 0) // Don't EVER divide by zero!
dx = 1;
if (dy == 0)
dy = 1;
if (abs(dx) > abs(dy)) {
obj1->_vx = objDx * -sign(dx);
obj1->_vy = abs((objDy * dy) / dx) * -sign(dy);
} else {
obj1->_vy = objDy * -sign(dy);
obj1->_vx = abs((objDx * dx) / dy) * -sign(dx);
}
}
} // End of namespace Hugo

267
engines/hugo/object_v3d.cpp Normal file
View File

@@ -0,0 +1,267 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/debug.h"
#include "common/system.h"
#include "common/random.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/object.h"
#include "hugo/display.h"
#include "hugo/file.h"
#include "hugo/route.h"
#include "hugo/util.h"
#include "hugo/parser.h"
#include "hugo/schedule.h"
namespace Hugo {
ObjectHandler_v3d::ObjectHandler_v3d(HugoEngine *vm) : ObjectHandler_v2d(vm) {
}
ObjectHandler_v3d::~ObjectHandler_v3d() {
}
/**
* Update all object positions. Process object 'local' events
* including boundary events and collisions
*/
void ObjectHandler_v3d::moveObjects() {
debugC(4, kDebugObject, "moveObjects");
// Added to DOS version in order to handle mouse properly
// Do special route processing
_vm->_route->processRoute();
// Perform any adjustments to velocity based on special path types
// and store all (visible) object baselines into the boundary file.
// Don't store foreground or background objects
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if (obj->_screenIndex == *_vm->_screenPtr) {
switch (obj->_pathType) {
case kPathChase:
case kPathChase2: {
int8 radius = obj->_radius; // Default to object's radius
if (radius < 0) // If radius infinity, use closer value
radius = kStepDx;
// Allowable motion wrt boundary
int dx = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1 - obj->_x - currImage->_x1;
int dy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - obj->_y - currImage->_y2 - 1;
if (abs(dx) <= radius)
obj->_vx = 0;
else
obj->_vx = (dx > 0) ? MIN(dx, obj->_vxPath) : MAX(dx, -obj->_vxPath);
if (abs(dy) <= radius)
obj->_vy = 0;
else
obj->_vy = (dy > 0) ? MIN(dy, obj->_vyPath) : MAX(dy, -obj->_vyPath);
// Set first image in sequence (if multi-seq object)
switch (obj->_seqNumb) {
case 4:
if (!obj->_vx) { // Got 4 directions
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dy >= 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (dx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
case 3:
case 2:
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (dx > 0) // Left & right only
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
break;
default:
break;
}
if (obj->_vx || obj->_vy) {
obj->_cycling = kCycleForward;
} else {
obj->_cycling = kCycleNotCycling;
boundaryCollision(obj); // Must have got hero!
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
break;
}
case kPathWander2:
case kPathWander:
if (!_vm->_rnd->getRandomNumber(3 * _vm->_normalTPS)) { // Kick on random interval
obj->_vx = _vm->_rnd->getRandomNumber(obj->_vxPath << 1) - obj->_vxPath;
obj->_vy = _vm->_rnd->getRandomNumber(obj->_vyPath << 1) - obj->_vyPath;
// Set first image in sequence (if multi-seq object)
if (obj->_seqNumb > 1) {
if (!obj->_vx && (obj->_seqNumb >= 4)) {
if (obj->_vx != obj->_oldvx) { // vx just stopped
if (obj->_vy > 0)
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
}
} else if (obj->_vx != obj->_oldvx) {
if (obj->_vx > 0)
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
else
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
}
}
obj->_oldvx = obj->_vx;
obj->_oldvy = obj->_vy;
currImage = obj->_currImagePtr; // Get (new) ptr to current image
}
if (obj->_vx || obj->_vy)
obj->_cycling = kCycleForward;
break;
default:
; // Really, nothing
}
// Store boundaries
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(obj->_x + currImage->_x1, obj->_x + currImage->_x2, obj->_y + currImage->_y2);
}
}
// Move objects, allowing for boundaries
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_vx || obj->_vy)) {
// Only process if it's moving
// Do object movement. Delta_x,y return allowed movement in x,y
// to move as close to a boundary as possible without crossing it.
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
// object coordinates
int x1 = obj->_x + currImage->_x1; // Left edge of object
int x2 = obj->_x + currImage->_x2; // Right edge
int y1 = obj->_y + currImage->_y1; // Top edge
int y2 = obj->_y + currImage->_y2; // Bottom edge
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(x1, x2, y2); // Clear our own boundary
// Allowable motion wrt boundary
int dx = deltaX(x1, x2, obj->_vx, y2);
if (dx != obj->_vx) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vx = 0;
}
int dy = deltaY(x1, x2, obj->_vy, y2);
if (dy != obj->_vy) {
// An object boundary collision!
boundaryCollision(obj);
obj->_vy = 0;
}
if ((obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
storeBoundary(x1, x2, y2); // Re-store our own boundary
obj->_x += dx; // Update object position
obj->_y += dy;
// Don't let object go outside screen
if (x1 < kEdge)
obj->_x = kEdge2;
if (x2 > (kXPix - kEdge))
obj->_x = kXPix - kEdge2 - (x2 - x1);
if (y1 < kEdge)
obj->_y = kEdge2;
if (y2 > (kYPix - kEdge))
obj->_y = kYPix - kEdge2 - (y2 - y1);
if ((obj->_vx == 0) && (obj->_vy == 0) && (obj->_pathType != kPathWander2) && (obj->_pathType != kPathChase2))
obj->_cycling = kCycleNotCycling;
}
}
// Clear all object baselines from the boundary file.
for (int i = 0; i < _numObj; i++) {
Object *obj = &_objects[i]; // Get pointer to object
Seq *currImage = obj->_currImagePtr; // Get ptr to current image
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling > kCycleAlmostInvisible) && (obj->_priority == kPriorityFloating))
clearBoundary(obj->_oldx + currImage->_x1, obj->_oldx + currImage->_x2, obj->_oldy + currImage->_y2);
}
// If maze mode is enabled, do special maze processing
if (_vm->_maze._enabledFl) {
Seq *currImage = _vm->_hero->_currImagePtr;// Get ptr to current image
// hero coordinates
int x1 = _vm->_hero->_x + currImage->_x1; // Left edge of object
int x2 = _vm->_hero->_x + currImage->_x2; // Right edge
int y1 = _vm->_hero->_y + currImage->_y1; // Top edge
int y2 = _vm->_hero->_y + currImage->_y2; // Bottom edge
_vm->_scheduler->processMaze(x1, x2, y1, y2);
}
}
/**
* Swap all the images of one object with another. Set hero_image (we make
* the assumption for now that the first obj is always the HERO) to the object
* number of the swapped image
*/
void ObjectHandler_v3d::swapImages(int objIndex1, int objIndex2, bool restoring) {
debugC(1, kDebugObject, "swapImages(%d, %d, %d)", objIndex1, objIndex2, restoring);
saveSeq(&_objects[objIndex1]);
SeqList tmpSeqList[kMaxSeqNumb];
int seqListSize = sizeof(SeqList) * kMaxSeqNumb;
memmove(tmpSeqList, _objects[objIndex1]._seqList, seqListSize);
memmove(_objects[objIndex1]._seqList, _objects[objIndex2]._seqList, seqListSize);
memmove(_objects[objIndex2]._seqList, tmpSeqList, seqListSize);
restoreSeq(&_objects[objIndex1]);
_objects[objIndex2]._currImagePtr = _objects[objIndex2]._seqList[0]._seqPtr;
_vm->_heroImage = (_vm->_heroImage == kHeroIndex) ? objIndex2 : kHeroIndex;
// Make sure baseline stays constant
_objects[objIndex1]._y += _objects[objIndex2]._currImagePtr->_y2 - _objects[objIndex1]._currImagePtr->_y2;
}
} // End of namespace Hugo

591
engines/hugo/parser.cpp Normal file
View File

@@ -0,0 +1,591 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/events.h"
#include "common/textconsole.h"
#include "gui/debugger.h"
#include "hugo/hugo.h"
#include "hugo/display.h"
#include "hugo/parser.h"
#include "hugo/file.h"
#include "hugo/util.h"
#include "hugo/route.h"
#include "hugo/sound.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "hugo/inventory.h"
#include "hugo/mouse.h"
namespace Hugo {
Parser::Parser(HugoEngine *vm) : _vm(vm), _putIndex(0), _getIndex(0) {
_catchallList = nullptr;
_arrayReqs = nullptr;
_backgroundObjects = nullptr;
_backgroundObjectsSize = 0;
_cmdList = nullptr;
_cmdListSize = 0;
_cmdLineIndex = 0;
_cmdLineTick = 0;
_cmdLineCursor = '_';
_cmdLine[0] = '\0';
_checkDoubleF1Fl = false;
}
Parser::~Parser() {
}
uint16 Parser::getCmdDefaultVerbIdx(const uint16 index) const {
return _cmdList[index][0]._verbIndex;
}
/**
* Read a cmd structure from Hugo.dat
*/
void Parser::readCmd(Common::ReadStream &in, cmd &curCmd) {
curCmd._verbIndex = in.readUint16BE();
curCmd._reqIndex = in.readUint16BE();
curCmd._textDataNoCarryIndex = in.readUint16BE();
curCmd._reqState = in.readByte();
curCmd._newState = in.readByte();
curCmd._textDataWrongIndex = in.readUint16BE();
curCmd._textDataDoneIndex = in.readUint16BE();
curCmd._actIndex = in.readUint16BE();
}
/**
* Load _cmdList from Hugo.dat
*/
void Parser::loadCmdList(Common::ReadStream &in) {
cmd tmpCmd;
memset(&tmpCmd, 0, sizeof(tmpCmd));
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_cmdListSize = numElem;
_cmdList = (cmd **)malloc(sizeof(cmd *) * _cmdListSize);
}
for (int16 i = 0; i < numElem; i++) {
uint16 numSubElem = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_cmdList[i] = (cmd *)malloc(sizeof(cmd) * numSubElem);
for (int16 j = 0; j < numSubElem; j++)
readCmd(in, (varnt == _vm->_gameVariant) ? _cmdList[i][j] : tmpCmd);
}
}
}
void Parser::readBG(Common::ReadStream &in, Background &curBG) {
curBG._verbIndex = in.readUint16BE();
curBG._nounIndex = in.readUint16BE();
curBG._commentIndex = in.readSint16BE();
curBG._matchFl = (in.readByte() != 0);
curBG._roomState = in.readByte();
curBG._bonusIndex = in.readByte();
}
/**
* Read _backgrounObjects from Hugo.dat
*/
void Parser::loadBackgroundObjects(Common::ReadStream &in) {
Background tmpBG;
memset(&tmpBG, 0, sizeof(tmpBG));
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
_backgroundObjectsSize = numElem;
_backgroundObjects = (Background **)malloc(sizeof(Background *) * numElem);
}
for (int i = 0; i < numElem; i++) {
uint16 numSubElem = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_backgroundObjects[i] = (Background *)malloc(sizeof(Background) * numSubElem);
for (int j = 0; j < numSubElem; j++)
readBG(in, (varnt == _vm->_gameVariant) ? _backgroundObjects[i][j] : tmpBG);
}
}
}
/**
* Read _catchallList from Hugo.dat
*/
void Parser::loadCatchallList(Common::ReadStream &in) {
Background *wrkCatchallList = nullptr;
Background tmpBG;
memset(&tmpBG, 0, sizeof(tmpBG));
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numElem = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_catchallList = wrkCatchallList = (Background *)malloc(sizeof(Background) * numElem);
for (int i = 0; i < numElem; i++)
readBG(in, (varnt == _vm->_gameVariant) ? wrkCatchallList[i] : tmpBG);
}
}
void Parser::loadArrayReqs(Common::SeekableReadStream &in) {
_arrayReqs = _vm->loadLongArray(in);
}
/**
* Search background command list for this screen for supplied object.
* Return first associated verb (not "look") or 0 if none found.
*/
const char *Parser::useBG(const char *name) {
debugC(1, kDebugEngine, "useBG(%s)", name);
ObjectList p = _backgroundObjects[*_vm->_screenPtr];
for (int i = 0; p[i]._verbIndex != 0; i++) {
if ((name == _vm->_text->getNoun(p[i]._nounIndex, 0) &&
p[i]._verbIndex != _vm->_look) &&
((p[i]._roomState == kStateDontCare) || (p[i]._roomState == _vm->_screenStates[*_vm->_screenPtr])))
return _vm->_text->getVerb(p[i]._verbIndex, 0);
}
return nullptr;
}
void Parser::freeParser() {
if (_arrayReqs) {
for (int i = 0; _arrayReqs[i] != nullptr; i++)
free(_arrayReqs[i]);
free(_arrayReqs);
_arrayReqs = nullptr;
}
free(_catchallList);
_catchallList = nullptr;
if (_backgroundObjects) {
for (int i = 0; i < _backgroundObjectsSize; i++)
free(_backgroundObjects[i]);
free(_backgroundObjects);
_backgroundObjects = nullptr;
}
if (_cmdList) {
for (int i = 0; i < _cmdListSize; i++)
free(_cmdList[i]);
free(_cmdList);
_cmdList = nullptr;
}
}
void Parser::switchTurbo() {
_vm->_config._turboFl = !_vm->_config._turboFl;
#ifdef USE_TTS
if (_vm->_config._turboFl) {
_vm->sayText("T");
}
#endif
}
/**
* Add any new chars to line buffer and display them.
* If CR pressed, pass line to LineHandler()
*/
void Parser::charHandler() {
debugC(4, kDebugParser, "charHandler");
Status &gameStatus = _vm->getGameStatus();
// Check for one or more characters in ring buffer
while (_getIndex != _putIndex) {
char c = _ringBuffer[_getIndex++];
if (_getIndex >= sizeof(_ringBuffer))
_getIndex = 0;
switch (c) {
case Common::KEYCODE_BACKSPACE: // Rubout key
if (_cmdLineIndex)
_cmdLine[--_cmdLineIndex] = '\0';
break;
case Common::KEYCODE_RETURN: // EOL, pass line to line handler
if (_cmdLineIndex && (_vm->_hero->_pathType != kPathQuiet)) {
// Remove inventory bar if active
if (_vm->_inventory->getInventoryState() == kInventoryActive)
_vm->_inventory->setInventoryState(kInventoryUp);
#ifdef USE_TTS
_vm->sayText(_cmdLine);
_vm->_previousSaid.clear();
#endif
// Call Line handler and reset line
command(_cmdLine);
_cmdLine[_cmdLineIndex = 0] = '\0';
}
break;
default: // Normal text key, add to line
if (_cmdLineIndex >= kMaxLineSize) {
//MessageBeep(MB_ICONASTERISK);
warning("STUB: MessageBeep() - Command line too long");
} else if (Common::isPrint(c)) {
_cmdLine[_cmdLineIndex++] = c;
_cmdLine[_cmdLineIndex] = '\0';
}
break;
}
}
// See if time to blink cursor, set cursor character
if (_vm->useWindowsInterface()) {
if ((_cmdLineTick++ % (_vm->getTPS() / kBlinksPerSec)) == 0)
_cmdLineCursor = (_cmdLineCursor == '_') ? ' ' : '_';
} else {
// DOS: No blinking cursor
_cmdLineCursor = ' ';
}
// See if recall button pressed
if (gameStatus._recallFl) {
// Copy previous line to current cmdline
gameStatus._recallFl = false;
Common::strcpy_s(_cmdLine, _vm->_line);
_cmdLineIndex = strlen(_cmdLine);
}
_vm->_screen->updateStatusText();
_vm->_screen->updatePromptText(_cmdLine, _cmdLineCursor);
#ifdef USE_TTS
if (_vm->_previousScore != _vm->getScore()) {
_vm->sayText(Common::String::format("Score: %d of %d", _vm->getScore(), _vm->getMaxScore()));
_vm->_previousScore = _vm->getScore();
}
if (_vm->_voiceScoreLine) {
_vm->sayText(Common::String::format("F1: Help\n%s\nScore: %d of %d\nSound %s", (_vm->_config._turboFl) ? "T" : " ", _vm->getScore(), _vm->getMaxScore(), (_vm->_config._soundFl) ? "On" : "Off"));
_vm->_voiceScoreLine = false;
}
if (_vm->_voiceSoundSetting) {
_vm->sayText(Common::String::format("Sound %s", (_vm->_config._soundFl) ? "On" : "Off"));
// If the mouse is in the top menu range, the top menu will close and reopen after the sound setting is changed,
// causing the new sound setting voicing to be interrupted. Therefore, keep trying to voice the new sound setting
// as long as the top menu can be opened
int mouseY = _vm->_mouse->getMouseY();
if (mouseY <= 0 || mouseY >= 5) {
_vm->_voiceSoundSetting = false;
}
}
#endif
// See if "look" button pressed
if (gameStatus._lookFl) {
command("look around");
gameStatus._lookFl = false;
}
}
void Parser::keyHandler(Common::Event event) {
debugC(1, kDebugParser, "keyHandler(%d)", event.kbd.keycode);
Status &gameStatus = _vm->getGameStatus();
if (event.kbd.flags & (Common::KBD_ALT | Common::KBD_SCRL))
return;
// Process key down input - called from OnKeyDown()
if (!gameStatus._storyModeFl) { // Keyboard disabled
// Add printable keys to ring buffer
uint16 bnext = _putIndex + 1;
if (bnext >= sizeof(_ringBuffer))
bnext = 0;
if (bnext != _getIndex) {
_ringBuffer[_putIndex] = event.kbd.ascii;
_putIndex = bnext;
}
}
}
void Parser::actionHandler(Common::Event event) {
debugC(1, kDebugParser, "ActionHandler(%d)", event.customType);
Status &gameStatus = _vm->getGameStatus();
switch (event.customType) {
case kActionUserHelp:
if (_vm->useWindowsInterface()) {
// Windows: Track double-F1 with a flag
if (_checkDoubleF1Fl)
gameStatus._helpFl = true;
else
_vm->_screen->userHelp();
_checkDoubleF1Fl = !_checkDoubleF1Fl;
} else {
// DOS: userHelp() handles double-F1
_vm->_screen->userHelp();
}
break;
case kActionToggleSound:
_vm->_sound->toggleSound();
_vm->_sound->toggleMusic();
break;
case kActionRepeatLine:
gameStatus._recallFl = true;
break;
case kActionSaveGame:
if (gameStatus._viewState == kViewPlay) {
if (gameStatus._gameOverFl)
_vm->gameOverMsg();
else
_vm->_file->saveGame(-1, Common::String());
}
break;
case kActionRestoreGame:
_vm->_file->restoreGame(-1);
break;
case kActionNewGame: {
// DOS requires shorter text for message boxes
const char *message = _vm->useWindowsInterface() ?
"Are you sure you want to start a new game?" :
"Are you sure you want to RESTART?";
if (_vm->yesNoBox(message, true))
_vm->_file->restoreGame(99);
break;
}
case kActionInventory:
showInventory();
break;
case kActionToggleTurbo:
switchTurbo();
break;
case kActionEscape: // Escape key, may want to QUIT
if (gameStatus._viewState == kViewIntro) {
gameStatus._skipIntroFl = true;
} else if (gameStatus._viewState == kViewPlay) {
endGamePrompt();
} else {
if (_vm->_inventory->getInventoryState() == kInventoryActive) // Remove inventory, if displayed
_vm->_inventory->setInventoryState(kInventoryUp);
_vm->_screen->resetInventoryObjId();
}
break;
case kActionMoveTop:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_UP); // Direction of hero travel
break;
case kActionMoveBottom:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_DOWN); // Direction of hero travel
break;
case kActionMoveLeft:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_LEFT); // Direction of hero travel
break;
case kActionMoveRight:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_RIGHT); // Direction of hero travel
break;
case kActionMoveTopLeft:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_KP7); // Direction of hero travel
break;
case kActionMoveTopRight:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_KP9); // Direction of hero travel
break;
case kActionMoveBottomLeft:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_KP1); // Direction of hero travel
break;
case kActionMoveBottomRight:
_vm->_route->resetRoute(); // Stop any automatic route
_vm->_route->setWalk(Common::KEYCODE_KP3); // Direction of hero travel
break;
default:
break;
}
if (_checkDoubleF1Fl && (event.customType != kActionUserHelp))
_checkDoubleF1Fl = false;
}
/**
* Perform an immediate command. Takes parameters a la sprintf
* Assumes final string will not overrun line[] length
*/
void Parser::command(const char *format, ...) {
debugC(1, kDebugParser, "Command(%s, ...)", format);
va_list marker;
va_start(marker, format);
Common::vsprintf_s(_vm->_line, format, marker);
va_end(marker);
lineHandler();
}
void Parser::resetCommandLine() {
_cmdLine[_cmdLineIndex = 0] = '\0';
}
/**
* Locate any member of object name list appearing in command line
*/
bool Parser::isWordPresent(char **wordArr) const {
if (wordArr != nullptr) {
debugC(1, kDebugParser, "isWordPresent(%s)", wordArr[0]);
for (int i = 0; strlen(wordArr[i]); i++) {
if (strstr(_vm->_line, wordArr[i]))
return true;
}
}
return false;
}
/**
* Locate word in list of nouns and return ptr to first string in noun list
*/
const char *Parser::findNoun() const {
debugC(1, kDebugParser, "findNoun()");
for (int i = 0; _vm->_text->getNounArray(i); i++) {
for (int j = 0; strlen(_vm->_text->getNoun(i, j)); j++) {
if (strstr(_vm->_line, _vm->_text->getNoun(i, j)))
return _vm->_text->getNoun(i, 0);
}
}
return nullptr;
}
/**
* Locate word in list of verbs and return ptr to first string in verb list
*/
const char *Parser::findVerb() const {
debugC(1, kDebugParser, "findVerb()");
for (int i = 0; _vm->_text->getVerbArray(i); i++) {
for (int j = 0; strlen(_vm->_text->getVerb(i, j)); j++) {
if (strstr(_vm->_line, _vm->_text->getVerb(i, j)))
return _vm->_text->getVerb(i, 0);
}
}
return nullptr;
}
/**
* Show user all objects being carried in a variable width 2 column format
*/
void Parser::showDosInventory() const {
debugC(1, kDebugParser, "showDosInventory()");
static const char *const blanks = " ";
uint16 index = 0, len1 = 0, len2 = 0;
const char *intro = _vm->_text->getTextParser(kTBIntro);
const char *outro = _vm->_text->getTextParser(kTBOutro);
if (_vm->getGameType() == kGameTypeHugo3) {
outro = "\nPress any key to continue";
}
const int nounIndex2 = (_vm->getGameType() <= kGameTypeHugo2) ? 0 : 1;
for (int i = 0; i < _vm->_object->_numObj; i++) { // Find widths of 2 columns
if (_vm->_object->isCarried(i)) {
uint16 len = strlen(_vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, nounIndex2));
if (index++ & 1) // Right hand column
len2 = (len > len2) ? len : len2;
else
len1 = (len > len1) ? len : len1;
}
}
Common::String buffer;
if (_vm->getGameType() <= kGameTypeHugo2) {
len1 += 1; // For gap between columns
if (len1 + len2 < (uint16)strlen(outro)) {
len1 = strlen(outro);
}
assert(len1 + len2 - strlen(intro) / 2 < strlen(blanks));
buffer = Common::String(blanks, (len1 + len2 - strlen(intro)) / 2);
} else {
len1 += 4; // For gap between columns
if (len1 + len2 > (uint16)strlen(intro)) {
assert(len1 + len2 - strlen(intro) / 2 < strlen(blanks));
buffer = Common::String(blanks, (len1 + len2 - strlen(intro)) / 2);
}
}
buffer += intro;
buffer += '\n';
index = 0;
for (int i = 0; i < _vm->_object->_numObj; i++) { // Assign strings
if (_vm->_object->isCarried(i)) {
const char *objectName = _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, nounIndex2);
buffer += objectName;
if (index++ & 1) {
buffer += '\n';
} else {
buffer += Common::String(blanks, len1 - strlen(objectName));
}
}
}
if (index & 1)
buffer += '\n';
buffer += outro;
#ifdef USE_TTS
sayInventory(intro, outro, nounIndex2);
#endif
_vm->notifyBox(buffer, false, kTtsNoSpeech);
}
#ifdef USE_TTS
void Parser::sayInventory(const char *intro, const char *outro, int nounIndex2) const {
Common::String text = intro;
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->isCarried(i)) {
text += '\n';
text += _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, nounIndex2);
}
}
text += '\n';
text += outro;
_vm->sayText(text, Common::TextToSpeechManager::INTERRUPT);
}
#endif
void Parser::endGamePrompt() {
if (_vm->yesNoBox(_vm->_text->getTextParser(kTBExit_1d), true)) {
_vm->endGame();
}
}
} // End of namespace Hugo

200
engines/hugo/parser.h Normal file
View File

@@ -0,0 +1,200 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_PARSER_H
#define HUGO_PARSER_H
namespace Common {
struct Event;
class ReadStream;
}
namespace Hugo {
enum seqTextParser {
kTBExit = 0, kTBMaze, kTBNoPoint, kTBNoun, kTBVerb,
kTBEh, kTBUnusual, kTBHave, kTBNoUse, kTBDontHave,
kTBNeed, kTBOk, kCmtAny1, kCmtAny2, kCmtAny3,
kCmtClose, kTBIntro, kTBOutro, kTBUnusual_1d, kCmtAny4,
kCmtAny5, kTBExit_1d, kTBEh_1d, kTBEh_2d, kTBNoUse_2d
};
/**
* The following determines how a verb is acted on, for an object
*/
struct cmd {
uint16 _verbIndex; // the verb
uint16 _reqIndex; // ptr to list of required objects
uint16 _textDataNoCarryIndex; // ptr to string if any of above not carried
byte _reqState; // required state for verb to be done
byte _newState; // new states if verb done
uint16 _textDataWrongIndex; // ptr to string if wrong state
uint16 _textDataDoneIndex; // ptr to string if verb done
uint16 _actIndex; // Ptr to action list if verb done
};
/**
* Following is structure of verbs and nouns for 'background' objects
* These are objects that appear in the various screens, but nothing
* interesting ever happens with them. Rather than just be dumb and say
* "don't understand" we produce an interesting msg to keep user sane.
*/
struct Background {
uint16 _verbIndex;
uint16 _nounIndex;
int _commentIndex; // Index of comment produced on match
bool _matchFl; // TRUE if noun must match when present
byte _roomState; // "State" of room. Comments might differ.
byte _bonusIndex; // Index of bonus score (0 = no bonus)
};
typedef Background *ObjectList;
class Parser {
public:
Parser(HugoEngine *vm);
virtual ~Parser();
bool isWordPresent(char **wordArr) const;
uint16 getCmdDefaultVerbIdx(const uint16 index) const;
void charHandler();
void command(const char *format, ...);
void resetCommandLine();
void freeParser();
void keyHandler(Common::Event event);
void actionHandler(Common::Event event);
void loadArrayReqs(Common::SeekableReadStream &in);
void loadBackgroundObjects(Common::ReadStream &in);
void loadCatchallList(Common::ReadStream &in);
void loadCmdList(Common::ReadStream &in);
void switchTurbo();
const char *useBG(const char *name);
virtual void lineHandler() = 0;
virtual void showInventory() const = 0;
virtual void takeObject(Object *obj) = 0;
protected:
HugoEngine *_vm;
int16 _cmdLineIndex; // Index into line
uint32 _cmdLineTick; // For flashing cursor
char _cmdLineCursor;
Command _cmdLine; // Build command line
uint16 _backgroundObjectsSize;
uint16 _cmdListSize;
uint16 **_arrayReqs;
Background **_backgroundObjects;
Background *_catchallList;
cmd **_cmdList;
const char *findNoun() const;
const char *findVerb() const;
void readBG(Common::ReadStream &in, Background &curBG);
void readCmd(Common::ReadStream &in, cmd &curCmd);
void showDosInventory() const;
#ifdef USE_TTS
void sayInventory(const char *intro, const char *outro, int nounIndex2) const;
#endif
void endGamePrompt();
bool _checkDoubleF1Fl; // Flag used to display user help or instructions
uint16 _getIndex; // Index into ring buffer
uint16 _putIndex;
char _ringBuffer[32]; // Ring buffer
private:
static const int kBlinksPerSec = 2; // Cursor blinks per second
};
class Parser_v1d : public Parser {
public:
Parser_v1d(HugoEngine *vm);
~Parser_v1d() override;
void lineHandler() override;
void showInventory() const override;
void takeObject(Object *obj) override;
protected:
virtual void dropObject(Object *obj);
const char *findNextNoun(const char *noun) const;
bool isBackgroundWord_v1(const char *noun, const char *verb, ObjectList obj) const;
bool isCatchallVerb_v1(bool testNounFl, const char *noun, const char *verb, ObjectList obj) const;
bool isGenericVerb_v1(const char *word, Object *obj);
bool isNear_v1(const char *verb, const char *noun, Object *obj, char *comment) const;
bool isObjectVerb_v1(const char *word, Object *obj);
};
class Parser_v2d : public Parser_v1d {
public:
Parser_v2d(HugoEngine *vm);
~Parser_v2d() override;
void lineHandler() override;
protected:
bool isBackgroundWord_v2(const char *noun, const char *verb, ObjectList obj) const;
bool isCatchallVerb_v2(bool testNounFl, const char *noun, const char *verb, ObjectList obj) const;
bool isGenericVerb_v2(const char *word, Object *obj);
bool isObjectVerb_v2(const char *word, Object *obj);
};
class Parser_v3d : public Parser_v1d {
public:
Parser_v3d(HugoEngine *vm);
~Parser_v3d() override;
void lineHandler() override;
protected:
void dropObject(Object *obj) override;
bool isBackgroundWord_v3(ObjectList obj) const;
bool isCatchallVerb_v3(ObjectList obj) const;
bool isGenericVerb_v3(Object *obj, char *comment);
bool isNear_v3(Object *obj, const char *verb, char *comment) const;
bool isObjectVerb_v3(Object *obj, char *comment);
void takeObject(Object *obj) override;
};
class Parser_v1w : public Parser_v3d {
public:
Parser_v1w(HugoEngine *vm);
~Parser_v1w() override;
void showInventory() const override;
void lineHandler() override;
};
} // End of namespace Hugo
#endif //HUGO_PARSER_H

436
engines/hugo/parser_v1d.cpp Normal file
View File

@@ -0,0 +1,436 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// parser.c - handles all keyboard/command input
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/display.h"
#include "hugo/parser.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/object.h"
#include "hugo/text.h"
namespace Hugo {
Parser_v1d::Parser_v1d(HugoEngine *vm) : Parser(vm) {
}
Parser_v1d::~Parser_v1d() {
}
/**
* Locate word in list of nouns and return ptr to string in noun list
* If n is NULL, start at beginning of list, else with n
*/
const char *Parser_v1d::findNextNoun(const char *noun) const {
debugC(1, kDebugParser, "findNextNoun(%s)", noun);
int currNounIndex = -1;
if (noun) { // If noun not NULL, find index
for (currNounIndex = 0; _vm->_text->getNounArray(currNounIndex); currNounIndex++) {
if (noun == _vm->_text->getNoun(currNounIndex, 0))
break;
}
}
for (int i = currNounIndex + 1; _vm->_text->getNounArray(i); i++) {
for (int j = 0; strlen(_vm->_text->getNoun(i, j)); j++) {
if (strstr(_vm->_line, _vm->_text->getNoun(i, j)))
return _vm->_text->getNoun(i, 0);
}
}
return nullptr;
}
/**
* Test whether hero is close to object. Return TRUE or FALSE
* If no noun specified, check context flag in object before other tests.
* If object not near, return suitable string; may be similar object closer
* If radius is -1, treat radius as infinity
*/
bool Parser_v1d::isNear_v1(const char *verb, const char *noun, Object *obj, char *comment) const {
debugC(1, kDebugParser, "isNear(%s, %s, obj, %s)", verb, noun, comment);
if (!noun && !obj->_verbOnlyFl) { // No noun specified & object not context senesitive
return false;
} else if (noun && (noun != _vm->_text->getNoun(obj->_nounIndex, 0))) { // Noun specified & not same as object
return false;
} else if (obj->_carriedFl) { // Object is being carried
return true;
} else if (obj->_screenIndex != *_vm->_screenPtr) { // Not in same screen
if (obj->_objValue)
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny4));
return false;
}
if (obj->_cycling == kCycleInvisible) {
if (obj->_seqNumb) { // There is an image
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny5));
return false;
} else { // No image, assume visible
if ((obj->_radius < 0) ||
((abs(obj->_x - _vm->_hero->_x) <= obj->_radius) &&
(abs(obj->_y - _vm->_hero->_y - _vm->_hero->_currImagePtr->_y2) <= obj->_radius))) {
return true;
} else {
// User is either not close enough (stationary, valueless objects)
// or is not carrying it (small, portable objects of value)
if (noun) { // Don't say unless object specified
if (obj->_objValue && (verb != _vm->_text->getVerb(_vm->_take, 0)))
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny4));
else
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtClose));
}
return false;
}
}
}
if ((obj->_radius < 0) ||
((abs(obj->_x - _vm->_hero->_x) <= obj->_radius) &&
(abs(obj->_y + obj->_currImagePtr->_y2 - _vm->_hero->_y - _vm->_hero->_currImagePtr->_y2) <= obj->_radius))) {
return true;
} else {
// User is either not close enough (stationary, valueless objects)
// or is not carrying it (small, portable objects of value)
if (noun) { // Don't say unless object specified
if (obj->_objValue && (verb != _vm->_text->getVerb(_vm->_take, 0)))
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny4));
else
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtClose));
}
return false;
}
return true;
}
/**
* Test whether supplied verb is one of the common variety for this object
* say_ok needed for special case of take/drop which may be handled not only
* here but also in a cmd_list with a donestr string simultaneously
*/
bool Parser_v1d::isGenericVerb_v1(const char *word, Object *obj) {
debugC(1, kDebugParser, "isGenericVerb(%s, Object *obj)", word);
if (!obj->_genericCmd)
return false;
// Following is equivalent to switch, but couldn't do one
if (word == _vm->_text->getVerb(_vm->_look, 0)) {
if ((LOOK & obj->_genericCmd) == LOOK)
_vm->notifyBox(_vm->_text->getTextData(obj->_dataIndex));
else
_vm->notifyBox(_vm->_text->getTextParser(kTBUnusual_1d));
} else if (word == _vm->_text->getVerb(_vm->_take, 0)) {
if (obj->_carriedFl)
_vm->notifyBox(_vm->_text->getTextParser(kTBHave));
else if ((TAKE & obj->_genericCmd) == TAKE)
takeObject(obj);
else if (!obj->_verbOnlyFl) // Make sure not taking object in context!
_vm->notifyBox(_vm->_text->getTextParser(kTBNoUse));
else
return false;
} else if (word == _vm->_text->getVerb(_vm->_drop, 0)) {
if (!obj->_carriedFl)
_vm->notifyBox(_vm->_text->getTextParser(kTBDontHave));
else if ((DROP & obj->_genericCmd) == DROP)
dropObject(obj);
else
_vm->notifyBox(_vm->_text->getTextParser(kTBNeed));
} else { // It was not a generic cmd
return false;
}
return true;
}
/**
* Test whether supplied verb is included in the list of allowed verbs for
* this object. If it is, then perform the tests on it from the cmd list
* and if it passes, perform the actions in the action list. If the verb
* is catered for, return TRUE
*/
bool Parser_v1d::isObjectVerb_v1(const char *word, Object *obj) {
debugC(1, kDebugParser, "isObjectVerb(%s, Object *obj)", word);
// First, find matching verb in cmd list
uint16 cmdIndex = obj->_cmdIndex; // ptr to list of commands
if (!cmdIndex) // No commands for this obj
return false;
int i;
for (i = 0; _cmdList[cmdIndex][i]._verbIndex != 0; i++) { // For each cmd
if (!strcmp(word, _vm->_text->getVerb(_cmdList[cmdIndex][i]._verbIndex, 0))) // Is this verb catered for?
break;
}
if (_cmdList[cmdIndex][i]._verbIndex == 0) // No
return false;
// Verb match found, check all required objects are being carried
cmd *cmnd = &_cmdList[cmdIndex][i]; // ptr to struct cmd
if (cmnd->_reqIndex) { // At least 1 thing in list
uint16 *reqs = _arrayReqs[cmnd->_reqIndex]; // ptr to list of required objects
for (i = 0; reqs[i]; i++) { // for each obj
if (!_vm->_object->isCarrying(reqs[i])) {
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataNoCarryIndex));
return true;
}
}
}
// Required objects are present, now check state is correct
if ((obj->_state != cmnd->_reqState) && (cmnd->_reqState != kStateDontCare)){
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataWrongIndex));
return true;
}
// Everything checked. Change the state and carry out any actions
if (cmnd->_reqState != kStateDontCare) // Don't change new state if required state didn't care
obj->_state = cmnd->_newState;
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataDoneIndex));
_vm->_scheduler->insertActionList(cmnd->_actIndex);
// Special case if verb is Take or Drop. Assume additional generic actions
if ((word == _vm->_text->getVerb(_vm->_take, 0)) || (word == _vm->_text->getVerb(_vm->_drop, 0)))
isGenericVerb_v1(word, obj);
return true;
}
/**
* Print text for possible background object. Return TRUE if match found
* Only match if both verb and noun found. Test_ca will match verb-only
*/
bool Parser_v1d::isBackgroundWord_v1(const char *noun, const char *verb, ObjectList obj) const {
debugC(1, kDebugParser, "isBackgroundWord(%s, %s, object_list_t obj)", noun, verb);
if (!noun)
return false;
for (int i = 0; obj[i]._verbIndex; i++) {
if ((verb == _vm->_text->getVerb(obj[i]._verbIndex, 0)) && (noun == _vm->_text->getNoun(obj[i]._nounIndex, 0))) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
return true;
}
}
return false;
}
/**
* Do all things necessary to carry an object
*/
void Parser_v1d::takeObject(Object *obj) {
debugC(1, kDebugParser, "takeObject(Object *obj)");
obj->_carriedFl = true;
if (obj->_seqNumb) // Don't change if no image to display
obj->_cycling = kCycleAlmostInvisible;
_vm->adjustScore(obj->_objValue);
_vm->_screen->updateStatusText();
_vm->takeObjectBox(_vm->_text->getNoun(obj->_nounIndex, TAKE_NAME));
}
/**
* Do all necessary things to drop an object
*/
void Parser_v1d::dropObject(Object *obj) {
debugC(1, kDebugParser, "dropObject(Object *obj)");
obj->_carriedFl = false;
obj->_screenIndex = *_vm->_screenPtr;
if (obj->_seqNumb) // Don't change if no image to display
obj->_cycling = kCycleNotCycling;
obj->_x = _vm->_hero->_x - 1;
obj->_y = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - 1;
_vm->adjustScore(-obj->_objValue);
_vm->_screen->updateStatusText();
_vm->notifyBox("Ok");
}
/**
* Print text for possible background object. Return TRUE if match found
* If test_noun TRUE, must have a noun given
*/
bool Parser_v1d::isCatchallVerb_v1(bool testNounFl, const char *noun, const char *verb, ObjectList obj) const {
debugC(1, kDebugParser, "isCatchallVerb(%d, %s, %s, object_list_t obj)", (testNounFl) ? 1 : 0, noun, verb);
if (testNounFl && !noun)
return false;
for (int i = 0; obj[i]._verbIndex; i++) {
if ((verb == _vm->_text->getVerb(obj[i]._verbIndex, 0)) && ((noun == _vm->_text->getNoun(obj[i]._nounIndex, 0)) || (obj[i]._nounIndex == 0))) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
return true;
}
}
return false;
}
/**
* Parse the user's line of text input. Generate events as necessary
*/
void Parser_v1d::lineHandler() {
debugC(1, kDebugParser, "lineHandler()");
// Reset the prompt on screen (DOS only)
_vm->_screen->updatePromptText("", ' ');
_vm->_screen->displayPromptText();
Status &gameStatus = _vm->getGameStatus();
// Toggle God Mode
if (!strncmp(_vm->_line, "PPG", 3)) {
_vm->_sound->playSound(_vm->_soundTest, kSoundPriorityHigh);
gameStatus._godModeFl = !gameStatus._godModeFl;
return;
}
Utils::hugo_strlwr(_vm->_line); // Convert to lower case
// God Mode cheat commands:
// goto <screen> Takes hero to named screen
// fetch <object name> Hero carries named object
// fetch all Hero carries all possible objects
// find <object name> Takes hero to screen containing named object
if (gameStatus._godModeFl) {
// Special code to allow me to go straight to any screen
if (strstr(_vm->_line, "goto")) {
for (int i = 0; i < _vm->_numScreens; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("goto") + 1], _vm->_text->getScreenNames(i))) {
_vm->_scheduler->newScreen(i);
return;
}
}
}
// Special code to allow me to get objects from anywhere
if (strstr(_vm->_line, "fetch all")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
takeObject(&_vm->_object->_objects[i]);
}
return;
}
if (strstr(_vm->_line, "fetch")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("fetch") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
takeObject(&_vm->_object->_objects[i]);
return;
}
}
}
// Special code to allow me to goto objects
if (strstr(_vm->_line, "find")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("find") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
_vm->_scheduler->newScreen(_vm->_object->_objects[i]._screenIndex);
return;
}
}
}
}
if (!strcmp("exit", _vm->_line) || strstr(_vm->_line, "quit")) {
endGamePrompt();
return;
}
// SAVE/RESTORE
if (!strcmp("save", _vm->_line)) {
if (gameStatus._gameOverFl)
_vm->gameOverMsg();
else
_vm->_file->saveGame(-1, Common::String());
return;
}
if (!strcmp("restore", _vm->_line)) {
_vm->_file->restoreGame(-1);
return;
}
if (*_vm->_line == '\0') // Empty line
return;
if (strspn(_vm->_line, " ") == strlen(_vm->_line)) // Nothing but spaces!
return;
if (gameStatus._gameOverFl) {
// No commands allowed!
_vm->gameOverMsg();
return;
}
// Find the first verb in the line
const char *verb = findVerb();
const char *noun = nullptr; // Noun not found yet
char farComment[kCompLineSize * 5] = ""; // hold 5 line comment if object not nearby
if (verb) { // OK, verb found. Try to match with object
do {
noun = findNextNoun(noun); // Find a noun in the line
// Must try at least once for objects allowing verb-context
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (isNear_v1(verb, noun, obj, farComment)) {
if (isObjectVerb_v1(verb, obj) // Foreground object
|| isGenericVerb_v1(verb, obj))// Common action type
return;
}
}
if ((*farComment == '\0') && isBackgroundWord_v1(noun, verb, _backgroundObjects[*_vm->_screenPtr]))
return;
} while (noun);
}
noun = findNextNoun(noun);
if (*farComment != '\0') // An object matched but not near enough
_vm->notifyBox(farComment);
else if (!isCatchallVerb_v1(true, noun, verb, _catchallList) &&
!isCatchallVerb_v1(false, noun, verb, _backgroundObjects[*_vm->_screenPtr]) &&
!isCatchallVerb_v1(false, noun, verb, _catchallList))
_vm->notifyBox(_vm->_text->getTextParser(kTBEh_1d));
}
void Parser_v1d::showInventory() const {
Status &gameStatus = _vm->getGameStatus();
if (gameStatus._viewState == kViewPlay) {
if (gameStatus._gameOverFl)
_vm->gameOverMsg();
else
showDosInventory();
}
}
} // End of namespace Hugo

214
engines/hugo/parser_v1w.cpp Normal file
View File

@@ -0,0 +1,214 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// parser.c - handles all keyboard/command input
#include "common/debug.h"
#include "hugo/hugo.h"
#include "hugo/parser.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/route.h"
#include "hugo/display.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/object.h"
#include "hugo/text.h"
#include "hugo/inventory.h"
namespace Hugo {
Parser_v1w::Parser_v1w(HugoEngine *vm) : Parser_v3d(vm) {
}
Parser_v1w::~Parser_v1w() {
}
/**
* Parse the user's line of text input. Generate events as necessary
*/
void Parser_v1w::lineHandler() {
debugC(1, kDebugParser, "lineHandler()");
Status &gameStatus = _vm->getGameStatus();
// Toggle God Mode
if (!strncmp(_vm->_line, "PPG", 3)) {
_vm->_sound->playSound(_vm->_soundTest, kSoundPriorityHigh);
gameStatus._godModeFl = !gameStatus._godModeFl;
return;
}
Utils::hugo_strlwr(_vm->_line); // Convert to lower case
// God Mode cheat commands:
// goto <screen> Takes hero to named screen
// fetch <object name> Hero carries named object
// fetch all Hero carries all possible objects
// find <object name> Takes hero to screen containing named object
if (gameStatus._godModeFl) {
// Special code to allow me to go straight to any screen
if (strstr(_vm->_line, "goto")) {
for (int i = 0; i < _vm->_numScreens; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("goto") + 1], _vm->_text->getScreenNames(i))) {
_vm->_scheduler->newScreen(i);
return;
}
}
}
// Special code to allow me to get objects from anywhere
if (strstr(_vm->_line, "fetch all")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
takeObject(&_vm->_object->_objects[i]);
}
return;
}
if (strstr(_vm->_line, "fetch")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("fetch") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
takeObject(&_vm->_object->_objects[i]);
return;
}
}
}
// Special code to allow me to goto objects
if (strstr(_vm->_line, "find")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("find") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
_vm->_scheduler->newScreen(_vm->_object->_objects[i]._screenIndex);
return;
}
}
}
}
// Special meta commands
// EXIT/QUIT
if (!strcmp("exit", _vm->_line) || strstr(_vm->_line, "quit")) {
endGamePrompt();
return;
}
// SAVE/RESTORE
if (!strcmp("save", _vm->_line) && gameStatus._viewState == kViewPlay) {
_vm->_file->saveGame(-1, Common::String());
return;
}
if (!strcmp("restore", _vm->_line) && (gameStatus._viewState == kViewPlay || gameStatus._viewState == kViewIdle)) {
_vm->_file->restoreGame(-1);
return;
}
// Empty line
if (*_vm->_line == '\0') // Empty line
return;
if (strspn(_vm->_line, " ") == strlen(_vm->_line)) // Nothing but spaces!
return;
if (gameStatus._gameOverFl) {
// No commands allowed!
_vm->gameOverMsg();
return;
}
char farComment[kCompLineSize * 5] = ""; // hold 5 line comment if object not nearby
// Test for nearby objects referenced explicitly
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (isWordPresent(_vm->_text->getNounArray(obj->_nounIndex))) {
if (isObjectVerb_v3(obj, farComment) || isGenericVerb_v3(obj, farComment))
return;
}
}
// Test for nearby objects that only require a verb
// Note comment is unused if not near.
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (obj->_verbOnlyFl) {
char contextComment[kCompLineSize * 5] = ""; // Unused comment for context objects
if (isObjectVerb_v3(obj, contextComment) || isGenericVerb_v3(obj, contextComment))
return;
}
}
// No objects match command line, try background and catchall commands
if (isBackgroundWord_v3(_backgroundObjects[*_vm->_screenPtr]))
return;
if (isCatchallVerb_v3(_backgroundObjects[*_vm->_screenPtr]))
return;
if (isBackgroundWord_v3(_catchallList))
return;
if (isCatchallVerb_v3(_catchallList))
return;
// If a not-near comment was generated, print it
if (*farComment != '\0') {
_vm->notifyBox(farComment);
return;
}
// Nothing matches. Report recognition success to user.
const char *verb = findVerb();
const char *noun = findNoun();
if (verb == _vm->_text->getVerb(_vm->_look, 0) && _vm->_maze._enabledFl) {
_vm->notifyBox(_vm->_text->getTextParser(kTBMaze));
_vm->_object->showTakeables();
} else if (verb && noun) { // A combination I didn't think of
_vm->notifyBox(_vm->_text->getTextParser(kTBNoPoint));
} else if (noun) {
_vm->notifyBox(_vm->_text->getTextParser(kTBNoun));
} else if (verb) {
_vm->notifyBox(_vm->_text->getTextParser(kTBVerb));
} else {
_vm->notifyBox(_vm->_text->getTextParser(kTBEh));
}
}
void Parser_v1w::showInventory() const {
Status &gameStatus = _vm->getGameStatus();
Istate inventState = _vm->_inventory->getInventoryState();
if (gameStatus._gameOverFl) {
_vm->gameOverMsg();
} else if ((inventState == kInventoryOff) && (gameStatus._viewState == kViewPlay)) {
_vm->_inventory->setInventoryState(kInventoryDown);
gameStatus._viewState = kViewInvent;
} else if (inventState == kInventoryActive) {
_vm->_inventory->setInventoryState(kInventoryUp);
gameStatus._viewState = kViewInvent;
}
}
} // End of namespace Hugo

352
engines/hugo/parser_v2d.cpp Normal file
View File

@@ -0,0 +1,352 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// parser.c - handles all keyboard/command input
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/display.h"
#include "hugo/parser.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/object.h"
#include "hugo/text.h"
namespace Hugo {
Parser_v2d::Parser_v2d(HugoEngine *vm) : Parser_v1d(vm) {
}
Parser_v2d::~Parser_v2d() {
}
/**
* Parse the user's line of text input. Generate events as necessary
*/
void Parser_v2d::lineHandler() {
debugC(1, kDebugParser, "lineHandler()");
// Reset the prompt on screen (DOS only)
_vm->_screen->updatePromptText("", ' ');
_vm->_screen->displayPromptText();
Status &gameStatus = _vm->getGameStatus();
// Toggle God Mode
if (!strncmp(_vm->_line, "PPG", 3)) {
_vm->_sound->playSound(_vm->_soundTest, kSoundPriorityHigh);
gameStatus._godModeFl = !gameStatus._godModeFl;
return;
}
Utils::hugo_strlwr(_vm->_line); // Convert to lower case
// God Mode cheat commands:
// goto <screen> Takes hero to named screen
// fetch <object name> Hero carries named object
// fetch all Hero carries all possible objects
// find <object name> Takes hero to screen containing named object
if (gameStatus._godModeFl) {
// Special code to allow me to go straight to any screen
if (strstr(_vm->_line, "goto")) {
for (int i = 0; i < _vm->_numScreens; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("goto") + 1], _vm->_text->getScreenNames(i))) {
_vm->_scheduler->newScreen(i);
return;
}
}
}
// Special code to allow me to get objects from anywhere
if (strstr(_vm->_line, "fetch all")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
takeObject(&_vm->_object->_objects[i]);
}
return;
}
if (strstr(_vm->_line, "fetch")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("fetch") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
takeObject(&_vm->_object->_objects[i]);
return;
}
}
}
// Special code to allow me to goto objects
if (strstr(_vm->_line, "find")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("find") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
_vm->_scheduler->newScreen(_vm->_object->_objects[i]._screenIndex);
return;
}
}
}
}
if (!strcmp("exit", _vm->_line) || strstr(_vm->_line, "quit")) {
endGamePrompt();
return;
}
// SAVE/RESTORE
if (!strcmp("save", _vm->_line)) {
if (gameStatus._gameOverFl)
_vm->gameOverMsg();
else
_vm->_file->saveGame(-1, Common::String());
return;
}
if (!strcmp("restore", _vm->_line)) {
_vm->_file->restoreGame(-1);
return;
}
if (*_vm->_line == '\0') // Empty line
return;
if (strspn(_vm->_line, " ") == strlen(_vm->_line)) // Nothing but spaces!
return;
if (gameStatus._gameOverFl) {
// No commands allowed!
_vm->gameOverMsg();
return;
}
// Find the first verb in the line
const char *verb = findVerb();
const char *noun = nullptr; // Noun not found yet
char farComment[kCompLineSize * 5] = ""; // hold 5 line comment if object not nearby
if (verb) { // OK, verb found. Try to match with object
do {
noun = findNextNoun(noun); // Find a noun in the line
// Must try at least once for objects allowing verb-context
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (isNear_v1(verb, noun, obj, farComment)) {
if (isObjectVerb_v2(verb, obj) // Foreground object
|| isGenericVerb_v2(verb, obj))// Common action type
return;
}
}
if ((*farComment == '\0') && isBackgroundWord_v2(noun, verb, _backgroundObjects[*_vm->_screenPtr]))
return;
} while (noun);
}
noun = findNextNoun(noun);
if ( !isCatchallVerb_v2(true, noun, verb, _backgroundObjects[*_vm->_screenPtr])
&& !isCatchallVerb_v2(true, noun, verb, _catchallList)
&& !isCatchallVerb_v2(false, noun, verb, _backgroundObjects[*_vm->_screenPtr])
&& !isCatchallVerb_v2(false, noun, verb, _catchallList)) {
if (*farComment != '\0') { // An object matched but not near enough
_vm->notifyBox(farComment);
} else if (_vm->_maze._enabledFl && (verb == _vm->_text->getVerb(_vm->_look, 0))) {
_vm->notifyBox(_vm->_text->getTextParser(kTBMaze));
_vm->_object->showTakeables();
} else if (verb && noun) { // A combination I didn't think of
_vm->notifyBox(_vm->_text->getTextParser(kTBNoUse_2d));
} else if (verb || noun) {
_vm->notifyBox(_vm->_text->getTextParser(kTBNoun));
} else {
_vm->notifyBox(_vm->_text->getTextParser(kTBEh_2d));
}
}
}
/**
* Test whether supplied verb is one of the common variety for this object
* say_ok needed for special case of take/drop which may be handled not only
* here but also in a cmd_list with a donestr string simultaneously
*/
bool Parser_v2d::isGenericVerb_v2(const char *word, Object *obj) {
debugC(1, kDebugParser, "isGenericVerb(%s, Object *obj)", word);
if (!obj->_genericCmd)
return false;
// Following is equivalent to switch, but couldn't do one
if (word == _vm->_text->getVerb(_vm->_look, 0)) {
if ((LOOK & obj->_genericCmd) == LOOK)
if (obj->_dataIndex != 0)
_vm->notifyBox(_vm->_text->getTextData(obj->_dataIndex));
else
return false;
else
_vm->notifyBox(_vm->_text->getTextParser(kTBUnusual_1d));
} else if (word == _vm->_text->getVerb(_vm->_take, 0)) {
if (obj->_carriedFl)
_vm->notifyBox(_vm->_text->getTextParser(kTBHave));
else if ((TAKE & obj->_genericCmd) == TAKE)
takeObject(obj);
else if (obj->_cmdIndex) // No comment if possible commands
return false;
else if (!obj->_verbOnlyFl) // Make sure not taking object in context!
_vm->notifyBox(_vm->_text->getTextParser(kTBNoUse));
else
return false;
} else if (word == _vm->_text->getVerb(_vm->_drop, 0)) {
if (!obj->_carriedFl)
_vm->notifyBox(_vm->_text->getTextParser(kTBDontHave));
else if ((DROP & obj->_genericCmd) == DROP)
dropObject(obj);
else
_vm->notifyBox(_vm->_text->getTextParser(kTBNeed));
} else { // It was not a generic cmd
return false;
}
return true;
}
/**
* Test whether supplied verb is included in the list of allowed verbs for
* this object. If it is, then perform the tests on it from the cmd list
* and if it passes, perform the actions in the action list. If the verb
* is catered for, return TRUE
*/
bool Parser_v2d::isObjectVerb_v2(const char *word, Object *obj) {
debugC(1, kDebugParser, "isObjectVerb(%s, Object *obj)", word);
// First, find matching verb in cmd list
uint16 cmdIndex = obj->_cmdIndex; // ptr to list of commands
if (!cmdIndex) // No commands for this obj
return false;
int i;
for (i = 0; _cmdList[cmdIndex][i]._verbIndex != 0; i++) { // For each cmd
if (word == _vm->_text->getVerb(_cmdList[cmdIndex][i]._verbIndex, 0)) // Is this verb catered for?
break;
}
if (_cmdList[cmdIndex][i]._verbIndex == 0) // No
return false;
// Verb match found, check all required objects are being carried
cmd *cmnd = &_cmdList[cmdIndex][i]; // ptr to struct cmd
if (cmnd->_reqIndex) { // At least 1 thing in list
uint16 *reqs = _arrayReqs[cmnd->_reqIndex]; // ptr to list of required objects
for (i = 0; reqs[i]; i++) { // for each obj
if (!_vm->_object->isCarrying(reqs[i])) {
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataNoCarryIndex));
return true;
}
}
}
// Required objects are present, now check state is correct
if ((obj->_state != cmnd->_reqState) && (cmnd->_reqState != kStateDontCare)){
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataWrongIndex));
return true;
}
// Everything checked. Change the state and carry out any actions
if (cmnd->_reqState != kStateDontCare) // Don't change new state if required state didn't care
obj->_state = cmnd->_newState;
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataDoneIndex));
_vm->_scheduler->insertActionList(cmnd->_actIndex);
// Special case if verb is Take or Drop. Assume additional generic actions
if ((word == _vm->_text->getVerb(_vm->_take, 0)) || (word == _vm->_text->getVerb(_vm->_drop, 0)))
isGenericVerb_v2(word, obj);
return true;
}
/**
* Print text for possible background object. Return TRUE if match found
* Only match if both verb and noun found. Test_ca will match verb-only
*/
bool Parser_v2d::isBackgroundWord_v2(const char *noun, const char *verb, ObjectList obj) const {
debugC(1, kDebugParser, "isBackgroundWord(%s, %s, object_list_t obj)", noun, verb);
// WORKAROUND: obj is an invalid pointer if in the Hugo2 maze (original bug)
if (*(_vm->_screenPtr) >= _backgroundObjectsSize) {
return false;
}
if (!noun)
return false;
for (int i = 0; obj[i]._verbIndex; i++) {
if ((verb == _vm->_text->getVerb(obj[i]._verbIndex, 0)) && (noun == _vm->_text->getNoun(obj[i]._nounIndex, 0)) &&
(obj[i]._roomState == kStateDontCare ||
obj[i]._roomState == _vm->_screenStates[*_vm->_screenPtr])) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
_vm->_scheduler->processBonus(obj[i]._bonusIndex);
return true;
}
}
return false;
}
/**
* Print text for possible background object. Return TRUE if match found
* If test_noun TRUE, must have a noun given
* Algorithm: If (noun matches) OR (match not required AND match specifies NULL) OR
* (no noun present and match required) print comment
* i.e. NULL allows any or no noun, match TRUE allows no noun or matching noun
*/
bool Parser_v2d::isCatchallVerb_v2(bool testNounFl, const char *noun, const char *verb, ObjectList obj) const {
debugC(1, kDebugParser, "isCatchallVerb(%d, %s, %s, object_list_t obj)", (testNounFl) ? 1 : 0, noun, verb);
// WORKAROUND: obj is an invalid pointer if in the Hugo2 maze (original bug)
if (*(_vm->_screenPtr) >= _backgroundObjectsSize) {
return false;
}
if (testNounFl && !noun)
return false;
for (int i = 0; obj[i]._verbIndex; i++) {
if ((verb == _vm->_text->getVerb(obj[i]._verbIndex, 0)) &&
((noun == _vm->_text->getNoun(obj[i]._nounIndex, 0)) ||
(obj[i]._nounIndex == 0 && !obj[i]._matchFl) ||
(!noun && obj[i]._matchFl)) &&
(obj[i]._roomState == kStateDontCare ||
obj[i]._roomState == _vm->_screenStates[*_vm->_screenPtr])) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
_vm->_scheduler->processBonus(obj[i]._bonusIndex);
// If this is LOOK without a noun, show any takeable objects
if (!noun && verb == _vm->_text->getVerb(_vm->_look, 0))
_vm->_object->showTakeables();
return true;
}
}
return false;
}
} // End of namespace Hugo

466
engines/hugo/parser_v3d.cpp Normal file
View File

@@ -0,0 +1,466 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// parser.c - handles all keyboard/command input
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/display.h"
#include "hugo/parser.h"
#include "hugo/file.h"
#include "hugo/schedule.h"
#include "hugo/util.h"
#include "hugo/sound.h"
#include "hugo/object.h"
#include "hugo/text.h"
namespace Hugo {
Parser_v3d::Parser_v3d(HugoEngine *vm) : Parser_v1d(vm) {
}
Parser_v3d::~Parser_v3d() {
}
/**
* Parse the user's line of text input. Generate events as necessary
*/
void Parser_v3d::lineHandler() {
debugC(1, kDebugParser, "lineHandler()");
// Reset the prompt on screen (DOS only)
_vm->_screen->updatePromptText("", ' ');
_vm->_screen->displayPromptText();
Status &gameStatus = _vm->getGameStatus();
// Toggle God Mode
if (!strncmp(_vm->_line, "PPG", 3)) {
_vm->_sound->playSound(_vm->_soundTest, kSoundPriorityHigh);
gameStatus._godModeFl = !gameStatus._godModeFl;
return;
}
Utils::hugo_strlwr(_vm->_line); // Convert to lower case
// God Mode cheat commands:
// goto <screen> Takes hero to named screen
// fetch <object name> Hero carries named object
// fetch all Hero carries all possible objects
// find <object name> Takes hero to screen containing named object
if (gameStatus._godModeFl) {
// Special code to allow me to go straight to any screen
if (strstr(_vm->_line, "goto")) {
for (int i = 0; i < _vm->_numScreens; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("goto") + 1], _vm->_text->getScreenNames(i))) {
_vm->_scheduler->newScreen(i);
return;
}
}
}
// Special code to allow me to get objects from anywhere
if (strstr(_vm->_line, "fetch all")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (_vm->_object->_objects[i]._genericCmd & TAKE)
takeObject(&_vm->_object->_objects[i]);
}
return;
}
if (strstr(_vm->_line, "fetch")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("fetch") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
takeObject(&_vm->_object->_objects[i]);
return;
}
}
}
// Special code to allow me to goto objects
if (strstr(_vm->_line, "find")) {
for (int i = 0; i < _vm->_object->_numObj; i++) {
if (!scumm_stricmp(&_vm->_line[strlen("find") + 1], _vm->_text->getNoun(_vm->_object->_objects[i]._nounIndex, 0))) {
_vm->_scheduler->newScreen(_vm->_object->_objects[i]._screenIndex);
return;
}
}
}
}
// Special meta commands
// EXIT/QUIT
if (!strcmp("exit", _vm->_line) || strstr(_vm->_line, "quit")) {
endGamePrompt();
return;
}
// SAVE/RESTORE
if (!strcmp("save", _vm->_line)) {
if (gameStatus._gameOverFl)
_vm->gameOverMsg();
else
_vm->_file->saveGame(-1, Common::String());
return;
}
if (!strcmp("restore", _vm->_line)) {
_vm->_file->restoreGame(-1);
return;
}
// Empty line
if (*_vm->_line == '\0') // Empty line
return;
if (strspn(_vm->_line, " ") == strlen(_vm->_line)) // Nothing but spaces!
return;
if (gameStatus._gameOverFl) {
// No commands allowed!
_vm->gameOverMsg();
return;
}
char farComment[kCompLineSize * 5] = ""; // hold 5 line comment if object not nearby
// Test for nearby objects referenced explicitly
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (isWordPresent(_vm->_text->getNounArray(obj->_nounIndex))) {
if (isObjectVerb_v3(obj, farComment) || isGenericVerb_v3(obj, farComment))
return;
}
}
// Test for nearby objects that only require a verb
// Note comment is unused if not near.
for (int i = 0; i < _vm->_object->_numObj; i++) {
Object *obj = &_vm->_object->_objects[i];
if (obj->_verbOnlyFl) {
char contextComment[kCompLineSize * 5] = ""; // Unused comment for context objects
if (isObjectVerb_v3(obj, contextComment) || isGenericVerb_v3(obj, contextComment))
return;
}
}
// No objects match command line, try background and catchall commands
if (isBackgroundWord_v3(_backgroundObjects[*_vm->_screenPtr]))
return;
if (isCatchallVerb_v3(_backgroundObjects[*_vm->_screenPtr]))
return;
if (isBackgroundWord_v3(_catchallList))
return;
if (isCatchallVerb_v3(_catchallList))
return;
// If a not-near comment was generated, print it
if (*farComment != '\0') {
_vm->notifyBox(farComment);
return;
}
// Nothing matches. Report recognition success to user.
const char *verb = findVerb();
const char *noun = findNoun();
if (verb && noun) { // A combination I didn't think of
_vm->notifyBox(_vm->_text->getTextParser(kTBNoPoint));
} else if (noun) {
_vm->notifyBox(_vm->_text->getTextParser(kTBNoun));
} else if (verb) {
_vm->notifyBox(_vm->_text->getTextParser(kTBVerb));
} else {
_vm->notifyBox(_vm->_text->getTextParser(kTBEh));
}
}
/**
* Test whether command line contains a verb allowed by this object.
* If it does, and the object is near and passes the tests in the command
* list then carry out the actions in the action list and return TRUE
*/
bool Parser_v3d::isObjectVerb_v3(Object *obj, char *comment) {
debugC(1, kDebugParser, "isObjectVerb(Object *obj, %s)", comment);
// First, find matching verb in cmd list
uint16 cmdIndex = obj->_cmdIndex; // ptr to list of commands
if (cmdIndex == 0) // No commands for this obj
return false;
int i;
for (i = 0; _cmdList[cmdIndex][i]._verbIndex != 0; i++) { // For each cmd
if (isWordPresent(_vm->_text->getVerbArray(_cmdList[cmdIndex][i]._verbIndex))) // Was this verb used?
break;
}
if (_cmdList[cmdIndex][i]._verbIndex == 0) // No verbs used.
return false;
// Verb match found. Check if object is Near
char *verb = *_vm->_text->getVerbArray(_cmdList[cmdIndex][i]._verbIndex);
if (!isNear_v3(obj, verb, comment))
return false;
// Check all required objects are being carried
cmd *cmnd = &_cmdList[cmdIndex][i]; // ptr to struct cmd
if (cmnd->_reqIndex) { // At least 1 thing in list
uint16 *reqs = _arrayReqs[cmnd->_reqIndex]; // ptr to list of required objects
for (i = 0; reqs[i]; i++) { // for each obj
if (!_vm->_object->isCarrying(reqs[i])) {
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataNoCarryIndex));
return true;
}
}
}
// Required objects are present, now check state is correct
if ((obj->_state != cmnd->_reqState) && (cmnd->_reqState != kStateDontCare)) {
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataWrongIndex));
return true;
}
// Everything checked. Change the state and carry out any actions
if (cmnd->_reqState != kStateDontCare) // Don't change new state if required state didn't care
obj->_state = cmnd->_newState;
_vm->notifyBox(_vm->_text->getTextData(cmnd->_textDataDoneIndex));
_vm->_scheduler->insertActionList(cmnd->_actIndex);
// See if any additional generic actions
if ((verb == _vm->_text->getVerb(_vm->_look, 0)) || (verb == _vm->_text->getVerb(_vm->_take, 0)) || (verb == _vm->_text->getVerb(_vm->_drop, 0)))
isGenericVerb_v3(obj, comment);
return true;
}
/**
* Test whether command line contains one of the generic actions
*/
bool Parser_v3d::isGenericVerb_v3(Object *obj, char *comment) {
debugC(1, kDebugParser, "isGenericVerb(Object *obj, %s)", comment);
if (!obj->_genericCmd)
return false;
// Following is equivalent to switch, but couldn't do one
if (isWordPresent(_vm->_text->getVerbArray(_vm->_look)) && isNear_v3(obj, _vm->_text->getVerb(_vm->_look, 0), comment)) {
// Test state-dependent look before general look
if ((obj->_genericCmd & LOOK_S) == LOOK_S) {
_vm->notifyBox(_vm->_text->getTextData(obj->_stateDataIndex[obj->_state]));
} else {
if ((LOOK & obj->_genericCmd) == LOOK) {
if (obj->_dataIndex != 0)
_vm->notifyBox(_vm->_text->getTextData(obj->_dataIndex));
else
return false;
} else {
_vm->notifyBox(_vm->_text->getTextParser(kTBUnusual));
}
}
} else if (isWordPresent(_vm->_text->getVerbArray(_vm->_take)) && isNear_v3(obj, _vm->_text->getVerb(_vm->_take, 0), comment)) {
if (obj->_carriedFl)
_vm->notifyBox(_vm->_text->getTextParser(kTBHave));
else if ((TAKE & obj->_genericCmd) == TAKE)
takeObject(obj);
else if (obj->_cmdIndex) // No comment if possible commands
return false;
else if (!obj->_verbOnlyFl && (TAKE & obj->_genericCmd) == TAKE) // Make sure not taking object in context!
_vm->notifyBox(_vm->_text->getTextParser(kTBNoUse));
else
return false;
} else if (isWordPresent(_vm->_text->getVerbArray(_vm->_drop))) {
if (!obj->_carriedFl && ((DROP & obj->_genericCmd) == DROP))
_vm->notifyBox(_vm->_text->getTextParser(kTBDontHave));
else if (obj->_carriedFl && ((DROP & obj->_genericCmd) == DROP))
dropObject(obj);
else if (obj->_cmdIndex == 0)
_vm->notifyBox(_vm->_text->getTextParser(kTBNeed));
else
return false;
} else { // It was not a generic cmd
return false;
}
return true;
}
/**
* Test whether hero is close to object. Return TRUE or FALSE
* If object not near, return suitable comment; may be another object close
* If radius is -1, treat radius as infinity
* Verb is included to determine correct comment if not near
*/
bool Parser_v3d::isNear_v3(Object *obj, const char *verb, char *comment) const {
debugC(1, kDebugParser, "isNear(Object *obj, %s, %s)", verb, comment);
if (obj->_carriedFl) // Object is being carried
return true;
if (obj->_screenIndex != *_vm->_screenPtr) {
// Not in same screen
if (obj->_objValue)
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny1));
else
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny2));
return false;
}
if (obj->_cycling == kCycleInvisible) {
if (obj->_seqNumb) {
// There is an image
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny3));
return false;
} else {
// No image, assume visible
if ((obj->_radius < 0) ||
((abs(obj->_x - _vm->_hero->_x) <= obj->_radius) &&
(abs(obj->_y - _vm->_hero->_y - _vm->_hero->_currImagePtr->_y2) <= obj->_radius))) {
return true;
} else {
// User is not close enough
if (obj->_objValue && (verb != _vm->_text->getVerb(_vm->_take, 0)))
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny1));
else
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtClose));
return false;
}
}
}
if ((obj->_radius < 0) ||
((abs(obj->_x - _vm->_hero->_x) <= obj->_radius) &&
(abs(obj->_y + obj->_currImagePtr->_y2 - _vm->_hero->_y - _vm->_hero->_currImagePtr->_y2) <= obj->_radius))) {
return true;
} else {
// User is not close enough
if (obj->_objValue && (verb != _vm->_text->getVerb(_vm->_take, 0)))
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtAny1));
else
Common::strcpy_s(comment, kCompLineSize * 5, _vm->_text->getTextParser(kCmtClose));
return false;
}
return true;
}
/**
* Do all things necessary to carry an object
*/
void Parser_v3d::takeObject(Object *obj) {
debugC(1, kDebugParser, "takeObject(Object *obj)");
obj->_carriedFl = true;
if (obj->_seqNumb) { // Don't change if no image to display
obj->_cycling = kCycleInvisible;
}
_vm->adjustScore(obj->_objValue);
_vm->_screen->updateStatusText();
if (obj->_seqNumb > 0) // If object has an image, force walk to dropped
obj->_viewx = -1; // (possibly moved) object next time taken!
_vm->takeObjectBox(_vm->_text->getNoun(obj->_nounIndex, TAKE_NAME));
}
/**
* Do all necessary things to drop an object
*/
void Parser_v3d::dropObject(Object *obj) {
debugC(1, kDebugParser, "dropObject(Object *obj)");
obj->_carriedFl = false;
obj->_screenIndex = *_vm->_screenPtr;
if ((obj->_seqNumb > 1) || (obj->_seqList[0]._imageNbr > 1))
obj->_cycling = kCycleForward;
else
obj->_cycling = kCycleNotCycling;
obj->_x = _vm->_hero->_x - 1;
obj->_y = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2 - 1;
obj->_y = (obj->_y + obj->_currImagePtr->_y2 < kYPix) ? obj->_y : kYPix - obj->_currImagePtr->_y2 - 10;
_vm->adjustScore(-obj->_objValue);
_vm->_screen->updateStatusText();
_vm->notifyBox(_vm->_text->getTextParser(kTBOk));
}
/**
* Search for matching verbs in background command list.
* Noun is not required. Return TRUE if match found
* Note that if the background command list has match set TRUE then do not
* print text if there are any recognizable nouns in the command line
*/
bool Parser_v3d::isCatchallVerb_v3(ObjectList obj) const {
debugC(1, kDebugParser, "isCatchallVerb(object_list_t obj)");
// WORKAROUND: obj is an invalid pointer if in the Hugo2 maze (original bug)
if (*(_vm->_screenPtr) >= _backgroundObjectsSize) {
return false;
}
for (int i = 0; obj[i]._verbIndex != 0; i++) {
if (isWordPresent(_vm->_text->getVerbArray(obj[i]._verbIndex)) && obj[i]._nounIndex == 0 &&
(!obj[i]._matchFl || !findNoun()) &&
((obj[i]._roomState == kStateDontCare) ||
(obj[i]._roomState == _vm->_screenStates[*_vm->_screenPtr]))) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
_vm->_scheduler->processBonus(obj[i]._bonusIndex);
// If this is LOOK (without a noun), show any takeable objects
if (*(_vm->_text->getVerbArray(obj[i]._verbIndex)) == _vm->_text->getVerb(_vm->_look, 0))
_vm->_object->showTakeables();
return true;
}
}
return false;
}
/**
* Search for matching verb/noun pairs in background command list
* Print text for possible background object. Return TRUE if match found
*/
bool Parser_v3d::isBackgroundWord_v3(ObjectList obj) const {
debugC(1, kDebugParser, "isBackgroundWord(object_list_t obj)");
// WORKAROUND: obj is an invalid pointer if in the Hugo2 maze (original bug)
if (*(_vm->_screenPtr) >= _backgroundObjectsSize) {
return false;
}
for (int i = 0; obj[i]._verbIndex != 0; i++) {
if (isWordPresent(_vm->_text->getVerbArray(obj[i]._verbIndex)) &&
isWordPresent(_vm->_text->getNounArray(obj[i]._nounIndex)) &&
((obj[i]._roomState == kStateDontCare) ||
(obj[i]._roomState == _vm->_screenStates[*_vm->_screenPtr]))) {
_vm->notifyBox(_vm->_file->fetchString(obj[i]._commentIndex));
_vm->_scheduler->processBonus(obj[i]._bonusIndex);
return true;
}
}
return false;
}
} // End of namespace Hugo

544
engines/hugo/route.cpp Normal file
View File

@@ -0,0 +1,544 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// Find shortest route from hero to destination
#include "common/debug.h"
#include "common/system.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/route.h"
#include "hugo/object.h"
#include "hugo/inventory.h"
#include "hugo/mouse.h"
namespace Hugo {
Route::Route(HugoEngine *vm) : _vm(vm) {
_oldWalkDirection = 0;
_routeIndex = -1; // Hero not following a route
_routeType = kRouteSpace; // Hero walking to space
_routeObjId = -1; // Hero not walking to anything
for (int i = 0; i < kMaxSeg; i++)
_segment[i]._y = _segment[i]._x1 = _segment[i]._x2 = 0;
_segmentNumb = 0;
_routeListIndex = 0;
_destX = _destY = 0;
_heroWidth = 0;
_routeFoundFl = false;
_fullStackFl = false;
_fullSegmentFl = false;
}
void Route::resetRoute() {
_routeIndex = -1;
}
int16 Route::getRouteIndex() const {
return _routeIndex;
}
/**
* Face hero in new direction, based on cursor key input by user.
*/
void Route::setDirection(const uint16 keyCode) {
debugC(1, kDebugRoute, "setDirection(%d)", keyCode);
Object *obj = _vm->_hero; // Pointer to hero object
// Set first image in sequence
switch (keyCode) {
case Common::KEYCODE_UP:
case Common::KEYCODE_KP8:
obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
break;
case Common::KEYCODE_DOWN:
case Common::KEYCODE_KP2:
obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
break;
case Common::KEYCODE_LEFT:
case Common::KEYCODE_KP4:
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
break;
case Common::KEYCODE_RIGHT:
case Common::KEYCODE_KP6:
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
break;
case Common::KEYCODE_HOME:
case Common::KEYCODE_KP7:
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
break;
case Common::KEYCODE_END:
case Common::KEYCODE_KP1:
obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
break;
case Common::KEYCODE_PAGEUP:
case Common::KEYCODE_KP9:
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
break;
case Common::KEYCODE_PAGEDOWN:
case Common::KEYCODE_KP3:
obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
break;
default:
break;
}
}
/**
* Set hero walking, based on cursor key input by user.
* Hitting same key twice will stop hero.
*/
void Route::setWalk(const uint16 direction) {
debugC(1, kDebugRoute, "setWalk(%d)", direction);
Object *obj = _vm->_hero; // Pointer to hero object
if (_vm->getGameStatus()._storyModeFl || obj->_pathType != kPathUser) // Make sure user has control
return;
if (!obj->_vx && !obj->_vy)
_oldWalkDirection = 0; // Fix for consistent restarts
if (direction != _oldWalkDirection) {
// Direction has changed
setDirection(direction); // Face new direction
obj->_vx = obj->_vy = 0;
switch (direction) { // And set correct velocity
case Common::KEYCODE_UP:
case Common::KEYCODE_KP8:
obj->_vy = -kStepDy;
break;
case Common::KEYCODE_DOWN:
case Common::KEYCODE_KP2:
obj->_vy = kStepDy;
break;
case Common::KEYCODE_LEFT:
case Common::KEYCODE_KP4:
obj->_vx = -kStepDx;
break;
case Common::KEYCODE_RIGHT:
case Common::KEYCODE_KP6:
obj->_vx = kStepDx;
break;
case Common::KEYCODE_HOME:
case Common::KEYCODE_KP7:
obj->_vx = -kStepDx;
// Note: in v1 Dos and v2 Dos, obj->vy is set to DY
obj->_vy = -kStepDy / 2;
break;
case Common::KEYCODE_END:
case Common::KEYCODE_KP1:
obj->_vx = -kStepDx;
// Note: in v1 Dos and v2 Dos, obj->vy is set to -DY
obj->_vy = kStepDy / 2;
break;
case Common::KEYCODE_PAGEUP:
case Common::KEYCODE_KP9:
obj->_vx = kStepDx;
// Note: in v1 Dos and v2 Dos, obj->vy is set to -DY
obj->_vy = -kStepDy / 2;
break;
case Common::KEYCODE_PAGEDOWN:
case Common::KEYCODE_KP3:
obj->_vx = kStepDx;
// Note: in v1 Dos and v2 Dos, obj->vy is set to DY
obj->_vy = kStepDy / 2;
break;
default:
break;
}
_oldWalkDirection = direction;
obj->_cycling = kCycleForward;
} else {
// Same key twice - halt hero
obj->_vy = 0;
obj->_vx = 0;
_oldWalkDirection = 0;
obj->_cycling = kCycleNotCycling;
}
}
/**
* Recursive algorithm! Searches from hero to dest_x, dest_y
* Find horizontal line segment about supplied point and recursively
* find line segments for each point above and below that segment.
* When destination point found in segment, start surfacing and leave
* a trail in segment[] from destination back to hero.
*
* Note: there is a bug which allows a route through a 1-pixel high
* narrow gap if between 2 segments wide enough for hero. To work
* around this, make sure any narrow gaps are 2 or more pixels high.
* An example of this was the blocking guard in Hugo1/Dead-End.
*/
void Route::segment(int16 x, int16 y) {
debugC(1, kDebugRoute, "segment(%d, %d)", x, y);
// Note: use of static - can't waste stack
static ImagePtr p; // Ptr to _boundaryMap[y]
static Segment *segPtr; // Ptr to segment
// Bomb out if stack exhausted
// Vinterstum: Is this just a safeguard, or actually used?
//_fullStackFl = _stackavail () < 256;
_fullStackFl = false;
// Find and fill on either side of point
p = _boundaryMap[y];
int16 x1, x2; // Range of segment
for (x1 = x; x1 > 0; x1--) {
if (p[x1] == 0) {
p[x1] = kMapFill;
} else {
break;
}
}
for (x2 = x + 1; x2 < kXPix; x2++) {
if (p[x2] == 0) {
p[x2] = kMapFill;
} else {
break;
}
}
x1++;
x2--;
// Discard path if not wide enough for hero - dead end
if (_heroWidth > x2 - x1 + 1)
return;
// Have we found the destination yet?
if (y == _destY && x1 <= _destX && x2 >= _destX)
_routeFoundFl = true;
// Bounds check y in case no boundary around screen
if (y <= 0 || y >= kYPix - 1)
return;
if (_vm->_hero->_x < x1) {
// Hero x not in segment, search x1..x2
// Find all segments above current
for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
if (_boundaryMap[y - 1][x] == 0)
segment(x, y - 1);
}
// Find all segments below current
for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
if (_boundaryMap[y + 1][x] == 0)
segment(x, y + 1);
}
} else if (_vm->_hero->_x + kHeroMaxWidth > x2) {
// Hero x not in segment, search x1..x2
// Find all segments above current
for (x = x2; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x >= x1; x--) {
if (_boundaryMap[y - 1][x] == 0)
segment(x, y - 1);
}
// Find all segments below current
for (x = x2; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x >= x1; x--) {
if (_boundaryMap[y + 1][x] == 0)
segment(x, y + 1);
}
} else {
// Organize search around hero x position - this gives
// better chance for more direct route.
for (x = _vm->_hero->_x; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
if (_boundaryMap[y - 1][x] == 0)
segment(x, y - 1);
}
for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x < _vm->_hero->_x; x++) {
if (_boundaryMap[y - 1][x] == 0)
segment(x, y - 1);
}
for (x = _vm->_hero->_x; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
if (_boundaryMap[y + 1][x] == 0)
segment(x, y + 1);
}
for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x < _vm->_hero->_x; x++) {
if (_boundaryMap[y + 1][x] == 0)
segment(x, y + 1);
}
}
// If found, surface, leaving trail back to hero
if (_routeFoundFl) {
// Bomb out if too many segments (leave one spare)
if (_segmentNumb >= kMaxSeg - 1) {
_fullSegmentFl = true;
} else {
// Create segment
segPtr = &_segment[_segmentNumb];
segPtr->_y = y;
segPtr->_x1 = x1;
segPtr->_x2 = x2;
_segmentNumb++;
}
}
}
/**
* Create and return ptr to new node. Initialize with previous node.
* Returns 0 if MAX_NODES exceeded
*/
Common::Point *Route::newNode() {
debugC(1, kDebugRoute, "newNode");
_routeListIndex++;
if (_routeListIndex >= kMaxNodes) // Too many nodes
return nullptr; // Incomplete route - failure
_route[_routeListIndex] = _route[_routeListIndex - 1]; // Initialize with previous node
return &_route[_routeListIndex];
}
/**
* Construct route to cx, cy. Return TRUE if successful.
* 1. Copy boundary bitmap to local byte map (include object bases)
* 2. Construct list of segments segment[] from hero to destination
* 3. Compress to shortest route in route[]
*/
bool Route::findRoute(const int16 cx, const int16 cy) {
debugC(1, kDebugRoute, "findRoute(%d, %d)", cx, cy);
// Initialize for search
_routeFoundFl = false; // Path not found yet
_fullStackFl = false; // Stack not exhausted
_fullSegmentFl = false; // Segments not exhausted
_segmentNumb = 0; // Segment index
_heroWidth = kHeroMinWidth; // Minimum width of hero
_destY = cy; // Destination coords
_destX = cx; // Destination coords
int16 herox1 = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1; // Hero baseline
int16 herox2 = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x2; // Hero baseline
int16 heroy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2; // Hero baseline
// Store all object baselines into objbound (except hero's = [0])
Object *obj; // Ptr to object
int i;
for (i = 1, obj = &_vm->_object->_objects[i]; i < _vm->_object->_numObj; i++, obj++) {
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling != kCycleInvisible) && (obj->_priority == kPriorityFloating))
_vm->_object->storeBoundary(obj->_oldx + obj->_currImagePtr->_x1, obj->_oldx + obj->_currImagePtr->_x2, obj->_oldy + obj->_currImagePtr->_y2);
}
// Combine objbound and boundary bitmaps to local byte map
for (uint16 y = 0; y < kYPix; y++) {
for (uint16 x = 0; x < kCompLineSize; x++) {
uint16 boundIdx = y * kCompLineSize + x;
for (i = 0; i < 8; i++)
_boundaryMap[y][x * 8 + i] = ((_vm->_object->getObjectBoundary(boundIdx) | _vm->_object->getBoundaryOverlay(boundIdx)) & (0x80 >> i)) ? kMapBound : 0;
}
}
// Clear all object baselines from objbound
for (i = 0, obj = _vm->_object->_objects; i < _vm->_object->_numObj; i++, obj++) {
if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling != kCycleInvisible) && (obj->_priority == kPriorityFloating))
_vm->_object->clearBoundary(obj->_oldx + obj->_currImagePtr->_x1, obj->_oldx + obj->_currImagePtr->_x2, obj->_oldy + obj->_currImagePtr->_y2);
}
// Search from hero to destination
segment(herox1, heroy);
// Not found or not enough stack or MAX_SEG exceeded
if (!_routeFoundFl || _fullStackFl || _fullSegmentFl) {
return false;
}
// Now find the route of nodes from destination back to hero
// Assign first node as destination
_route[0].x = _destX;
_route[0].y = _destY;
// Make a final segment for hero's base (we left a spare)
_segment[_segmentNumb]._y = heroy;
_segment[_segmentNumb]._x1 = herox1;
_segment[_segmentNumb]._x2 = herox2;
_segmentNumb++;
// Look in segments[] for straight lines from destination to hero
for (i = 0, _routeListIndex = 0; i < _segmentNumb - 1; i++) {
Common::Point *routeNode; // Ptr to route node
if ((routeNode = newNode()) == nullptr) // New node for new segment
return false; // Too many nodes
routeNode->y = _segment[i]._y;
// Look ahead for furthest straight line
for (int16 j = i + 1; j < _segmentNumb; j++) {
Segment *segPtr = &_segment[j];
// Can we get to this segment from previous node?
if (segPtr->_x1 <= routeNode->x && segPtr->_x2 >= routeNode->x + _heroWidth - 1) {
routeNode->y = segPtr->_y; // Yes, keep updating node
} else {
// No, create another node on previous segment to reach it
if ((routeNode = newNode()) == nullptr) // Add new route node
return false; // Too many nodes
// Find overlap between old and new segments
int16 x1 = MAX(_segment[j - 1]._x1, segPtr->_x1);
int16 x2 = MIN(_segment[j - 1]._x2, segPtr->_x2);
// If room, add a little offset to reduce staircase effect
int16 dx = kHeroMaxWidth >> 1;
if (x2 - x1 < _heroWidth + dx)
dx = 0;
// Bear toward final hero position
if (j == _segmentNumb - 1)
routeNode->x = herox1;
else if (herox1 < x1)
routeNode->x = x1 + dx;
else if (herox1 > x2 - _heroWidth + 1)
routeNode->x = x2 - _heroWidth - dx;
else
routeNode->x = herox1;
i = j - 2; // Restart segment (-1 to offset auto increment)
break;
}
}
// Terminate loop if we've reached hero
if (routeNode->x == herox1 && routeNode->y == heroy)
break;
}
return true;
}
/**
* Process hero in route mode - called from Move_objects()
*/
void Route::processRoute() {
debugC(1, kDebugRoute, "processRoute");
static bool turnedFl = false; // Used to get extra cycle for turning
if (_routeIndex < 0)
return;
// Current hero position
int16 herox = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1;
int16 heroy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2;
Common::Point *routeNode = &_route[_routeIndex];
// Arrived at node?
if (abs(herox - routeNode->x) < kStepDx + 1 && abs(heroy - routeNode->y) < kStepDy) {
// kStepDx too low
// Close enough - position hero exactly
_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
_vm->_hero->_y = _vm->_hero->_oldy = routeNode->y - _vm->_hero->_currImagePtr->_y2;
_vm->_hero->_vx = _vm->_hero->_vy = 0;
_vm->_hero->_cycling = kCycleNotCycling;
// Arrived at final node?
if (--_routeIndex < 0) {
// See why we walked here
switch (_routeType) {
case kRouteExit: // Walked to an exit, proceed into it
setWalk(_vm->_mouse->getDirection(_routeObjId));
break;
case kRouteLook: // Look at an object
if (turnedFl) {
_vm->_object->lookObject(&_vm->_object->_objects[_routeObjId]);
turnedFl = false;
} else {
setDirection(_vm->_object->_objects[_routeObjId]._direction);
_routeIndex++; // Come round again
turnedFl = true;
}
break;
case kRouteGet: // Get (or use) an object
if (turnedFl) {
_vm->_object->useObject(_routeObjId);
turnedFl = false;
} else {
setDirection(_vm->_object->_objects[_routeObjId]._direction);
_routeIndex++; // Come round again
turnedFl = true;
}
break;
default:
break;
}
}
} else if (_vm->_hero->_vx == 0 && _vm->_hero->_vy == 0) {
// Set direction of travel if at a node
// Note realignment when changing to (thinner) up/down sprite,
// otherwise hero could bump into boundaries along route.
if (herox < routeNode->x) {
setWalk(Common::KEYCODE_RIGHT);
} else if (herox > routeNode->x) {
setWalk(Common::KEYCODE_LEFT);
} else if (heroy < routeNode->y) {
setWalk(Common::KEYCODE_DOWN);
_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
} else if (heroy > routeNode->y) {
setWalk(Common::KEYCODE_UP);
_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
}
}
}
/**
* Start a new route from hero to cx, cy
* go_for is the purpose, id indexes the exit or object to walk to
* Returns FALSE if route not found
*/
bool Route::startRoute(const RouteType routeType, const int16 objId, int16 cx, int16 cy) {
debugC(1, kDebugRoute, "startRoute(%d, %d, %d, %d)", routeType, objId, cx, cy);
// Don't attempt to walk if user does not have control
if (_vm->_hero->_pathType != kPathUser)
return false;
// if inventory showing, make it go away
if (_vm->_inventory->getInventoryState() != kInventoryOff)
_vm->_inventory->setInventoryState(kInventoryUp);
_routeType = routeType; // Purpose of trip
_routeObjId = objId; // Index of exit/object
// Adjust destination to center hero if walking to cursor
if (_routeType == kRouteSpace)
cx -= kHeroMinWidth / 2;
bool foundFl = false; // TRUE if route found ok
if ((foundFl = findRoute(cx, cy))) { // Found a route?
_routeIndex = _routeListIndex; // Node index
_vm->_hero->_vx = _vm->_hero->_vy = 0; // Stop manual motion
}
return foundFl;
}
} // End of namespace Hugo

93
engines/hugo/route.h Normal file
View File

@@ -0,0 +1,93 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_ROUTE_H
#define HUGO_ROUTE_H
#include "common/rect.h"
namespace Hugo {
/**
* Purpose of an automatic route
*/
enum RouteType {kRouteSpace, kRouteExit, kRouteLook, kRouteGet};
struct Segment { // Search segment
int16 _y; // y position
int16 _x1, _x2; // Range of segment
};
class Route {
public:
Route(HugoEngine *vm);
void resetRoute();
int16 getRouteIndex() const;
void processRoute();
bool startRoute(const RouteType routeType, const int16 objId, int16 cx, int16 cy);
void setDirection(const uint16 keyCode);
void setWalk(const uint16 direction);
private:
HugoEngine *_vm;
static const int kMapBound = 1; // Mark a boundary outline
static const int kMapFill = 2; // Mark a boundary filled
static const int kMaxSeg = 256; // Maximum number of segments
static const int kMaxNodes = 256; // Maximum nodes in route
uint16 _oldWalkDirection; // Last direction char
int16 _routeIndex; // Index into route list, or -1
RouteType _routeType; // Purpose of an automatic route
int16 _routeObjId; // Index of exit of object walking to
byte _boundaryMap[kYPix][kXPix]; // Boundary byte map
Segment _segment[kMaxSeg]; // List of points in fill-path
Common::Point _route[kMaxNodes]; // List of nodes in route (global)
int16 _segmentNumb; // Count number of segments
int16 _routeListIndex; // Index into route list
int16 _destX;
int16 _destY;
int16 _heroWidth; // Hero width
bool _routeFoundFl; // TRUE when path found
bool _fullSegmentFl; // Segments exhausted
// CHECKME: Never set to true, could be removed
bool _fullStackFl; // TRUE if stack exhausted
void segment(int16 x, int16 y);
bool findRoute(const int16 cx, const int16 cy);
Common::Point *newNode();
};
} // End of namespace Hugo
#endif //HUGO_ROUTE_H

1713
engines/hugo/schedule.cpp Normal file

File diff suppressed because it is too large Load Diff

645
engines/hugo/schedule.h Normal file
View File

@@ -0,0 +1,645 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_SCHEDULE_H
#define HUGO_SCHEDULE_H
#include "common/file.h"
namespace Hugo {
/**
* Following defines the action types and action list
*/
enum Action { // Parameters:
ANULL = 0xff, // Special NOP used to 'delete' events in DEL_EVENTS
ASCHEDULE = 0, // 0 - Ptr to action list to be rescheduled
START_OBJ, // 1 - Object number
INIT_OBJXY, // 2 - Object number, x,y
PROMPT, // 3 - index of prompt & response string, ptrs to action
// lists. First if response matches, 2nd if not.
BKGD_COLOR, // 4 - new background color
INIT_OBJVXY, // 5 - Object number, vx, vy
INIT_CARRY, // 6 - Object number, carried status
INIT_HF_COORD, // 7 - Object number (gets hero's 'feet' coordinates)
NEW_SCREEN, // 8 - New screen number
INIT_OBJSTATE, // 9 - Object number, new object state
INIT_PATH, // 10 - Object number, new path type
COND_R, // 11 - Conditional on object state - req state, 2 act_lists
TEXT, // 12 - Simple text box
SWAP_IMAGES, // 13 - Swap 2 object images
COND_SCR, // 14 - Conditional on current screen
AUTOPILOT, // 15 - Set object to home in on another (stationary) object
INIT_OBJ_SEQ, // 16 - Object number, sequence index to set curr_seqPtr to
SET_STATE_BITS, // 17 - Objnum, mask to OR with obj states word
CLEAR_STATE_BITS, // 18 - Objnum, mask to ~AND with obj states word
TEST_STATE_BITS, // 19 - Objnum, mask to test obj states word
DEL_EVENTS, // 20 - Action type to delete all occurrences of
GAMEOVER, // 21 - Disable hero & commands. Game is over
INIT_HH_COORD, // 22 - Object number (gets hero's actual coordinates)
EXIT, // 23 - Exit game back to DOS
BONUS, // 24 - Get score bonus for an action
COND_BOX, // 25 - Conditional on object within bounding box
SOUND, // 26 - Set currently playing sound
ADD_SCORE, // 27 - Add object's value to current score
SUB_SCORE, // 28 - Subtract object's value from current score
COND_CARRY, // 29 - Conditional on carrying object
INIT_MAZE, // 30 - Start special maze hotspot processing
EXIT_MAZE, // 31 - Exit special maze processing
INIT_PRIORITY, // 32 - Initialize fbg field
INIT_SCREEN, // 33 - Initialize screen field of object
AGSCHEDULE, // 34 - Global schedule - lasts over new screen
REMAPPAL, // 35 - Remappe palette - palette index, color
COND_NOUN, // 36 - Conditional on noun appearing in line
SCREEN_STATE, // 37 - Set new screen state - used for comments
INIT_LIPS, // 38 - Position lips object for supplied object
INIT_STORY_MODE, // 39 - Set story mode TRUE/FALSE (user can't type)
WARN, // 40 - Same as TEXT but can't dismiss box by typing
COND_BONUS, // 41 - Conditional on bonus having been scored
TEXT_TAKE, // 42 - Issue text box with "take" info string
YESNO, // 43 - Prompt user for Yes or No
STOP_ROUTE, // 44 - Skip any route in progress (hero still walks)
COND_ROUTE, // 45 - Conditional on route in progress
INIT_JUMPEXIT, // 46 - Initialize status.jumpexit
INIT_VIEW, // 47 - Initialize viewx, viewy, dir
INIT_OBJ_FRAME, // 48 - Object number, seq,frame to set curr_seqPtr to
OLD_SONG = 49 // Added by Strangerke - Set currently playing sound, old way: that is, using a string index instead of a reference in a file
};
struct act0 { // Type 0 - Schedule
Action _actType; // The type of action
int _timer; // Time to set off the action
uint16 _actIndex; // Ptr to an action list
};
struct act1 { // Type 1 - Start an object
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _cycleNumb; // Number of times to cycle
Cycle _cycle; // Direction to start cycling
};
struct act2 { // Type 2 - Initialize an object coords
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _x, _y; // Coordinates
};
struct act3 { // Type 3 - Prompt user for text
Action _actType; // The type of action
int _timer; // Time to set off the action
uint16 _promptIndex; // Index of prompt string
int *_responsePtr; // Array of indexes to valid response string(s) (terminate list with -1)
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
bool _encodedFl; // (HUGO 1 DOS ONLY) Whether response is encoded or not
};
struct act4 { // Type 4 - Set new background color
Action _actType; // The type of action
int _timer; // Time to set off the action
long _newBackgroundColor; // New color
};
struct act5 { // Type 5 - Initialize an object velocity
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _vx, _vy; // velocity
};
struct act6 { // Type 6 - Initialize an object carrying
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
bool _carriedFl; // carrying
};
struct act7 { // Type 7 - Initialize an object to hero's coords
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
};
struct act8 { // Type 8 - switch to new screen
Action _actType; // The type of action
int _timer; // Time to set off the action
int _screenIndex; // The new screen number
};
struct act9 { // Type 9 - Initialize an object state
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
byte _newState; // New state
};
struct act10 { // Type 10 - Initialize an object path type
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _newPathType; // New path type
int8 _vxPath, _vyPath; // Max delta velocities e.g. for CHASE
};
struct act11 { // Type 11 - Conditional on object's state
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
byte _stateReq; // Required state
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
};
struct act12 { // Type 12 - Simple text box
Action _actType; // The type of action
int _timer; // Time to set off the action
int _stringIndex; // Index (enum) of string in strings.dat
};
struct act13 { // Type 13 - Swap first object image with second
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex1; // Index of first object
int _objIndex2; // 2nd
};
struct act14 { // Type 14 - Conditional on current screen
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The required object
int _screenReq; // The required screen number
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
};
struct act15 { // Type 15 - Home in on an object
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex1; // The object number homing in
int _objIndex2; // The object number to home in on
int8 _dx, _dy; // Max delta velocities
};
// Note: Don't set a sequence at time 0 of a new screen, it causes
// problems clearing the boundary bits of the object! timer > 0 is safe
struct act16 { // Type 16 - Set curr_seqPtr to seq
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _seqIndex; // The index of seq array to set to
};
struct act17 { // Type 17 - SET obj individual state bits
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _stateMask; // The mask to OR with current obj state
};
struct act18 { // Type 18 - CLEAR obj individual state bits
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _stateMask; // The mask to ~AND with current obj state
};
struct act19 { // Type 19 - TEST obj individual state bits
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _stateMask; // The mask to AND with current obj state
uint16 _actPassIndex; // Ptr to action list (all bits set)
uint16 _actFailIndex; // Ptr to action list (not all set)
};
struct act20 { // Type 20 - Remove all events with this type of action
Action _actType; // The type of action
int _timer; // Time to set off the action
Action _actTypeDel; // The action type to remove
};
struct act21 { // Type 21 - Gameover. Disable hero & commands
Action _actType; // The type of action
int _timer; // Time to set off the action
};
struct act22 { // Type 22 - Initialize an object to hero's coords
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
};
struct act23 { // Type 23 - Exit game back to DOS
Action _actType; // The type of action
int _timer; // Time to set off the action
};
struct act24 { // Type 24 - Get bonus score
Action _actType; // The type of action
int _timer; // Time to set off the action
int _pointIndex; // Index into points array
};
struct act25 { // Type 25 - Conditional on bounding box
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The required object number
int _x1, _y1, _x2, _y2; // The bounding box
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
};
struct act26 { // Type 26 - Play a sound
Action _actType; // The type of action
int _timer; // Time to set off the action
int16 _soundIndex; // Sound index in data file
};
struct act27 { // Type 27 - Add object's value to score
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // object number
};
struct act28 { // Type 28 - Subtract object's value from score
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // object number
};
struct act29 { // Type 29 - Conditional on object carried
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The required object number
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
};
struct act30 { // Type 30 - Start special maze processing
Action _actType; // The type of action
int _timer; // Time to set off the action
byte _mazeSize; // Size of (square) maze
int _x1, _y1, _x2, _y2; // Bounding box of maze
int _x3, _x4; // Extra x points for perspective correction
byte _firstScreenIndex; // First (top left) screen of maze
};
struct act31 { // Type 31 - Exit special maze processing
Action _actType; // The type of action
int _timer; // Time to set off the action
};
struct act32 { // Type 32 - Init fbg field of object
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
byte _priority; // Value of foreground/background field
};
struct act33 { // Type 33 - Init screen field of object
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _screenIndex; // Screen number
};
struct act34 { // Type 34 - Global Schedule
Action _actType; // The type of action
int _timer; // Time to set off the action
uint16 _actIndex; // Ptr to an action list
};
struct act35 { // Type 35 - Remappe palette
Action _actType; // The type of action
int _timer; // Time to set off the action
int16 _oldColorIndex; // Old color index, 0..15
int16 _newColorIndex; // New color index, 0..15
};
struct act36 { // Type 36 - Conditional on noun mentioned
Action _actType; // The type of action
int _timer; // Time to set off the action
uint16 _nounIndex; // The required noun (list)
uint16 _actPassIndex; // Ptr to action list if success
uint16 _actFailIndex; // Ptr to action list if failure
};
struct act37 { // Type 37 - Set new screen state
Action _actType; // The type of action
int _timer; // Time to set off the action
int _screenIndex; // The screen number
byte _newState; // The new state
};
struct act38 { // Type 38 - Position lips
Action _actType; // The type of action
int _timer; // Time to set off the action
int _lipsObjIndex; // The LIPS object
int _objIndex; // The object to speak
byte _dxLips; // Relative offset of x
byte _dyLips; // Relative offset of y
};
struct act39 { // Type 39 - Init story mode
Action _actType; // The type of action
int _timer; // Time to set off the action
bool _storyModeFl; // New state of story_mode flag
};
struct act40 { // Type 40 - Unsolicited text box
Action _actType; // The type of action
int _timer; // Time to set off the action
int _stringIndex; // Index (enum) of string in strings.dat
};
struct act41 { // Type 41 - Conditional on bonus scored
Action _actType; // The type of action
int _timer; // Time to set off the action
int _bonusIndex; // Index into bonus list
uint16 _actPassIndex; // Index of the action list if scored for the first time
uint16 _actFailIndex; // Index of the action list if already scored
};
struct act42 { // Type 42 - Text box with "take" string
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object taken
};
struct act43 { // Type 43 - Prompt user for Yes or No
Action _actType; // The type of action
int _timer; // Time to set off the action
int _promptIndex; // index of prompt string
uint16 _actYesIndex; // Ptr to action list if YES
uint16 _actNoIndex; // Ptr to action list if NO
};
struct act44 { // Type 44 - Stop any route in progress
Action _actType; // The type of action
int _timer; // Time to set off the action
};
struct act45 { // Type 45 - Conditional on route in progress
Action _actType; // The type of action
int _timer; // Time to set off the action
int _routeIndex; // Must be >= current status.rindex
uint16 _actPassIndex; // Ptr to action list if en-route
uint16 _actFailIndex; // Ptr to action list if not
};
struct act46 { // Type 46 - Init status.jumpexit
Action _actType; // The type of action
int _timer; // Time to set off the action
bool _jumpExitFl; // New state of jumpexit flag
};
struct act47 { // Type 47 - Init viewx,viewy,dir
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object
int16 _viewx; // object.viewx
int16 _viewy; // object.viewy
int16 _direction; // object.dir
};
struct act48 { // Type 48 - Set curr_seqPtr to frame n
Action _actType; // The type of action
int _timer; // Time to set off the action
int _objIndex; // The object number
int _seqIndex; // The index of seq array to set to
int _frameIndex; // The index of frame to set to
};
struct act49 { // Added by Strangerke - Type 49 - Play a song (DOS way)
Action _actType; // The type of action
int _timer; // Time to set off the action
uint16 _songIndex; // Song index in string array
};
union Act {
act0 _a0;
act1 _a1;
act2 _a2;
act3 _a3;
act4 _a4;
act5 _a5;
act6 _a6;
act7 _a7;
act8 _a8;
act9 _a9;
act10 _a10;
act11 _a11;
act12 _a12;
act13 _a13;
act14 _a14;
act15 _a15;
act16 _a16;
act17 _a17;
act18 _a18;
act19 _a19;
act20 _a20;
act21 _a21;
act22 _a22;
act23 _a23;
act24 _a24;
act25 _a25;
act26 _a26;
act27 _a27;
act28 _a28;
act29 _a29;
act30 _a30;
act31 _a31;
act32 _a32;
act33 _a33;
act34 _a34;
act35 _a35;
act36 _a36;
act37 _a37;
act38 _a38;
act39 _a39;
act40 _a40;
act41 _a41;
act42 _a42;
act43 _a43;
act44 _a44;
act45 _a45;
act46 _a46;
act47 _a47;
act48 _a48;
act49 _a49;
};
struct Event {
Act *_action; // Ptr to action to perform
bool _localActionFl; // true if action is only for this screen
uint32 _time; // (absolute) time to perform action
struct Event *_prevEvent; // Chain to previous event
struct Event *_nextEvent; // Chain to next event
};
/**
* Following are points for achieving certain actions.
*/
struct Point {
byte _score; // The value of the point
bool _scoredFl; // Whether scored yet
};
class Scheduler {
public:
Scheduler(HugoEngine *vm);
virtual ~Scheduler();
virtual void decodeString(char *line) = 0;
virtual void runScheduler() = 0;
int16 calcMaxPoints() const;
void freeScheduler();
void initCypher();
void initEventQueue();
void insertActionList(const uint16 actIndex);
void loadActListArr(Common::ReadStream &in);
void loadAlNewscrIndex(Common::ReadStream &in);
void loadPoints(Common::SeekableReadStream &in);
void loadScreenAct(Common::SeekableReadStream &in);
void newScreen(const int screenIndex);
void processBonus(const int bonusIndex);
virtual void processMaze(const int x1, const int x2, const int y1, const int y2);
void restoreSchedulerData(Common::ReadStream *in);
void restoreScreen(const int screenIndex);
void saveSchedulerData(Common::WriteStream *out);
void waitForRefresh();
protected:
HugoEngine *_vm;
static const int kFilenameLength = 12; // Max length of a DOS file name
static const int kMaxEvents = 50; // Max events in event queue
static const int kShiftSize = 8; // Place hero this far inside bounding box
Common::String _cypher;
uint16 _actListArrSize;
uint16 _alNewscrIndex;
uint16 _screenActsSize;
uint16 **_screenActs;
byte _numBonuses;
Point *_points;
uint32 _curTick; // Current system time in ticks
uint32 _oldTime; // The previous wall time in ticks
uint32 _refreshTimeout;
Event *_freeEvent; // Free list of event structures
Event *_headEvent; // Head of list (earliest time)
Event *_tailEvent; // Tail of list (latest time)
Event _events[kMaxEvents]; // Statically declare event structures
Act **_actListArr;
virtual const char *getCypher() const = 0;
virtual uint32 getTicks() = 0;
virtual void promptAction(Act *action) = 0;
Event *doAction(Event *curEvent);
Event *getQueue();
uint32 getDosTicks(const bool updateFl);
uint32 getWinTicks() const;
void delEventType(const Action actTypeDel);
void delQueue(Event *curEvent);
void findAction(const Act* action, int16* index, int16* subElem);
void insertAction(Act *action);
void readAct(Common::ReadStream &in, Act &curAct);
void restoreActions(Common::ReadStream *f);
void restoreEvents(Common::ReadStream *f);
void restorePoints(Common::ReadStream *in);
void saveActions(Common::WriteStream* f) const;
void saveEvents(Common::WriteStream *f);
void savePoints(Common::WriteStream *out) const;
void screenActions(const int screenNum);
};
class Scheduler_v1d : public Scheduler {
public:
Scheduler_v1d(HugoEngine *vm);
~Scheduler_v1d() override;
void decodeString(char *line) override;
void runScheduler() override;
protected:
const char *getCypher() const override;
uint32 getTicks() override;
void promptAction(Act *action) override;
};
class Scheduler_v2d : public Scheduler_v1d {
public:
Scheduler_v2d(HugoEngine *vm);
~Scheduler_v2d() override;
void decodeString(char *line) override;
protected:
const char *getCypher() const override;
void promptAction(Act *action) override;
};
class Scheduler_v3d : public Scheduler_v2d {
public:
Scheduler_v3d(HugoEngine *vm);
~Scheduler_v3d() override;
protected:
const char *getCypher() const override;
};
class Scheduler_v1w : public Scheduler_v3d {
public:
Scheduler_v1w(HugoEngine *vm);
~Scheduler_v1w() override;
void runScheduler() override;
void processMaze(const int x1, const int x2, const int y1, const int y2) override;
protected:
uint32 getTicks() override;
};
} // End of namespace Hugo
#endif //HUGO_SCHEDULE_H

410
engines/hugo/sound.cpp Normal file
View File

@@ -0,0 +1,410 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
// sound.c - sound effects and music support
#include "common/debug.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/config-manager.h"
#include "audio/decoders/raw.h"
#include "audio/audiostream.h"
#include "audio/midiparser.h"
#include "audio/softsynth/pcspk.h"
#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/file.h"
#include "hugo/sound.h"
#include "hugo/text.h"
namespace Hugo {
MidiPlayer::MidiPlayer() {
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM);
_driver = MidiDriver::createMidi(dev);
assert(_driver);
_paused = false;
int ret = _driver->open();
if (ret == 0) {
_driver->sendGMReset();
_driver->setTimerCallback(this, &timerCallback);
}
}
void MidiPlayer::play(uint8 *stream, uint16 size) {
debugC(3, kDebugMusic, "MidiPlayer::play");
Common::StackLock lock(_mutex);
stop();
if (!stream)
return;
_midiData = (uint8 *)malloc(size);
if (_midiData) {
memcpy(_midiData, stream, size);
syncVolume(); // FIXME: syncVolume calls setVolume which in turn also locks the mutex! ugh
_parser = MidiParser::createParser_SMF();
_parser->loadMusic(_midiData, size);
_parser->setTrack(0);
_parser->setMidiDriver(this);
_parser->setTimerRate(_driver->getBaseTempo());
_isLooping = false;
_isPlaying = true;
}
}
void MidiPlayer::pause(bool p) {
_paused = p;
for (int i = 0; i < kNumChannels; ++i) {
if (_channelsTable[i]) {
_channelsTable[i]->volume(_paused ? 0 : _channelsVolume[i] * _masterVolume / 255);
}
}
}
void MidiPlayer::onTimer() {
Common::StackLock lock(_mutex);
if (!_paused && _isPlaying && _parser) {
_parser->onTimer();
}
}
void MidiPlayer::sendToChannel(byte channel, uint32 b) {
if (!_channelsTable[channel]) {
_channelsTable[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
// If a new channel is allocated during the playback, make sure
// its volume is correctly initialized.
if (_channelsTable[channel])
_channelsTable[channel]->volume(_channelsVolume[channel] * _masterVolume / 255);
}
if (_channelsTable[channel])
_channelsTable[channel]->send(b);
}
SoundHandler::SoundHandler(HugoEngine *vm) : _vm(vm) {
_midiPlayer = new MidiPlayer();
_speaker = new Audio::PCSpeaker();
_speaker->init();
_DOSSongPtr = nullptr;
_curPriority = 0;
_pcspkrTimer = 0;
_pcspkrOctave = 3;
_pcspkrNoteDuration = 2;
_DOSIntroSong = nullptr;
}
SoundHandler::~SoundHandler() {
_vm->getTimerManager()->removeTimerProc(&loopPlayer);
delete _speaker;
delete _midiPlayer;
}
/**
* Set the FM music volume from config.mvolume (0..100%)
*/
void SoundHandler::setMusicVolume() {
_midiPlayer->syncVolume();
}
/**
* Stop any sound that might be playing
*/
void SoundHandler::stopSound() {
_vm->_mixer->stopAll();
}
/**
* Stop any tune that might be playing
*/
void SoundHandler::stopMusic() {
_midiPlayer->stop();
}
/**
* Turn music on and off
*/
void SoundHandler::toggleMusic() {
_vm->_config._musicFl = !_vm->_config._musicFl;
_midiPlayer->pause(!_vm->_config._musicFl);
}
/**
* Turn digitized sound on and off
*/
void SoundHandler::toggleSound() {
_vm->_config._soundFl = !_vm->_config._soundFl;
#ifdef USE_TTS
_vm->_voiceSoundSetting = true;
#endif
}
void SoundHandler::playMIDI(SoundPtr seqPtr, uint16 size) {
_midiPlayer->play(seqPtr, size);
}
/**
* Read a tune sequence from the sound database and start playing it
*/
void SoundHandler::playMusic(int16 tune) {
SoundPtr seqPtr; // Sequence data from file
uint16 size; // Size of sequence data
if (_vm->_config._musicFl) {
_vm->getGameStatus()._song = tune;
seqPtr = _vm->_file->getSound(tune, &size);
playMIDI(seqPtr, size);
free(seqPtr);
}
}
/**
* Produce various sound effects on supplied stereo channel(s)
* Override currently playing sound only if lower or same priority
*/
void SoundHandler::playSound(int16 sound, const byte priority) {
// uint32 dwVolume; // Left, right volume of sound
SoundPtr soundPtr; // Sound data
uint16 size; // Size of data
// Sound disabled
if (!_vm->_config._soundFl || !_vm->_mixer->isReady())
return;
syncVolume();
_curPriority = priority;
// Get sound data
if ((soundPtr = _vm->_file->getSound(sound, &size)) == nullptr)
return;
Audio::AudioStream *stream = Audio::makeRawStream(soundPtr, size, 11025, Audio::FLAG_UNSIGNED);
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream);
}
/**
* Initialize for MCI sound and midi
*/
void SoundHandler::initSound() {
//_midiPlayer->open();
}
void SoundHandler::syncVolume() {
int soundVolume;
if (ConfMan.getBool("sfx_mute") || ConfMan.getBool("mute"))
soundVolume = -1;
else
soundVolume = MIN(255, ConfMan.getInt("sfx_volume"));
_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundVolume);
_midiPlayer->syncVolume();
}
/**
* Check if music is still playing.
* If not, select the next track in the playlist and play it
*/
void SoundHandler::checkMusic() {
if (_midiPlayer->isPlaying())
return;
for (int i = 0; _vm->_defltTunes[i] != -1; i++) {
if (_vm->_defltTunes[i] == _vm->getGameStatus()._song) {
if (_vm->_defltTunes[i + 1] != -1)
playMusic(_vm->_defltTunes[i + 1]);
else
playMusic(_vm->_defltTunes[0]);
break;
}
}
}
void SoundHandler::loopPlayer(void *refCon) {
((SoundHandler *)refCon)->pcspkr_player();
}
/**
* Decrement last note's timer and see if time to play next note yet.
* If so, interpret next note in string and play it. Update ptr to string
* Timer: >0 - song still going, 0 - Stop note, -1 - Set next note
*/
void SoundHandler::pcspkr_player() {
static const uint16 pcspkrNotes[8] = {1352, 1205, 2274, 2026, 1805, 1704, 1518}; // The 3rd octave note counts A..G
static const uint16 pcspkrSharps[8] = {1279, 1171, 2150, 1916, 1755, 1611, 1435}; // The sharps, A# to B#
static const uint16 pcspkrFlats[8] = {1435, 1279, 2342, 2150, 1916, 1755, 1611}; // The flats, Ab to Bb
// Does the user not want any sound?
if (!_vm->_config._soundFl) {
// If user turned off sound during a song then stop note and song
if (_DOSSongPtr != nullptr && *_DOSSongPtr != '\0') {
_speaker->stop();
// Advance to end of song
while (*_DOSSongPtr != '\0') {
_DOSSongPtr++;
}
}
return;
} else if (!_vm->_mixer->isReady()) {
return;
}
// Is there no song?
if (!_DOSSongPtr)
return;
// Did we reach the end of the song?
if (!*_DOSSongPtr)
return;
// Update the timer.
_pcspkrTimer--;
// Check the timer state..
if (!_pcspkrTimer) {
// A note just finished, stop the sound (if any) and return.
_speaker->stop();
return;
} else if (_pcspkrTimer > 0) {
// A (rest or normal) note is still playing, return.
return;
}
// The timer is <0, time to play the next note.
bool cmdNote = true;
do {
switch (*_DOSSongPtr) {
case 'O':
// Switch to new octave 0..7
_DOSSongPtr++;
_pcspkrOctave = *_DOSSongPtr - '0';
if ((_pcspkrOctave < 0) || (_pcspkrOctave > 7))
error("pcspkr_player() - Bad octave");
_DOSSongPtr++;
break;
case 'L':
// Switch to new duration (in ticks)
_DOSSongPtr++;
_pcspkrNoteDuration = *_DOSSongPtr - '0';
if (_pcspkrNoteDuration < 0)
error("pcspkr_player() - Bad duration");
_pcspkrNoteDuration--;
_DOSSongPtr++;
break;
case '<':
case '^':
// Move up an octave
_DOSSongPtr++;
_pcspkrOctave++;
break;
case '>':
case 'v':
// Move down an octave
_DOSSongPtr++;
_pcspkrOctave--;
break;
default:
// Not a command, probably a note; so we should stop
// processing commands and move onward now.
cmdNote = false;
break;
}
} while (cmdNote);
switch (*_DOSSongPtr) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
// Play a note.
// First, what frequency does this note get played at?
// We must check for sharp or flat (#, -).
uint16 count;
switch (_DOSSongPtr[1]) {
case '#':
count = pcspkrSharps[*_DOSSongPtr++ - 'A'];
break;
case 'b':
count = pcspkrFlats[*_DOSSongPtr++ - 'A'];
break;
default:
count = pcspkrNotes[*_DOSSongPtr - 'A'];
break;
}
// Adjust for the octave if needed.
if (_pcspkrOctave > 3)
count /= (1 << (_pcspkrOctave - 3));
else if (_pcspkrOctave < 3)
count *= (1 << (3 - _pcspkrOctave));
// Start a note playing (we will stop it when the timer expires).
_speaker->play(Audio::PCSpeaker::kWaveFormSquare, kHugoCNT / count, -1);
_pcspkrTimer = _pcspkrNoteDuration;
_DOSSongPtr++;
break;
case '.':
// Play a 'rest note' by being silent for a bit.
_speaker->stop();
_pcspkrTimer = _pcspkrNoteDuration;
_DOSSongPtr++;
break;
default:
warning("pcspkr_player() - Unhandled note");
}
}
void SoundHandler::loadIntroSong(Common::ReadStream &in) {
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
uint16 numBuf = in.readUint16BE();
if (varnt == _vm->_gameVariant)
_DOSIntroSong = _vm->_text->getTextData(numBuf);
}
}
void SoundHandler::initPcspkrPlayer() {
_vm->getTimerManager()->installTimerProc(&loopPlayer, 1000000 / _vm->_normalTPS, this, "hugoSoundLoop");
}
} // End of namespace Hugo

106
engines/hugo/sound.h Normal file
View File

@@ -0,0 +1,106 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_SOUND_H
#define HUGO_SOUND_H
#include "audio/mixer.h"
#include "audio/midiplayer.h"
namespace Audio {
class PCSpeaker;
}
namespace Hugo {
class MidiPlayer : public Audio::MidiPlayer {
public:
MidiPlayer();
void pause(bool p);
void play(uint8 *stream, uint16 size);
// The following line prevents compiler warnings about hiding the pause()
// method from the parent class.
// FIXME: Maybe the pause(bool p) method should be removed and the
// pause/resume methods of the parent class be used instead?
void pause() override { Audio::MidiPlayer::pause(); }
uint32 getBaseTempo();
// Overload Audio::MidiPlayer method
void sendToChannel(byte channel, uint32 b) override;
void onTimer() override;
private:
bool _paused;
};
class SoundHandler {
public:
SoundHandler(HugoEngine *vm);
~SoundHandler();
static const int kHugoCNT = 1190000;
int8 _pcspkrTimer; // Timer (ticks) for note being played
int8 _pcspkrOctave; // Current octave 1..7
int8 _pcspkrNoteDuration; // Current length of note (ticks)
const char *_DOSSongPtr;
const char *_DOSIntroSong;
void toggleMusic();
void toggleSound();
void setMusicVolume();
static void loopPlayer(void *refCon);
void pcspkr_player();
void playMusic(int16 tune);
void playSound(int16 sound, const byte priority);
void initSound();
void syncVolume();
void checkMusic();
void loadIntroSong(Common::ReadStream &in);
void initPcspkrPlayer();
protected:
byte _curPriority; // Priority of currently playing sound
private:
HugoEngine *_vm;
Audio::SoundHandle _soundHandle;
MidiPlayer *_midiPlayer;
Audio::PCSpeaker *_speaker;
void stopSound();
void stopMusic();
void playMIDI(SoundPtr seqPtr, uint16 size);
};
} // End of namespace Hugo
#endif //HUGO_SOUND_H

270
engines/hugo/text.cpp Normal file
View File

@@ -0,0 +1,270 @@
/* 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 "common/system.h"
#include "common/stream.h"
#include "hugo/hugo.h"
#include "hugo/text.h"
namespace Hugo {
TextHandler::TextHandler(HugoEngine *vm) : _vm(vm) {
_textData = nullptr;
_stringtData = nullptr;
_textEngine = nullptr;
_textIntro = nullptr;
_textMouse = nullptr;
_textParser = nullptr;
_textUtil = nullptr;
_screenNames = nullptr;
_arrayNouns = nullptr;
_arrayVerbs = nullptr;
}
TextHandler::~TextHandler() {
}
const char *TextHandler::getNoun(int idx1, int idx2) const {
return _arrayNouns[idx1][idx2];
}
const char *TextHandler::getScreenNames(int screenIndex) const {
return _screenNames[screenIndex];
}
const char *TextHandler::getStringtData(int stringIndex) const {
return _stringtData[stringIndex];
}
const char *TextHandler::getTextData(int textIndex) const {
return _textData[textIndex];
}
const char *TextHandler::getTextEngine(int engineIndex) const {
return _textEngine[engineIndex];
}
const char *TextHandler::getTextIntro(int introIndex) const {
return _textIntro[introIndex];
}
const char *TextHandler::getTextMouse(int mouseIndex) const {
return _textMouse[mouseIndex];
}
const char *TextHandler::getTextParser(int parserIndex) const {
return _textParser[parserIndex];
}
const char *TextHandler::getTextUtil(int utilIndex) const {
return _textUtil[utilIndex];
}
const char *TextHandler::getVerb(int idx1, int idx2) const {
return _arrayVerbs[idx1][idx2];
}
char **TextHandler::getNounArray(int idx1) const {
return _arrayNouns[idx1];
}
char **TextHandler::getVerbArray(int idx1) const {
return _arrayVerbs[idx1];
}
char **TextHandler::loadTextsVariante(Common::ReadStream &in, uint16 *arraySize) {
int len;
char **res = nullptr;
char *pos = nullptr;
char *posBck = nullptr;
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
int numTexts = in.readUint16BE();
int entryLen = in.readUint16BE();
pos = (char *)malloc(entryLen);
if (varnt == _vm->_gameVariant) {
if (arraySize)
*arraySize = numTexts;
res = (char **)malloc(sizeof(char *) * numTexts);
res[0] = pos;
in.read(res[0], entryLen);
res[0] += DATAALIGNMENT;
} else {
in.read(pos, entryLen);
posBck = pos;
}
pos += DATAALIGNMENT;
for (int i = 1; i < numTexts; i++) {
pos -= 2;
len = READ_BE_UINT16(pos);
pos += 2 + len;
if (varnt == _vm->_gameVariant)
res[i] = pos;
}
if (varnt != _vm->_gameVariant)
free(posBck);
}
return res;
}
char ***TextHandler::loadTextsArray(Common::ReadStream &in) {
char ***resArray = nullptr;
uint16 arraySize;
for (int varnt = 0; varnt < _vm->_numVariant; varnt++) {
arraySize = in.readUint16BE();
if (varnt == _vm->_gameVariant) {
resArray = (char ***)malloc(sizeof(char **) * (arraySize + 1));
resArray[arraySize] = nullptr;
}
for (int i = 0; i < arraySize; i++) {
int numTexts = in.readUint16BE();
int entryLen = in.readUint16BE();
char *pos = (char *)malloc(entryLen);
char *posBck = nullptr;
char **res = nullptr;
if (varnt == _vm->_gameVariant) {
res = (char **)malloc(sizeof(char *) * numTexts);
res[0] = pos;
in.read(res[0], entryLen);
res[0] += DATAALIGNMENT;
} else {
in.read(pos, entryLen);
posBck = pos;
}
pos += DATAALIGNMENT;
for (int j = 0; j < numTexts; j++) {
if (varnt == _vm->_gameVariant)
res[j] = pos;
pos -= 2;
int len = READ_BE_UINT16(pos);
pos += 2 + len;
}
if (varnt == _vm->_gameVariant)
resArray[i] = res;
else
free(posBck);
}
}
return resArray;
}
char **TextHandler::loadTexts(Common::ReadStream &in) {
int numTexts = in.readUint16BE();
char **res = (char **)malloc(sizeof(char *) * numTexts);
int entryLen = in.readUint16BE();
char *pos = (char *)malloc(entryLen);
in.read(pos, entryLen);
pos += DATAALIGNMENT;
res[0] = pos;
for (int i = 1; i < numTexts; i++) {
pos -= 2;
int len = READ_BE_UINT16(pos);
pos += 2 + len;
res[i] = pos;
}
return res;
}
void TextHandler::loadAllTexts(Common::ReadStream &in) {
// Read textData
_textData = loadTextsVariante(in, nullptr);
// Read stringtData
// Only Hugo 1 DOS should use this array
_stringtData = loadTextsVariante(in, nullptr);
// Read arrayNouns
_arrayNouns = loadTextsArray(in);
// Read arrayVerbs
_arrayVerbs = loadTextsArray(in);
// Read screenNames
_screenNames = loadTextsVariante(in, &_vm->_numScreens);
// Read textEngine
_textEngine = loadTexts(in);
// Read textIntro
_textIntro = loadTextsVariante(in, nullptr);
// Read textMouse
_textMouse = loadTexts(in);
// Read textParser
_textParser = loadTexts(in);
// Read textUtil
_textUtil = loadTextsVariante(in, nullptr);
}
void TextHandler::freeTexts(char **ptr) {
if (!ptr)
return;
free(*ptr - DATAALIGNMENT);
free(ptr);
ptr = nullptr;
}
void TextHandler::freeAllTexts() {
freeTexts(_textData);
freeTexts(_stringtData);
if (_arrayNouns) {
for (int i = 0; _arrayNouns[i]; i++)
freeTexts(_arrayNouns[i]);
free(_arrayNouns);
_arrayNouns = nullptr;
}
if (_arrayVerbs) {
for (int i = 0; _arrayVerbs[i]; i++)
freeTexts(_arrayVerbs[i]);
free(_arrayVerbs);
_arrayVerbs = nullptr;
}
freeTexts(_screenNames);
freeTexts(_textEngine);
freeTexts(_textIntro);
freeTexts(_textMouse);
freeTexts(_textParser);
freeTexts(_textUtil);
}
} // End of namespace Hugo

71
engines/hugo/text.h Normal file
View File

@@ -0,0 +1,71 @@
/* 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/>.
*
*/
#ifndef TEXT_H
#define TEXT_H
namespace Hugo {
class TextHandler {
public:
TextHandler(HugoEngine *vm);
~TextHandler();
const char *getNoun(int idx1, int idx2) const;
const char *getScreenNames(int screenIndex) const;
const char *getStringtData(int stringIndex) const;
const char *getTextData(int textIndex) const;
const char *getTextEngine(int engineIndex) const;
const char *getTextIntro(int introIndex) const;
const char *getTextMouse(int mouseIndex) const;
const char *getTextParser(int parserIndex) const;
const char *getTextUtil(int utilIndex) const;
const char *getVerb(int idx1, int idx2) const;
char **getNounArray(int idx1) const;
char **getVerbArray(int idx1) const;
void loadAllTexts(Common::ReadStream &in);
void freeAllTexts();
private:
HugoEngine *_vm;
char ***_arrayNouns;
char ***_arrayVerbs;
char **_screenNames;
char **_stringtData;
char **_textData;
char **_textEngine;
char **_textIntro;
char **_textMouse;
char **_textParser;
char **_textUtil;
char ***loadTextsArray(Common::ReadStream &in);
char **loadTextsVariante(Common::ReadStream &in, uint16 *arraySize);
char **loadTexts(Common::ReadStream &in);
void freeTexts(char **ptr);
};
} // End of namespace Hugo
#endif // TEXT_H

203
engines/hugo/util.cpp Normal file
View File

@@ -0,0 +1,203 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#include "common/system.h"
#include "common/util.h"
#include "gui/message.h"
#include "hugo/hugo.h"
#include "hugo/dialogs.h"
#include "hugo/util.h"
namespace Hugo {
namespace Utils {
/**
* Returns index (0 to 7) of first 1 in supplied byte, or 8 if not found
*/
int firstBit(byte data) {
if (!data)
return 8;
int i;
for (i = 0; i < 8; i++) {
if ((data << i) & 0x80)
break;
}
return i;
}
/**
* Returns index (0 to 7) of last 1 in supplied byte, or 8 if not found
*/
int lastBit(byte data) {
if (!data)
return 8;
int i;
for (i = 7; i >= 0; i--) {
if ((data << i) & 0x80)
break;
}
return i;
}
/**
* Reverse the bit order in supplied byte
*/
void reverseByte(byte *data) {
byte maskIn = 0x80;
byte maskOut = 0x01;
byte result = 0;
for (byte i = 0; i < 8; i++, maskIn >>= 1, maskOut <<= 1) {
if (*data & maskIn)
result |= maskOut;
}
*data = result;
}
void notifyBox(const Common::String &msg, TtsOptions ttsOptions) {
#ifdef USE_TTS
if (ttsOptions & kTtsSpeech) {
bool replaceNewlines = ((ttsOptions & kTtsReplaceNewlines) == kTtsReplaceNewlines);
sayText(msg, Common::TextToSpeechManager::QUEUE, replaceNewlines);
}
#endif
notifyBox(Common::U32String(msg));
}
void notifyBox(const Common::U32String &msg) {
if (msg.empty())
return;
GUI::MessageDialog dialog(msg);
dialog.runModal();
#ifdef USE_TTS
stopTextToSpeech();
#endif
}
Common::String promptBox(const Common::String &msg) {
if (msg.empty())
return Common::String();
#ifdef USE_TTS
sayText(msg, Common::TextToSpeechManager::QUEUE);
#endif
EntryDialog dialog(msg, "OK", "");
dialog.runModal();
Common::U32String input = dialog.getEditString();
#ifdef USE_TTS
sayText(input);
#endif
return input;
}
bool yesNoBox(const Common::String &msg) {
#ifdef USE_TTS
sayText(msg, Common::TextToSpeechManager::QUEUE);
#endif
return yesNoBox(Common::U32String(msg));
}
bool yesNoBox(const Common::U32String &msg) {
if (msg.empty())
return 0;
GUI::MessageDialog dialog(msg, Common::U32String("YES"), Common::U32String("NO"));
int result = dialog.runModal();
#ifdef USE_TTS
stopTextToSpeech();
#endif
return (result == GUI::kMessageOK);
}
char *hugo_strlwr(char *buffer) {
char *result = buffer;
while (*buffer != '\0') {
if (Common::isUpper(*buffer))
*buffer = tolower(*buffer);
buffer++;
}
return result;
}
#ifdef USE_TTS
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action, bool replaceNewlines) {
Common::String ttsMessage(text);
if (replaceNewlines) {
ttsMessage.replace('\n', ' ');
ttsMessage.replace('\r', ' ');
}
// Some text is enclosed in < and >, which won't be voiced unless they're replaced
Common::replace(ttsMessage, "<", ", ");
Common::replace(ttsMessage, ">", ", ");
sayText(Common::U32String(ttsMessage, Common::CodePage::kWindows1252), action);
}
void sayText(const Common::U32String &text, Common::TextToSpeechManager::Action action) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ConfMan.getBool("tts_enabled")) {
ttsMan->say(text, action);
}
}
void stopTextToSpeech() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
ttsMan->stop();
}
}
#endif
} // End of namespace Utils
} // End of namespace Hugo

88
engines/hugo/util.h Normal file
View File

@@ -0,0 +1,88 @@
/* 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/>.
*
*/
/*
* This code is based on original Hugo Trilogy source code
*
* Copyright (c) 1989-1995 David P. Gray
*
*/
#ifndef HUGO_UTIL_H
#define HUGO_UTIL_H
namespace Hugo {
enum seqTextUtil {
kGameOver = 0
};
namespace Utils {
int firstBit(byte data);
int lastBit(byte data);
void reverseByte(byte *data);
/**
* Show a dialog notifying the user about something, with
* only a simple "OK" button to dismiss it.
*/
void notifyBox(const Common::String &msg, TtsOptions ttsOptions = kTtsReplaceNewlines); // Redirect to call notifyBox with u32strings
void notifyBox(const Common::U32String &msg);
/**
* Show a dialog prompting the player to input some text.
*/
Common::String promptBox(const Common::String &msg);
/**
* Show a dialog prompting the player for a "yes"/"no" choice.
*/
bool yesNoBox(const Common::String &msg); // Redirect to call yesNoBox with u32strings
bool yesNoBox(const Common::U32String &msg);
/**
* Convert a string to lower case, in place.
* @param buffer string to convert to lower case
* @return the string which was passed in
*/
char *hugo_strlwr(char *buffer);
#ifdef USE_TTS
/**
* Voice text with the text-to-speech system.
*/
void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT,
bool replaceNewlines = true); // Clean up text, then redirect to call sayText with u32strings
void sayText(const Common::U32String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::INTERRUPT);
/**
* Stop TTS voicing.
*/
void stopTextToSpeech();
#endif
} // End of namespace Utils
} // End of namespace Hugo
#endif