Initial commit
This commit is contained in:
3
engines/hugo/POTFILES
Normal file
3
engines/hugo/POTFILES
Normal file
@@ -0,0 +1,3 @@
|
||||
engines/hugo/hugo.cpp
|
||||
engines/hugo/file.cpp
|
||||
engines/hugo/metaengine.cpp
|
||||
3
engines/hugo/configure.engine
Normal file
3
engines/hugo/configure.engine
Normal 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
151
engines/hugo/console.cpp
Normal 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
48
engines/hugo/console.h
Normal 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
5
engines/hugo/credits.pl
Normal file
@@ -0,0 +1,5 @@
|
||||
begin_section("Hugo");
|
||||
add_person("Arnaud Boutonné", "Strangerke", "");
|
||||
add_person("Oystein Eftevaag", "vinterstum", "");
|
||||
add_person("Eugene Sandulenko", "sev", "");
|
||||
end_section();
|
||||
154
engines/hugo/detection.cpp
Normal file
154
engines/hugo/detection.cpp
Normal 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
62
engines/hugo/detection.h
Normal 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
305
engines/hugo/dialogs.cpp
Normal 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
117
engines/hugo/dialogs.h
Normal 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
1247
engines/hugo/display.cpp
Normal file
File diff suppressed because it is too large
Load Diff
197
engines/hugo/display.h
Normal file
197
engines/hugo/display.h
Normal 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
614
engines/hugo/file.cpp
Normal 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
183
engines/hugo/file.h
Normal 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
145
engines/hugo/file_v1d.cpp
Normal 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
88
engines/hugo/file_v1w.cpp
Normal 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
188
engines/hugo/file_v2d.cpp
Normal 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
50
engines/hugo/file_v2w.cpp
Normal 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
206
engines/hugo/file_v3d.cpp
Normal 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
178
engines/hugo/game.h
Normal 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
840
engines/hugo/hugo.cpp
Normal 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
368
engines/hugo/hugo.h
Normal 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
577
engines/hugo/intro.cpp
Normal 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
134
engines/hugo/intro.h
Normal 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
293
engines/hugo/inventory.cpp
Normal 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
71
engines/hugo/inventory.h
Normal 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
396
engines/hugo/metaengine.cpp
Normal 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
43
engines/hugo/module.mk
Normal 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
418
engines/hugo/mouse.cpp
Normal 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
94
engines/hugo/mouse.h
Normal 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
834
engines/hugo/object.cpp
Normal 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
164
engines/hugo/object.h
Normal 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
386
engines/hugo/object_v1d.cpp
Normal 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
382
engines/hugo/object_v1w.cpp
Normal 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
382
engines/hugo/object_v2d.cpp
Normal 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
267
engines/hugo/object_v3d.cpp
Normal 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
591
engines/hugo/parser.cpp
Normal 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
200
engines/hugo/parser.h
Normal 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
436
engines/hugo/parser_v1d.cpp
Normal 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
214
engines/hugo/parser_v1w.cpp
Normal 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
352
engines/hugo/parser_v2d.cpp
Normal 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
466
engines/hugo/parser_v3d.cpp
Normal 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
544
engines/hugo/route.cpp
Normal 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
93
engines/hugo/route.h
Normal 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
1713
engines/hugo/schedule.cpp
Normal file
File diff suppressed because it is too large
Load Diff
645
engines/hugo/schedule.h
Normal file
645
engines/hugo/schedule.h
Normal 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
410
engines/hugo/sound.cpp
Normal 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
106
engines/hugo/sound.h
Normal 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
270
engines/hugo/text.cpp
Normal 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
71
engines/hugo/text.h
Normal 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
203
engines/hugo/util.cpp
Normal 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
88
engines/hugo/util.h
Normal 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
|
||||
Reference in New Issue
Block a user