1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 * MIT License:
22 *
23 * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko
24 *
25 * Permission is hereby granted, free of charge, to any person
26 * obtaining a copy of this software and associated documentation
27 * files (the "Software"), to deal in the Software without
28 * restriction, including without limitation the rights to use,
29 * copy, modify, merge, publish, distribute, sublicense, and/or sell
30 * copies of the Software, and to permit persons to whom the
31 * Software is furnished to do so, subject to the following
32 * conditions:
33 *
34 * The above copyright notice and this permission notice shall be
35 * included in all copies or substantial portions of the Software.
36 *
37 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
39 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
41 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
42 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
43 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
44 * OTHER DEALINGS IN THE SOFTWARE.
45 *
46 */
47
48 #include "common/file.h"
49 #include "graphics/macgui/macwindowmanager.h"
50 #include "graphics/macgui/macfontmanager.h"
51 #include "graphics/macgui/macmenu.h"
52
53 #include "wage/wage.h"
54 #include "wage/entities.h"
55 #include "wage/script.h"
56 #include "wage/sound.h"
57 #include "wage/world.h"
58
59 namespace Wage {
60
World(WageEngine * engine)61 World::World(WageEngine *engine) {
62 _storageScene = new Scene;
63 _storageScene->_name = STORAGESCENE;
64
65 _orderedScenes.push_back(_storageScene);
66 _scenes[STORAGESCENE] = _storageScene;
67
68 _gameOverMessage = nullptr;
69 _saveBeforeQuitMessage = nullptr;
70 _saveBeforeCloseMessage = nullptr;
71 _revertMessage = nullptr;
72
73 _globalScript = nullptr;
74 _player = nullptr;
75 _signature = 0;
76
77 _weaponMenuDisabled = true;
78
79 _engine = engine;
80
81 _patterns = new Graphics::MacPatterns;
82 }
83
~World()84 World::~World() {
85 for (uint i = 0; i < _orderedObjs.size(); i++)
86 delete _orderedObjs[i];
87
88 for (uint i = 0; i < _orderedChrs.size(); i++)
89 delete _orderedChrs[i];
90
91 for (uint i = 0; i < _orderedSounds.size(); i++)
92 delete _orderedSounds[i];
93
94 for (uint i = 0; i < _orderedScenes.size(); i++)
95 delete _orderedScenes[i];
96
97 for (uint i = 0; i < _patterns->size(); i++)
98 free(_patterns->operator[](i));
99
100 delete _patterns;
101
102 delete _globalScript;
103
104 delete _gameOverMessage;
105 delete _saveBeforeQuitMessage;
106 delete _saveBeforeCloseMessage;
107 delete _revertMessage;
108
109 }
110
loadWorld(Common::MacResManager * resMan)111 bool World::loadWorld(Common::MacResManager *resMan) {
112 Common::MacResIDArray resArray;
113 Common::SeekableReadStream *res;
114 Common::MacResIDArray::const_iterator iter;
115
116 // Dumping interpreter code
117 #if 0
118 res = resMan->getResource(MKTAG('C','O','D','E'), 1);
119 warning("Dumping interpreter code size: %d", res->size());
120 byte *buf = (byte *)malloc(res->size());
121 res->read(buf, res->size());
122 Common::DumpFile out;
123 out.open("code.bin");
124 out.write(buf, res->size());
125 out.close();
126 free(buf);
127 delete res;
128 #endif
129
130 if ((resArray = resMan->getResIDArray(MKTAG('G','C','O','D'))).size() == 0)
131 return false;
132
133 // Load global script
134 res = resMan->getResource(MKTAG('G','C','O','D'), resArray[0]);
135 _globalScript = new Script(res, -1, _engine);
136
137 // TODO: read creator
138
139 // Load main configuration
140 if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0)
141 return false;
142
143 _name = resMan->getBaseFileName().toString();
144
145 if (resArray.size() > 1)
146 warning("Too many VERS resources");
147
148 if (!resArray.empty()) {
149 debug(3, "Loading version info");
150
151 res = resMan->getResource(MKTAG('V','E','R','S'), resArray[0]);
152
153 _signature = res->readSint32LE();
154 res->skip(6);
155 byte b = res->readByte();
156 _weaponMenuDisabled = (b != 0);
157 if (b != 0 && b != 1)
158 error("Unexpected value for weapons menu");
159
160 res->skip(3);
161 _aboutMessage = res->readPascalString();
162
163 if (!scumm_stricmp(resMan->getBaseFileName().toString().c_str(), "Scepters"))
164 res->skip(1); // ????
165
166 _soundLibrary1 = res->readPascalString();
167 _soundLibrary2 = res->readPascalString();
168
169 delete res;
170 }
171
172 Common::String *message;
173 if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) {
174 message->trim();
175 debug(2, "_gameOverMessage: %s", message->c_str());
176 _gameOverMessage = message;
177 } else {
178 _gameOverMessage = new Common::String("Game Over!");
179 }
180 if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) {
181 message->trim();
182 debug(2, "_saveBeforeQuitMessage: %s", message->c_str());
183 _saveBeforeQuitMessage = message;
184 } else {
185 _saveBeforeQuitMessage = new Common::String("Save changes before quiting?");
186 }
187 if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL) {
188 message->trim();
189 debug(2, "_saveBeforeCloseMessage: %s", message->c_str());
190 _saveBeforeCloseMessage = message;
191 } else {
192 _saveBeforeCloseMessage = new Common::String("Save changes before closing?");
193 }
194 if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) {
195 message->trim();
196 debug(2, "_revertMessage: %s", message->c_str());
197 _revertMessage = message;
198 } else {
199 _revertMessage = new Common::String("Revert to the last saved version?");
200 }
201
202 // Load scenes
203 resArray = resMan->getResIDArray(MKTAG('A','S','C','N'));
204 debug(3, "Loading %d scenes", resArray.size());
205
206 for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
207 res = resMan->getResource(MKTAG('A','S','C','N'), *iter);
208 Scene *scene = new Scene(resMan->getResName(MKTAG('A','S','C','N'), *iter), res);
209
210 res = resMan->getResource(MKTAG('A','C','O','D'), *iter);
211 if (res != NULL)
212 scene->_script = new Script(res, *iter, _engine);
213
214 res = resMan->getResource(MKTAG('A','T','X','T'), *iter);
215 if (res != NULL) {
216 scene->_textBounds = readRect(res);
217 int fontType = res->readUint16BE();
218 int fontSize = res->readUint16BE();
219 scene->_font = new Graphics::MacFont(fontType, fontSize, Graphics::kMacFontRegular, Graphics::FontManager::kConsoleFont);
220
221 Common::String text;
222 while (res->pos() < res->size()) {
223 char c = res->readByte();
224 if (c == 0x0d)
225 c = '\n';
226 text += c;
227 }
228 scene->_text = text;
229
230 delete res;
231 }
232
233 scene->_resourceId = *iter;
234 addScene(scene);
235 }
236
237 // Load Objects
238 resArray = resMan->getResIDArray(MKTAG('A','O','B','J'));
239 debug(3, "Loading %d objects", resArray.size());
240
241 for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
242 res = resMan->getResource(MKTAG('A','O','B','J'), *iter);
243 addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res, *iter));
244 }
245
246 // Load Characters
247 resArray = resMan->getResIDArray(MKTAG('A','C','H','R'));
248 debug(3, "Loading %d characters", resArray.size());
249
250 for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
251 res = resMan->getResource(MKTAG('A','C','H','R'), *iter);
252 Chr *chr = new Chr(resMan->getResName(MKTAG('A','C','H','R'), *iter), res);
253 chr->_resourceId = *iter;
254 addChr(chr);
255
256 if (chr->_playerCharacter) {
257 if (_player)
258 warning("loadWorld: Player is redefined");
259
260 _player = chr;
261 }
262 }
263
264 if (!_player) {
265 warning("loadWorld: Player is not defined");
266
267 if (_chrs.empty()) {
268 error("loadWorld: and I have no characters");
269 }
270 _player = _orderedChrs[0];
271 }
272
273
274 // Load Sounds
275 resArray = resMan->getResIDArray(MKTAG('A','S','N','D'));
276 debug(3, "Loading %d sounds", resArray.size());
277
278 for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
279 res = resMan->getResource(MKTAG('A','S','N','D'), *iter);
280 addSound(new Sound(resMan->getResName(MKTAG('A','S','N','D'), *iter), res));
281 }
282
283 if (!_soundLibrary1.empty()) {
284 loadExternalSounds(_soundLibrary1);
285 }
286 if (!_soundLibrary2.empty()) {
287 loadExternalSounds(_soundLibrary2);
288 }
289
290 // Load Patterns
291 res = resMan->getResource(MKTAG('P','A','T','#'), 900);
292 if (res != NULL) {
293 int count = res->readUint16BE();
294 debug(3, "Loading %d patterns", count);
295
296 for (int i = 0; i < count; i++) {
297 byte *pattern = (byte *)malloc(8);
298
299 res->read(pattern, 8);
300 _patterns->push_back(pattern);
301 }
302
303 delete res;
304 } else {
305 /* Enchanted Scepters did not use the PAT# resource for the textures. */
306 res = resMan->getResource(MKTAG('C','O','D','E'), 1);
307 if (res != NULL) {
308 res->skip(0x55ac);
309 for (int i = 0; i < 29; i++) {
310 byte *pattern = (byte *)malloc(8);
311
312 res->read(pattern, 8);
313 _patterns->push_back(pattern);
314 }
315 }
316 delete res;
317 }
318
319 res = resMan->getResource(MKTAG('M','E','N','U'), 2001);
320 if (res != NULL) {
321 Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
322 _aboutMenuItemName.clear();
323 Common::String string = menu->operator[](1);
324
325 for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token
326 _aboutMenuItemName += string[i];
327
328 delete menu;
329 delete res;
330 }
331 res = resMan->getResource(MKTAG('M','E','N','U'), 2004);
332 if (res != NULL) {
333 Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
334 _commandsMenuName = menu->operator[](0);
335 _commandsMenu = menu->operator[](1);
336 delete menu;
337 delete res;
338 }
339 res = resMan->getResource(MKTAG('M','E','N','U'), 2005);
340 if (res != NULL) {
341 Common::StringArray *menu = Graphics::MacMenu::readMenuFromResource(res);
342 _weaponsMenuName = menu->operator[](0);
343 delete menu;
344 delete res;
345 }
346 // TODO: Read Apple menu and get the name of that menu item..
347
348 // store global info in state object for use with save/load actions
349 //world.setCurrentState(initialState); // pass off the state object to the world
350
351 return true;
352 }
353
addSound(Sound * sound)354 void World::addSound(Sound *sound) {
355 Common::String s = sound->_name;
356 s.toLowercase();
357 _sounds[s] = sound;
358 _orderedSounds.push_back(sound);
359 }
360
loadExternalSounds(Common::String fname)361 void World::loadExternalSounds(Common::String fname) {
362 Common::MacResManager resMan;
363 if (!resMan.open(fname)) {
364 warning("Cannot load sound file <%s>", fname.c_str());
365 return;
366 }
367
368 Common::MacResIDArray resArray;
369 Common::SeekableReadStream *res;
370 Common::MacResIDArray::const_iterator iter;
371
372 resArray = resMan.getResIDArray(MKTAG('A','S','N','D'));
373 for (iter = resArray.begin(); iter != resArray.end(); ++iter) {
374 res = resMan.getResource(MKTAG('A','S','N','D'), *iter);
375 addSound(new Sound(resMan.getResName(MKTAG('A','S','N','D'), *iter), res));
376 }
377 }
378
loadStringFromDITL(Common::MacResManager * resMan,int resourceId,int itemIndex)379 Common::String *World::loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex) {
380 Common::SeekableReadStream *res = resMan->getResource(MKTAG('D','I','T','L'), resourceId);
381 if (res) {
382 int itemCount = res->readSint16BE();
383 for (int i = 0; i <= itemCount; i++) {
384 // int placeholder; short rect[4]; byte flags; pstring str;
385 res->skip(13);
386 Common::String message = res->readPascalString();
387 if (i == itemIndex) {
388 Common::String *msg = new Common::String(message);
389 delete res;
390 return msg;
391 }
392 }
393
394 delete res;
395 }
396
397 return NULL;
398 }
399
invComparator(const Obj * l,const Obj * r)400 static bool invComparator(const Obj *l, const Obj *r) {
401 return l->_index < r->_index;
402 }
403
move(Obj * obj,Chr * chr)404 void World::move(Obj *obj, Chr *chr) {
405 if (obj == NULL)
406 return;
407
408 Designed *from = obj->removeFromCharOrScene();
409 obj->_currentOwner = chr;
410 chr->_inventory.push_back(obj);
411
412 Common::sort(chr->_inventory.begin(), chr->_inventory.end(), invComparator);
413
414 _engine->onMove(obj, from, chr);
415 }
416
objComparator(const Obj * o1,const Obj * o2)417 static bool objComparator(const Obj *o1, const Obj *o2) {
418 bool o1Immobile = (o1->_type == Obj::IMMOBILE_OBJECT);
419 bool o2Immobile = (o2->_type == Obj::IMMOBILE_OBJECT);
420 if (o1Immobile == o2Immobile) {
421 return o1->_index < o2->_index;
422 }
423 return o1Immobile;
424 }
425
move(Obj * obj,Scene * scene,bool skipSort)426 void World::move(Obj *obj, Scene *scene, bool skipSort) {
427 if (obj == NULL)
428 return;
429
430 Designed *from = obj->removeFromCharOrScene();
431 obj->_currentScene = scene;
432 scene->_objs.push_back(obj);
433
434 if (!skipSort)
435 Common::sort(scene->_objs.begin(), scene->_objs.end(), objComparator);
436
437 _engine->onMove(obj, from, scene);
438 }
439
chrComparator(const Chr * l,const Chr * r)440 static bool chrComparator(const Chr *l, const Chr *r) {
441 return l->_index < r->_index;
442 }
443
move(Chr * chr,Scene * scene,bool skipSort)444 void World::move(Chr *chr, Scene *scene, bool skipSort) {
445 if (chr == NULL)
446 return;
447 Scene *from = chr->_currentScene;
448 if (from == scene)
449 return;
450 if (from != NULL)
451 from->_chrs.remove(chr);
452 scene->_chrs.push_back(chr);
453
454 if (!skipSort)
455 Common::sort(scene->_chrs.begin(), scene->_chrs.end(), chrComparator);
456
457 if (scene == _storageScene) {
458 chr->resetState();
459 } else if (chr->_playerCharacter) {
460 scene->_visited = true;
461 _player->_context._visits++;
462 }
463 chr->_currentScene = scene;
464
465 _engine->onMove(chr, from, scene);
466 }
467
getRandomScene()468 Scene *World::getRandomScene() {
469 // Not including storage:
470 return _orderedScenes[1 + _engine->_rnd->getRandomNumber(_orderedScenes.size() - 2)];
471 }
472
getSceneAt(int x,int y)473 Scene *World::getSceneAt(int x, int y) {
474 for (uint i = 0; i < _orderedScenes.size(); i++) {
475 Scene *scene = _orderedScenes[i];
476
477 if (scene != _storageScene && scene->_worldX == x && scene->_worldY == y) {
478 return scene;
479 }
480 }
481 return NULL;
482 }
483
484 static const int directionsX[] = { 0, 0, 1, -1 };
485 static const int directionsY[] = { -1, 1, 0, 0 };
486
scenesAreConnected(Scene * scene1,Scene * scene2)487 bool World::scenesAreConnected(Scene *scene1, Scene *scene2) {
488 if (!scene1 || !scene2)
489 return false;
490
491 int x = scene2->_worldX;
492 int y = scene2->_worldY;
493
494 for (int dir = 0; dir < 4; dir++)
495 if (!scene2->_blocked[dir])
496 if (getSceneAt(x + directionsX[dir], y + directionsY[dir]) == scene1)
497 return true;
498
499 return false;
500 }
501
getAboutMenuItemName()502 const char *World::getAboutMenuItemName() {
503 static char menu[256];
504
505 *menu = '\0';
506
507 if (_aboutMenuItemName.empty()) {
508 sprintf(menu, "About %s...", _name.c_str());
509 } else { // Replace '@' with name
510 const char *str = _aboutMenuItemName.c_str();
511 const char *pos = strchr(str, '@');
512 if (pos) {
513 strncat(menu, str, (pos - str));
514 strncat(menu, _name.c_str(), 255);
515 strncat(menu, pos + 1, 255);
516 }
517 }
518
519 return menu;
520 }
521
522 } // End of namespace Wage
523