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 "titanic/npcs/doorbot.h"
24 #include "titanic/core/room_item.h"
25 #include "titanic/debugger.h"
26 #include "titanic/pet_control/pet_control.h"
27 #include "titanic/translation.h"
28 
29 namespace Titanic {
30 
31 BEGIN_MESSAGE_MAP(CDoorbot, CTrueTalkNPC)
32 	ON_MESSAGE(MovieEndMsg)
33 	ON_MESSAGE(OnSummonBotMsg)
34 	ON_MESSAGE(TrueTalkTriggerActionMsg)
35 	ON_MESSAGE(DoorbotNeededInHomeMsg)
36 	ON_MESSAGE(DoorbotNeededInElevatorMsg)
37 	ON_MESSAGE(LeaveViewMsg)
38 	ON_MESSAGE(TimerMsg)
39 	ON_MESSAGE(NPCPlayTalkingAnimationMsg)
40 	ON_MESSAGE(NPCPlayIdleAnimationMsg)
41 	ON_MESSAGE(PutBotBackInHisBoxMsg)
42 	ON_MESSAGE(DismissBotMsg)
43 	ON_MESSAGE(MovieFrameMsg)
44 	ON_MESSAGE(TrueTalkNotifySpeechEndedMsg)
45 	ON_MESSAGE(TextInputMsg)
46 	ON_MESSAGE(EnterViewMsg)
47 	ON_MESSAGE(ActMsg)
48 END_MESSAGE_MAP()
49 
50 int CDoorbot::_v1;
51 int CDoorbot::_v2;
52 
CDoorbot()53 CDoorbot::CDoorbot() : CTrueTalkNPC() {
54 	_introMovieNum = 0;
55 	_timerId = 0;
56 	_field110 = 0;
57 	_field114 = 0;
58 }
59 
save(SimpleFile * file,int indent)60 void CDoorbot::save(SimpleFile *file, int indent) {
61 	file->writeNumberLine(1, indent);
62 	file->writeNumberLine(_v1, indent);
63 	file->writeNumberLine(_v2, indent);
64 
65 	file->writeNumberLine(_introMovieNum, indent);
66 	file->writeNumberLine(_timerId, indent);
67 	file->writeNumberLine(_field110, indent);
68 	file->writeNumberLine(_field114, indent);
69 
70 	CTrueTalkNPC::save(file, indent);
71 }
72 
load(SimpleFile * file)73 void CDoorbot::load(SimpleFile *file) {
74 	file->readNumber();
75 	_v1 = file->readNumber();
76 	_v2 = file->readNumber();
77 
78 	_introMovieNum = file->readNumber();
79 	_timerId = file->readNumber();
80 	_field110 = file->readNumber();
81 	_field114 = file->readNumber();
82 
83 	CTrueTalkNPC::load(file);
84 }
85 
MovieEndMsg(CMovieEndMsg * msg)86 bool CDoorbot::MovieEndMsg(CMovieEndMsg *msg) {
87 	debugC(DEBUG_DETAILED, kDebugScripts, "CDoorbot MovieEndMsg flags=%x v=%d, start=%d, end=%d",
88 		_npcFlags, _introMovieNum, msg->_startFrame, msg->_endFrame);
89 
90 	if (_npcFlags & NPCFLAG_DOORBOT_INTRO) {
91 		switch (_introMovieNum) {
92 		case 3:
93 			startTalking(this, 221482);
94 			_introMovieNum = 4;
95 			break;
96 
97 		case 6:
98 			if (clipExistsByEnd("Cloak On", msg->_endFrame)) {
99 				petShow();
100 				petDecAreaLocks();
101 				stateSetSoundMakerAllowed(true);
102 				changeView("ServiceElevator.Node 1.S");
103 				changeView("ServiceElevator.Node 1.N");
104 			}
105 			break;
106 
107 		case 7:
108 			startTalking(this, 221467);
109 			_introMovieNum = 8;
110 			break;
111 
112 		case 9:
113 			if (msg->_endFrame == 949)
114 				startTalking(this, 221468);
115 			break;
116 
117 		case 11:
118 			changeView("ServiceElevator.Node 1.S");
119 			changeView("MoonEmbLobby.Node 1.NE");
120 			break;
121 
122 		default:
123 			break;
124 		}
125 
126 		CTrueTalkNPC::MovieEndMsg(msg);
127 	} else if (_npcFlags & NPCFLAG_MOVE_LEFT) {
128 		if (clipExistsByEnd("Cloak Off", msg->_endFrame)) {
129 			_npcFlags = (_npcFlags & ~NPCFLAG_DOORBOT_IN_HOME) | NPCFLAG_START_IDLING;
130 			setTalking(this, false);
131 			startTalking(this, 221474);
132 			_npcFlags |= NPCFLAG_DOORBOT_INTRO;
133 			_introMovieNum = 0;
134 		} else if (clipExistsByEnd("Cloak On", msg->_endFrame)) {
135 			petShow();
136 			stateSetSoundMakerAllowed(true);
137 			changeView("ServiceElevator.Node 1.S");
138 		} else {
139 			CTrueTalkNPC::MovieEndMsg(msg);
140 		}
141 	} else if (_npcFlags & NPCFLAG_MOVE_END) {
142 		if (clipExistsByEnd("Whizz On Left", msg->_endFrame)
143 				|| clipExistsByEnd("Whizz On Right", msg->_endFrame)) {
144 			setPosition(Point((600 - _bounds.width()) / 2 + 18, 42));
145 			loadFrame(0);
146 			setTalking(this, true);
147 			_npcFlags |= NPCFLAG_START_IDLING;
148 			petSetArea(PET_CONVERSATION);
149 		} else if (clipExistsByEnd("Whizz Off Left", msg->_endFrame)
150 				|| clipExistsByEnd("Whizz Off Right", msg->_endFrame)) {
151 			CPutBotBackInHisBoxMsg boxMsg;
152 			boxMsg.execute(this);
153 			if (_npcFlags & NPCFLAG_SUMMON_BELLBOT)
154 				startAnimTimer("SummonBellbot", 1500);
155 		} else {
156 			CTrueTalkNPC::MovieEndMsg(msg);
157 		}
158 	} else {
159 		CTrueTalkNPC::MovieEndMsg(msg);
160 	}
161 
162 	return true;
163 }
164 
OnSummonBotMsg(COnSummonBotMsg * msg)165 bool CDoorbot::OnSummonBotMsg(COnSummonBotMsg *msg) {
166 	struct RoomWave {
167 		const char *_room;
168 		const char *_enSound;
169 		const char *_deSound;
170 	};
171 	const RoomWave ROOM_WAVES[8] = {
172 		{ "EmbLobby", "z#186.wav", "z#716.wav" },
173 		{ "PromenadeDeck", "z#184.wav", "z#714.wav" },
174 		{ "Arboretum", "z#188.wav", "z#718.wav" },
175 		{ "Frozen Arboretum", "z#188.wav", "z#718.wav" },
176 		{ "Bar", "z#187.wav", "z#717.wav" },
177 		{ "MusicRoom", "z#185.wav", "z#715.wav" },
178 		{ "MusicRoomLobby", "z#185.wav", "z#715.wav" },
179 		{ "1stClassRestaurant", "z#183.wav", "z#713.wav" },
180 	};
181 
182 	if (msg->_value != -1) {
183 		int idx;
184 		for (idx = 0; idx < 8; ++idx) {
185 			if (compareRoomNameTo(ROOM_WAVES[idx]._room)) {
186 				playSound(TRANSLATE(ROOM_WAVES[idx]._enSound, ROOM_WAVES[idx]._deSound));
187 				break;
188 			}
189 		}
190 		if (idx == 8)
191 			playSound(TRANSLATE("z#146.wav", "z#702.wav"));
192 
193 		sleep(2000);
194 	}
195 
196 	playClip(getRandomNumber(1) ? "Whizz On Left" : "Whizz On Right",
197 		MOVIE_NOTIFY_OBJECT | MOVIE_WAIT_FOR_FINISH);
198 	movieEvent();
199 	_npcFlags |= NPCFLAG_MOVE_END;
200 
201 	return true;
202 }
203 
TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg * msg)204 bool CDoorbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
205 	switch (msg->_action) {
206 	case 3:
207 		playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
208 		break;
209 
210 	case 4:
211 		_npcFlags = (_npcFlags & ~NPCFLAG_IDLING) | NPCFLAG_SUMMON_BELLBOT;
212 		playClip("Whizz Off Left", MOVIE_NOTIFY_OBJECT | MOVIE_WAIT_FOR_FINISH);
213 		break;
214 
215 	case 28: {
216 		_npcFlags &= ~(NPCFLAG_IDLING | NPCFLAG_START_IDLING);
217 		CDismissBotMsg dismissMsg;
218 		dismissMsg.execute(this);
219 		break;
220 	}
221 
222 	default:
223 		break;
224 	}
225 
226 	return true;
227 }
228 
DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg * msg)229 bool CDoorbot::DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg *msg) {
230 	moveToView();
231 	setPosition(Point(90, 42));
232 	_npcFlags = NPCFLAG_MOVE_LEFT;
233 
234 	stopMovie();
235 	playClip("Cloak Off", MOVIE_NOTIFY_OBJECT);
236 
237 	_npcFlags |= NPCFLAG_DOORBOT_IN_HOME;
238 	return true;
239 }
240 
DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg * msg)241 bool CDoorbot::DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg *msg) {
242 	moveToView("ServiceElevator.Node 1.N");
243 	setPosition(Point(100, 42));
244 
245 	if (_npcFlags & NPCFLAG_DOORBOT_INTRO) {
246 		_introMovieNum = 7;
247 		_npcFlags |= NPCFLAG_MOVE_RIGHT;
248 		loadFrame(797);
249 	} else {
250 		_npcFlags = 0;
251 		if (msg->_value)
252 			setTalking(this, true);
253 	}
254 
255 	return true;
256 }
257 
LeaveViewMsg(CLeaveViewMsg * msg)258 bool CDoorbot::LeaveViewMsg(CLeaveViewMsg *msg) {
259 	if (!(_npcFlags & NPCFLAG_DOORBOT_INTRO) && (_npcFlags & NPCFLAG_MOVE_END)) {
260 		performAction(true);
261 		_npcFlags &= ~NPCFLAG_START_IDLING;
262 	}
263 
264 	return true;
265 }
266 
TimerMsg(CTimerMsg * msg)267 bool CDoorbot::TimerMsg(CTimerMsg *msg) {
268 	if (msg->_action == "NPCIdleAnim") {
269 		return CTrueTalkNPC::TimerMsg(msg);
270 	} else if (_npcFlags & NPCFLAG_DOORBOT_INTRO) {
271 		_timerId = 0;
272 
273 		switch (msg->_actionVal) {
274 		case 0:
275 			startTalking(this, 221475);
276 			break;
277 
278 		case 1:
279 			startTalking(this, 221476);
280 			break;
281 
282 		case 2:
283 			startTalking(this, 221477);
284 			break;
285 
286 		case 3:
287 			playClip("DoubleTake Start");
288 			playClip("DoubleTake End");
289 			playClip("DoubleTake Start");
290 			playClip("DoubleTake End", MOVIE_NOTIFY_OBJECT);
291 			_introMovieNum = 3;
292 			break;
293 
294 		case 4:
295 			startTalking(this, 221483);
296 			lockInputHandler();
297 			_field114 = true;
298 			break;
299 
300 		case 5:
301 			lockInputHandler();
302 			mouseDisableControl();
303 			_field114 = true;
304 			startTalking(this, 221485);
305 			break;
306 
307 		case 6:
308 			// Start dragging photograph to PET
309 			CMouseButtonDownMsg::generate();
310 			mouseSetPosition(Point(200, 430), 2500);
311 			_timerId = addTimer(7, 2500, 0);
312 			break;
313 
314 		case 7:
315 			// Drop photograph in PET
316 			CMouseButtonUpMsg::generate();
317 			startTalking(this, 221486);
318 			mouseEnableControl();
319 			unlockInputHandler();
320 			_field114 = false;
321 			disableMouse();
322 			break;
323 
324 		default:
325 			break;
326 		}
327 	} else if (msg->_action == "SummonBellbot") {
328 		CRoomItem *room = getRoom();
329 		if (room) {
330 			CSummonBotMsg botMsg;
331 			botMsg._npcName = "Bellbot";
332 			botMsg.execute(room);
333 		}
334 
335 		_npcFlags &= ~NPCFLAG_SUMMON_BELLBOT;
336 	}
337 
338 	return true;
339 }
340 
NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg * msg)341 bool CDoorbot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
342 	static const char *const NAMES1[] = {
343 		"Mutter Aside", "Rub Chin", "Drunken Eye Roll", "Drunken Head Move",
344 		"Look down and mutter",  "Look side to side", "Gesture forward and around",
345 		"Arms behind back", "Look down", "Rolling around", "Hold face",
346 		"Touch chin", "Cross hands in front", nullptr
347 	};
348 	static const char *const NAMES2[] = {
349 		"SE Talking 1", "SE Talking 2", "SE Talking 3", "SE Talking 4",
350 		nullptr
351 	};
352 	static const char *const NAMES3[] = {
353 		"SE Ask For Help", nullptr
354 	};
355 
356 	if (msg->_value2 != 2) {
357 		if (_npcFlags & NPCFLAG_MOVE_RIGHT) {
358 			switch (_introMovieNum) {
359 			case 8:
360 			case 10:
361 				msg->_names = NAMES2;
362 				break;
363 			case 9:
364 				msg->_names = NAMES3;
365 				_introMovieNum = 10;
366 				break;
367 			default:
368 				break;
369 			}
370 		} else if (_npcFlags & (NPCFLAG_MOVE_LEFT | NPCFLAG_MOVE_END)) {
371 			msg->_names = NAMES1;
372 		}
373 	}
374 
375 	return true;
376 }
377 
NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg * msg)378 bool CDoorbot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) {
379 	static const char *const NAMES[] = {
380 		"Hand swivel", "Prompt Push", "Eye Roll", "Say something", nullptr
381 	};
382 
383 	if (!(_npcFlags & (NPCFLAG_MOVE_LEFT | NPCFLAG_MOVE_RIGHT))
384 			&& (_npcFlags & NPCFLAG_MOVE_END))
385 		msg->_names = NAMES;
386 
387 	return true;
388 }
389 
PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg * msg)390 bool CDoorbot::PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg) {
391 	petMoveToHiddenRoom();
392 	_npcFlags &= ~(NPCFLAG_START_IDLING | NPCFLAG_MOVE_LEFT | NPCFLAG_MOVE_RIGHT | NPCFLAG_DOORBOT_INTRO);
393 	if (msg->_value)
394 		performAction(true);
395 
396 	return true;
397 }
398 
DismissBotMsg(CDismissBotMsg * msg)399 bool CDoorbot::DismissBotMsg(CDismissBotMsg *msg) {
400 	if (_npcFlags & NPCFLAG_MOVE_END) {
401 		playClip(getRandomNumber(1) ? "Whizz Off Left" : "Whizz Off Right",
402 			MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT | MOVIE_WAIT_FOR_FINISH);
403 		movieEvent();
404 
405 		if (_npcFlags & NPCFLAG_START_IDLING) {
406 			_npcFlags &= ~NPCFLAG_START_IDLING;
407 			performAction(true);
408 		} else {
409 			performAction(false);
410 		}
411 
412 		CActMsg actMsg("DoorbotDismissed");
413 		actMsg.execute("BotIdleSummons");
414 	}
415 
416 	return true;
417 }
418 
MovieFrameMsg(CMovieFrameMsg * msg)419 bool CDoorbot::MovieFrameMsg(CMovieFrameMsg *msg) {
420 	if (clipExistsByStart("Whizz Off Left", msg->_frameNumber)
421 			|| clipExistsByStart("Whizz On Left", msg->_frameNumber)) {
422 		setPosition(Point(20, 42));
423 	} else if (clipExistsByStart("Whizz Off Right", msg->_frameNumber)
424 			|| clipExistsByStart("Whizz On Right", msg->_frameNumber)) {
425 		setPosition(Point(620 - _bounds.width(), 42));
426 	}
427 
428 	return true;
429 }
430 
TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg * msg)431 bool CDoorbot::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) {
432 	CTrueTalkNPC::TrueTalkNotifySpeechEndedMsg(msg);
433 
434 	if (_npcFlags & NPCFLAG_DOORBOT_INTRO) {
435 		// Initial speech by Doorbot in
436 		switch (msg->_dialogueId - TRANSLATE(10552, 10563)) {
437 		case 0:
438 			playClip("SE Try Buttons", MOVIE_NOTIFY_OBJECT);
439 			_introMovieNum = 9;
440 			break;
441 
442 		case 1:
443 			enableMouse();
444 			break;
445 
446 		case 5:
447 			playClip("SE Move To Right", MOVIE_NOTIFY_OBJECT);
448 			_introMovieNum = 11;
449 			break;
450 
451 		case 7:
452 			stopAnimTimer(_timerId);
453 			_timerId = addTimer(0, 2500, 0);
454 			break;
455 
456 		case 8:
457 			petShow();
458 			petSetArea(PET_CONVERSATION);
459 			petIncAreaLocks();
460 			stopAnimTimer(_timerId);
461 			_timerId = addTimer(1, 1000, 0);
462 			break;
463 
464 		case 9:
465 			enableMouse();
466 			_introMovieNum = 1;
467 			stopAnimTimer(_timerId);
468 			_timerId = addTimer(2, 10000, 0);
469 			break;
470 
471 		case 10:
472 			if (_introMovieNum == 1) {
473 				stopAnimTimer(_timerId);
474 				_timerId = addTimer(2, getRandomNumber(5000) + 5000, 0);
475 			}
476 			break;
477 
478 		case 11:
479 		case 12:
480 			disableMouse();
481 			startTalking(this, 221480);
482 			break;
483 
484 		case 13:
485 			startTalking(this, 221481);
486 			break;
487 
488 		case 14:
489 			stopAnimTimer(_timerId);
490 			_timerId = 0;
491 			if (_field110 == 2) {
492 				playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
493 				_introMovieNum = 6;
494 			} else {
495 				_timerId = addTimer(3, 2000, 0);
496 			}
497 			break;
498 
499 		case 15: {
500 			CActMsg actMsg("BecomeGettable");
501 			actMsg.execute("Photograph");
502 			enableMouse();
503 			stopAnimTimer(_timerId);
504 			_timerId = addTimer(4, 5000, 0);
505 			break;
506 		}
507 
508 		case 16:
509 			// Start moving cursor to photograph
510 			mouseDisableControl();
511 			mouseSetPosition(Point(600, 250), 2500);
512 			_timerId = addTimer(6, 2500, 0);
513 			break;
514 
515 		case 17:
516 			if (_field110 != 2) {
517 				stopAnimTimer(_timerId);
518 				_timerId = addTimer(5, 3000, 0);
519 			}
520 			break;
521 
522 		case 18:
523 			mouseSetPosition(Point(200, 430), 2500);
524 			_timerId = addTimer(7, 3000, 0);
525 			break;
526 
527 		case 19:
528 			playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
529 			_introMovieNum = 6;
530 			break;
531 
532 		default:
533 			break;
534 		}
535 	}
536 
537 	return true;
538 }
539 
TextInputMsg(CTextInputMsg * msg)540 bool CDoorbot::TextInputMsg(CTextInputMsg *msg) {
541 	if (!(_npcFlags & NPCFLAG_DOORBOT_INTRO))
542 		return CTrueTalkNPC::TextInputMsg(msg);
543 
544 	if (_introMovieNum == 1) {
545 		stopAnimTimer(_timerId);
546 		_introMovieNum = 2;
547 		_timerId = 0;
548 
549 		if (msg->_input == "yes" || msg->_input == "yeah"
550 				|| msg->_input == "yea" || msg->_input == "yup"
551 				|| msg->_input == "yep" || msg->_input == "sure"
552 				|| msg->_input == "alright" || msg->_input == "all right"
553 				|| msg->_input == "ok") {
554 			startTalking(this, 221479);
555 		} else {
556 			startTalking(this, 221478);
557 		}
558 	}
559 
560 	return true;
561 }
562 
EnterViewMsg(CEnterViewMsg * msg)563 bool CDoorbot::EnterViewMsg(CEnterViewMsg *msg) {
564 	if ((_npcFlags & NPCFLAG_DOORBOT_INTRO) && _introMovieNum == 7)
565 		playClip("SE Move And Turn", MOVIE_NOTIFY_OBJECT);
566 	else if (!compareRoomNameTo("ServiceElevator") && msg->_newView == getParent() && getPetControl()->canSummonBot("DoorBot")) {
567 		// WORKAROUND: Calling bot in front of doors and then going through them
568 		// can leave it in the view. Detect this and properly remove him when
569 		// the player returns to that view
570 		petMoveToHiddenRoom();
571 	}
572 
573 	return true;
574 }
575 
ActMsg(CActMsg * msg)576 bool CDoorbot::ActMsg(CActMsg *msg) {
577 	debugC(DEBUG_DETAILED, kDebugScripts, "CDoorbot ActMsg action=%s v108=%d v110=%d v114=%d",
578 		msg->_action.c_str(), _introMovieNum, _field110, _field114);
579 
580 	if (msg->_action == "DoorbotPlayerPressedTopButton") {
581 		disableMouse();
582 		startTalking(this, 221471);
583 	} else if (msg->_action == "DoorbotPlayerPressedMiddleButton") {
584 		startTalking(this, 221470);
585 	} else if (msg->_action == "DoorbotPlayerPressedBottomButton") {
586 		startTalking(this, 221469);
587 	} else if (msg->_action == "DoorbotReachedEmbLobby") {
588 		startTalking(this, 221472);
589 	} else if (msg->_action == "PlayerPicksUpPhoto") {
590 		_field110 = 1;
591 		if (!_field114 && _introMovieNum == 4) {
592 			stopAnimTimer(_timerId);
593 			_timerId = 0;
594 			_introMovieNum = 5;
595 			startTalking(this, 221484);
596 		}
597 	} else if (msg->_action == "PlayerPutsPhotoInPet") {
598 		_field110 = 2;
599 		if (!_field114 && _introMovieNum == 5) {
600 			stopAnimTimer(_timerId);
601 			_timerId = 0;
602 			startTalking(this, 221486);
603 			disableMouse();
604 		}
605 	}
606 
607 	return true;
608 }
609 
610 } // End of namespace Titanic
611