Initial commit
This commit is contained in:
571
engines/wage/world.cpp
Normal file
571
engines/wage/world.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
/* 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/>.
|
||||
*
|
||||
* 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 "graphics/macgui/macwindowmanager.h"
|
||||
#include "graphics/macgui/macfontmanager.h"
|
||||
#include "graphics/macgui/macmenu.h"
|
||||
#include "graphics/fontman.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(const_cast<byte *>(_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 0
|
||||
res = resMan->getResource(MKTAG('C','O','D','E'), 1);
|
||||
warning("Dumping interpreter 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, -1, _engine);
|
||||
|
||||
// TODO: read creator
|
||||
|
||||
// Load main configuration
|
||||
if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0)
|
||||
return false;
|
||||
|
||||
_name = resMan->getBaseFileName().toString();
|
||||
|
||||
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 = res->readPascalString();
|
||||
|
||||
if (!scumm_stricmp(_name.c_str(), "Scepters"))
|
||||
res->skip(1); // ????
|
||||
|
||||
_soundLibrary1 = Common::Path(res->readPascalString());
|
||||
_soundLibrary2 = Common::Path(res->readPascalString());
|
||||
|
||||
delete res;
|
||||
}
|
||||
|
||||
Common::String *message;
|
||||
if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) {
|
||||
message->trim();
|
||||
debug(2, "_gameOverMessage: %s", message->c_str());
|
||||
_gameOverMessage = message;
|
||||
} else {
|
||||
_gameOverMessage = new Common::String("Game Over!");
|
||||
}
|
||||
if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) {
|
||||
message->trim();
|
||||
debug(2, "_saveBeforeQuitMessage: %s", message->c_str());
|
||||
_saveBeforeQuitMessage = message;
|
||||
} else {
|
||||
_saveBeforeQuitMessage = new Common::String("Save changes before quiting?");
|
||||
}
|
||||
if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL && !message->empty()) {
|
||||
message->trim();
|
||||
debug(2, "_saveBeforeCloseMessage: %s", message->c_str());
|
||||
_saveBeforeCloseMessage = message;
|
||||
} else {
|
||||
_saveBeforeCloseMessage = new Common::String("Save changes before closing?");
|
||||
}
|
||||
if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) {
|
||||
message->trim();
|
||||
debug(2, "_revertMessage: %s", message->c_str());
|
||||
_revertMessage = message;
|
||||
} else {
|
||||
_revertMessage = new Common::String("Revert to the last saved version?");
|
||||
}
|
||||
|
||||
// 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, *iter, _engine);
|
||||
|
||||
res = resMan->getResource(MKTAG('A','T','X','T'), *iter);
|
||||
if (res != NULL) {
|
||||
scene->_textBounds = readRect(res);
|
||||
int fontType = res->readUint16BE();
|
||||
// WORKAROUND: Dune Eternity has a weird fontType ID so we override it to the correct one
|
||||
if (_name == "***DUNE ETERNITY*** ")
|
||||
fontType = 3;
|
||||
|
||||
int fontSize = res->readUint16BE();
|
||||
scene->_font = new Graphics::MacFont(fontType, fontSize, Graphics::kMacFontRegular);
|
||||
const Graphics::Font *fallback = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont);
|
||||
scene->_font->setFallback(fallback);
|
||||
|
||||
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);
|
||||
|
||||
if (chr->_playerCharacter) {
|
||||
if (_player)
|
||||
warning("loadWorld: Player is redefined");
|
||||
|
||||
_player = chr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_player) {
|
||||
warning("loadWorld: Player is not defined");
|
||||
|
||||
if (_chrs.empty()) {
|
||||
error("loadWorld: and I have no characters");
|
||||
}
|
||||
_player = _orderedChrs[0];
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
const char *magic = "\x92\x40\x15\x81\x20\x00\x4E\x75";
|
||||
|
||||
int cnt = 0;
|
||||
bool found = false;
|
||||
|
||||
while (!res->eos()) {
|
||||
byte b = res->readByte();
|
||||
|
||||
if (b == (byte)magic[cnt]) {
|
||||
cnt++;
|
||||
|
||||
if (cnt == 8) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
error("World::loadWorld(): Could not find Enhanced Scepters' patterns");
|
||||
|
||||
res->skip(8); // Skip 8 more bytes
|
||||
|
||||
debug(3, "Loading 29 patterns for Enhanced Scepters at %" PRId64, res->pos());
|
||||
|
||||
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 = Graphics::MacMenu::readMenuFromResource(res);
|
||||
_aboutMenuItemName.clear();
|
||||
Common::String string = menu->operator[](1);
|
||||
|
||||
for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token
|
||||
_aboutMenuItemName += string[i];
|
||||
|
||||
debugC(1, kDebugLoading, "MENU: About: %s", toPrintable(_aboutMenuItemName).c_str());
|
||||
|
||||
delete menu;
|
||||
delete res;
|
||||
}
|
||||
res = resMan->getResource(MKTAG('M','E','N','U'), 2004);
|
||||
if (res != NULL) {
|
||||
Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
|
||||
_commandsMenuName = menu->operator[](0);
|
||||
_commandsMenuDefault = menu->operator[](1);
|
||||
_commandsMenu = _commandsMenuDefault;
|
||||
debugC(1, kDebugLoading, "MENU: Commands name: %s", toPrintable(_commandsMenuName).c_str());
|
||||
debugC(1, kDebugLoading, "MENU: Commands menu: %s", toPrintable(_commandsMenu).c_str());
|
||||
|
||||
delete menu;
|
||||
delete res;
|
||||
}
|
||||
res = resMan->getResource(MKTAG('M','E','N','U'), 2005);
|
||||
if (res != NULL) {
|
||||
Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
|
||||
_weaponsMenuName = menu->operator[](0);
|
||||
delete menu;
|
||||
delete res;
|
||||
|
||||
debugC(1, kDebugLoading, "MENU: Weapons name: %s", toPrintable(_weaponsMenuName).c_str());
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
void World::loadExternalSounds(const Common::Path &fname) {
|
||||
Common::MacResManager resMan;
|
||||
if (!resMan.open(fname)) {
|
||||
warning("Cannot load sound file <%s>", fname.toString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!res) {
|
||||
warning("Cannot load sound resource %d from file <%s>", *iter, fname.toString().c_str());
|
||||
continue;
|
||||
}
|
||||
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 = res->readPascalString();
|
||||
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->setCurrentOwner(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->setCurrentScene(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()) {
|
||||
Common::sprintf_s(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);
|
||||
} else {
|
||||
Common::strlcpy(menu, _aboutMenuItemName.c_str(), 256);
|
||||
}
|
||||
}
|
||||
|
||||
debugC(1, kDebugLoading, "MENU: About cleansed: %s", Common::toPrintable(menu).c_str());
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
} // End of namespace Wage
|
||||
Reference in New Issue
Block a user