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