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  */
22 
23 #include "common/system.h"
24 #include "common/config-manager.h"
25 #include "common/textconsole.h"
26 
27 #include "parallaction/parallaction.h"
28 #include "parallaction/exec.h"
29 #include "parallaction/input.h"
30 #include "parallaction/parser.h"
31 #include "parallaction/saveload.h"
32 #include "parallaction/sound.h"
33 #include "parallaction/walk.h"
34 
35 
36 namespace Parallaction {
37 
38 #define INITIAL_FREE_SARCOPHAGUS_SLOT_X	200
39 
40 
41 class LocationName {
42 
43 	Common::String _slide;
44 	Common::String _character;
45 	Common::String _location;
46 
47 	bool _hasCharacter;
48 	bool _hasSlide;
49 	Common::String _buf;
50 
51 public:
LocationName()52 	LocationName() {
53 		_hasSlide = false;
54 		_hasCharacter = false;
55 	}
56 
57 	void bind(const char*);
58 
location() const59 	const char *location() const {
60 		return _location.c_str();
61 	}
62 
hasCharacter() const63 	bool hasCharacter() const {
64 		return _hasCharacter;
65 	}
66 
character() const67 	const char *character() const {
68 		return _character.c_str();
69 	}
70 
hasSlide() const71 	bool hasSlide() const {
72 		return _hasSlide;
73 	}
74 
slide() const75 	const char *slide() const {
76 		return _slide.c_str();
77 	}
78 
c_str() const79 	const char *c_str() const {
80 		return _buf.c_str();
81 	}
82 };
83 
84 
85 
86 /*
87 	bind accept the following input formats:
88 
89 	1 - [S].slide.[L]{.[C]}
90 	2 - [L]{.[C]}
91 
92 	where:
93 
94 	[S] is the slide to be shown
95 	[L] is the location to switch to (immediately in case 2, or right after slide [S] in case 1)
96 	[C] is the character to be selected, and is optional
97 
98 	The routine tells one form from the other by searching for the '.slide.'
99 
100 	NOTE: there exists one script in which [L] is not used in the case 1, but its use
101 	is commented out, and would definitely crash the current implementation.
102 */
bind(const char * s)103 void LocationName::bind(const char *s) {
104 	_buf = s;
105 	_hasSlide = false;
106 	_hasCharacter = false;
107 
108 	Common::StringArray list;
109 	char *tok = strtok(_buf.begin(), ".");
110 	while (tok) {
111 		list.push_back(tok);
112 		tok = strtok(NULL, ".");
113 	}
114 
115 	if (list.size() < 1 || list.size() > 4)
116 		error("changeLocation: ill-formed location name '%s'", s);
117 
118 	if (list.size() > 1) {
119 		if (list[1] == "slide") {
120 			_hasSlide = true;
121 			_slide = list[0];
122 
123 			list.remove_at(0);		// removes slide name
124 			list.remove_at(0);		// removes 'slide'
125 		}
126 
127 		if (list.size() == 2) {
128 			_hasCharacter = true;
129 			_character = list[1];
130 		}
131 	}
132 
133 	_location = list[0];
134 	_buf = s;		// kept as reference
135 }
136 
Parallaction_ns(OSystem * syst,const PARALLACTIONGameDescription * gameDesc)137 Parallaction_ns::Parallaction_ns(OSystem* syst, const PARALLACTIONGameDescription *gameDesc) : Parallaction(syst, gameDesc),
138 	_locationParser(0), _programParser(0), _walker(0) {
139 	_soundManI = 0;
140 	_score = 0;
141 	_inTestResult = 0;
142 	_callables = 0;
143 	num_foglie = 0;
144 	_sarcophagusDeltaX = 0;
145 	_movingSarcophagus = 0;
146 	_freeSarcophagusSlotX = 0;
147 	_intro = 0;
148 
149 	_testResultLabels[0] = 0;
150 	_testResultLabels[1] = 0;
151 }
152 
init()153 Common::Error Parallaction_ns::init() {
154 
155 	_screenWidth = 320;
156 	_screenHeight = 200;
157 
158 	if (getPlatform() == Common::kPlatformDOS) {
159 		_disk = new DosDisk_ns(this);
160 	} else {
161 		if (getFeatures() & GF_DEMO) {
162 			strcpy(_location._name, "fognedemo");
163 		}
164 		_disk = new AmigaDisk_ns(this);
165 	}
166 
167 	_disk->init();
168 
169 	if (getPlatform() == Common::kPlatformDOS) {
170 		_soundManI = new DosSoundMan_ns(this);
171 		_soundManI->setMusicVolume(ConfMan.getInt("music_volume"));
172 	} else {
173 		_soundManI = new AmigaSoundMan_ns(this);
174 	}
175 
176 	_soundMan = new SoundMan(_soundManI);
177 
178 	initResources();
179 	initFonts();
180 	_locationParser = new LocationParser_ns(this);
181 	_locationParser->init();
182 	_programParser = new ProgramParser_ns(this);
183 	_programParser->init();
184 
185 	_cmdExec = new CommandExec_ns(this);
186 	_programExec = new ProgramExec_ns(this);
187 
188 	_walker = new PathWalker_NS(this);
189 
190 	_sarcophagusDeltaX = 0;
191 	_movingSarcophagus = false;
192 	_freeSarcophagusSlotX = INITIAL_FREE_SARCOPHAGUS_SLOT_X;
193 
194 	num_foglie = 0;
195 
196 	_intro = false;
197 	_inTestResult = false;
198 
199 	_location._animations.push_front(_char._ani);
200 
201 	_saveLoad = new SaveLoad_ns(this, _saveFileMan);
202 
203 	initInventory();
204 	setupBalloonManager();
205 
206 	_score = 1;
207 
208 	_testResultLabels[0] = 0;
209 	_testResultLabels[1] = 0;
210 
211 	Parallaction::init();
212 
213 	return Common::kNoError;
214 }
215 
~Parallaction_ns()216 Parallaction_ns::~Parallaction_ns() {
217 	freeFonts();
218 
219 	// TODO: we may want to add a ~Character instead
220 	freeCharacter();
221 	_char._ani.reset();
222 
223 	destroyInventory();
224 
225 	delete _locationParser;
226 	delete _programParser;
227 	freeLocation(true);
228 
229 	_location._animations.remove(_char._ani);
230 
231 	delete _walker;
232 
233 	destroyTestResultLabels();
234 }
235 
destroyTestResultLabels()236 void Parallaction_ns::destroyTestResultLabels() {
237 	for (int i = 0; i < 2; ++i) {
238 		_gfx->unregisterLabel(_testResultLabels[i]);
239 		delete _testResultLabels[i];
240 		_testResultLabels[i] = 0;
241 	}
242 }
243 
freeFonts()244 void Parallaction_ns::freeFonts() {
245 
246 	delete _dialogueFont;
247 	delete _labelFont;
248 	delete _menuFont;
249 	delete _introFont;
250 
251 	_menuFont  = 0;
252 	_dialogueFont = 0;
253 	_labelFont = 0;
254 	_introFont = 0;
255 }
256 
257 
callFunction(uint index,void * parm)258 void Parallaction_ns::callFunction(uint index, void* parm) {
259 	assert(index < 25);	// magic value 25 is maximum # of callables for Nippon Safes
260 
261 	(this->*_callables[index])(parm);
262 }
263 
processGameEvent(int event)264 bool Parallaction_ns::processGameEvent(int event) {
265 	if (event == kEvNone) {
266 		return true;
267 	}
268 
269 	bool c = true;
270 	_input->stopHovering();
271 
272 	switch (event) {
273 	case kEvSaveGame:
274 		_saveLoad->saveGame();
275 		break;
276 
277 	case kEvLoadGame:
278 		_saveLoad->loadGame();
279 		break;
280 	}
281 
282 	_input->setArrowCursor();
283 	_input->setMouseState(MOUSE_ENABLED_SHOW);
284 
285 	return c;
286 }
287 
go()288 Common::Error Parallaction_ns::go() {
289 	_saveLoad->renameOldSavefiles();
290 
291 	_globalFlagsNames = _disk->loadTable("global");
292 
293 	startGui();
294 
295 	while (!shouldQuit()) {
296 		runGame();
297 	}
298 
299 	return Common::kNoError;
300 }
301 
changeBackground(const char * background,const char * mask,const char * path)302 void Parallaction_ns::changeBackground(const char* background, const char* mask, const char* path) {
303 	Palette pal;
304 
305 	uint16 v2 = 0;
306 	if (!scumm_stricmp(background, "final")) {
307 		_gfx->clearScreen();
308 		for (uint16 _si = 0; _si < 32; _si++) {
309 			pal.setEntry(_si, v2, v2, v2);
310 			v2 += 4;
311 		}
312 
313 		_system->delayMillis(20);
314 		_gfx->setPalette(pal);
315 		_gfx->updateScreen();
316 		return;
317 	}
318 
319 	if (path == 0) {
320 		path = mask;
321 	}
322 
323 	BackgroundInfo *info = new BackgroundInfo;
324 	_disk->loadScenery(*info, background, mask, path);
325 	_gfx->setBackground(kBackgroundLocation, info);
326 }
327 
328 
runPendingZones()329 void Parallaction_ns::runPendingZones() {
330 	if (_activeZone) {
331 		ZonePtr z = _activeZone;	// speak Zone or sound
332 		_activeZone.reset();
333 		runZone(z);
334 	}
335 }
336 
337 //	changeLocation handles transitions between locations, and is able to display slides
338 //	between one and the other.
339 //
changeLocation()340 void Parallaction_ns::changeLocation() {
341 	if (_newLocationName.empty()) {
342 		return;
343 	}
344 
345 	char location[200];
346 	Common::strlcpy(location, _newLocationName.c_str(), 200);
347 	Common::strlcpy(_location._name, _newLocationName.c_str(), 100);
348 
349 	debugC(1, kDebugExec, "changeLocation(%s)", location);
350 
351 	MouseTriState oldMouseState = _input->getMouseState();
352 	_input->setMouseState(MOUSE_DISABLED);
353 
354 	if (!_intro) {
355 		// prevent music changes during the introduction
356 		_soundManI->playLocationMusic(location);
357 	}
358 
359 	_input->stopHovering();
360 	// this is still needed to remove the floatingLabel
361 	_gfx->freeLabels();
362 
363 	_zoneTrap.reset();
364 
365 	_input->setArrowCursor();
366 
367 	_gfx->showGfxObj(_char._ani->gfxobj, false);
368 
369 	LocationName locname;
370 	locname.bind(location);
371 
372 	freeLocation(false);
373 
374 	if (locname.hasSlide()) {
375 		showSlide(locname.slide());
376 		GfxObj *label = _gfx->createLabel(_menuFont, _location._slideText[0].c_str(), 1);
377 		_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 14);
378 		_gfx->updateScreen();
379 
380 		_input->waitForButtonEvent(kMouseLeftUp);
381 		_gfx->unregisterLabel(label);
382 		delete label;
383 	}
384 
385 	if (locname.hasCharacter()) {
386 		changeCharacter(locname.character());
387 	}
388 
389 	Common::strlcpy(g_saveData1, locname.location(), 30);
390 	parseLocation(g_saveData1);
391 
392 	if (_location._startPosition.x != -1000) {
393 		_char._ani->setX(_location._startPosition.x);
394 		_char._ani->setY(_location._startPosition.y);
395 		_char._ani->setF(_location._startFrame);
396 		_location._startPosition.y = -1000;
397 		_location._startPosition.x = -1000;
398 	}
399 
400 
401 	_gfx->setBlackPalette();
402 	_gfx->updateScreen();
403 
404 	// BUG #1837503: kEngineChangeLocation flag must be cleared *before* commands
405 	// and acommands are executed, so that it can be set again if needed.
406 	g_engineFlags &= ~kEngineChangeLocation;
407 
408 	_cmdExec->run(_location._commands);
409 
410 	doLocationEnterTransition();
411 
412 	_cmdExec->run(_location._aCommands);
413 
414 	if (_location._hasSound)
415 		_soundManI->playSfx(_location._soundFile, 0, true);
416 
417 	if (!_intro) {
418 		_input->setMouseState(oldMouseState);
419 		// WORKAROUND: Fix a script bug in the Multilingual DOS version of
420 		// Nippon Safes: the mouse cursor is incorrectly hidden outside the
421 		// cave at the end of the game. Fix it here.
422 		if (!strcmp(_location._name, "ingressocav"))
423 			_input->setMouseState(MOUSE_ENABLED_SHOW);
424 	}
425 
426 	debugC(1, kDebugExec, "changeLocation() done");
427 	_newLocationName.clear();
428 }
429 
430 
parseLocation(const char * filename)431 void Parallaction_ns::parseLocation(const char *filename) {
432 	debugC(1, kDebugParser, "parseLocation('%s')", filename);
433 
434 	allocateLocationSlot(filename);
435 	Script *script = _disk->loadLocation(filename);
436 
437 	// TODO: the following two lines are specific to Nippon Safes
438 	// and should be moved into something like 'initializeParsing()'
439 	_location._hasSound = false;
440 
441 	_locationParser->parse(script);
442 
443 	delete script;
444 
445 	// this loads animation scripts
446 	AnimationList::iterator it = _location._animations.begin();
447 	for ( ; it != _location._animations.end(); ++it) {
448 		if (!(*it)->_scriptName.empty()) {
449 			loadProgram(*it, (*it)->_scriptName.c_str());
450 		}
451 	}
452 
453 	debugC(1, kDebugParser, "parseLocation('%s') done", filename);
454 	return;
455 }
456 
457 
458 
changeCharacter(const char * name)459 void Parallaction_ns::changeCharacter(const char *name) {
460 	debugC(1, kDebugExec, "changeCharacter(%s)", name);
461 
462 	_char.setName(name);
463 
464 	if (!scumm_stricmp(_char.getFullName(), _characterName1)) {
465 		debugC(3, kDebugExec, "changeCharacter: nothing done");
466 		return;
467 	}
468 
469 	freeCharacter();
470 
471 	_char._ani->gfxobj = _gfx->loadCharacterAnim(_char.getFullName());
472 
473 	if (!_char.dummy()) {
474 		_char._head = _disk->loadHead(_char.getBaseName());
475 		_char._talk = _disk->loadTalk(_char.getBaseName());
476 		_objects = _disk->loadObjects(_char.getBaseName());
477 		_objectsNames = _disk->loadTable(_char.getBaseName());
478 
479 		if (!_intro) {
480 			// prevent music changes during the introduction
481 			_soundManI->playCharacterMusic(_char.getBaseName());
482 		}
483 
484 		// The original engine used to reload 'common' only on loadgames. We are reloading here since 'common'
485 		// contains character specific stuff. This causes crashes like bug #1816899, because parseLocation tries
486 		// to reload scripts but the data archive selected is occasionally wrong. This has been solved by having
487 		// parseLocation only load scripts when they aren't already loaded - which it should have done since the
488 		// beginning nevertheless.
489 		if (!(getFeatures() & GF_DEMO))
490 			parseLocation("common");
491 	}
492 
493 	strcpy(_characterName1, _char.getFullName());
494 
495 	debugC(3, kDebugExec, "changeCharacter: switch completed");
496 
497 	return;
498 }
499 
freeCharacter()500 void Parallaction_ns::freeCharacter() {
501 	_gfx->freeCharacterObjects();
502 
503 	delete _char._talk;
504 	delete _char._head;
505 	delete _char._ani->gfxobj;
506 	delete _objects;
507 	delete _objectsNames;
508 
509 	_char._talk = 0;
510 	_char._head = 0;
511 	_char._ani->gfxobj = 0;
512 
513 	_objects = 0;
514 	_objectsNames = 0;
515 }
516 
freeLocation(bool removeAll)517 void Parallaction_ns::freeLocation(bool removeAll) {
518 	debugC(2, kDebugExec, "freeLocation");
519 
520 	_soundManI->stopSfx(0);
521 	_soundManI->stopSfx(1);
522 	_soundManI->stopSfx(2);
523 	_soundManI->stopSfx(3);
524 
525 	_localFlagNames->clear();
526 
527 	_gfx->freeLocationObjects();
528 
529 	_location._animations.remove(_char._ani);
530 	_location.cleanup(removeAll);
531 	_location._animations.push_front(_char._ani);
532 }
533 
cleanupGame()534 void Parallaction_ns::cleanupGame() {
535 	_soundManI->stopMusic();
536 
537 	_inTestResult = false;
538 	g_engineFlags &= ~kEngineTransformedDonna;
539 
540 	_numLocations = 0;
541 	g_globalFlags = 0;
542 	memset(_localFlags, 0, sizeof(_localFlags));
543 	memset(_locationNames, 0, sizeof(_locationNames));
544 
545 	_location.freeZones(true);
546 
547 	_score = 0;
548 	_freeSarcophagusSlotX = INITIAL_FREE_SARCOPHAGUS_SLOT_X;
549 	_movingSarcophagus = false;
550 }
551 
updateWalkers()552 void Parallaction_ns::updateWalkers() {
553 	_walker->walk();
554 }
555 
556 
scheduleWalk(int16 x,int16 y,bool fromUser)557 void Parallaction_ns::scheduleWalk(int16 x, int16 y, bool fromUser) {
558 	AnimationPtr a = _char._ani;
559 
560 	if ((a->_flags & kFlagsRemove) || (a->_flags & kFlagsActive) == 0) {
561 		return;
562 	}
563 
564 	_walker->buildPath(a, x, y);
565 	g_engineFlags |= kEngineWalking;
566 }
567 
568 }// namespace Parallaction
569