scummvm/engines/wage/world.cpp
2016-07-31 14:05:14 +02:00

546 lines
14 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* 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;
_signature = 0;
_weaponMenuDisabled = true;
_engine = engine;
_patterns = new Graphics::MacPatterns;
}
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]);
_signature = res->readSint32LE();
res->skip(6);
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;
}
scene->_resourceId = *iter;
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, *iter));
}
// 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);
chr->_resourceId = *iter;
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