diff options
Diffstat (limited to 'engines/wage/world.cpp')
-rw-r--r-- | engines/wage/world.cpp | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp new file mode 100644 index 0000000000..0e40e114b4 --- /dev/null +++ b/engines/wage/world.cpp @@ -0,0 +1,541 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/file.h" + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/script.h" +#include "wage/sound.h" +#include "wage/world.h" + +namespace Wage { + +World::World(WageEngine *engine) { + _storageScene = new Scene; + _storageScene->_name = STORAGESCENE; + + _orderedScenes.push_back(_storageScene); + _scenes[STORAGESCENE] = _storageScene; + + _gameOverMessage = nullptr; + _saveBeforeQuitMessage = nullptr; + _saveBeforeCloseMessage = nullptr; + _revertMessage = nullptr; + + _globalScript = nullptr; + _player = nullptr; + + _weaponMenuDisabled = true; + + _engine = engine; + + _patterns = new Patterns; +} + +World::~World() { + for (uint i = 0; i < _orderedObjs.size(); i++) + delete _orderedObjs[i]; + + for (uint i = 0; i < _orderedChrs.size(); i++) + delete _orderedChrs[i]; + + for (uint i = 0; i < _orderedSounds.size(); i++) + delete _orderedSounds[i]; + + for (uint i = 0; i < _orderedScenes.size(); i++) + delete _orderedScenes[i]; + + for (uint i = 0; i < _patterns->size(); i++) + free(_patterns->operator[](i)); + + delete _patterns; + + delete _globalScript; + + delete _gameOverMessage; + delete _saveBeforeQuitMessage; + delete _saveBeforeCloseMessage; + delete _revertMessage; + +} + +bool World::loadWorld(Common::MacResManager *resMan) { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + // Dumping interpreter code +#if 1 + res = resMan->getResource(MKTAG('C','O','D','E'), 1); + warning("code size: %d", res->size()); + byte *buf = (byte *)malloc(res->size()); + res->read(buf, res->size()); + Common::DumpFile out; + out.open("code.bin"); + out.write(buf, res->size()); + out.close(); + free(buf); + delete res; +#endif + + if ((resArray = resMan->getResIDArray(MKTAG('G','C','O','D'))).size() == 0) + return false; + + // Load global script + res = resMan->getResource(MKTAG('G','C','O','D'), resArray[0]); + _globalScript = new Script(res); + + // TODO: read creator + + // Load main configuration + if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0) + return false; + + _name = resMan->getBaseFileName(); + + if (resArray.size() > 1) + warning("Too many VERS resources"); + + if (!resArray.empty()) { + debug(3, "Loading version info"); + + res = resMan->getResource(MKTAG('V','E','R','S'), resArray[0]); + + res->skip(10); + byte b = res->readByte(); + _weaponMenuDisabled = (b != 0); + if (b != 0 && b != 1) + error("Unexpected value for weapons menu"); + + res->skip(3); + _aboutMessage = readPascalString(res); + + if (!scumm_stricmp(resMan->getBaseFileName().c_str(), "Scepters")) + res->skip(1); // ???? + + _soundLibrary1 = readPascalString(res); + _soundLibrary2 = readPascalString(res); + + delete res; + } + + Common::String *message; + if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) { + message->trim(); + debug(2, "_gameOverMessage: %s", message->c_str()); + _gameOverMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) { + message->trim(); + debug(2, "_saveBeforeQuitMessage: %s", message->c_str()); + _saveBeforeQuitMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL) { + message->trim(); + debug(2, "_saveBeforeCloseMessage: %s", message->c_str()); + _saveBeforeCloseMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) { + message->trim(); + debug(2, "_revertMessage: %s", message->c_str()); + _revertMessage = message; + } + + // Load scenes + resArray = resMan->getResIDArray(MKTAG('A','S','C','N')); + debug(3, "Loading %d scenes", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','S','C','N'), *iter); + Scene *scene = new Scene(resMan->getResName(MKTAG('A','S','C','N'), *iter), res); + + res = resMan->getResource(MKTAG('A','C','O','D'), *iter); + if (res != NULL) + scene->_script = new Script(res); + + res = resMan->getResource(MKTAG('A','T','X','T'), *iter); + if (res != NULL) { + scene->_textBounds = readRect(res); + scene->_fontType = res->readUint16BE(); + scene->_fontSize = res->readUint16BE(); + + Common::String text; + while (res->pos() < res->size()) { + char c = res->readByte(); + if (c == 0x0d) + c = '\n'; + text += c; + } + scene->_text = text; + + delete res; + } + addScene(scene); + } + + // Load Objects + resArray = resMan->getResIDArray(MKTAG('A','O','B','J')); + debug(3, "Loading %d objects", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','O','B','J'), *iter); + addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res)); + } + + // Load Characters + resArray = resMan->getResIDArray(MKTAG('A','C','H','R')); + debug(3, "Loading %d characters", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','C','H','R'), *iter); + Chr *chr = new Chr(resMan->getResName(MKTAG('A','C','H','R'), *iter), res); + + addChr(chr); + // TODO: What if there's more than one player character? + if (chr->_playerCharacter) + _player = chr; + } + + // Load Sounds + resArray = resMan->getResIDArray(MKTAG('A','S','N','D')); + debug(3, "Loading %d sounds", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','S','N','D'), *iter); + addSound(new Sound(resMan->getResName(MKTAG('A','S','N','D'), *iter), res)); + } + + if (!_soundLibrary1.empty()) { + loadExternalSounds(_soundLibrary1); + } + if (!_soundLibrary2.empty()) { + loadExternalSounds(_soundLibrary2); + } + + // Load Patterns + res = resMan->getResource(MKTAG('P','A','T','#'), 900); + if (res != NULL) { + int count = res->readUint16BE(); + debug(3, "Loading %d patterns", count); + + for (int i = 0; i < count; i++) { + byte *pattern = (byte *)malloc(8); + + res->read(pattern, 8); + _patterns->push_back(pattern); + } + + delete res; + } else { + /* Enchanted Scepters did not use the PAT# resource for the textures. */ + res = resMan->getResource(MKTAG('C','O','D','E'), 1); + if (res != NULL) { + res->skip(0x55ac); + for (int i = 0; i < 29; i++) { + byte *pattern = (byte *)malloc(8); + + res->read(pattern, 8); + _patterns->push_back(pattern); + } + } + delete res; + } + + res = resMan->getResource(MKTAG('M','E','N','U'), 2001); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _aboutMenuItemName.clear(); + Common::String string = menu->operator[](1); + + for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token + _aboutMenuItemName += string[i]; + + delete menu; + delete res; + } + res = resMan->getResource(MKTAG('M','E','N','U'), 2004); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _commandsMenuName = menu->operator[](0); + _commandsMenu = menu->operator[](1); + delete menu; + delete res; + } + res = resMan->getResource(MKTAG('M','E','N','U'), 2005); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _weaponsMenuName = menu->operator[](0); + delete menu; + delete res; + } + // TODO: Read Apple menu and get the name of that menu item.. + + // store global info in state object for use with save/load actions + //world.setCurrentState(initialState); // pass off the state object to the world + + return true; +} + +void World::addSound(Sound *sound) { + Common::String s = sound->_name; + s.toLowercase(); + _sounds[s] = sound; + _orderedSounds.push_back(sound); +} + +Common::StringArray *World::readMenu(Common::SeekableReadStream *res) { + res->skip(10); + int enableFlags = res->readUint32BE(); + Common::String menuName = readPascalString(res); + Common::String menuItem = readPascalString(res); + int menuItemNumber = 1; + Common::String menu; + byte itemData[4]; + + while (!menuItem.empty()) { + if (!menu.empty()) { + menu += ';'; + } + if ((enableFlags & (1 << menuItemNumber)) == 0) { + menu += '('; + } + menu += menuItem; + res->read(itemData, 4); + static const char styles[] = {'B', 'I', 'U', 'O', 'S', 'C', 'E', 0}; + for (int i = 0; styles[i] != 0; i++) { + if ((itemData[3] & (1 << i)) != 0) { + menu += '<'; + menu += styles[i]; + } + } + if (itemData[1] != 0) { + menu += '/'; + menu += (char)itemData[1]; + } + menuItem = readPascalString(res); + menuItemNumber++; + } + + Common::StringArray *result = new Common::StringArray; + result->push_back(menuName); + result->push_back(menu); + + debug(4, "menuName: %s", menuName.c_str()); + debug(4, "menu: %s", menu.c_str()); + + return result; +} + +void World::loadExternalSounds(Common::String fname) { + Common::File in; + + in.open(fname); + if (!in.isOpen()) { + warning("Cannot load sound file <%s>", fname.c_str()); + return; + } + in.close(); + + Common::MacResManager resMan; + resMan.open(fname); + + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + resArray = resMan.getResIDArray(MKTAG('A','S','N','D')); + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan.getResource(MKTAG('A','S','N','D'), *iter); + addSound(new Sound(resMan.getResName(MKTAG('A','S','N','D'), *iter), res)); + } +} + +Common::String *World::loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex) { + Common::SeekableReadStream *res = resMan->getResource(MKTAG('D','I','T','L'), resourceId); + if (res) { + int itemCount = res->readSint16BE(); + for (int i = 0; i <= itemCount; i++) { + // int placeholder; short rect[4]; byte flags; pstring str; + res->skip(13); + Common::String message = readPascalString(res); + if (i == itemIndex) { + Common::String *msg = new Common::String(message); + delete res; + return msg; + } + } + + delete res; + } + + return NULL; +} + +static bool invComparator(const Obj *l, const Obj *r) { + return l->_index < r->_index; +} + +void World::move(Obj *obj, Chr *chr) { + if (obj == NULL) + return; + + Designed *from = obj->removeFromCharOrScene(); + obj->_currentOwner = chr; + chr->_inventory.push_back(obj); + + Common::sort(chr->_inventory.begin(), chr->_inventory.end(), invComparator); + + _engine->onMove(obj, from, chr); +} + +static bool objComparator(const Obj *o1, const Obj *o2) { + bool o1Immobile = (o1->_type == Obj::IMMOBILE_OBJECT); + bool o2Immobile = (o2->_type == Obj::IMMOBILE_OBJECT); + if (o1Immobile == o2Immobile) { + return o1->_index < o2->_index; + } + return o1Immobile; +} + +void World::move(Obj *obj, Scene *scene, bool skipSort) { + if (obj == NULL) + return; + + Designed *from = obj->removeFromCharOrScene(); + obj->_currentScene = scene; + scene->_objs.push_back(obj); + + if (!skipSort) + Common::sort(scene->_objs.begin(), scene->_objs.end(), objComparator); + + _engine->onMove(obj, from, scene); +} + +static bool chrComparator(const Chr *l, const Chr *r) { + return l->_index < r->_index; +} + +void World::move(Chr *chr, Scene *scene, bool skipSort) { + if (chr == NULL) + return; + Scene *from = chr->_currentScene; + if (from == scene) + return; + if (from != NULL) + from->_chrs.remove(chr); + scene->_chrs.push_back(chr); + + if (!skipSort) + Common::sort(scene->_chrs.begin(), scene->_chrs.end(), chrComparator); + + if (scene == _storageScene) { + chr->resetState(); + } else if (chr->_playerCharacter) { + scene->_visited = true; + _player->_context._visits++; + } + chr->_currentScene = scene; + + _engine->onMove(chr, from, scene); +} + +Scene *World::getRandomScene() { + // Not including storage: + return _orderedScenes[1 + _engine->_rnd->getRandomNumber(_orderedScenes.size() - 2)]; +} + +Scene *World::getSceneAt(int x, int y) { + for (uint i = 0; i < _orderedScenes.size(); i++) { + Scene *scene = _orderedScenes[i]; + + if (scene != _storageScene && scene->_worldX == x && scene->_worldY == y) { + return scene; + } + } + return NULL; +} + +static const int directionsX[] = { 0, 0, 1, -1 }; +static const int directionsY[] = { -1, 1, 0, 0 }; + +bool World::scenesAreConnected(Scene *scene1, Scene *scene2) { + if (!scene1 || !scene2) + return false; + + int x = scene2->_worldX; + int y = scene2->_worldY; + + for (int dir = 0; dir < 4; dir++) + if (!scene2->_blocked[dir]) + if (getSceneAt(x + directionsX[dir], y + directionsY[dir]) == scene1) + return true; + + return false; +} + +const char *World::getAboutMenuItemName() { + static char menu[256]; + + *menu = '\0'; + + if (_aboutMenuItemName.empty()) { + sprintf(menu, "About %s...", _name.c_str()); + } else { // Replace '@' with name + const char *str = _aboutMenuItemName.c_str(); + const char *pos = strchr(str, '@'); + if (pos) { + strncat(menu, str, (pos - str)); + strncat(menu, _name.c_str(), 255); + strncat(menu, pos + 1, 255); + } + } + + return menu; +} + +} // End of namespace Wage |