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