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 "neverhood/modules/module1400_sprites.h"
24 #include "neverhood/modules/module1400.h"
25
26 namespace Neverhood {
27
AsScene1401Pipe(NeverhoodEngine * vm)28 AsScene1401Pipe::AsScene1401Pipe(NeverhoodEngine *vm)
29 : AnimatedSprite(vm, 1100), _countdown1(0), _countdown2(0) {
30
31 createSurface(900, 152, 147);
32 _x = 454;
33 _y = 217;
34 startAnimation(0x4C210500, 0, -1);
35 SetUpdateHandler(&AsScene1401Pipe::update);
36 SetMessageHandler(&AsScene1401Pipe::handleMessage);
37 }
38
~AsScene1401Pipe()39 AsScene1401Pipe::~AsScene1401Pipe() {
40 _vm->_soundMan->deleteSoundGroup(0x01104C08);
41 }
42
update()43 void AsScene1401Pipe::update() {
44 AnimatedSprite::update();
45 if (_countdown1 != 0 && (--_countdown1 == 0))
46 stDoneSucking();
47 if (_countdown2 != 0 && (--_countdown2 == 0)) {
48 _vm->_soundMan->addSound(0x01104C08, 0x4A116437);
49 _vm->_soundMan->playSoundLooping(0x4A116437);
50 }
51 }
52
upSuckInProjector()53 void AsScene1401Pipe::upSuckInProjector() {
54 AnimatedSprite::update();
55 if (_countdown1 != 0)
56 _countdown1--;
57 }
58
handleMessage(int messageNum,const MessageParam & param,Entity * sender)59 uint32 AsScene1401Pipe::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
60 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
61 switch (messageNum) {
62 case NM_ANIMATION_START:
63 if (param.asInteger() == 0x0A8A1490)
64 playSound(1, 0x6AB6666F);
65 break;
66 case NM_ANIMATION_UPDATE:
67 _countdown1 = 70;
68 _countdown2 = 8;
69 stStartSucking();
70 break;
71 case 0x483A:
72 stSuckInProjector();
73 break;
74 default:
75 break;
76 }
77 return messageResult;
78 }
79
hmSuckInProjector(int messageNum,const MessageParam & param,Entity * sender)80 uint32 AsScene1401Pipe::hmSuckInProjector(int messageNum, const MessageParam ¶m, Entity *sender) {
81 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
82 switch (messageNum) {
83 case NM_ANIMATION_STOP:
84 if (_countdown1 != 0)
85 stStartSucking();
86 else
87 stDoneSucking();
88 SetMessageHandler(&AsScene1401Pipe::handleMessage);
89 SetUpdateHandler(&AsScene1401Pipe::update);
90 break;
91 default:
92 break;
93 }
94 return messageResult;
95 }
96
stStartSucking()97 void AsScene1401Pipe::stStartSucking() {
98 startAnimation(0x4C240100, 0, -1);
99 playSound(0, 0x4A30063F);
100 }
101
stDoneSucking()102 void AsScene1401Pipe::stDoneSucking() {
103 _vm->_soundMan->deleteSound(0x4A116437);
104 playSound(0, 0x4A120435);
105 startAnimation(0x4C210500, 0, -1);
106 }
107
stSuckInProjector()108 void AsScene1401Pipe::stSuckInProjector() {
109 startAnimation(0x6C210810, 0, -1);
110 SetUpdateHandler(&AsScene1401Pipe::upSuckInProjector);
111 SetMessageHandler(&AsScene1401Pipe::hmSuckInProjector);
112 }
113
AsScene1401Mouse(NeverhoodEngine * vm)114 AsScene1401Mouse::AsScene1401Mouse(NeverhoodEngine *vm)
115 : AnimatedSprite(vm, 1100) {
116
117 createSurface(100, 71, 41);
118 _x = 478;
119 _y = 433;
120 startAnimation(0xA282C472, 0, -1);
121 SetUpdateHandler(&AnimatedSprite::update);
122 SetMessageHandler(&AsScene1401Mouse::handleMessage);
123 }
124
handleMessage(int messageNum,const MessageParam & param,Entity * sender)125 uint32 AsScene1401Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
126 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
127 switch (messageNum) {
128 case NM_ANIMATION_START:
129 if (param.asInteger() == 0x66382026)
130 playSound(0, 0x0CD84468);
131 else if (param.asInteger() == 0x6E28061C)
132 playSound(0, 0x78C8402C);
133 else if (param.asInteger() == 0x462F0410)
134 playSound(0, 0x60984E28);
135 break;
136 case 0x4839:
137 stSuckedIn();
138 break;
139 default:
140 break;
141 }
142 return messageResult;
143 }
144
suSuckedIn()145 void AsScene1401Mouse::suSuckedIn() {
146 AnimatedSprite::updateDeltaXY();
147 if (_collisionBounds.y1 <= 150) {
148 playSound(0, 0x0E32247F);
149 stopAnimation();
150 setVisible(false);
151 SetMessageHandler(NULL);
152 SetSpriteUpdate(NULL);
153 }
154 }
155
stSuckedIn()156 void AsScene1401Mouse::stSuckedIn() {
157 startAnimation(0x34880040, 0, -1);
158 SetSpriteUpdate(&AsScene1401Mouse::suSuckedIn);
159 }
160
AsScene1401Cheese(NeverhoodEngine * vm)161 AsScene1401Cheese::AsScene1401Cheese(NeverhoodEngine *vm)
162 : AnimatedSprite(vm, 1100) {
163
164 createSurface(200, 152, 147);
165 _x = 427;
166 _y = 433;
167 startAnimation(0x461A1490, 0, -1);
168 SetUpdateHandler(&AnimatedSprite::update);
169 SetMessageHandler(&AsScene1401Cheese::handleMessage);
170 }
171
handleMessage(int messageNum,const MessageParam & param,Entity * sender)172 uint32 AsScene1401Cheese::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
173 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
174 switch (messageNum) {
175 case 0x4839:
176 stSuckedIn();
177 break;
178 default:
179 break;
180 }
181 return messageResult;
182 }
183
suSuckedIn()184 void AsScene1401Cheese::suSuckedIn() {
185 AnimatedSprite::updateDeltaXY();
186 if (_collisionBounds.y1 <= 150) {
187 playSound(0, 0x18020439);
188 stopAnimation();
189 setVisible(false);
190 SetMessageHandler(NULL);
191 SetSpriteUpdate(NULL);
192 }
193 }
194
stSuckedIn()195 void AsScene1401Cheese::stSuckedIn() {
196 startAnimation(0x103B8020, 0, -1);
197 SetSpriteUpdate(&AsScene1401Cheese::suSuckedIn);
198 }
199
AsScene1401BackDoor(NeverhoodEngine * vm,Sprite * klaymen,bool isOpen)200 AsScene1401BackDoor::AsScene1401BackDoor(NeverhoodEngine *vm, Sprite *klaymen, bool isOpen)
201 : AnimatedSprite(vm, 1100), _klaymen(klaymen), _countdown(0), _isOpen(isOpen) {
202
203 _x = 320;
204 _y = 240;
205 createSurface1(0x04551900, 100);
206 if (isOpen) {
207 startAnimation(0x04551900, -1, -1);
208 _countdown = 48;
209 } else {
210 stopAnimation();
211 setVisible(false);
212 }
213 _newStickFrameIndex = STICK_LAST_FRAME;
214 SetUpdateHandler(&AsScene1401BackDoor::update);
215 SetMessageHandler(&AsScene1401BackDoor::handleMessage);
216 }
217
update()218 void AsScene1401BackDoor::update() {
219 if (_countdown != 0 && (--_countdown == 0))
220 stCloseDoor();
221 AnimatedSprite::update();
222 }
223
224
handleMessage(int messageNum,const MessageParam & param,Entity * sender)225 uint32 AsScene1401BackDoor::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
226 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
227 switch (messageNum) {
228 case 0x2001:
229 if (_isOpen)
230 _countdown = 168;
231 messageResult = _isOpen ? 1 : 0;
232 break;
233 case NM_ANIMATION_STOP:
234 gotoNextState();
235 break;
236 case NM_KLAYMEN_OPEN_DOOR:
237 _countdown = 168;
238 if (!_isOpen)
239 stOpenDoor();
240 break;
241 default:
242 break;
243 }
244 return messageResult;
245 }
246
stOpenDoor()247 void AsScene1401BackDoor::stOpenDoor() {
248 _isOpen = true;
249 setVisible(true);
250 startAnimation(0x04551900, 0, -1);
251 _newStickFrameIndex = STICK_LAST_FRAME;
252 playSound(0, calcHash("fxDoorOpen24"));
253 }
254
stCloseDoor()255 void AsScene1401BackDoor::stCloseDoor() {
256 _isOpen = false;
257 setVisible(true);
258 startAnimation(0x04551900, -1, -1);
259 playSound(0, calcHash("fxDoorClose24"));
260 _playBackwards = true;
261 NextState(&AsScene1401BackDoor::stCloseDoorDone);
262 }
263
stCloseDoorDone()264 void AsScene1401BackDoor::stCloseDoorDone() {
265 stopAnimation();
266 setVisible(false);
267 }
268
269 static const AsCommonProjectorItem kAsCommonProjectorItems[] = {
270 {{154, 453}, 4, 2, 0, 0, 1},
271 {{104, 391}, 4, -1, -1, 1, 1},
272 {{ 22, 447}, 6, -1, -1, 1, 1},
273 {{112, 406}, 2, -1, -1, 1, 0},
274 {{262, 433}, 1, 1, 0, 0, 0}
275 };
276
AsCommonProjector(NeverhoodEngine * vm,Scene * parentScene,Sprite * klaymen,Sprite * asPipe)277 AsCommonProjector::AsCommonProjector(NeverhoodEngine *vm, Scene *parentScene, Sprite *klaymen, Sprite *asPipe)
278 : AnimatedSprite(vm, 1100), _parentScene(parentScene), _klaymen(klaymen), _asPipe(asPipe) {
279
280 _asProjectorItem = &kAsCommonProjectorItems[getGlobalVar(V_PROJECTOR_LOCATION)];
281 createSurface(990, 101, 182);
282 startAnimation(0x10E3042B, 0, -1);
283 SetUpdateHandler(&AnimatedSprite::update);
284 SetMessageHandler(&AsCommonProjector::handleMessage);
285 _x = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x;
286 _lockedInSlot = true;
287 moveProjector();
288 setDoDeltaX(1);
289 if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->lockSlotIndex)
290 stStayLockedInSlot();
291 loadSound(2, 0xC8C2507C);
292 }
293
~AsCommonProjector()294 AsCommonProjector::~AsCommonProjector() {
295 _vm->_soundMan->deleteSoundGroup(0x05331081);
296 }
297
handleMessage(int messageNum,const MessageParam & param,Entity * sender)298 uint32 AsCommonProjector::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
299 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
300 switch (messageNum) {
301 case 0x1011:
302 sendMessage(_parentScene, 0x4826, 0);
303 messageResult = 1;
304 break;
305 case NM_KLAYMEN_RAISE_LEVER:
306 setGlobalVar(V_PROJECTOR_SLOT, (_x - _asProjectorItem->point.x) / 108);
307 if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->lockSlotIndex)
308 stStartLockedInSlot();
309 else
310 stIdle();
311 break;
312 case 0x480B:
313 if (param.asInteger() != 1) {
314 if ((int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount)
315 incGlobalVar(V_PROJECTOR_SLOT, 1);
316 } else if (getGlobalVar(V_PROJECTOR_SLOT) > 0)
317 incGlobalVar(V_PROJECTOR_SLOT, -1);
318 stMoving();
319 break;
320 case 0x480C:
321 // Check if the projector can be moved
322 if (param.asInteger() != 1)
323 messageResult = (int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount ? 1 : 0;
324 else
325 messageResult = getGlobalVar(V_PROJECTOR_SLOT) > 0 ? 1 : 0;
326 break;
327 case NM_MOVE_TO_BACK:
328 sendMessage(_parentScene, NM_PRIORITY_CHANGE, 990);
329 break;
330 case NM_MOVE_TO_FRONT:
331 sendMessage(_parentScene, NM_PRIORITY_CHANGE, 1010);
332 break;
333 case 0x4839:
334 stStartSuckedIn();
335 break;
336 default:
337 break;
338 }
339 return messageResult;
340 }
341
hmLockedInSlot(int messageNum,const MessageParam & param,Entity * sender)342 uint32 AsCommonProjector::hmLockedInSlot(int messageNum, const MessageParam ¶m, Entity *sender) {
343 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
344 switch (messageNum) {
345 case 0x1011:
346 if (param.asPoint().x - _x >= 17 && param.asPoint().x - _x <= 56 &&
347 param.asPoint().y - _y >= -120 && param.asPoint().y - _y <= -82) {
348 sendMessage(_parentScene, 0x4826, 1);
349 } else
350 sendMessage(_parentScene, 0x4826, 0);
351 messageResult = 1;
352 break;
353 case NM_KLAYMEN_RAISE_LEVER:
354 sendMessage(_parentScene, NM_KLAYMEN_RAISE_LEVER, 0);
355 stStopProjecting();
356 break;
357 case 0x480B:
358 if (param.asInteger() != 1) {
359 if ((int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount)
360 incGlobalVar(V_PROJECTOR_SLOT, 1);
361 } else if (getGlobalVar(V_PROJECTOR_SLOT) > 0)
362 incGlobalVar(V_PROJECTOR_SLOT, -1);
363 stTurnToFront();
364 break;
365 case 0x480C:
366 // Check if the projector can be moved
367 if (param.asInteger() != 1)
368 messageResult = (int8)getGlobalVar(V_PROJECTOR_SLOT) < _asProjectorItem->maxSlotCount ? 1 : 0;
369 else
370 messageResult = getGlobalVar(V_PROJECTOR_SLOT) > 0 ? 1 : 0;
371 break;
372 case NM_KLAYMEN_LOWER_LEVER:
373 stStartProjecting();
374 break;
375 case NM_MOVE_TO_BACK:
376 sendMessage(_parentScene, NM_PRIORITY_CHANGE, 990);
377 break;
378 case NM_MOVE_TO_FRONT:
379 sendMessage(_parentScene, NM_PRIORITY_CHANGE, 1010);
380 break;
381 default:
382 break;
383 }
384 return messageResult;
385 }
386
hmAnimation(int messageNum,const MessageParam & param,Entity * sender)387 uint32 AsCommonProjector::hmAnimation(int messageNum, const MessageParam ¶m, Entity *sender) {
388 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
389 switch (messageNum) {
390 case NM_ANIMATION_STOP:
391 gotoNextState();
392 break;
393 default:
394 break;
395 }
396 return messageResult;
397 }
398
suMoving()399 void AsCommonProjector::suMoving() {
400 if (_x <= _klaymen->getX())
401 _x = _klaymen->getX() - 100;
402 else
403 _x = _klaymen->getX() + 100;
404 moveProjector();
405 if (_beforeMoveX == _x) {
406 if (getGlobalVar(V_PROJECTOR_SLOT) == 0 && _asProjectorItem->leftBorderLeaves != 0) {
407 sendMessage(_parentScene, NM_SCENE_LEAVE, 0);
408 incGlobalVar(V_PROJECTOR_LOCATION, -1);
409 setGlobalVar(V_PROJECTOR_SLOT, kAsCommonProjectorItems[getGlobalVar(V_PROJECTOR_LOCATION)].maxSlotCount);
410 } else if ((int8)getGlobalVar(V_PROJECTOR_SLOT) == _asProjectorItem->maxSlotCount && _asProjectorItem->rightBorderLeaves != 0) {
411 sendMessage(_parentScene, NM_SCENE_LEAVE, 1);
412 incGlobalVar(V_PROJECTOR_LOCATION, +1);
413 setGlobalVar(V_PROJECTOR_SLOT, 0);
414 }
415 }
416 Sprite::updateBounds();
417 }
418
moveProjector()419 void AsCommonProjector::moveProjector() {
420
421 bool nowLockedInSlot = false;
422
423 _y = _asProjectorItem->point.y;
424
425 if (_asProjectorItem->index1 != -1) {
426 int16 elX = _asProjectorItem->index1 * 108 + _asProjectorItem->point.x;
427 if (elX - 20 < _x && elX + 20 > _x) {
428 nowLockedInSlot = true;
429 _y = _asProjectorItem->point.y + 10;
430 }
431 }
432
433 if (_asProjectorItem->lockSlotIndex != -1) {
434 int16 elX = _asProjectorItem->lockSlotIndex * 108 + _asProjectorItem->point.x;
435 if (elX - 20 < _x && elX + 20 > _x) {
436 nowLockedInSlot = true;
437 _y = _asProjectorItem->point.y + 10;
438 }
439 }
440
441 if (_lockedInSlot && !nowLockedInSlot)
442 _lockedInSlot = false;
443 else if (!_lockedInSlot && nowLockedInSlot) {
444 playSound(1, 0x5440E474);
445 _lockedInSlot = true;
446 }
447
448 }
449
stSuckedIn()450 void AsCommonProjector::stSuckedIn() {
451 AnimatedSprite::updateDeltaXY();
452 if (_collisionBounds.y1 <= 150) {
453 sendMessage(_asPipe, 0x483A, 0);
454 stopAnimation();
455 setVisible(false);
456 SetMessageHandler(&Sprite::handleMessage);
457 SetSpriteUpdate(NULL);
458 }
459 }
460
stIdle()461 void AsCommonProjector::stIdle() {
462 startAnimation(0x10E3042B, 0, -1);
463 SetMessageHandler(&AsCommonProjector::handleMessage);
464 SetSpriteUpdate(NULL);
465 }
466
stMoving()467 void AsCommonProjector::stMoving() {
468 _beforeMoveX = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x;
469 startAnimation(0x14A10137, 0, -1);
470 playSound(1, 0xEC008474);
471 SetMessageHandler(&AsCommonProjector::handleMessage);
472 SetSpriteUpdate(&AsCommonProjector::suMoving);
473 }
474
stStartLockedInSlot()475 void AsCommonProjector::stStartLockedInSlot() {
476 startAnimation(0x80C32213, 0, -1);
477 SetMessageHandler(&AsCommonProjector::hmAnimation);
478 SetSpriteUpdate(NULL);
479 NextState(&AsCommonProjector::stStayLockedInSlot);
480 }
481
stStayLockedInSlot()482 void AsCommonProjector::stStayLockedInSlot() {
483 startAnimation(0xD23B207F, 0, -1);
484 SetMessageHandler(&AsCommonProjector::hmLockedInSlot);
485 SetSpriteUpdate(NULL);
486 }
487
stStartProjecting()488 void AsCommonProjector::stStartProjecting() {
489 startAnimation(0x50A80517, 0, -1);
490 setGlobalVar(V_PROJECTOR_ACTIVE, 1);
491 playSound(0, 0xCC4A8456);
492 _vm->_soundMan->addSound(0x05331081, 0xCE428854);
493 _vm->_soundMan->playSoundLooping(0xCE428854);
494 SetMessageHandler(&AsCommonProjector::hmAnimation);
495 SetSpriteUpdate(NULL);
496 NextState(&AsCommonProjector::stLockedInSlot);
497 }
498
stLockedInSlot()499 void AsCommonProjector::stLockedInSlot() {
500 sendMessage(_parentScene, NM_KLAYMEN_LOWER_LEVER, 0);
501 startAnimation(0xD833207F, 0, -1);
502 SetMessageHandler(&AsCommonProjector::hmLockedInSlot);
503 SetSpriteUpdate(NULL);
504 }
505
stStopProjecting()506 void AsCommonProjector::stStopProjecting() {
507 startAnimation(0x50A94417, 0, -1);
508 setGlobalVar(V_PROJECTOR_ACTIVE, 0);
509 playSound(0, 0xCC4A8456);
510 _vm->_soundMan->deleteSound(0xCE428854);
511 SetMessageHandler(&AsCommonProjector::hmAnimation);
512 SetSpriteUpdate(NULL);
513 NextState(&AsCommonProjector::stStayLockedInSlot);
514 }
515
stTurnToFront()516 void AsCommonProjector::stTurnToFront() {
517 _beforeMoveX = getGlobalVar(V_PROJECTOR_SLOT) * 108 + _asProjectorItem->point.x;
518 startAnimation(0x22CB4A33, 0, -1);
519 SetMessageHandler(&AsCommonProjector::hmAnimation);
520 SetSpriteUpdate(&AsCommonProjector::suMoving);
521 NextState(&AsCommonProjector::stMoving);
522 }
523
stStartSuckedIn()524 void AsCommonProjector::stStartSuckedIn() {
525 setGlobalVar(V_PROJECTOR_LOCATION, 4);
526 setGlobalVar(V_PROJECTOR_SLOT, 0);
527 startAnimation(0x708D4712, 0, -1);
528 playSound(2);
529 SetMessageHandler(&Sprite::handleMessage);
530 SetSpriteUpdate(&AsCommonProjector::stSuckedIn);
531 }
532
SsScene1402BridgePart(NeverhoodEngine * vm,uint32 fileHash,int surfacePriority)533 SsScene1402BridgePart::SsScene1402BridgePart(NeverhoodEngine *vm, uint32 fileHash, int surfacePriority)
534 : StaticSprite(vm, fileHash, surfacePriority) {
535
536 SetFilterY(&Sprite::defFilterY);
537 SetUpdateHandler(&StaticSprite::updatePosition);
538 }
539
AsScene1402PuzzleBox(NeverhoodEngine * vm,Scene * parentScene,int status)540 AsScene1402PuzzleBox::AsScene1402PuzzleBox(NeverhoodEngine *vm, Scene *parentScene, int status)
541 : AnimatedSprite(vm, 1100), _parentScene(parentScene) {
542
543 createSurface(900, 347, 230);
544
545 SetFilterY(&Sprite::defFilterY);
546 SetUpdateHandler(&AnimatedSprite::update);
547 SetMessageHandler(&AsScene1402PuzzleBox::handleMessage);
548 _x = 279;
549 _y = 270;
550 if (status == 2) {
551 // Puzzle box after the puzzle was solved
552 startAnimation(0x20060259, 0, -1);
553 playSound(0, 0x419014AC);
554 loadSound(1, 0x61901C29);
555 NextState(&AsScene1402PuzzleBox::stMoveDownSolvedDone);
556 } else if (status == 1) {
557 // Puzzle box appears
558 startAnimation(0x210A0213, 0, -1);
559 playSound(0, 0x41809C6C);
560 NextState(&AsScene1402PuzzleBox::stMoveUpDone);
561 } else {
562 // Puzzle box is here
563 startAnimation(0x20060259, -1, -1);
564 loadSound(1, 0x61901C29);
565 _newStickFrameIndex = STICK_LAST_FRAME;
566 }
567 }
568
handleMessage(int messageNum,const MessageParam & param,Entity * sender)569 uint32 AsScene1402PuzzleBox::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
570 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
571 switch (messageNum) {
572 case NM_POSITION_CHANGE:
573 playSound(1);
574 startAnimation(0x20060259, -1, -1);
575 _playBackwards = true;
576 NextState(&AsScene1402PuzzleBox::stMoveDownDone);
577 break;
578 case NM_ANIMATION_STOP:
579 gotoNextState();
580 break;
581 default:
582 break;
583 }
584 return messageResult;
585 }
586
stMoveUpDone()587 void AsScene1402PuzzleBox::stMoveUpDone() {
588 sendMessage(_parentScene, 0x2000, 0);
589 stopAnimation();
590 setVisible(false);
591 }
592
stMoveDownDone()593 void AsScene1402PuzzleBox::stMoveDownDone() {
594 sendMessage(_parentScene, 0x2001, 0);
595 stopAnimation();
596 setVisible(false);
597 }
598
stMoveDownSolvedDone()599 void AsScene1402PuzzleBox::stMoveDownSolvedDone() {
600 sendMessage(_parentScene, 0x2003, 0);
601 stopAnimation();
602 }
603
604 static const int16 kScene1407MouseFloorY[] = {
605 106, 150, 191, 230, 267, 308, 350, 395
606 };
607
608 static const struct {
609 int16 x;
610 int16 floorIndex;
611 int16 sectionIndex;
612 int16 nextHoleIndex;
613 } kScene1407MouseHoles[] = {
614 {125, 0, 0, 7},
615 {452, 7, 21, 0},
616 {337, 4, 11, 4},
617 {286, 6, 17, 6},
618 {348, 6, 17, 39},
619 {536, 6, 18, 42},
620 {111, 1, 3, 18},
621 {203, 1, 3, 38},
622 {270, 1, 3, 9},
623 {197, 5, 14, 3},
624 {252, 5, 14, 35},
625 {297, 5, 14, 7},
626 {359, 5, 14, 8},
627 {422, 4, 12, 26},
628 {467, 4, 12, 2},
629 {539, 4, 12, 40},
630 {111, 5, 13, 17},
631 {211, 0, 1, 20},
632 {258, 0, 1, 11},
633 {322, 0, 1, 16},
634 { 99, 6, 16, 31},
635 {142, 6, 16, 27},
636 {194, 6, 16, 12},
637 {205, 2, 6, 45},
638 {264, 2, 6, 10},
639 { 98, 4, 10, 2},
640 {152, 4, 10, 37},
641 {199, 4, 10, 13},
642 {258, 4, 10, 16},
643 {100, 7, 19, 43},
644 {168, 7, 19, 23},
645 {123, 3, 8, 14},
646 {181, 3, 8, 39},
647 {230, 3, 8, 28},
648 {292, 3, 8, 22},
649 {358, 3, 8, 36},
650 {505, 3, 9, 44},
651 {400, 2, 7, 34},
652 {454, 2, 7, 32},
653 {532, 2, 7, 46},
654 {484, 5, 15, 25},
655 {529, 5, 15, 30},
656 {251, 7, 20, 48},
657 {303, 7, 20, 21},
658 {360, 7, 20, 33},
659 {503, 0, 2, 5},
660 {459, 1, 4, 19},
661 {530, 1, 4, 42},
662 {111, 2, 5, 47},
663 {442, 6, 18, 1}
664 };
665
666 static const struct {
667 int16 x1, x2;
668 int16 goodHoleIndex;
669 } kScene1407MouseSections[] = {
670 {100, 149, 0},
671 {182, 351, 17},
672 {430, 524, 45},
673 { 89, 293, 7},
674 {407, 555, 47},
675 { 89, 132, 48},
676 {178, 303, 23},
677 {367, 551, 38},
678 {105, 398, 31},
679 {480, 537, 36},
680 { 84, 275, 27},
681 {318, 359, 2},
682 {402, 560, 15},
683 { 91, 132, 16},
684 {179, 400, 10},
685 {461, 552, 41},
686 { 86, 218, 21},
687 {267, 376, 4},
688 {420, 560, 49},
689 { 77, 188, 30},
690 {237, 394, 44},
691 {438, 515, 5}
692 };
693
AsScene1407Mouse(NeverhoodEngine * vm,Scene * parentScene)694 AsScene1407Mouse::AsScene1407Mouse(NeverhoodEngine *vm, Scene *parentScene)
695 : AnimatedSprite(vm, 1100), _parentScene(parentScene), _currSectionIndex(0) {
696
697 createSurface(100, 117, 45);
698 _x = 108;
699 _y = 106;
700 stIdleLookAtGoodHole();
701 SetUpdateHandler(&AnimatedSprite::update);
702 }
703
suWalkTo()704 void AsScene1407Mouse::suWalkTo() {
705 int16 xdelta = _walkDestX - _x;
706 if (xdelta > _deltaX)
707 xdelta = _deltaX;
708 else if (xdelta < -_deltaX)
709 xdelta = -_deltaX;
710 _deltaX = 0;
711 if (_walkDestX == _x)
712 sendMessage(this, NM_SCENE_LEAVE, 0);
713 else {
714 _x += xdelta;
715 updateBounds();
716 }
717 }
718
upGoThroughHole()719 void AsScene1407Mouse::upGoThroughHole() {
720 if (_countdown != 0 && (--_countdown == 0)) {
721 SetUpdateHandler(&AnimatedSprite::update);
722 gotoNextState();
723 }
724 AnimatedSprite::update();
725 }
726
handleMessage(int messageNum,const MessageParam & param,Entity * sender)727 uint32 AsScene1407Mouse::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
728 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
729 switch (messageNum) {
730 case NM_MOUSE_CLICK:
731 {
732 int16 mouseX = param.asPoint().x;
733 int16 mouseY = param.asPoint().y;
734 int holeIndex;
735 for (holeIndex = 0; holeIndex < 50; holeIndex++) {
736 int16 holeX = kScene1407MouseHoles[holeIndex].x;
737 int16 holeY = kScene1407MouseFloorY[kScene1407MouseHoles[holeIndex].floorIndex];
738 if (mouseX >= holeX - 14 && mouseX <= holeX + 14 && mouseY >= holeY - 36 && mouseY <= holeY)
739 break;
740 }
741 if (holeIndex < 50 && kScene1407MouseHoles[holeIndex].sectionIndex == _currSectionIndex) {
742 _nextHoleIndex = kScene1407MouseHoles[holeIndex].nextHoleIndex;
743 _walkDestX = kScene1407MouseHoles[holeIndex].x;
744 stWalkToHole();
745 } else {
746 if (mouseX < kScene1407MouseSections[_currSectionIndex].x1)
747 _walkDestX = kScene1407MouseSections[_currSectionIndex].x1;
748 else if (mouseX > kScene1407MouseSections[_currSectionIndex].x2)
749 _walkDestX = kScene1407MouseSections[_currSectionIndex].x2;
750 else
751 _walkDestX = mouseX;
752 stWalkToDest();
753 }
754 }
755 break;
756 case NM_SCENE_LEAVE:
757 gotoNextState();
758 break;
759 case 0x2001:
760 {
761 // Reset the position
762 // Find the nearest hole and go through it, and exit at the first hole
763 int16 distance = 640;
764 int matchIndex = 50;
765 for (int index = 0; index < 50; index++)
766 if (kScene1407MouseHoles[index].sectionIndex == _currSectionIndex &&
767 ABS(kScene1407MouseHoles[index].x - _x) < distance) {
768 matchIndex = index;
769 distance = ABS(kScene1407MouseHoles[index].x - _x);
770 }
771 if (matchIndex < 50) {
772 _nextHoleIndex = 0;
773 _walkDestX = kScene1407MouseHoles[matchIndex].x;
774 stWalkToHole();
775 }
776 }
777 break;
778 default:
779 break;
780 }
781 return messageResult;
782 }
783
stIdleLookAtGoodHole()784 void AsScene1407Mouse::stIdleLookAtGoodHole() {
785 setDoDeltaX(kScene1407MouseHoles[kScene1407MouseSections[_currSectionIndex].goodHoleIndex].x < _x ? 1 : 0);
786 startAnimation(0x72215194, 0, -1);
787 SetMessageHandler(&AsScene1407Mouse::handleMessage);
788 SetSpriteUpdate(NULL);
789 }
790
stWalkToDest()791 void AsScene1407Mouse::stWalkToDest() {
792 if (_walkDestX != _x) {
793 setDoDeltaX(_walkDestX < _x ? 1 : 0);
794 startAnimation(0x22291510, 0, -1);
795 SetMessageHandler(&AsScene1407Mouse::handleMessage);
796 SetSpriteUpdate(&AsScene1407Mouse::suWalkTo);
797 NextState(&AsScene1407Mouse::stIdleLookAtGoodHole);
798 }
799 }
800
stWalkToHole()801 void AsScene1407Mouse::stWalkToHole() {
802 setDoDeltaX(_walkDestX < _x ? 1 : 0);
803 startAnimation(0x22291510, 0, -1);
804 SetMessageHandler(&AsScene1407Mouse::handleMessage);
805 SetSpriteUpdate(&AsScene1407Mouse::suWalkTo);
806 NextState(&AsScene1407Mouse::stGoThroughHole);
807 }
808
stGoThroughHole()809 void AsScene1407Mouse::stGoThroughHole() {
810 startAnimation(0x72215194, 0, -1);
811 setVisible(false);
812 _countdown = 12;
813 SetUpdateHandler(&AsScene1407Mouse::upGoThroughHole);
814 SetMessageHandler(NULL);
815 SetSpriteUpdate(NULL);
816 NextState(&AsScene1407Mouse::stArriveAtHole);
817 }
818
stArriveAtHole()819 void AsScene1407Mouse::stArriveAtHole() {
820 _currSectionIndex = kScene1407MouseHoles[_nextHoleIndex].sectionIndex;
821 _x = kScene1407MouseHoles[_nextHoleIndex].x;
822 _y = kScene1407MouseFloorY[kScene1407MouseHoles[_nextHoleIndex].floorIndex];
823 if (_nextHoleIndex == 1) {
824 sendMessage(_parentScene, 0x2000, 0);
825 _walkDestX = 512;
826 stWalkToDest();
827 setVisible(true);
828 } else {
829 _walkDestX = _x + 14;
830 stWalkToDest();
831 setVisible(true);
832 }
833 }
834
835 static const NPoint kAsScene1405TileItemPositions[] = {
836 {100, 80}, {162, 78}, {222, 76}, {292, 76},
837 {356, 82}, {422, 84}, {488, 86}, {550, 90},
838 {102, 134}, {164, 132}, {224, 136}, {294, 136},
839 {360, 136}, {422, 138}, {484, 144}, {548, 146},
840 { 98, 196}, {160, 200}, {228, 200}, {294, 202},
841 {360, 198}, {424, 200}, {482, 202}, {548, 206},
842 { 98, 260}, {160, 264}, {226, 260}, {296, 262},
843 {358, 260}, {424, 262}, {486, 264}, {550, 266},
844 { 94, 322}, {160, 316}, {226, 316}, {296, 320},
845 {358, 322}, {422, 324}, {488, 322}, {550, 322},
846 { 98, 380}, {160, 376}, {226, 376}, {294, 378},
847 {356, 380}, {420, 380}, {490, 378}, {552, 376}
848 };
849
AsScene1405Tile(NeverhoodEngine * vm,Scene1405 * parentScene,uint32 tileIndex)850 AsScene1405Tile::AsScene1405Tile(NeverhoodEngine *vm, Scene1405 *parentScene, uint32 tileIndex)
851 : AnimatedSprite(vm, 1100), _parentScene(parentScene), _tileIndex(tileIndex), _countdown(0), _isShowing(false) {
852
853 loadSound(0, 0x05308101);
854 setSoundPan(0, (tileIndex % 8 * 4 + 4) * 25 / 8);
855 _x = kAsScene1405TileItemPositions[_tileIndex].x;
856 _y = kAsScene1405TileItemPositions[_tileIndex].y;
857 createSurface1(0x844B805C, 1100);
858 setVisible(false);
859 if (getSubVar(VA_IS_TILE_MATCH, _tileIndex))
860 _countdown = _vm->_rnd->getRandomNumber(36 - 1) + 1;
861 startAnimation(0x844B805C, getSubVar(VA_TILE_SYMBOLS, _tileIndex), -1);
862 _newStickFrameIndex = (int16)getSubVar(VA_TILE_SYMBOLS, _tileIndex);
863 SetUpdateHandler(&AsScene1405Tile::update);
864 SetMessageHandler(&AsScene1405Tile::handleMessage);
865 }
866
update()867 void AsScene1405Tile::update() {
868 updateAnim();
869 updatePosition();
870 if (_countdown != 0 && (--_countdown == 0))
871 show();
872 }
873
handleMessage(int messageNum,const MessageParam & param,Entity * sender)874 uint32 AsScene1405Tile::handleMessage(int messageNum, const MessageParam ¶m, Entity *sender) {
875 uint32 messageResult = Sprite::handleMessage(messageNum, param, sender);
876 switch (messageNum) {
877 case 0x1011:
878 if (getSubVar(VA_IS_TILE_MATCH, _tileIndex) == 0 && _parentScene->getCountdown() == 0) {
879 show();
880 sendMessage(_parentScene, 0x2000, _tileIndex);
881 }
882 messageResult = 1;
883 break;
884 default:
885 break;
886 }
887 return messageResult;
888 }
889
show()890 void AsScene1405Tile::show() {
891 if (!_isShowing) {
892 _isShowing = true;
893 playSound(0);
894 setVisible(true);
895 }
896 }
897
hide(bool playClickSound)898 void AsScene1405Tile::hide(bool playClickSound) {
899 if (_isShowing) {
900 _isShowing = false;
901 if (playClickSound)
902 playSound(0);
903 setVisible(false);
904 }
905 }
906
KmScene1401(NeverhoodEngine * vm,Scene * parentScene,int16 x,int16 y)907 KmScene1401::KmScene1401(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y)
908 : Klaymen(vm, parentScene, x, y) {
909
910 // Empty
911 }
912
xHandleMessage(int messageNum,const MessageParam & param)913 uint32 KmScene1401::xHandleMessage(int messageNum, const MessageParam ¶m) {
914 switch (messageNum) {
915 case 0x4001:
916 case 0x4800:
917 startWalkToX(param.asPoint().x, false);
918 break;
919 case NM_KLAYMEN_STAND_IDLE:
920 GotoState(&Klaymen::stTryStandIdle);
921 break;
922 case NM_KLAYMEN_MOVE_OBJECT:
923 if (param.asInteger() == 1)
924 GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject);
925 else
926 GotoState(&Klaymen::stMoveObjectFaceObject);
927 break;
928 case NM_KLAYMEN_PRESS_BUTTON:
929 if (param.asInteger() == 1)
930 GotoState(&Klaymen::stPressButton);
931 else if (param.asInteger() == 2)
932 GotoState(&Klaymen::stPressFloorButton);
933 else
934 GotoState(&Klaymen::stPressButtonSide);
935 break;
936 case 0x4817:
937 setDoDeltaX(param.asInteger());
938 gotoNextStateExt();
939 break;
940 case 0x481B:
941 if (param.asPoint().y != 0)
942 startWalkToXDistance(param.asPoint().y, param.asPoint().x);
943 else
944 startWalkToAttachedSpriteXDistance(param.asPoint().x);
945 break;
946 case 0x481F:
947 if (param.asInteger() == 1)
948 GotoState(&Klaymen::stTurnAwayFromUse);
949 else if (param.asInteger() == 0)
950 GotoState(&Klaymen::stTurnToUseHalf);
951 else
952 GotoState(&Klaymen::stWonderAbout);
953 break;
954 case 0x482D:
955 setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0);
956 gotoNextStateExt();
957 break;
958 case 0x482E:
959 if (param.asInteger() == 1)
960 GotoState(&Klaymen::stWalkToFrontNoStep);
961 else
962 GotoState(&Klaymen::stWalkToFront);
963 break;
964 case 0x482F:
965 if (param.asInteger() == 1)
966 GotoState(&Klaymen::stTurnToFront);
967 else
968 GotoState(&Klaymen::stTurnToBack);
969 break;
970 default:
971 break;
972 }
973 return 0;
974 }
975
KmScene1402(NeverhoodEngine * vm,Scene * parentScene,int16 x,int16 y)976 KmScene1402::KmScene1402(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y)
977 : Klaymen(vm, parentScene, x, y) {
978
979 SetFilterY(&Sprite::defFilterY);
980 }
981
xHandleMessage(int messageNum,const MessageParam & param)982 uint32 KmScene1402::xHandleMessage(int messageNum, const MessageParam ¶m) {
983 switch (messageNum) {
984 case 0x4001:
985 case 0x4800:
986 startWalkToX(param.asPoint().x, false);
987 break;
988 case NM_KLAYMEN_STAND_IDLE:
989 GotoState(&Klaymen::stTryStandIdle);
990 break;
991 case NM_KLAYMEN_MOVE_OBJECT:
992 if (param.asInteger() == 1)
993 GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject);
994 else
995 GotoState(&Klaymen::stMoveObjectFaceObject);
996 break;
997 case 0x4817:
998 setDoDeltaX(param.asInteger());
999 gotoNextStateExt();
1000 break;
1001 case 0x481B:
1002 if (param.asPoint().y != 0)
1003 startWalkToXDistance(param.asPoint().y, param.asPoint().x);
1004 else
1005 startWalkToAttachedSpriteXDistance(param.asPoint().x);
1006 break;
1007 case NM_KLAYMEN_TURN_TO_USE:
1008 GotoState(&Klaymen::stTurnToUse);
1009 break;
1010 case NM_KLAYMEN_RETURN_FROM_USE:
1011 GotoState(&Klaymen::stReturnFromUse);
1012 break;
1013 default:
1014 break;
1015 }
1016 return 0;
1017 }
1018
1019 static const KlaymenIdleTableItem klaymenIdleTable1403[] = {
1020 {1, kIdleSpinHead},
1021 {1, kIdleChest},
1022 {1, kIdleHeadOff},
1023 };
1024
KmScene1403(NeverhoodEngine * vm,Scene * parentScene,int16 x,int16 y)1025 KmScene1403::KmScene1403(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y)
1026 : Klaymen(vm, parentScene, x, y) {
1027
1028 setKlaymenIdleTable(klaymenIdleTable1403, ARRAYSIZE(klaymenIdleTable1403));
1029 }
1030
xHandleMessage(int messageNum,const MessageParam & param)1031 uint32 KmScene1403::xHandleMessage(int messageNum, const MessageParam ¶m) {
1032 switch (messageNum) {
1033 case 0x4001:
1034 case 0x4800:
1035 startWalkToX(param.asPoint().x, false);
1036 break;
1037 case NM_KLAYMEN_STAND_IDLE:
1038 GotoState(&Klaymen::stTryStandIdle);
1039 break;
1040 case NM_KLAYMEN_MOVE_OBJECT:
1041 if (param.asInteger() == 1)
1042 GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject);
1043 else
1044 GotoState(&Klaymen::stMoveObjectFaceObject);
1045 break;
1046 case 0x480D:
1047 GotoState(&Klaymen::stUseLever);
1048 break;
1049 case NM_KLAYMEN_PICKUP:
1050 if (param.asInteger() == 2)
1051 GotoState(&Klaymen::stPickUpNeedle);
1052 else if (param.asInteger() == 1)
1053 GotoState(&Klaymen::stPickUpTube);
1054 else
1055 GotoState(&Klaymen::stPickUpGeneric);
1056 break;
1057 case 0x4817:
1058 setDoDeltaX(param.asInteger());
1059 gotoNextStateExt();
1060 break;
1061 case 0x481B:
1062 if (param.asPoint().y != 0)
1063 startWalkToXDistance(param.asPoint().y, param.asPoint().x);
1064 else
1065 startWalkToAttachedSpriteXDistance(param.asPoint().x);
1066 break;
1067 case NM_KLAYMEN_RELEASE_LEVER:
1068 GotoState(&Klaymen::stReleaseLever);
1069 break;
1070 case 0x483F:
1071 startSpecialWalkRight(param.asInteger());
1072 break;
1073 case 0x4840:
1074 startSpecialWalkLeft(param.asInteger());
1075 break;
1076 default:
1077 break;
1078 }
1079 return 0;
1080 }
1081
1082 // KmScene1404
1083
KmScene1404(NeverhoodEngine * vm,Scene * parentScene,int16 x,int16 y)1084 KmScene1404::KmScene1404(NeverhoodEngine *vm, Scene *parentScene, int16 x, int16 y)
1085 : Klaymen(vm, parentScene, x, y) {
1086
1087 // Empty
1088 }
1089
xHandleMessage(int messageNum,const MessageParam & param)1090 uint32 KmScene1404::xHandleMessage(int messageNum, const MessageParam ¶m) {
1091 switch (messageNum) {
1092 case 0x4001:
1093 case 0x4800:
1094 startWalkToX(param.asPoint().x, false);
1095 break;
1096 case NM_KLAYMEN_STAND_IDLE:
1097 GotoState(&Klaymen::stTryStandIdle);
1098 break;
1099 case NM_KLAYMEN_MOVE_OBJECT:
1100 if (param.asInteger() == 1)
1101 GotoState(&Klaymen::stMoveObjectSkipTurnFaceObject);
1102 else
1103 GotoState(&Klaymen::stMoveObjectFaceObject);
1104 break;
1105 case NM_KLAYMEN_PICKUP:
1106 if (param.asInteger() == 2)
1107 GotoState(&Klaymen::stPickUpNeedle);
1108 else if (param.asInteger() == 1)
1109 GotoState(&Klaymen::stPickUpTube);
1110 else
1111 GotoState(&Klaymen::stPickUpGeneric);
1112 break;
1113 case 0x4817:
1114 setDoDeltaX(param.asInteger());
1115 gotoNextStateExt();
1116 break;
1117 case NM_KLAYMEN_INSERT_DISK:
1118 GotoState(&Klaymen::stInsertDisk);
1119 break;
1120 case 0x481B:
1121 if (param.asPoint().y != 0)
1122 startWalkToXDistance(param.asPoint().y, param.asPoint().x);
1123 else
1124 startWalkToAttachedSpriteXDistance(param.asPoint().x);
1125 break;
1126 case NM_KLAYMEN_TURN_TO_USE:
1127 GotoState(&Klaymen::stTurnToUse);
1128 break;
1129 case NM_KLAYMEN_RETURN_FROM_USE:
1130 GotoState(&Klaymen::stReturnFromUse);
1131 break;
1132 case 0x481F:
1133 if (param.asInteger() == 1)
1134 GotoState(&Klaymen::stWonderAboutAfter);
1135 else if (param.asInteger() == 0)
1136 GotoState(&Klaymen::stWonderAboutHalf);
1137 else if (param.asInteger() == 4)
1138 GotoState(&Klaymen::stTurnAwayFromUse);
1139 else if (param.asInteger() == 3)
1140 GotoState(&Klaymen::stTurnToUseHalf);
1141 else
1142 GotoState(&Klaymen::stWonderAbout);
1143 break;
1144 case 0x482D:
1145 setDoDeltaX(_x > (int16)param.asInteger() ? 1 : 0);
1146 gotoNextStateExt();
1147 break;
1148 case 0x483F:
1149 startSpecialWalkRight(param.asInteger());
1150 break;
1151 case 0x4840:
1152 startSpecialWalkLeft(param.asInteger());
1153 break;
1154 default:
1155 break;
1156 }
1157 return 0;
1158 }
1159
1160 } // End of namespace Neverhood
1161