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" // for setFocusRectangle/clearFocusRectangle
24 #include "scumm/scumm.h"
25 #include "scumm/actor.h"
26 #include "scumm/actor_he.h"
27 #include "scumm/akos.h"
28 #include "scumm/boxes.h"
29 #include "scumm/charset.h"
30 #include "scumm/costume.h"
31 #include "scumm/he/intern_he.h"
32 #include "scumm/object.h"
33 #include "scumm/resource.h"
34 #include "scumm/scumm_v7.h"
35 #include "scumm/scumm_v0.h"
36 #include "scumm/he/sound_he.h"
37 #include "scumm/he/sprite_he.h"
38 #include "scumm/usage_bits.h"
39 #include "scumm/util.h"
40
41 namespace Scumm {
42
43 byte Actor::kInvalidBox = 0;
44
45 static const byte v0ActorDemoTalk[25] = {
46 0x00,
47 0x06, // Syd
48 0x06, // Razor
49 0x06, // Dave
50 0x06, // Michael
51 0x06, // Bernard
52 0x06, // Wendy
53 0x00, // Jeff
54 0x46, // Radiation Suit
55 0x06, // Dr Fred
56 0x06, // Nurse Edna
57 0x06, // Weird Ed
58 0x06, // Dead Cousin Ted
59 0xE2, // Purple Tentacle
60 0xE2, // Green Tentacle
61 0x06, // Meteor police
62 0xC0, // Meteor
63 0x06, // Mark Eteer
64 0x06, // Talkshow Host
65 0x00, // Plant
66 0xC0, // Meteor Radiation
67 0xC0, // Edsel (small, outro)
68 0x00, // Meteor (small, intro)
69 0x06, // Sandy (Lab)
70 0x06, // Sandy (Cut-Scene)
71 };
72
73 static const byte v0ActorTalk[25] = {
74 0x00,
75 0x06, // Syd
76 0x06, // Razor
77 0x06, // Dave
78 0x06, // Michael
79 0x06, // Bernard
80 0x06, // Wendy
81 0x00, // Jeff
82 0x46, // Radiation Suit
83 0x06, // Dr Fred
84 0x06, // Nurse Edna
85 0x06, // Weird Ed
86 0x06, // Dead Cousin Ted
87 0xFF, // Purple Tentacle
88 0xFF, // Green Tentacle
89 0x06, // Meteor police
90 0xC0, // Meteor
91 0x06, // Mark Eteer
92 0x06, // Talkshow Host
93 0x00, // Plant
94 0xC0, // Meteor Radiation
95 0xC0, // Edsel (small, outro)
96 0x00, // Meteor (small, intro)
97 0x06, // Sandy (Lab)
98 0x06, // Sandy (Cut-Scene)
99 };
100
101 static const byte v0WalkboxSlantedModifier[0x16] = {
102 0x00,0x01,0x02,0x03,0x03,0x04,0x05,0x06,
103 0x06,0x07,0x08,0x09,0x09,0x0A,0x0B,
104 0x0C,0x0C,0x0D,0x0E,0x0F,0x10,0x10
105 };
106
Actor(ScummEngine * scumm,int id)107 Actor::Actor(ScummEngine *scumm, int id) :
108 _vm(scumm), _number(id) {
109 assert(_vm != 0);
110 }
111
initActor(int mode)112 void ActorHE::initActor(int mode) {
113 Actor::initActor(mode);
114
115 if (mode == -1) {
116 _heOffsX = _heOffsY = 0;
117 _heSkipLimbs = false;
118 memset(_heTalkQueue, 0, sizeof(_heTalkQueue));
119 }
120
121 if (mode == 1 || mode == -1) {
122 _heCondMask = 1;
123 _heNoTalkAnimation = 0;
124 _heSkipLimbs = false;
125 } else if (mode == 2) {
126 _heCondMask = 1;
127 _heSkipLimbs = false;
128 }
129
130 _heXmapNum = 0;
131 _hePaletteNum = 0;
132 _heFlags = 0;
133 _heTalking = false;
134
135 if (_vm->_game.heversion >= 61)
136 _flip = 0;
137
138 _clipOverride = ((ScummEngine_v60he *)_vm)->_actorClipOverride;
139
140 _auxBlock.reset();
141 }
142
initActor(int mode)143 void Actor::initActor(int mode) {
144
145 if (mode == -1) {
146 _top = _bottom = 0;
147 _needRedraw = false;
148 _needBgReset = false;
149 _costumeNeedsInit = false;
150 _visible = false;
151 _flip = false;
152 _speedx = 8;
153 _speedy = 2;
154 _frame = 0;
155 _walkbox = 0;
156 _animProgress = 0;
157 _drawToBackBuf = false;
158 memset(_animVariable, 0, sizeof(_animVariable));
159 memset(_palette, 0, sizeof(_palette));
160 memset(_sound, 0, sizeof(_sound));
161 memset(&_cost, 0, sizeof(CostumeData));
162 _walkdata.reset();
163 _walkdata.point3.x = 32000;
164 _walkScript = 0;
165 }
166
167 if (mode == 1 || mode == -1) {
168 _costume = 0;
169 _room = 0;
170 _pos.x = 0;
171 _pos.y = 0;
172 _facing = 180;
173 if (_vm->_game.version >= 7)
174 _visible = false;
175 } else if (mode == 2) {
176 _facing = 180;
177 }
178 _elevation = 0;
179 _width = 24;
180 _talkColor = 15;
181 _talkPosX = 0;
182 _talkPosY = -80;
183 _boxscale = _scaley = _scalex = 0xFF;
184 _charset = 0;
185 memset(_sound, 0, sizeof(_sound));
186 _targetFacing = _facing;
187
188 _shadowMode = 0;
189 _layer = 0;
190
191 stopActorMoving();
192
193 setActorWalkSpeed(8, 2);
194
195 _animSpeed = 0;
196 if (_vm->_game.version >= 6)
197 _animProgress = 0;
198
199 _ignoreBoxes = false;
200 _forceClip = (_vm->_game.version >= 7) ? 100 : 0;
201 _ignoreTurns = false;
202
203 _talkFrequency = 256;
204 _talkPan = 64;
205 _talkVolume = 127;
206
207 _initFrame = 1;
208 _walkFrame = 2;
209 _standFrame = 3;
210 _talkStartFrame = 4;
211 _talkStopFrame = 5;
212
213 _walkScript = 0;
214 _talkScript = 0;
215
216 _vm->_classData[_number] = (_vm->_game.version >= 7) ? _vm->_classData[0] : 0;
217 }
218
initActor(int mode)219 void Actor_v2::initActor(int mode) {
220 Actor_v3::initActor(mode);
221
222 _speedx = 1;
223 _speedy = 1;
224
225 _initFrame = 2;
226 _walkFrame = 0;
227 _standFrame = 1;
228 _talkStartFrame = 5;
229 _talkStopFrame = 4;
230 }
231
initActor(int mode)232 void Actor_v3::initActor(int mode) {
233 if (mode == -1) {
234 _stepX = 1;
235 _stepThreshold = 0;
236 }
237 Actor::initActor(mode);
238 }
239
initActor(int mode)240 void Actor_v0::initActor(int mode) {
241 Actor_v2::initActor(mode);
242
243 _costCommandNew = 0xFF;
244 _costCommand = 0xFF;
245 _miscflags = 0;
246 _speaking = 0;
247
248 _walkCountModulo = 0;
249 _newWalkBoxEntered = false;
250 _walkDirX = 0;
251 _walkDirY = 0;
252 _walkYCountGreaterThanXCount = 0;
253 _walkXCount = 0;
254 _walkXCountInc = 0;
255 _walkYCount = 0;
256 _walkYCountInc = 0;
257 _walkMaxXYCountInc = 0;
258
259 _tmp_WalkBox = 0;
260 _tmp_NewWalkBoxEntered = 0;
261
262 _animFrameRepeat = 0;
263 for (int i = 0; i < 8; ++i) {
264 _limbFrameRepeatNew[i] = 0;
265 _limbFrameRepeat[i] = 0;
266 _limb_flipped[i] = false;
267 }
268
269 walkBoxQueueReset();
270
271 if (_vm->_game.features & GF_DEMO) {
272 _sound[0] = v0ActorDemoTalk[_number];
273 } else {
274 _sound[0] = v0ActorTalk[_number];
275 }
276 }
277
walkBoxQueueReset()278 void Actor_v0::walkBoxQueueReset() {
279 _walkboxHistory.clear();
280 _walkboxQueueIndex = 0;
281
282 for (uint i = 0; i < ARRAYSIZE(_walkboxQueue); ++i) {
283 _walkboxQueue[i] = kInvalidBox;
284 }
285 }
286
walkBoxQueueAdd(int box)287 bool Actor_v0::walkBoxQueueAdd(int box) {
288
289 if (_walkboxQueueIndex == ARRAYSIZE(_walkboxQueue))
290 return false;
291
292 _walkboxQueue[_walkboxQueueIndex++] = box;
293 _walkboxHistory.push_back(box);
294 return true;
295 }
296
walkboxQueueReverse()297 void Actor_v0::walkboxQueueReverse() {
298 int j = ARRAYSIZE(_walkboxQueue) - 1;
299
300 while (_walkboxQueue[j] == kInvalidBox && j >= 1)
301 --j;
302
303 if (j <= 1)
304 return;
305
306 for (int i = 1; i < j && j >= 1 ; ++i, --j) {
307
308 byte tmp = _walkboxQueue[i];
309
310 _walkboxQueue[i] = _walkboxQueue[j];
311 _walkboxQueue[j] = tmp;
312 }
313 }
314
walkBoxQueueFind(int box)315 bool Actor_v0::walkBoxQueueFind(int box) {
316
317 for (uint i = 0; i < _walkboxHistory.size(); ++i) {
318 if (box == _walkboxHistory[i])
319 return true;
320 }
321
322 return false;
323 }
324
walkBoxQueuePrepare()325 bool Actor_v0::walkBoxQueuePrepare() {
326 walkBoxQueueReset();
327 int BoxFound = _walkbox;
328
329 if (BoxFound == _walkdata.destbox) {
330
331 _newWalkBoxEntered = true;
332 return true;
333 }
334
335 // Build a series of walkboxes from our current position, to the target
336 do {
337 // Add the current box to the queue
338 if (!walkBoxQueueAdd(BoxFound))
339 return false;
340
341 // Loop until we find a walkbox which hasn't been tested
342 while (_walkboxQueueIndex > 0) {
343
344 // Check if the dest box is a direct neighbour
345 if ((BoxFound = _vm->getNextBox(BoxFound, _walkdata.destbox)) == kInvalidBox) {
346
347 // Its not, start hunting through this boxes immediate connections
348 byte* boxm = _vm->getBoxConnectionBase(_walkboxQueue[_walkboxQueueIndex - 1]);
349
350 // Attempt to find one, which we havn't already used
351 for (; *boxm != kInvalidBox; ++boxm) {
352 if (walkBoxQueueFind(*boxm) != true)
353 break;
354 }
355
356 BoxFound = *boxm;
357 }
358
359 // Found one?
360 if (BoxFound != kInvalidBox) {
361
362 // Did we find a connection to the final walkbox
363 if (BoxFound == _walkdata.destbox) {
364
365 _newWalkBoxEntered = true;
366
367 walkBoxQueueAdd(BoxFound);
368
369 walkboxQueueReverse();
370 return true;
371 }
372
373 // Nope, check the next box
374 break;
375 }
376
377 // Drop this box, its useless to us
378 _walkboxQueue[--_walkboxQueueIndex] = kInvalidBox;
379
380 BoxFound = _walkboxQueue[_walkboxQueueIndex - 1];
381 }
382
383 } while (_walkboxQueueIndex > 0);
384
385 return false;
386 }
387
setBox(int box)388 void Actor::setBox(int box) {
389 _walkbox = box;
390 setupActorScale();
391 }
392
setupActorScale()393 void Actor_v3::setupActorScale() {
394 // WORKAROUND bug #2556: Under certain circumstances, it is possible
395 // for Henry Sr. to reach the front side of Castle Brunwald (following
396 // Indy there). But it seems the game has no small costume for Henry,
397 // hence he is shown as a giant, triple in size compared to Indy.
398 // To workaround this, we override the scale of Henry. Since V3 games
399 // like Indy3 don't use the costume scale otherwise, this works fine.
400 // The scale factor 0x50 was determined by some guess work.
401 if (_number == 2 && _costume == 7 && _vm->_game.id == GID_INDY3 && _vm->_currentRoom == 12) {
402 _scalex = 0x50;
403 _scaley = 0x50;
404 } else {
405 // TODO: The following could probably be removed
406 _scalex = 0xFF;
407 _scaley = 0xFF;
408 }
409 }
410
setupActorScale()411 void Actor::setupActorScale() {
412 if (_ignoreBoxes)
413 return;
414
415 // For some boxes, we ignore the scaling and use whatever values the
416 // scripts set. This is used e.g. in the Mystery Vortex in Sam&Max.
417 // Older games used the flag 0x20 differently, though.
418 if (_vm->_game.id == GID_SAMNMAX && (_vm->getBoxFlags(_walkbox) & kBoxIgnoreScale))
419 return;
420
421 _boxscale = _vm->getBoxScale(_walkbox);
422
423 uint16 scale = _vm->getScale(_walkbox, _pos.x, _pos.y);
424 assert(scale <= 0xFF);
425
426 _scalex = _scaley = (byte)scale;
427 }
428
429
430 #pragma mark -
431 #pragma mark --- Actor walking ---
432 #pragma mark -
433
434
walkActors()435 void ScummEngine::walkActors() {
436 for (int i = 1; i < _numActors; ++i) {
437 if (_actors[i]->isInCurrentRoom())
438 _actors[i]->walkActor();
439 }
440 }
441
stopActorMoving()442 void Actor::stopActorMoving() {
443 if (_walkScript)
444 _vm->stopScript(_walkScript);
445
446 if (_vm->_game.version == 0) {
447 _moving = 2;
448 setDirection(_facing);
449 } else {
450 _moving = 0;
451 }
452 }
453
setActorWalkSpeed(uint newSpeedX,uint newSpeedY)454 void Actor::setActorWalkSpeed(uint newSpeedX, uint newSpeedY) {
455 if (newSpeedX == _speedx && newSpeedY == _speedy)
456 return;
457
458 _speedx = newSpeedX;
459 _speedy = newSpeedY;
460
461 if (_moving) {
462 if (_vm->_game.version == 8 && (_moving & MF_IN_LEG) == 0)
463 return;
464 calcMovementFactor(_walkdata.next);
465 }
466 }
467
getAngleFromPos(int x,int y)468 int getAngleFromPos(int x, int y) {
469 if (ABS(y) * 2 < ABS(x)) {
470 if (x > 0)
471 return 90;
472 return 270;
473 } else {
474 if (y > 0)
475 return 180;
476 return 0;
477 }
478 }
479
calcMovementFactor(const Common::Point & next)480 int Actor::calcMovementFactor(const Common::Point& next) {
481 int diffX, diffY;
482 int32 deltaXFactor, deltaYFactor;
483
484 if (_pos == next)
485 return 0;
486
487 diffX = next.x - _pos.x;
488 diffY = next.y - _pos.y;
489
490 deltaYFactor = _speedy << 16;
491 if (diffY < 0)
492 deltaYFactor = -deltaYFactor;
493
494 deltaXFactor = deltaYFactor * diffX;
495 if (diffY != 0) {
496 deltaXFactor /= diffY;
497 } else {
498 deltaYFactor = 0;
499 }
500
501 if ((uint)ABS(deltaXFactor >> 16) > _speedx) {
502 deltaXFactor = _speedx << 16;
503 if (diffX < 0)
504 deltaXFactor = -deltaXFactor;
505
506 deltaYFactor = deltaXFactor * diffY;
507 if (diffX != 0) {
508 deltaYFactor /= diffX;
509 } else {
510 deltaXFactor = 0;
511 }
512 }
513
514 _walkdata.xfrac = 0;
515 _walkdata.yfrac = 0;
516 _walkdata.cur = _pos;
517 _walkdata.next = next;
518 _walkdata.deltaXFactor = deltaXFactor;
519 _walkdata.deltaYFactor = deltaYFactor;
520
521 if (_vm->_game.version >= 7)
522 _targetFacing = normalizeAngle((int)(atan2((double)deltaXFactor, (double)-deltaYFactor) * 180 / M_PI));
523 else
524 _targetFacing = (ABS(diffY) * 3 > ABS(diffX)) ? (deltaYFactor > 0 ? 180 : 0) : (deltaXFactor > 0 ? 90 : 270);
525
526 return actorWalkStep();
527 }
528
calcMovementFactor(const Common::Point & next)529 int Actor_v3::calcMovementFactor(const Common::Point& next) {
530 int32 deltaXFactor, deltaYFactor;
531
532 if (_pos == next)
533 return 0;
534
535 int diffX = next.x - _pos.x;
536 int diffY = next.y - _pos.y;
537
538 if (_vm->_game.version == 3) {
539 // These two lines fix bug #1052 (INDY3: Hitler facing wrong directions in the Berlin scene).
540 // I can't see anything like this in the original SCUMM1/2 code, so I limit this to SCUMM3.
541 if (!(_moving & MF_LAST_LEG) && (int)_speedx > ABS(diffX) && (int)_speedy > ABS(diffY))
542 return 0;
543
544 _stepX = ((ABS(diffY) / (int)_speedy) >> 1) > (ABS(diffX) / (int)_speedx) ? _speedy + 1 : _speedx;
545 }
546
547 _stepThreshold = MAX(ABS(diffY) / _speedy, ABS(diffX) / _stepX);
548 deltaXFactor = (int32)_stepX;
549 deltaYFactor = (int32)_speedy;
550
551 if (diffX < 0)
552 deltaXFactor = -deltaXFactor;
553 if (diffY < 0)
554 deltaYFactor = -deltaYFactor;
555
556 _walkdata.xfrac = _walkdata.xAdd = deltaXFactor ? diffX / deltaXFactor : 0;
557 _walkdata.yfrac = _walkdata.yAdd = deltaYFactor ? diffY / deltaYFactor : 0;
558 _walkdata.cur = _pos;
559 _walkdata.next = next;
560 _walkdata.deltaXFactor = deltaXFactor;
561 _walkdata.deltaYFactor = deltaYFactor;
562
563 // The x/y distance ratio which determines whether to face up/down instead of left/right is different for SCUMM1/2 and SCUMM3.
564 _targetFacing = oldDirToNewDir(((ABS(diffY) * _facingXYratio) > ABS(diffX)) ? 3 - (diffY >= 0 ? 1 : 0) : (diffX >= 0 ? 1 : 0));
565
566 if (_vm->_game.version <= 2 && _facing != updateActorDirection(true))
567 _moving |= MF_TURN;
568
569 return actorWalkStep();
570 }
571
actorWalkStep()572 int Actor::actorWalkStep() {
573 _needRedraw = true;
574
575 int nextFacing = updateActorDirection(true);
576 if (!(_moving & MF_IN_LEG) || _facing != nextFacing) {
577 if (_walkFrame != _frame || _facing != nextFacing) {
578 startWalkAnim(1, nextFacing);
579 }
580 _moving |= MF_IN_LEG;
581 }
582
583 if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y))
584 setBox(_walkdata.curbox);
585
586 int distX = ABS(_walkdata.next.x - _walkdata.cur.x);
587 int distY = ABS(_walkdata.next.y - _walkdata.cur.y);
588
589 if (ABS(_pos.x - _walkdata.cur.x) >= distX && ABS(_pos.y - _walkdata.cur.y) >= distY) {
590 // I have checked that only the v7/8 games have this different (non-)handling of the moving flag. Our code was
591 // correct for the lower versions. For COMI this fixes one part of the issues that caused ticket #4424 (wrong
592 // movement data being reported by ScummEngine_v8::o8_wait()).
593 if (_vm->_game.version < 7)
594 _moving &= ~MF_IN_LEG;
595 return 0;
596 }
597
598 int tmpX = (_pos.x << 16) + _walkdata.xfrac + (_walkdata.deltaXFactor >> 8) * _scalex;
599 _walkdata.xfrac = (uint16)tmpX;
600 _pos.x = (tmpX >> 16);
601
602 int tmpY = (_pos.y << 16) + _walkdata.yfrac + (_walkdata.deltaYFactor >> 8) * _scaley;
603 _walkdata.yfrac = (uint16)tmpY;
604 _pos.y = (tmpY >> 16);
605
606 if (ABS(_pos.x - _walkdata.cur.x) > distX)
607 _pos.x = _walkdata.next.x;
608
609 if (ABS(_pos.y - _walkdata.cur.y) > distY)
610 _pos.y = _walkdata.next.y;
611
612 if (_vm->_game.version >= 4 && _vm->_game.version <= 6 && _pos == _walkdata.next) {
613 _moving &= ~MF_IN_LEG;
614 return 0;
615 }
616
617 return 1;
618 }
619
actorWalkStep()620 int Actor_v3::actorWalkStep() {
621 _needRedraw = true;
622
623 int nextFacing = updateActorDirection(true);
624 if (!(_moving & MF_IN_LEG) || _facing != nextFacing) {
625 if (_walkFrame != _frame || _facing != nextFacing)
626 startWalkAnim(1, nextFacing);
627
628 _moving |= MF_IN_LEG;
629 // The next two lines fix bug #12278 for ZAK FM-TOWNS (SCUMM3). They are alse required for SCUMM 1/2 to prevent movement while
630 // turning, but only if the character has to make a turn. The correct behavior for v1/2 can be tested by letting Zak (only v1/2
631 // versions) walk in the starting room from the torn wallpaper to the desk drawer: Zak should first turn around clockwise by
632 // 180�, then walk one step to the left, then turn clockwise 90�. For ZAK FM-TOWNS (SCUMM3) this part will look quite different
633 // (and a bit weird), but I have confirmed the correctness with the FM-Towns emulator, too.
634 if (_vm->_game.version == 3 || (_vm->_game.version <= 2 && (_moving & MF_TURN)))
635 return 1;
636 }
637
638 if (_vm->_game.version == 3) {
639 if (_walkdata.next.x - (int)_stepX <= _pos.x && _walkdata.next.x + (int)_stepX >= _pos.x)
640 _pos.x = _walkdata.next.x;
641 if (_walkdata.next.y - (int)_speedy <= _pos.y && _walkdata.next.y + (int)_speedy >= _pos.y)
642 _pos.y = _walkdata.next.y;
643
644 if (_walkbox != _walkdata.curbox && _vm->checkXYInBoxBounds(_walkdata.curbox, _pos.x, _pos.y))
645 setBox(_walkdata.curbox);
646
647 if (_pos == _walkdata.next) {
648 _moving &= ~MF_IN_LEG;
649 return 0;
650 }
651 }
652
653 if ((_walkdata.xfrac += _walkdata.xAdd) >= _stepThreshold) {
654 _pos.x += _walkdata.deltaXFactor;
655 _walkdata.xfrac -= _stepThreshold;
656 }
657 if ((_walkdata.yfrac += _walkdata.yAdd) >= _stepThreshold) {
658 _pos.y += _walkdata.deltaYFactor;
659 _walkdata.yfrac -= _stepThreshold;
660 }
661
662 if (_vm->_game.version <= 2 && _pos == _walkdata.next) {
663 _moving &= ~MF_IN_LEG;
664 return 0;
665 }
666
667 return 1;
668 }
669
calcWalkDistances()670 bool Actor_v0::calcWalkDistances() {
671 _walkDirX = 0;
672 _walkDirY = 0;
673 _walkYCountGreaterThanXCount = 0;
674 uint16 A = 0;
675
676 if (_CurrentWalkTo.x >= _tmp_NewPos.x) {
677 A = _CurrentWalkTo.x - _tmp_NewPos.x;
678 _walkDirX = 1;
679 } else {
680 A = _tmp_NewPos.x - _CurrentWalkTo.x;
681 }
682
683 _walkXCountInc = A;
684
685 if (_CurrentWalkTo.y >= _tmp_NewPos.y) {
686 A = _CurrentWalkTo.y - _tmp_NewPos.y;
687 _walkDirY = 1;
688 } else {
689 A = _tmp_NewPos.y - _CurrentWalkTo.y;
690 }
691
692 _walkYCountInc = A;
693 if (!_walkXCountInc && !_walkYCountInc)
694 return true;
695
696 if (_walkXCountInc <= _walkYCountInc)
697 _walkYCountGreaterThanXCount = 1;
698
699 // 2FCC
700 A = _walkXCountInc;
701 if (A <= _walkYCountInc)
702 A = _walkYCountInc;
703
704 _walkMaxXYCountInc = A;
705 _walkXCount = _walkXCountInc;
706 _walkYCount = _walkYCountInc;
707 _walkCountModulo = _walkMaxXYCountInc;
708
709 return false;
710 }
711
712 /* Calculate the result of moving X+1 or X-1 */
actorWalkXCalculate()713 byte Actor_v0::actorWalkXCalculate() {
714 byte A = _walkXCount;
715 A += _walkXCountInc;
716 if (A >= _walkCountModulo) {
717 if (!_walkDirX) {
718 _tmp_NewPos.x--;
719 } else {
720 _tmp_NewPos.x++;
721 }
722
723 A -= _walkCountModulo;
724 }
725 // 2EAC
726 _walkXCount = A;
727 setActorToTempPosition();
728 if (updateWalkbox() == kInvalidBox) {
729 // 2EB9
730 setActorToOriginalPosition();
731
732 return 3;
733 }
734 // 2EBF
735 if (_tmp_NewPos.x == _CurrentWalkTo.x)
736 return 1;
737
738 return 0;
739 }
740
741 /* Calculate the result of moving Y+1 or Y-1 */
actorWalkYCalculate()742 byte Actor_v0::actorWalkYCalculate() {
743 byte A = _walkYCount;
744 A += _walkYCountInc;
745 if (A >= _walkCountModulo) {
746 if (!_walkDirY) {
747 _tmp_NewPos.y--;
748 } else {
749 _tmp_NewPos.y++;
750 }
751
752 A -= _walkCountModulo;
753 }
754 // 2EEB
755 _walkYCount = A;
756 setActorToTempPosition();
757 if (updateWalkbox() == kInvalidBox) {
758 // 2EF8
759 setActorToOriginalPosition();
760 return 4;
761 }
762 // 2EFE
763 if (_walkYCountInc != 0) {
764 if (_walkYCountInc == 0xFF) {
765 setActorToOriginalPosition();
766 return 4;
767 }
768 }
769 // 2F0D
770 if (_CurrentWalkTo.y == _tmp_NewPos.y)
771 return 1;
772
773 return 0;
774 }
775
startWalkActor(int destX,int destY,int dir)776 void Actor::startWalkActor(int destX, int destY, int dir) {
777 AdjustBoxResult abr;
778
779 if (!isInCurrentRoom() && _vm->_game.version >= 7) {
780 debugC(DEBUG_ACTORS, "startWalkActor: attempting to walk actor %d who is not in this room", _number);
781 return;
782 }
783
784 if (_vm->_game.version <= 4) {
785 abr.x = destX;
786 abr.y = destY;
787 } else {
788 abr = adjustXYToBeInBox(destX, destY);
789 }
790
791 if (!isInCurrentRoom() && _vm->_game.version <= 6) {
792 _pos.x = abr.x;
793 _pos.y = abr.y;
794 if (!_ignoreTurns && dir != -1)
795 _facing = dir;
796 return;
797 }
798
799 if (_vm->_game.version <= 2) {
800 abr = adjustXYToBeInBox(abr.x, abr.y);
801 if (_pos.x == abr.x && _pos.y == abr.y && (dir == -1 || _facing == dir))
802 return;
803 } else {
804 if (_ignoreBoxes) {
805 abr.box = kInvalidBox;
806 _walkbox = kInvalidBox;
807 } else {
808 if (_vm->_game.version < 7) {
809 if (_vm->checkXYInBoxBounds(_walkdata.destbox, abr.x, abr.y)) {
810 abr.box = _walkdata.destbox;
811 } else {
812 abr = adjustXYToBeInBox(abr.x, abr.y);
813 }
814 }
815 if (_moving && _walkdata.destdir == dir && _walkdata.dest.x == abr.x && _walkdata.dest.y == abr.y)
816 return;
817 }
818
819 if (_pos.x == abr.x && _pos.y == abr.y) {
820 if (dir != _facing)
821 turnToDirection(dir);
822 return;
823 }
824 }
825
826 _walkdata.dest.x = abr.x;
827 _walkdata.dest.y = abr.y;
828 _walkdata.destbox = abr.box;
829 _walkdata.destdir = dir;
830 _walkdata.point3.x = 32000;
831 _walkdata.curbox = _walkbox;
832
833 if (_vm->_game.version == 0) {
834 ((Actor_v0 *)this)->walkBoxQueuePrepare();
835
836 } else if (_vm->_game.version <= 2) {
837 _moving = (_moving & ~(MF_LAST_LEG | MF_IN_LEG)) | MF_NEW_LEG;
838 } else {
839 _moving = (_moving & MF_IN_LEG) | MF_NEW_LEG;
840 }
841
842 }
843
startWalkAnim(int cmd,int angle)844 void Actor::startWalkAnim(int cmd, int angle) {
845 if (angle == -1)
846 angle = _facing;
847
848 /* Note: walk scripts aren't required to make the Dig
849 * work as usual
850 */
851 if (_walkScript) {
852 int args[NUM_SCRIPT_LOCAL];
853 memset(args, 0, sizeof(args));
854 args[0] = _number;
855 args[1] = cmd;
856 args[2] = angle;
857 _vm->runScript(_walkScript, 1, 0, args);
858 } else {
859 switch (cmd) {
860 case 1: /* start walk */
861 setDirection(angle);
862 startAnimActor(_walkFrame);
863 break;
864 case 2: /* change dir only */
865 setDirection(angle);
866 break;
867 case 3: /* stop walk */
868 turnToDirection(angle);
869 startAnimActor(_standFrame);
870 break;
871 default:
872 break;
873 }
874 }
875 }
876
walkActor()877 void Actor::walkActor() {
878 int new_dir, next_box;
879 Common::Point foundPath;
880
881 if (_vm->_game.version >= 7) {
882 if (_moving & MF_FROZEN) {
883 if (_moving & MF_TURN) {
884 new_dir = updateActorDirection(false);
885 if (_facing != new_dir)
886 setDirection(new_dir);
887 else
888 _moving &= ~MF_TURN;
889 }
890 return;
891 }
892 }
893
894 if (!_moving)
895 return;
896
897 if (!(_moving & MF_NEW_LEG)) {
898 if (_moving & MF_IN_LEG && actorWalkStep())
899 return;
900
901 if (_moving & MF_LAST_LEG) {
902 _moving = 0;
903 setBox(_walkdata.destbox);
904 if (_vm->_game.version <= 6) {
905 startAnimActor(_standFrame);
906 if (_targetFacing != _walkdata.destdir)
907 turnToDirection(_walkdata.destdir);
908 } else {
909 startWalkAnim(3, _walkdata.destdir);
910 }
911 return;
912 }
913
914 if (_moving & MF_TURN) {
915 new_dir = updateActorDirection(false);
916 if (_facing != new_dir)
917 setDirection(new_dir);
918 else
919 // I have checked that only the v7/8 games have this different handling of the moving flag. Our code was
920 // correct for the lower versions. For COMI this fixes one part of the issues that caused ticket #4424
921 // (wrong movement data being reported by ScummEngine_v8::o8_wait()).
922 _moving = (_vm->_game.version >= 7) ? (_moving & ~MF_TURN) : 0;
923 return;
924 }
925
926 setBox(_walkdata.curbox);
927 _moving &= MF_IN_LEG;
928 }
929
930 _moving &= ~MF_NEW_LEG;
931 do {
932 if (_walkbox == kInvalidBox) {
933 setBox(_walkdata.destbox);
934 _walkdata.curbox = _walkdata.destbox;
935 break;
936 }
937
938 if (_walkbox == _walkdata.destbox)
939 break;
940
941 next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
942 if (next_box < 0) {
943 _walkdata.destbox = _walkbox;
944 _moving |= MF_LAST_LEG;
945 return;
946 }
947
948 _walkdata.curbox = next_box;
949
950 if (findPathTowards(_walkbox, next_box, _walkdata.destbox, foundPath))
951 break;
952
953 if (calcMovementFactor(foundPath))
954 return;
955
956 setBox(_walkdata.curbox);
957 } while (1);
958
959 _moving |= MF_LAST_LEG;
960 calcMovementFactor(_walkdata.dest);
961 }
962
walkActor()963 void Actor_v0::walkActor() {
964 actorSetWalkTo();
965
966 _needRedraw = true;
967 if (_NewWalkTo != _CurrentWalkTo) {
968 _CurrentWalkTo = _NewWalkTo;
969
970 UpdateActorDirection:;
971 _tmp_NewPos = _pos;
972
973 byte tmp = calcWalkDistances();
974 _moving &= 0xF0;
975 _moving |= tmp;
976
977 if (!_walkYCountGreaterThanXCount) {
978 if (_walkDirX) {
979 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*1, V12_Y_MULTIPLIER*0);
980 } else {
981 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*-1, V12_Y_MULTIPLIER*0);
982 }
983 } else {
984 if (_walkDirY) {
985 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*1);
986 } else {
987 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*-1);
988 }
989 }
990
991 directionUpdate();
992
993 // Need to turn again?
994 if (_moving & 0x80)
995 return;
996
997 animateActor(newDirToOldDir(_facing));
998
999 } else {
1000 // 2A0A
1001 if ((_moving & 0x7F) != 1) {
1002
1003 if (_NewWalkTo == _pos)
1004 return;
1005 }
1006 }
1007
1008 // 2A9A: Nothing to do
1009 if (_moving == 2)
1010 return;
1011
1012 // Reached Target
1013 if ((_moving & 0x0F) == 1)
1014 return stopActorMoving();
1015
1016 // 2AAD: Turn actor?
1017 if (_moving & 0x80) {
1018 directionUpdate();
1019
1020 // Turn again?
1021 if (_moving & 0x80)
1022 return;
1023
1024 // Start Walk animation
1025 animateActor(newDirToOldDir(_facing));
1026 }
1027
1028 // Walk X
1029 if ((_moving & 0x0F) == 3) {
1030 WalkX:;
1031 setActorToTempPosition();
1032
1033 if (!_walkDirX) {
1034 _pos.x--;
1035 } else {
1036 _pos.x++;
1037 }
1038
1039 // 2C51
1040 // Does this move us into the walkbox?
1041 if (updateWalkbox() != kInvalidBox) {
1042
1043 // Yes, Lets update our direction
1044 setActorToOriginalPosition();
1045 goto UpdateActorDirection;
1046 }
1047
1048 setActorToOriginalPosition();
1049
1050 // Have we reached Y Target?
1051 if (_CurrentWalkTo.y == _tmp_NewPos.y) {
1052 stopActorMoving();
1053 return;
1054 }
1055
1056 // Lets check one more pixel up or down
1057 if (!_walkDirY) {
1058 _tmp_NewPos.y--;
1059 } else {
1060 _tmp_NewPos.y++;
1061 }
1062
1063 setActorToTempPosition();
1064
1065 // Are we still inside an invalid walkbox?
1066 if (updateWalkbox() == kInvalidBox) {
1067 setActorToOriginalPosition();
1068 stopActorMoving();
1069 return;
1070 }
1071
1072 // Found a valid walkbox
1073 return;
1074 }
1075
1076 // 2ADA: Walk Y
1077 if ((_moving & 0x0F) == 4) {
1078 setActorToTempPosition();
1079
1080 if (!_walkDirY) {
1081 _pos.y--;
1082 } else {
1083 _pos.y++;
1084 }
1085
1086 // Moved out of walkbox?
1087 if (updateWalkbox() == kInvalidBox) {
1088 // 2CC7
1089 setActorToOriginalPosition();
1090
1091 // Reached X?
1092 if (_CurrentWalkTo.x == _tmp_NewPos.x) {
1093 stopActorMoving();
1094 return;
1095 }
1096
1097 // Lets check one more pixel to left or right
1098 if (!_walkDirX) {
1099 _tmp_NewPos.x--;
1100 } else {
1101 _tmp_NewPos.x++;
1102 }
1103
1104 setActorToTempPosition();
1105
1106 // Still in an invalid walkbox?
1107 if (updateWalkbox() == kInvalidBox) {
1108
1109 setActorToOriginalPosition();
1110 stopActorMoving();
1111 }
1112
1113 return;
1114 } else {
1115
1116 // Lets update our direction
1117 setActorToOriginalPosition();
1118 goto UpdateActorDirection;
1119 }
1120 }
1121
1122 if ((_moving & 0x0F) == 0) {
1123 // 2AE8
1124 byte A = actorWalkXCalculate();
1125
1126 // Will X movement reach destination
1127 if (A == 1) {
1128
1129 A = actorWalkYCalculate();
1130
1131 // Will Y movement also reach destination?
1132 if (A == 1) {
1133 _moving &= 0xF0;
1134 _moving |= A;
1135 } else {
1136 if (A == 4)
1137 stopActorMoving();
1138 }
1139
1140 return;
1141
1142 } else {
1143 // 2B0C: Moving X will put us in an invalid walkbox
1144 if (A == 3) {
1145 _moving &= 0xF0;
1146 _moving |= A;
1147
1148 if (_walkDirY) {
1149 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*1);
1150 } else {
1151 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*0, V12_Y_MULTIPLIER*-1);
1152 }
1153
1154 directionUpdate();
1155 animateActor(newDirToOldDir(_facing));
1156
1157 // FIXME: During the hands-free-demo in the library (room 5), Purple Tentacle gets stuck following Sandy due to the corner of the stairs,
1158 // This is due to distance, and walkbox gap/layout. This works fine with the original engine, because it 'brute forces'
1159 // another pixel move in the walk direction before giving up, allowing us to move enough pixels to hit the next walkbox.
1160 // Why this fails with the return is because script-10 is executing a 'walkActorToActor' every cycle, which restarts the movement process
1161 // As a work around, we implement the original engine behaviour only for Purple Tentacle in the Demo. Doing this for other actors
1162 // causes a skipping effect while transitioning walkboxes (the original has another bug in this situation, in which the actor just changes direction for 1 frame during this moment)
1163 if ((_vm->_game.features & GF_DEMO) && _number == 13)
1164 goto WalkX;
1165
1166 return;
1167
1168 } else {
1169 // 2B39: Moving X was ok, do we also move Y
1170 A = actorWalkYCalculate();
1171
1172 // Are we in a valid walkbox?
1173 if (A != 4)
1174 return;
1175
1176 // No, we need to change direction
1177 _moving &= 0xF0;
1178 _moving |= A;
1179
1180 if (_walkDirX) {
1181 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*1, V12_Y_MULTIPLIER*0);
1182 } else {
1183 _targetFacing = getAngleFromPos(V12_X_MULTIPLIER*-1, V12_Y_MULTIPLIER*0);
1184 }
1185
1186 directionUpdate();
1187 animateActor(newDirToOldDir(_facing));
1188
1189 return;
1190 }
1191 }
1192 }
1193 }
1194
walkActor()1195 void Actor_v2::walkActor() {
1196 Common::Point foundPath, tmp;
1197 int new_dir, next_box;
1198
1199 if (_moving & MF_TURN) {
1200 new_dir = updateActorDirection(false);
1201 if (_facing != new_dir) {
1202 setDirection(new_dir);
1203 } else {
1204 _moving &= ~MF_TURN;
1205 }
1206 return;
1207 }
1208
1209 if (!_moving)
1210 return;
1211
1212 if (_moving & MF_IN_LEG) {
1213 actorWalkStep();
1214 } else {
1215 if (_moving & MF_LAST_LEG) {
1216 _moving = MF_TURN;
1217 startAnimActor(_standFrame);
1218 if (_targetFacing != _walkdata.destdir)
1219 turnToDirection(_walkdata.destdir);
1220 } else {
1221 setBox(_walkdata.curbox);
1222 if (_walkbox == _walkdata.destbox) {
1223 foundPath = _walkdata.dest;
1224 _moving |= MF_LAST_LEG;
1225 } else {
1226 next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
1227 if (next_box < 0) {
1228 _moving |= MF_LAST_LEG;
1229 return;
1230 }
1231
1232 // Can't walk through locked boxes
1233 int flags = _vm->getBoxFlags(next_box);
1234 if ((flags & kBoxLocked) && !((flags & kBoxPlayerOnly) && !isPlayer())) {
1235 _moving |= MF_LAST_LEG;
1236 //_walkdata.destdir = -1;
1237 }
1238
1239 _walkdata.curbox = next_box;
1240
1241 getClosestPtOnBox(_vm->getBoxCoordinates(_walkdata.curbox), _pos.x, _pos.y, tmp.x, tmp.y);
1242 getClosestPtOnBox(_vm->getBoxCoordinates(_walkbox), tmp.x, tmp.y, foundPath.x, foundPath.y);
1243 }
1244 calcMovementFactor(foundPath);
1245 }
1246 }
1247 }
1248
walkActor()1249 void Actor_v3::walkActor() {
1250 Common::Point p2, p3; // Gate locations
1251 int new_dir, next_box;
1252
1253 if (!_moving)
1254 return;
1255
1256 if (!(_moving & MF_NEW_LEG)) {
1257 if (_moving & MF_IN_LEG && actorWalkStep())
1258 return;
1259
1260 if (_moving & MF_LAST_LEG) {
1261 _moving = 0;
1262 startAnimActor(_standFrame);
1263 if (_targetFacing != _walkdata.destdir)
1264 turnToDirection(_walkdata.destdir);
1265 return;
1266 }
1267
1268 if (_moving & MF_TURN) {
1269 new_dir = updateActorDirection(false);
1270 if (_facing != new_dir) {
1271 setDirection(new_dir);
1272 } else {
1273 // WORKAROUND for bug #4594 ("SCUMM: Zak McKracken - Zak keeps walk animation without moving")
1274 // This bug also happens with the original SCUMM3 (ZAK FM-TOWNS) interpreter (unlike SCUMM1/2
1275 // where the actors are apparently supposed to continue walking after being turned). We have
1276 // to stop the walking animation here...
1277 // This also fixes bug #4601 ("SCUMM: Zak McKracken (FM-Towns) - shopkeeper keeps walking"),
1278 // although that one does not happen with the original interpreter.
1279 if (_vm->_game.id == GID_ZAK && _moving == MF_TURN)
1280 startAnimActor(_standFrame);
1281 _moving = 0;
1282 }
1283 return;
1284 }
1285
1286 if (_walkdata.point3.x != 32000) {
1287 if (calcMovementFactor(_walkdata.point3)) {
1288 _walkdata.point3.x = 32000;
1289 return;
1290 }
1291 _walkdata.point3.x = 32000;
1292 }
1293
1294 setBox(_walkdata.curbox);
1295 _moving &= MF_IN_LEG;
1296 }
1297
1298 _moving &= ~MF_NEW_LEG;
1299 do {
1300 if (_walkbox == kInvalidBox) {
1301 setBox(_walkdata.destbox);
1302 _walkdata.curbox = _walkdata.destbox;
1303 break;
1304 }
1305
1306 if (_walkbox == _walkdata.destbox)
1307 break;
1308
1309 next_box = _vm->getNextBox(_walkbox, _walkdata.destbox);
1310 if (next_box < 0) {
1311 _moving |= MF_LAST_LEG;
1312 return;
1313 }
1314
1315 // Can't walk through locked boxes
1316 int flags = _vm->getBoxFlags(next_box);
1317 if ((flags & kBoxLocked) && !((flags & kBoxPlayerOnly) && !isPlayer())) {
1318 _moving |= MF_LAST_LEG;
1319 return;
1320 }
1321
1322 _walkdata.curbox = next_box;
1323
1324 findPathTowardsOld(_walkbox, next_box, _walkdata.destbox, p2, p3);
1325 if (p2.x == 32000 && p3.x == 32000) {
1326 break;
1327 }
1328
1329 if (p2.x != 32000) {
1330 if (calcMovementFactor(p2)) {
1331 _walkdata.point3 = p3;
1332 return;
1333 }
1334 }
1335 if (calcMovementFactor(p3))
1336 return;
1337
1338 setBox(_walkdata.curbox);
1339 } while (1);
1340
1341 _moving |= MF_LAST_LEG;
1342 calcMovementFactor(_walkdata.dest);
1343 }
1344
1345
1346 #pragma mark -
1347 #pragma mark --- Actor direction ---
1348 #pragma mark -
1349
1350
remapDirection(int dir,bool is_walking)1351 int Actor::remapDirection(int dir, bool is_walking) {
1352 int specdir;
1353 byte flags;
1354 byte mask;
1355 bool flipX;
1356 bool flipY;
1357
1358 // FIXME: It seems that at least in The Dig the original code does
1359 // check _ignoreBoxes here. However, it breaks some animations in Loom,
1360 // causing Bobbin to face towards the camera instead of away from it
1361 // in some places: After the tree has been destroyed by lightning, and
1362 // when entering the dark tunnels beyond the dragon's lair at the very
1363 // least. Possibly other places as well.
1364 //
1365 // The Dig also checks if the actor is in the current room, but that's
1366 // not necessary here because we never call the function unless the
1367 // actor is in the current room anyway.
1368
1369 if (!_ignoreBoxes || _vm->_game.id == GID_LOOM) {
1370 specdir = _vm->_extraBoxFlags[_walkbox];
1371 if (specdir) {
1372 if (specdir & 0x8000) {
1373 dir = specdir & 0x3FFF;
1374 } else {
1375 specdir = specdir & 0x3FFF;
1376 if (specdir - 90 < dir && dir < specdir + 90)
1377 dir = specdir;
1378 else
1379 dir = specdir + 180;
1380 }
1381 }
1382
1383 flags = _vm->getBoxFlags(_walkbox);
1384
1385 flipX = (_walkdata.deltaXFactor > 0);
1386 flipY = (_walkdata.deltaYFactor > 0);
1387
1388 // Check for X-Flip
1389 if ((flags & kBoxXFlip) || isInClass(kObjectClassXFlip)) {
1390 dir = 360 - dir;
1391 flipX = !flipX;
1392 }
1393 // Check for Y-Flip
1394 if ((flags & kBoxYFlip) || isInClass(kObjectClassYFlip)) {
1395 dir = 180 - dir;
1396 flipY = !flipY;
1397 }
1398
1399 switch (flags & 7) {
1400 case 1:
1401 if (_vm->_game.version >= 7) {
1402 if (dir < 180)
1403 return 90;
1404 else
1405 return 270;
1406 } else {
1407 if (is_walking) // Actor is walking
1408 return flipX ? 90 : 270;
1409 else // Actor is standing/turning
1410 return (dir == 90) ? 90 : 270;
1411 }
1412 case 2:
1413 if (_vm->_game.version >= 7) {
1414 if (dir > 90 && dir < 270)
1415 return 180;
1416 else
1417 return 0;
1418 } else {
1419 if (is_walking) // Actor is walking
1420 return flipY ? 180 : 0;
1421 else // Actor is standing/turning
1422 return (dir == 0) ? 0 : 180;
1423 }
1424 case 3:
1425 return 270;
1426 case 4:
1427 return 90;
1428 case 5:
1429 return 0;
1430 case 6:
1431 return 180;
1432 default:
1433 break;
1434 }
1435
1436 // MM v0 stores flags as a part of the mask
1437 if (_vm->_game.version == 0) {
1438 mask = _vm->getMaskFromBox(_walkbox);
1439 // face the wall if climbing/descending a ladder
1440 if ((mask & 0x8C) == 0x84)
1441 return 0;
1442 }
1443 }
1444 // OR 1024 in to signal direction interpolation should be done
1445 return normalizeAngle(dir) | 1024;
1446 }
1447
updateActorDirection(bool is_walking)1448 int Actor::updateActorDirection(bool is_walking) {
1449 int from;
1450 bool dirType = false;
1451 int dir;
1452 bool shouldInterpolate;
1453
1454 if ((_vm->_game.version == 6) && _ignoreTurns)
1455 return _facing;
1456
1457 dirType = (_vm->_game.version >= 7) ? _vm->_costumeLoader->hasManyDirections(_costume) : false;
1458
1459 from = toSimpleDir(dirType, _facing);
1460 dir = remapDirection(_targetFacing, is_walking);
1461
1462 if (_vm->_game.version >= 7)
1463 // Direction interpolation interfers with walk scripts in Dig; they perform
1464 // (much better) interpolation themselves.
1465 shouldInterpolate = false;
1466 else
1467 shouldInterpolate = (dir & 1024) ? true : false;
1468 dir &= 1023;
1469
1470 if (shouldInterpolate) {
1471 if (_vm->_game.version <= 3) {
1472 static const uint8 tbl[] = { 0, 2, 2, 3, 2, 1, 2, 3, 0, 1, 2, 1, 0, 1, 0, 3 };
1473 dir = oldDirToNewDir(tbl[newDirToOldDir(dir) | (newDirToOldDir(_facing) << 2)]);
1474 } else {
1475 int to = toSimpleDir(dirType, dir);
1476 int num = dirType ? 8 : 4;
1477
1478 // Turn left or right, depending on which is shorter.
1479 int diff = to - from;
1480 if (ABS(diff) > (num >> 1))
1481 diff = -diff;
1482
1483 if (diff > 0) {
1484 to = from + 1;
1485 } else if (diff < 0) {
1486 to = from - 1;
1487 }
1488
1489 dir = fromSimpleDir(dirType, (to + num) % num);
1490 }
1491 }
1492
1493 return dir;
1494 }
1495
setDirection(int direction)1496 void Actor::setDirection(int direction) {
1497 uint aMask;
1498 int i;
1499 uint16 vald;
1500
1501 // Do nothing if actor is already facing in the given direction
1502 if (_facing == direction)
1503 return;
1504
1505 // Normalize the angle
1506 _facing = normalizeAngle(direction);
1507
1508 // If there is no costume set for this actor, we are finished
1509 if (_costume == 0)
1510 return;
1511
1512 // Update the costume for the new direction (and mark the actor for redraw)
1513 aMask = 0x8000;
1514 for (i = 0; i < 16; i++, aMask >>= 1) {
1515 vald = _cost.frame[i];
1516 if (vald == 0xFFFF)
1517 continue;
1518 _vm->_costumeLoader->costumeDecodeData(this, vald, (_vm->_game.version <= 2) ? 0xFFFF : aMask);
1519 }
1520
1521 _needRedraw = true;
1522 }
1523
setDirection(int direction)1524 void Actor_v0::setDirection(int direction) {
1525 int dir = newDirToOldDir(direction);
1526 int res = 0;
1527
1528 switch (dir) {
1529 case 0:
1530 res = 4; // Left
1531 break;
1532
1533 case 1:
1534 res = 5; // Right
1535 break;
1536
1537 case 2:
1538 res = 6; // Face Camera
1539 break;
1540
1541 default:
1542 res = 7; // Face Away
1543 break;
1544 }
1545
1546 _animFrameRepeat = -1;
1547 animateActor(res);
1548 }
1549
faceToObject(int obj)1550 void Actor::faceToObject(int obj) {
1551 int x2, y2, dir;
1552
1553 if (!isInCurrentRoom())
1554 return;
1555
1556 if (_vm->getObjectOrActorXY(obj, x2, y2) == -1)
1557 return;
1558
1559 dir = (x2 > _pos.x) ? 90 : 270;
1560 turnToDirection(dir);
1561 }
1562
turnToDirection(int newdir)1563 void Actor::turnToDirection(int newdir) {
1564 if (newdir == -1 || _ignoreTurns)
1565 return;
1566
1567 if (_vm->_game.version <= 6) {
1568 _targetFacing = newdir;
1569
1570 if (_vm->_game.version == 0)
1571 setDirection(newdir);
1572 else if (_vm->_game.version <= 2)
1573 _moving |= MF_TURN;
1574 else
1575 _moving = MF_TURN;
1576
1577 } else {
1578 _moving &= ~MF_TURN;
1579 if (newdir != _facing) {
1580 _moving |= MF_TURN;
1581 _targetFacing = newdir;
1582 }
1583 }
1584 }
1585
1586
1587 #pragma mark -
1588 #pragma mark --- Actor position ---
1589 #pragma mark -
1590
1591
putActors()1592 void ScummEngine::putActors() {
1593 Actor *a;
1594 int i;
1595
1596 for (i = 1; i < _numActors; i++) {
1597 a = _actors[i];
1598 if (a && a->isInCurrentRoom())
1599 a->putActor();
1600 }
1601 }
1602
putActor(int dstX,int dstY,int newRoom)1603 void Actor::putActor(int dstX, int dstY, int newRoom) {
1604 if (_visible && _vm->_currentRoom != newRoom && _vm->getTalkingActor() == _number) {
1605 _vm->stopTalk();
1606 }
1607
1608 // WORKAROUND: The green transparency of the tank in the Hall of Oddities
1609 // is positioned one pixel too far to the left. This appears to be a bug
1610 // in the original game as well.
1611 if (_vm->_game.id == GID_SAMNMAX && newRoom == 16 && _number == 5 && dstX == 235 && dstY == 236)
1612 dstX++;
1613
1614 _pos.x = dstX;
1615 _pos.y = dstY;
1616 _room = newRoom;
1617 _needRedraw = true;
1618
1619 if (_vm->VAR(_vm->VAR_EGO) == _number) {
1620 _vm->_egoPositioned = true;
1621 }
1622
1623 if (_visible) {
1624 if (isInCurrentRoom()) {
1625 if (_moving) {
1626 stopActorMoving();
1627 startAnimActor(_standFrame);
1628 }
1629 adjustActorPos();
1630 } else {
1631 #ifdef ENABLE_HE
1632 if (_vm->_game.heversion >= 71)
1633 ((ScummEngine_v71he *)_vm)->queueAuxBlock((ActorHE *)this);
1634 #endif
1635 hideActor();
1636 }
1637 } else {
1638 if (isInCurrentRoom())
1639 showActor();
1640 }
1641
1642 if (_vm->_game.version == 0) {
1643
1644 ((Actor_v0 *)this)->_newWalkBoxEntered = false;
1645 ((Actor_v0 *)this)->_CurrentWalkTo = _pos;
1646 ((Actor_v0 *)this)->_NewWalkTo = _pos;
1647 }
1648
1649 // V0-V1 Maniac always sets the actor to face the camera upon entering a room
1650 if (_vm->_game.id == GID_MANIAC && _vm->_game.version <= 1 && _vm->_game.platform != Common::kPlatformNES)
1651 setDirection(oldDirToNewDir(2));
1652 }
1653
inBoxQuickReject(const BoxCoords & box,int x,int y,int threshold)1654 static bool inBoxQuickReject(const BoxCoords &box, int x, int y, int threshold) {
1655 int t;
1656
1657 t = x - threshold;
1658 if (t > box.ul.x && t > box.ur.x && t > box.lr.x && t > box.ll.x)
1659 return true;
1660
1661 t = x + threshold;
1662 if (t < box.ul.x && t < box.ur.x && t < box.lr.x && t < box.ll.x)
1663 return true;
1664
1665 t = y - threshold;
1666 if (t > box.ul.y && t > box.ur.y && t > box.lr.y && t > box.ll.y)
1667 return true;
1668
1669 t = y + threshold;
1670 if (t < box.ul.y && t < box.ur.y && t < box.lr.y && t < box.ll.y)
1671 return true;
1672
1673 return false;
1674 }
1675
checkXYInBoxBounds(int boxnum,int x,int y,int & destX,int & destY)1676 static int checkXYInBoxBounds(int boxnum, int x, int y, int &destX, int &destY) {
1677 BoxCoords box = g_scumm->getBoxCoordinates(boxnum);
1678 int xmin, xmax;
1679
1680 // We are supposed to determine the point (destX,destY) contained in
1681 // the given box which is closest to the point (x,y), and then return
1682 // some kind of "distance" between the two points.
1683
1684 // First, we determine destY and a range (xmin to xmax) in which destX
1685 // is contained.
1686 if (y < box.ul.y) {
1687 // Point is above the box
1688 destY = box.ul.y;
1689 xmin = box.ul.x;
1690 xmax = box.ur.x;
1691 } else if (y >= box.ll.y) {
1692 // Point is below the box
1693 destY = box.ll.y;
1694 xmin = box.ll.x;
1695 xmax = box.lr.x;
1696 } else if ((x >= box.ul.x) && (x >= box.ll.x) && (x < box.ur.x) && (x < box.lr.x)) {
1697 // Point is strictly inside the box
1698 destX = x;
1699 destY = y;
1700 xmin = xmax = x;
1701 } else {
1702 // Point is to the left or right of the box,
1703 // so the y coordinate remains unchanged
1704 destY = y;
1705 int ul = box.ul.x;
1706 int ll = box.ll.x;
1707 int ur = box.ur.x;
1708 int lr = box.lr.x;
1709 int top = box.ul.y;
1710 int bottom = box.ll.y;
1711 int cury;
1712
1713 // Perform a binary search to determine the x coordinate.
1714 // Note: It would be possible to compute this value in a
1715 // single step simply by calculating the slope of the left
1716 // resp. right side and using that to find the correct
1717 // result. However, the original engine did use the search
1718 // approach, so we do that, too.
1719 do {
1720 xmin = (ul + ll) / 2;
1721 xmax = (ur + lr) / 2;
1722 cury = (top + bottom) / 2;
1723
1724 if (cury < y) {
1725 top = cury;
1726 ul = xmin;
1727 ur = xmax;
1728 } else if (cury > y) {
1729 bottom = cury;
1730 ll = xmin;
1731 lr = xmax;
1732 }
1733 } while (cury != y);
1734 }
1735
1736 // Now that we have limited the value of destX to a fixed
1737 // interval, it's a trivial matter to finally determine it.
1738 if (x < xmin) {
1739 destX = xmin;
1740 } else if (x > xmax) {
1741 destX = xmax;
1742 } else {
1743 destX = x;
1744 }
1745
1746 // Compute the distance of the points. We measure the
1747 // distance with a granularity of 8x8 blocks only (hence
1748 // yDist must be divided by 4, as we are using 8x2 pixels
1749 // blocks for actor coordinates).
1750 int xDist = ABS(x - destX);
1751 int yDist = ABS(y - destY) / 4;
1752 int dist;
1753
1754 if (g_scumm->_game.version == 0)
1755 xDist *= 2;
1756
1757 if (xDist < yDist)
1758 dist = (xDist >> 1) + yDist;
1759 else
1760 dist = (yDist >> 1) + xDist;
1761
1762 return dist;
1763 }
1764
adjustPosInBorderWalkbox(AdjustBoxResult box)1765 AdjustBoxResult Actor_v0::adjustPosInBorderWalkbox(AdjustBoxResult box) {
1766 AdjustBoxResult Result = box;
1767 BoxCoords BoxCoord = _vm->getBoxCoordinates(box.box);
1768
1769 byte boxMask = _vm->getMaskFromBox(box.box);
1770 if (!(boxMask & 0x80))
1771 return Result;
1772
1773 int16 A;
1774 boxMask &= 0x7C;
1775 if (boxMask == 0x0C)
1776 A = 2;
1777 else {
1778 if (boxMask != 0x08)
1779 return Result;
1780
1781 A = 1;
1782 }
1783
1784 // 1BC6
1785 byte Modifier = box.y - BoxCoord.ul.y;
1786 assert(Modifier < 0x16);
1787
1788 if (A == 1) {
1789 // 1BCF
1790 A = BoxCoord.ur.x - v0WalkboxSlantedModifier[ Modifier ];
1791 if (A < box.x)
1792 return box;
1793
1794 if (A <= 0xA0)
1795 A = 0;
1796
1797 Result.x = A;
1798 } else {
1799 // 1BED
1800 A = BoxCoord.ul.x + v0WalkboxSlantedModifier[ Modifier ];
1801
1802 if (A < box.x || A == box.x)
1803 Result.x = A;
1804 }
1805
1806 return Result;
1807 }
1808
adjustXYToBeInBox(int dstX,int dstY)1809 AdjustBoxResult Actor_v0::adjustXYToBeInBox(int dstX, int dstY) {
1810 AdjustBoxResult Result = Actor_v2::adjustXYToBeInBox(dstX, dstY);
1811
1812 if (Result.box == kInvalidBox)
1813 return Result;
1814
1815 return adjustPosInBorderWalkbox(Result);
1816 }
1817
adjustXYToBeInBox(const int dstX,const int dstY)1818 AdjustBoxResult Actor_v2::adjustXYToBeInBox(const int dstX, const int dstY) {
1819 AdjustBoxResult abr;
1820
1821 abr.x = dstX;
1822 abr.y = dstY;
1823 abr.box = kInvalidBox;
1824
1825 int numBoxes = _vm->getNumBoxes() - 1;
1826 int bestDist = 0xFF;
1827 for (int i = 0; i <= numBoxes; i++) {
1828 // MM v0 prioritizes lower boxes, other engines higher boxes
1829 int box = (_vm->_game.version == 0 ? i : numBoxes - i);
1830 int foundX, foundY;
1831 int flags = _vm->getBoxFlags(box);
1832 if ((flags & kBoxInvisible) && !((flags & kBoxPlayerOnly) && !isPlayer()))
1833 continue;
1834 int dist = checkXYInBoxBounds(box, dstX, dstY, foundX, foundY); // also merged with getClosestPtOnBox
1835 if (dist == 0) {
1836 abr.x = foundX;
1837 abr.y = foundY;
1838 abr.box = box;
1839
1840 break;
1841 }
1842 if (dist < bestDist) {
1843 bestDist = dist;
1844 abr.x = foundX;
1845 abr.y = foundY;
1846 abr.box = box;
1847 }
1848 }
1849
1850 return abr;
1851 }
1852
adjustXYToBeInBox(int dstX,int dstY)1853 AdjustBoxResult Actor::adjustXYToBeInBox(int dstX, int dstY) {
1854 const uint thresholdTable[] = { 30, 80, 0 };
1855 AdjustBoxResult abr;
1856 int16 tmpX, tmpY;
1857 int tmpDist, bestDist, threshold, numBoxes;
1858 byte flags, bestBox;
1859 int box;
1860 const int firstValidBox = (_vm->_game.features & GF_SMALL_HEADER) ? 0 : 1;
1861
1862 abr.x = dstX;
1863 abr.y = dstY;
1864 abr.box = kInvalidBox;
1865
1866 if (_ignoreBoxes)
1867 return abr;
1868
1869 for (int tIdx = 0; tIdx < ARRAYSIZE(thresholdTable); tIdx++) {
1870 threshold = thresholdTable[tIdx];
1871
1872 numBoxes = _vm->getNumBoxes() - 1;
1873 if (numBoxes < firstValidBox)
1874 return abr;
1875
1876 bestDist = (_vm->_game.version >= 7) ? 0x7FFFFFFF : 0xFFFF;
1877 bestBox = kInvalidBox;
1878
1879 // We iterate (backwards) over all boxes, searching the one closest
1880 // to the desired coordinates.
1881 for (box = numBoxes; box >= firstValidBox; box--) {
1882 flags = _vm->getBoxFlags(box);
1883
1884 // Skip over invisible boxes
1885 if ((flags & kBoxInvisible) && !((flags & kBoxPlayerOnly) && !isPlayer()))
1886 continue;
1887
1888 // For increased performance, we perform a quick test if
1889 // the coordinates can even be within a distance of 'threshold'
1890 // pixels of the box.
1891 if (threshold > 0 && inBoxQuickReject(_vm->getBoxCoordinates(box), dstX, dstY, threshold))
1892 continue;
1893
1894 // Check if the point is contained in the box. If it is,
1895 // we don't have to search anymore.
1896 if (_vm->checkXYInBoxBounds(box, dstX, dstY)) {
1897 abr.x = dstX;
1898 abr.y = dstY;
1899 abr.box = box;
1900 return abr;
1901 }
1902
1903 // Find the point in the box which is closest to our point.
1904 tmpDist = getClosestPtOnBox(_vm->getBoxCoordinates(box), dstX, dstY, tmpX, tmpY);
1905
1906 // Check if the box is closer than the previous boxes.
1907 if (tmpDist < bestDist) {
1908 abr.x = tmpX;
1909 abr.y = tmpY;
1910
1911 if (tmpDist == 0) {
1912 abr.box = box;
1913 return abr;
1914 }
1915 bestDist = tmpDist;
1916 bestBox = box;
1917 }
1918 }
1919
1920 // If the closest ('best') box we found is within the threshold, or if
1921 // we are on the last run (i.e. threshold == 0), return that box.
1922 if (threshold == 0 || threshold * threshold >= bestDist) {
1923 abr.box = bestBox;
1924 return abr;
1925 }
1926 }
1927
1928 return abr;
1929 }
1930
adjustActorPos()1931 void Actor::adjustActorPos() {
1932 AdjustBoxResult abr;
1933
1934 abr = adjustXYToBeInBox(_pos.x, _pos.y);
1935
1936 _pos.x = abr.x;
1937 _pos.y = abr.y;
1938 _walkdata.destbox = abr.box;
1939
1940 setBox(abr.box);
1941
1942 _walkdata.dest.x = -1;
1943
1944 stopActorMoving();
1945 _cost.soundCounter = 0;
1946 _cost.soundPos = 0;
1947
1948 if (_walkbox != kInvalidBox) {
1949 byte flags = _vm->getBoxFlags(_walkbox);
1950 if (flags & 7) {
1951 turnToDirection(_facing);
1952 }
1953 }
1954 }
1955
getActorFromPos(int x,int y)1956 int ScummEngine::getActorFromPos(int x, int y) {
1957 int i;
1958
1959 if (!testGfxAnyUsageBits(x / 8))
1960 return 0;
1961
1962 for (i = 1; i < _numActors; i++) {
1963 if (testGfxUsageBit(x / 8, i) && !getClass(i, kObjectClassUntouchable)
1964 && y >= _actors[i]->_top && y <= _actors[i]->_bottom) {
1965 if (_game.version > 2 || i != VAR(VAR_EGO))
1966 return i;
1967 }
1968 }
1969
1970 return 0;
1971 }
1972
getActorFromPos(int x,int y)1973 int ScummEngine_v70he::getActorFromPos(int x, int y) {
1974 int curActor, i;
1975
1976 if (!testGfxAnyUsageBits(x / 8))
1977 return 0;
1978
1979 curActor = 0;
1980 for (i = 1; i < _numActors; i++) {
1981 if (testGfxUsageBit(x / 8, i) && !getClass(i, kObjectClassUntouchable)
1982 && y >= _actors[i]->_top && y <= _actors[i]->_bottom
1983 && (_actors[i]->getPos().y > _actors[curActor]->getPos().y || curActor == 0))
1984 curActor = i;
1985 }
1986
1987 return curActor;
1988 }
1989
1990
1991 #pragma mark -
1992 #pragma mark --- TODO ---
1993 #pragma mark -
1994
1995
hideActor()1996 void Actor::hideActor() {
1997 if (!_visible)
1998 return;
1999
2000 if (_moving) {
2001 stopActorMoving();
2002 startAnimActor(_standFrame);
2003 }
2004 _visible = false;
2005 _cost.soundCounter = 0;
2006 _cost.soundPos = 0;
2007 _needRedraw = false;
2008 _needBgReset = true;
2009 }
2010
hideActor()2011 void ActorHE::hideActor() {
2012 Actor::hideActor();
2013 _auxBlock.reset();
2014 }
2015
showActor()2016 void Actor::showActor() {
2017 if (_vm->_currentRoom == 0 || _visible)
2018 return;
2019
2020 adjustActorPos();
2021
2022 _vm->ensureResourceLoaded(rtCostume, _costume);
2023
2024 if (_vm->_game.version == 0) {
2025 Actor_v0 *a = ((Actor_v0 *)this);
2026
2027 a->_costCommand = a->_costCommandNew = 0xFF;
2028 _walkdata.dest = a->_CurrentWalkTo;
2029
2030 for (int i = 0; i < 8; ++i) {
2031 a->_limbFrameRepeat[i] = 0;
2032 a->_limbFrameRepeatNew[i] = 0;
2033 }
2034
2035 _cost.reset();
2036
2037 a->_animFrameRepeat = 1;
2038 a->_speaking = 0;
2039
2040 startAnimActor(_standFrame);
2041 _visible = true;
2042 return;
2043
2044 } else if (_vm->_game.version <= 2) {
2045 _cost.reset();
2046 startAnimActor(_standFrame);
2047 startAnimActor(_initFrame);
2048 startAnimActor(_talkStopFrame);
2049 } else {
2050 if (_costumeNeedsInit) {
2051 startAnimActor(_initFrame);
2052 _costumeNeedsInit = false;
2053 }
2054 }
2055
2056 stopActorMoving();
2057 _visible = true;
2058 _needRedraw = true;
2059 }
2060
showActors()2061 void ScummEngine::showActors() {
2062 int i;
2063
2064 for (i = 1; i < _numActors; i++) {
2065 if (_actors[i]->isInCurrentRoom())
2066 _actors[i]->showActor();
2067 }
2068 }
2069
2070 /* Used in Scumm v5 only. Play sounds associated with actors */
playActorSounds()2071 void ScummEngine::playActorSounds() {
2072 int i, j;
2073 int sound;
2074
2075 for (i = 1; i < _numActors; i++) {
2076 if (_actors[i]->_cost.soundCounter && _actors[i]->isInCurrentRoom()) {
2077 _currentScript = 0xFF;
2078 if (_game.version == 0) {
2079 sound = _actors[i]->_sound[0] & 0x3F;
2080 } else {
2081 sound = _actors[i]->_sound[0];
2082 }
2083 // fast mode will flood the queue with walk sounds
2084 if (!_fastMode) {
2085 _sound->addSoundToQueue(sound);
2086 }
2087 for (j = 1; j < _numActors; j++) {
2088 _actors[j]->_cost.soundCounter = 0;
2089 }
2090 return;
2091 }
2092 }
2093 }
2094
isValidActor(int id) const2095 bool ScummEngine::isValidActor(int id) const {
2096 return id >= 0 && id < _numActors && _actors[id]->_number == id;
2097 }
2098
derefActor(int id,const char * errmsg) const2099 Actor *ScummEngine::derefActor(int id, const char *errmsg) const {
2100 if (id == 0)
2101 debugC(DEBUG_ACTORS, "derefActor(0, \"%s\") in script %d, opcode 0x%x",
2102 errmsg, vm.slot[_currentScript].number, _opcode);
2103
2104 if (!isValidActor(id)) {
2105 if (errmsg)
2106 error("Invalid actor %d in %s", id, errmsg);
2107 else
2108 error("Invalid actor %d", id);
2109 }
2110 return _actors[id];
2111 }
2112
derefActorSafe(int id,const char * errmsg) const2113 Actor *ScummEngine::derefActorSafe(int id, const char *errmsg) const {
2114 if (id == 0)
2115 debugC(DEBUG_ACTORS, "derefActorSafe(0, \"%s\") in script %d, opcode 0x%x",
2116 errmsg, vm.slot[_currentScript].number, _opcode);
2117
2118 if (!isValidActor(id)) {
2119 debugC(DEBUG_ACTORS, "Invalid actor %d in %s (script %d, opcode 0x%x)",
2120 id, errmsg, vm.slot[_currentScript].number, _opcode);
2121 return NULL;
2122 }
2123 return _actors[id];
2124 }
2125
2126
2127 #pragma mark -
2128 #pragma mark --- Actor drawing ---
2129 #pragma mark -
2130
2131
processActors()2132 void ScummEngine::processActors() {
2133 int numactors = 0;
2134
2135 // Make a list of all actors in this room
2136 for (int i = 1; i < _numActors; i++) {
2137 if (_game.version == 8 && _actors[i]->_layer < 0)
2138 continue;
2139 if (_actors[i]->isInCurrentRoom()) {
2140 _sortedActors[numactors++] = _actors[i];
2141 }
2142 }
2143 if (!numactors) {
2144 return;
2145 }
2146
2147 // Sort actors by position before drawing them (to ensure that actors
2148 // in front are drawn after those "behind" them).
2149 //
2150 // Note: This algorithm works exactly the way the original engine did.
2151 // Please resist any urge to 'optimize' this. Many of the games rely on
2152 // the quirks of this particular sorting algorithm, and since we are
2153 // dealing with far less than 100 objects being sorted here, any
2154 // 'optimization' wouldn't yield a useful gain anyway.
2155 //
2156 // In particular, changing this loop caused a number of bugs in the
2157 // past, including bugs #912, #1055, and #1864.
2158 //
2159 // Note that Sam & Max uses a stable sorting method. Older games don't
2160 // and, according to cyx, neither do newer ones. At least not FT and
2161 // COMI. See bug #2064 for more details.
2162
2163 if (_game.id == GID_SAMNMAX) {
2164 for (int j = 0; j < numactors; ++j) {
2165 for (int i = 0; i < numactors; ++i) {
2166 int sc_actor1 = _sortedActors[j]->getPos().y;
2167 int sc_actor2 = _sortedActors[i]->getPos().y;
2168 if (sc_actor1 == sc_actor2) {
2169 sc_actor1 += _sortedActors[j]->_number;
2170 sc_actor2 += _sortedActors[i]->_number;
2171 }
2172 if (sc_actor1 < sc_actor2) {
2173 SWAP(_sortedActors[i], _sortedActors[j]);
2174 }
2175 }
2176 }
2177 } else if (_game.heversion >= 90) {
2178 for (int j = 0; j < numactors; ++j) {
2179 for (int i = 0; i < numactors; ++i) {
2180 int sc_actor1 = _sortedActors[j]->_layer;
2181 int sc_actor2 = _sortedActors[i]->_layer;
2182 if (sc_actor1 < sc_actor2) {
2183 SWAP(_sortedActors[i], _sortedActors[j]);
2184 } else if (sc_actor1 == sc_actor2) {
2185 sc_actor1 = _sortedActors[j]->getPos().y;
2186 sc_actor2 = _sortedActors[i]->getPos().y;
2187 if (sc_actor1 < sc_actor2) {
2188 SWAP(_sortedActors[i], _sortedActors[j]);
2189 }
2190 }
2191 }
2192 }
2193 } else if (_game.version == 0) {
2194 for (int j = 0; j < numactors; ++j) {
2195 for (int i = 0; i < numactors; ++i) {
2196 // Note: the plant is handled different in v0, the y value is not used.
2197 // In v1/2 this is done by the actor's elevation instead.
2198 int sc_actor1 = (_sortedActors[j]->_number == 19 ? 0 : _sortedActors[j]->getPos().y);
2199 int sc_actor2 = (_sortedActors[i]->_number == 19 ? 0 : _sortedActors[i]->getPos().y);
2200 if (sc_actor1 < sc_actor2) {
2201 SWAP(_sortedActors[i], _sortedActors[j]);
2202 }
2203 }
2204 }
2205 } else {
2206 for (int j = 0; j < numactors; ++j) {
2207 for (int i = 0; i < numactors; ++i) {
2208 int sc_actor1 = _sortedActors[j]->getPos().y - _sortedActors[j]->_layer * 2000;
2209 int sc_actor2 = _sortedActors[i]->getPos().y - _sortedActors[i]->_layer * 2000;
2210 if (sc_actor1 < sc_actor2) {
2211 SWAP(_sortedActors[i], _sortedActors[j]);
2212 }
2213 }
2214 }
2215 }
2216
2217 // Finally draw the now sorted actors
2218 Actor** end = _sortedActors + numactors;
2219 for (Actor** ac = _sortedActors; ac != end; ++ac) {
2220 Actor* a = *ac;
2221
2222 if (_game.version == 0) {
2223 // 0x057B
2224 Actor_v0 *a0 = (Actor_v0*) a;
2225 if (a0->_speaking & 1) {
2226 a0->_speaking ^= 0xFE;
2227 ++_V0Delay._actorRedrawCount;
2228 }
2229 // 0x22B5
2230 if (a0->_miscflags & kActorMiscFlagHide)
2231 continue;
2232
2233 // Sound
2234 if (a0->_moving != 2 && _currentRoom != 1 && _currentRoom != 44) {
2235 if (a0->_cost.soundPos == 0)
2236 a0->_cost.soundCounter++;
2237
2238 // Is this the correct location?
2239 // 0x073C
2240 if (a0->_sound[0] & 0x3F)
2241 a0->_cost.soundPos = (a0->_cost.soundPos + 1) % 3;
2242 }
2243 }
2244 // Draw and animate the actors, except those w/o a costume.
2245 // Note: We could 'optimize' this a little bit by only putting
2246 // actors with a costume into the _sortedActors array in the
2247 // first place. However, that would mess up the sorting, and
2248 // would hence cause regressions. See also the other big
2249 // comment further up in this method for some details.
2250 if (a->_costume) {
2251
2252 // Unfortunately in V0, the 'animateCostume' call happens right after the call to 'walkActor' (which is before drawing the actor)...
2253 // doing it the other way with V0, causes animation glitches (when beginnning to walk, as the costume hasnt been updated).
2254 // Updating the costume directly after 'walkActor' and again, after drawing... causes frame skipping
2255 if (_game.version == 0) {
2256 a->animateCostume();
2257 a->drawActorCostume();
2258 } else {
2259 a->drawActorCostume();
2260 a->animateCostume();
2261 }
2262 }
2263 }
2264 }
2265
processActors()2266 void ScummEngine_v6::processActors() {
2267 ScummEngine::processActors();
2268
2269 if (_game.features & GF_NEW_COSTUMES)
2270 akos_processQueue();
2271 }
2272
2273 #ifdef ENABLE_HE
processActors()2274 void ScummEngine_v71he::processActors() {
2275 preProcessAuxQueue();
2276
2277 if (!_skipProcessActors)
2278 ScummEngine_v6::processActors();
2279
2280 _fullRedraw = false;
2281
2282 postProcessAuxQueue();
2283 }
2284
processActors()2285 void ScummEngine_v90he::processActors() {
2286 preProcessAuxQueue();
2287
2288 _sprite->setRedrawFlags(false);
2289 _sprite->processImages(true);
2290
2291 if (!_skipProcessActors)
2292 ScummEngine_v6::processActors();
2293
2294 _fullRedraw = false;
2295
2296 postProcessAuxQueue();
2297
2298 _sprite->setRedrawFlags(true);
2299 _sprite->processImages(false);
2300 }
2301 #endif
2302
2303 // Used in Scumm v8, to allow the verb coin to be drawn over the inventory
2304 // chest. I'm assuming that draw order won't matter here.
processUpperActors()2305 void ScummEngine::processUpperActors() {
2306 int i;
2307
2308 for (i = 1; i < _numActors; i++) {
2309 if (_actors[i]->isInCurrentRoom() && _actors[i]->_costume && _actors[i]->_layer < 0) {
2310 _actors[i]->drawActorCostume();
2311 _actors[i]->animateCostume();
2312 }
2313 }
2314 }
2315
drawActorCostume(bool hitTestMode)2316 void Actor::drawActorCostume(bool hitTestMode) {
2317 if (_costume == 0)
2318 return;
2319
2320 if (!hitTestMode) {
2321 if (!_needRedraw)
2322 return;
2323
2324 _needRedraw = false;
2325 }
2326
2327 setupActorScale();
2328
2329 BaseCostumeRenderer *bcr = _vm->_costumeRenderer;
2330 prepareDrawActorCostume(bcr);
2331
2332 // If the actor is partially hidden, redraw it next frame.
2333 if (bcr->drawCostume(_vm->_virtscr[kMainVirtScreen], _vm->_gdi->_numStrips, this, _drawToBackBuf) & 1) {
2334 _needRedraw = (_vm->_game.version <= 6);
2335 }
2336
2337 if (!hitTestMode) {
2338 // Record the vertical extent of the drawn actor
2339 _top = bcr->_draw_top;
2340 _bottom = bcr->_draw_bottom;
2341 }
2342 }
2343
2344
prepareDrawActorCostume(BaseCostumeRenderer * bcr)2345 void Actor::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
2346
2347 bcr->_actorID = _number;
2348 bcr->_actorX = _pos.x - _vm->_virtscr[kMainVirtScreen].xstart;
2349 bcr->_actorY = _pos.y - _elevation;
2350
2351 if (_vm->_game.version == 4 && (_boxscale & 0x8000)) {
2352 bcr->_scaleX = bcr->_scaleY = _vm->getScaleFromSlot((_boxscale & 0x7fff) + 1, _pos.x, _pos.y);
2353 } else {
2354 bcr->_scaleX = _scalex;
2355 bcr->_scaleY = _scaley;
2356 }
2357
2358 bcr->_shadow_mode = _shadowMode;
2359 if (_vm->_game.version >= 5 && _vm->_game.heversion == 0) {
2360 bcr->_shadow_table = _vm->_shadowPalette;
2361 }
2362
2363 bcr->setCostume(_costume, (_vm->_game.heversion == 0) ? 0 : _heXmapNum);
2364 bcr->setPalette(_palette);
2365 bcr->setFacing(this);
2366
2367 if (_vm->_game.version >= 7) {
2368
2369 bcr->_zbuf = _forceClip;
2370 if (bcr->_zbuf == 100) {
2371 bcr->_zbuf = _vm->getMaskFromBox(_walkbox);
2372 if (bcr->_zbuf > _vm->_gdi->_numZBuffer-1)
2373 bcr->_zbuf = _vm->_gdi->_numZBuffer-1;
2374 }
2375
2376 } else {
2377 if (_forceClip)
2378 bcr->_zbuf = _forceClip;
2379 else if (isInClass(kObjectClassNeverClip))
2380 bcr->_zbuf = 0;
2381 else {
2382 bcr->_zbuf = _vm->getMaskFromBox(_walkbox);
2383 if (_vm->_game.version == 0)
2384 bcr->_zbuf &= 0x03;
2385 if (bcr->_zbuf > _vm->_gdi->_numZBuffer-1)
2386 bcr->_zbuf = _vm->_gdi->_numZBuffer-1;
2387 }
2388
2389 }
2390
2391 bcr->_draw_top = 0x7fffffff;
2392 bcr->_draw_bottom = 0;
2393 }
2394
prepareDrawActorCostume(BaseCostumeRenderer * bcr)2395 void ActorHE::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
2396 // HE palette number must be set, before setting the costume palette
2397 bcr->_paletteNum = _hePaletteNum;
2398
2399 Actor::prepareDrawActorCostume(bcr);
2400
2401 bcr->_actorX += _heOffsX;
2402 bcr->_actorY += _heOffsY;
2403
2404 bcr->_clipOverride = _clipOverride;
2405
2406 if (_vm->_game.heversion == 70) {
2407 bcr->_shadow_table = _vm->_HEV7ActorPalette;
2408 }
2409
2410 bcr->_skipLimbs = (_heSkipLimbs != 0);
2411
2412 if (_vm->_game.heversion >= 80 && _heNoTalkAnimation == 0 && _animProgress == 0) {
2413 if (_vm->getTalkingActor() == _number && !_vm->_string[0].no_talk_anim) {
2414 int talkState = 0;
2415
2416 if (((SoundHE *)_vm->_sound)->isSoundCodeUsed(1))
2417 talkState = ((SoundHE *)_vm->_sound)->getSoundVar(1, 19);
2418 if (talkState == 0)
2419 talkState = _vm->_rnd.getRandomNumberRng(1, 10);
2420
2421 assertRange(1, talkState, 13, "Talk state");
2422 setTalkCondition(talkState);
2423 } else {
2424 setTalkCondition(1);
2425 }
2426 }
2427 _heNoTalkAnimation = 0;
2428 }
2429
prepareDrawActorCostume(BaseCostumeRenderer * bcr)2430 void Actor_v2::prepareDrawActorCostume(BaseCostumeRenderer *bcr) {
2431 Actor::prepareDrawActorCostume(bcr);
2432
2433 bcr->_actorX = _pos.x;
2434 bcr->_actorY = _pos.y - _elevation;
2435
2436 if (_vm->_game.version <= 2) {
2437 bcr->_actorX *= V12_X_MULTIPLIER;
2438 bcr->_actorY *= V12_Y_MULTIPLIER;
2439 }
2440 bcr->_actorX -= _vm->_virtscr[kMainVirtScreen].xstart;
2441
2442 if (_vm->_game.platform == Common::kPlatformNES) {
2443 // In the NES version, when the actor is facing right,
2444 // we need to shift it 8 pixels to the left
2445 if (_facing == 90)
2446 bcr->_actorX -= 8;
2447 } else if (_vm->_game.version == 0) {
2448 bcr->_actorX += 12;
2449 } else if (_vm->_game.version <= 2) {
2450 // HACK: We have to adjust the x position by one strip (8 pixels) in
2451 // V2 games. However, it is not quite clear to me why. And to fully
2452 // match the original, it seems we have to offset by 2 strips if the
2453 // actor is facing left (270 degree).
2454 // V1 games are once again slightly different, here we only have
2455 // to adjust the 270 degree case...
2456 if (_facing == 270)
2457 bcr->_actorX += 16;
2458 else if (_vm->_game.version == 2)
2459 bcr->_actorX += 8;
2460 }
2461 }
2462
2463 #ifdef ENABLE_SCUMM_7_8
actorHitTest(int x,int y)2464 bool Actor::actorHitTest(int x, int y) {
2465 AkosRenderer *ar = (AkosRenderer *)_vm->_costumeRenderer;
2466
2467 ar->_actorHitX = x;
2468 ar->_actorHitY = y;
2469 ar->_actorHitMode = true;
2470 ar->_actorHitResult = false;
2471
2472 drawActorCostume(true);
2473
2474 ar->_actorHitMode = false;
2475
2476 return ar->_actorHitResult;
2477 }
2478 #endif
2479
startAnimActor(int f)2480 void Actor::startAnimActor(int f) {
2481 if (_vm->_game.version >= 7 && !((_vm->_game.id == GID_FT) && (_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformDOS))) {
2482 switch (f) {
2483 case 1001:
2484 f = _initFrame;
2485 break;
2486 case 1002:
2487 f = _walkFrame;
2488 break;
2489 case 1003:
2490 f = _standFrame;
2491 break;
2492 case 1004:
2493 f = _talkStartFrame;
2494 break;
2495 case 1005:
2496 f = _talkStopFrame;
2497 break;
2498 default:
2499 break;
2500 }
2501
2502 if (_costume != 0) {
2503 _animProgress = 0;
2504 _needRedraw = true;
2505 if (f == _initFrame)
2506 _cost.reset();
2507 _vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1);
2508 _frame = f;
2509 }
2510 } else {
2511 switch (f) {
2512 case 0x38:
2513 f = _initFrame;
2514 break;
2515 case 0x39:
2516 f = _walkFrame;
2517 break;
2518 case 0x3A:
2519 f = _standFrame;
2520 break;
2521 case 0x3B:
2522 f = _talkStartFrame;
2523 break;
2524 case 0x3C:
2525 f = _talkStopFrame;
2526 break;
2527 default:
2528 break;
2529 }
2530
2531 assert(f != 0x3E);
2532
2533 if (isInCurrentRoom() && _costume != 0) {
2534 _animProgress = 0;
2535 _needRedraw = true;
2536 _cost.animCounter = 0;
2537 // V1 - V2 games don't seem to need a _cost.reset() at this point.
2538 // Causes Zak to lose his body in several scenes, see bug #1032
2539 if (_vm->_game.version >= 3 && f == _initFrame) {
2540 _cost.reset();
2541 if (_vm->_game.heversion != 0) {
2542 ((ActorHE *)this)->_auxBlock.reset();
2543 }
2544 }
2545 _vm->_costumeLoader->costumeDecodeData(this, f, (uint) - 1);
2546 _frame = f;
2547 }
2548 }
2549 }
2550
startAnimActor(int f)2551 void Actor_v0::startAnimActor(int f) {
2552 if (f == _talkStartFrame) {
2553 if (_sound[0] & 0x40)
2554 return;
2555
2556 _speaking = 1;
2557 speakCheck();
2558 return;
2559 }
2560
2561 if (f == _talkStopFrame) {
2562
2563 _speaking = 0;
2564 return;
2565 }
2566
2567 if (f == _standFrame)
2568 setDirection(_facing);
2569 }
2570
animateActor(int anim)2571 void Actor::animateActor(int anim) {
2572 int cmd, dir;
2573
2574 if (_vm->_game.version >= 7 && !((_vm->_game.id == GID_FT) && (_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformDOS))) {
2575
2576 if (anim == 0xFF)
2577 anim = 2000;
2578
2579 cmd = anim / 1000;
2580 dir = anim % 1000;
2581
2582 } else {
2583
2584 cmd = anim / 4;
2585 dir = oldDirToNewDir(anim % 4);
2586
2587 // Convert into old cmd code
2588 cmd = 0x3F - cmd + 2;
2589
2590 }
2591
2592 switch (cmd) {
2593 case 2: // stop walking
2594 startAnimActor(_standFrame);
2595 stopActorMoving();
2596 break;
2597 case 3: // change direction immediatly
2598 _moving &= ~MF_TURN;
2599 setDirection(dir);
2600 break;
2601 case 4: // turn to new direction
2602 turnToDirection(dir);
2603 break;
2604 case 64:
2605 if (_vm->_game.version == 0) {
2606 _moving &= ~MF_TURN;
2607 setDirection(dir);
2608 break;
2609 }
2610 // fall through
2611 default:
2612 if (_vm->_game.version <= 2)
2613 startAnimActor(anim / 4);
2614 else
2615 startAnimActor(anim);
2616 }
2617 }
2618
animateCostume()2619 void Actor::animateCostume() {
2620 if (_costume == 0)
2621 return;
2622
2623 _animProgress++;
2624 if (_animProgress >= _animSpeed) {
2625 _animProgress = 0;
2626
2627 _vm->_costumeLoader->loadCostume(_costume);
2628 if (_vm->_costumeLoader->increaseAnims(this)) {
2629 _needRedraw = true;
2630 }
2631 }
2632 }
2633
limbFrameCheck(int limb)2634 void Actor_v0::limbFrameCheck(int limb) {
2635 if (_cost.frame[limb] == 0xFFFF)
2636 return;
2637
2638 if (_cost.start[limb] == _cost.frame[limb])
2639 return;
2640
2641 // 0x25A4
2642 _cost.start[limb] = _cost.frame[limb];
2643
2644 _limbFrameRepeat[limb] = _limbFrameRepeatNew[limb];
2645
2646 // 0x25C3
2647 _cost.active[limb] = ((V0CostumeLoader *)_vm->_costumeLoader)->getFrame(this, limb);
2648 _cost.curpos[limb] = 0;
2649
2650 _needRedraw = true;
2651 }
2652
animateCostume()2653 void Actor_v0::animateCostume() {
2654 speakCheck();
2655
2656 byte count = _vm->_costumeLoader->increaseAnims(this);
2657
2658 if (count) {
2659 _vm->_V0Delay._actorLimbRedrawDrawCount += count;
2660
2661 _needRedraw = true;
2662 }
2663 }
2664
speakCheck()2665 void Actor_v0::speakCheck() {
2666 if (_sound[0] & 0x80)
2667 return;
2668
2669 int cmd = newDirToOldDir(_facing);
2670
2671 if (_speaking & 0x80)
2672 cmd += 0x0C;
2673 else
2674 cmd += 0x10;
2675
2676 _animFrameRepeat = -1;
2677 animateActor(cmd);
2678 }
2679
2680 #ifdef ENABLE_SCUMM_7_8
animateLimb(int limb,int f)2681 void Actor::animateLimb(int limb, int f) {
2682 // This methods is very similiar to animateCostume().
2683 // However, instead of animating *all* the limbs, it only animates
2684 // the specified limb to be at the frame specified by "f".
2685
2686 if (!f)
2687 return;
2688
2689 _animProgress++;
2690 if (_animProgress >= _animSpeed) {
2691 _animProgress = 0;
2692
2693 if (_costume == 0)
2694 return;
2695
2696 const byte *aksq, *akfo;
2697 uint size;
2698 byte *akos = _vm->getResourceAddress(rtCostume, _costume);
2699 assert(akos);
2700
2701 aksq = _vm->findResourceData(MKTAG('A','K','S','Q'), akos);
2702 akfo = _vm->findResourceData(MKTAG('A','K','F','O'), akos);
2703
2704 size = _vm->getResourceDataSize(akfo) / 2;
2705
2706 while (f--) {
2707 if (_cost.active[limb] != 0)
2708 ((ScummEngine_v6 *)_vm)->akos_increaseAnim(this, limb, aksq, (const uint16 *)akfo, size);
2709 }
2710
2711 // _needRedraw = true;
2712 // _needBgReset = true;
2713 }
2714 }
2715 #endif
2716
redrawAllActors()2717 void ScummEngine::redrawAllActors() {
2718 int i;
2719
2720 for (i = 1; i < _numActors; ++i) {
2721 _actors[i]->_needRedraw = true;
2722 _actors[i]->_needBgReset = true;
2723 }
2724 }
2725
setActorRedrawFlags()2726 void ScummEngine::setActorRedrawFlags() {
2727 int i, j;
2728
2729 // Redraw all actors if a full redraw was requested.
2730 // Also redraw all actors in COMI (see bug #1825 for details).
2731 if (_fullRedraw || _game.version == 8 || (VAR_REDRAW_ALL_ACTORS != 0xFF && VAR(VAR_REDRAW_ALL_ACTORS) != 0)) {
2732 for (j = 1; j < _numActors; j++) {
2733 _actors[j]->_needRedraw = true;
2734 }
2735 } else {
2736 if (_game.heversion >= 72) {
2737 for (j = 1; j < _numActors; j++) {
2738 if (_actors[j]->_costume && _actors[j]->_heXmapNum)
2739 _actors[j]->_needRedraw = true;
2740 }
2741 }
2742
2743 for (i = 0; i < _gdi->_numStrips; i++) {
2744 int strip = _screenStartStrip + i;
2745 if (testGfxAnyUsageBits(strip)) {
2746 for (j = 1; j < _numActors; j++) {
2747 if (testGfxUsageBit(strip, j) && testGfxOtherUsageBits(strip, j)) {
2748 _actors[j]->_needRedraw = true;
2749 }
2750 }
2751 }
2752 }
2753 }
2754 }
2755
resetActorBgs()2756 void ScummEngine::resetActorBgs() {
2757 int i, j;
2758
2759 for (i = 0; i < _gdi->_numStrips; i++) {
2760 int strip = _screenStartStrip + i;
2761 clearGfxUsageBit(strip, USAGE_BIT_DIRTY);
2762 clearGfxUsageBit(strip, USAGE_BIT_RESTORED);
2763 for (j = 1; j < _numActors; j++) {
2764 if (_game.heversion != 0 && ((ActorHE *)_actors[j])->_heFlags & 1)
2765 continue;
2766
2767 if (testGfxUsageBit(strip, j) &&
2768 ((_actors[j]->_top != 0x7fffffff && _actors[j]->_needRedraw) || _actors[j]->_needBgReset)) {
2769 clearGfxUsageBit(strip, j);
2770 if ((_actors[j]->_bottom - _actors[j]->_top) >= 0)
2771 _gdi->resetBackground(_actors[j]->_top, _actors[j]->_bottom, i);
2772 }
2773 }
2774 }
2775
2776 for (i = 1; i < _numActors; i++) {
2777 _actors[i]->_needBgReset = false;
2778 }
2779 }
2780
2781 // HE specific
drawActorToBackBuf(int x,int y)2782 void ActorHE::drawActorToBackBuf(int x, int y) {
2783 int curTop = _top;
2784 int curBottom = _bottom;
2785
2786 _pos.x = x;
2787 _pos.y = y;
2788
2789 _drawToBackBuf = true;
2790 _needRedraw = true;
2791 drawActorCostume();
2792
2793 _drawToBackBuf = false;
2794 _needRedraw = true;
2795 drawActorCostume();
2796 _needRedraw = false;
2797
2798 if (_top > curTop)
2799 _top = curTop;
2800 if (_bottom < curBottom)
2801 _bottom = curBottom;
2802 }
2803
2804
2805 #pragma mark -
2806 #pragma mark --- Actor talking ---
2807 #pragma mark -
2808
2809
2810 // V1 Maniac doesn't have a ScummVar for VAR_TALK_ACTOR, and just uses
2811 // an internal variable. Emulate this to prevent overwriting script vars...
2812 // Maniac NES (V1), however, DOES have a ScummVar for VAR_TALK_ACTOR
getTalkingActor()2813 int ScummEngine::getTalkingActor() {
2814 if (_game.id == GID_MANIAC && _game.version <= 1 && !(_game.platform == Common::kPlatformNES))
2815 return _V1TalkingActor;
2816 else
2817 return VAR(VAR_TALK_ACTOR);
2818 }
2819
setTalkingActor(int i)2820 void ScummEngine::setTalkingActor(int i) {
2821
2822 if (i == 255) {
2823 _system->clearFocusRectangle();
2824 } else {
2825 // Work out the screen co-ordinates of the actor
2826 int x = _actors[i]->getPos().x - (camera._cur.x - (_screenWidth >> 1));
2827 int y = _actors[i]->_top - (camera._cur.y - (_screenHeight >> 1));
2828
2829 // Set the focus area to the calculated position
2830 // TODO: Make the size adjust depending on what it's focusing on.
2831 _system->setFocusRectangle(Common::Rect::center(x, y, 192, 128));
2832 }
2833
2834 if (_game.id == GID_MANIAC && _game.version <= 1 && !(_game.platform == Common::kPlatformNES))
2835 _V1TalkingActor = i;
2836 else
2837 VAR(VAR_TALK_ACTOR) = i;
2838 }
2839
2840 static const int v0MMActorTalkColor[25] = {
2841 1, 7, 2, 14, 8, 15, 3, 7, 7, 15, 1, 13, 1, 4, 5, 5, 4, 3, 1, 5, 1, 1, 1, 1, 7
2842 };
2843 static const int v1MMActorTalkColor[25] = {
2844 1, 7, 2, 14, 8, 1, 3, 7, 7, 12, 1, 13, 1, 4, 5, 5, 4, 3, 1, 5, 1, 1, 1, 7, 7
2845 };
2846
resetV1ActorTalkColor()2847 void ScummEngine::resetV1ActorTalkColor() {
2848 int i;
2849
2850 for (i = 1; i < _numActors; i++) {
2851 if (_game.version == 0) {
2852 _actors[i]->_talkColor = v0MMActorTalkColor[i];
2853 } else {
2854 _actors[i]->_talkColor = v1MMActorTalkColor[i];
2855 }
2856 }
2857 }
2858
2859 #ifdef ENABLE_SCUMM_7_8
actorTalk(const byte * msg)2860 void ScummEngine_v7::actorTalk(const byte *msg) {
2861 Actor *a;
2862 bool stringWrap = false;
2863
2864 convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer));
2865
2866 // Play associated speech, if any
2867 playSpeech((byte *)_lastStringTag);
2868
2869 if (_game.id == GID_DIG || _game.id == GID_CMI) {
2870 if (VAR(VAR_HAVE_MSG)) {
2871 if (_game.id == GID_DIG && _roomResource == 58 && msg[0] == ' ' && !msg[1])
2872 return;
2873 stopTalk();
2874 }
2875 } else {
2876 if (!_keepText)
2877 stopTalk();
2878 }
2879 if (_actorToPrintStrFor == 0xFF) {
2880 setTalkingActor(0xFF);
2881 _charsetColor = (byte)_string[0].color;
2882 } else {
2883 a = derefActor(_actorToPrintStrFor, "actorTalk");
2884 setTalkingActor(a->_number);
2885 if (!_string[0].no_talk_anim) {
2886 a->runActorTalkScript(a->_talkStartFrame);
2887 }
2888 _charsetColor = a->_talkColor;
2889
2890 // This is what the original COMI CJK interpreter does here.
2891 if (_game.id == GID_CMI && _useCJKMode) {
2892 if (a->_number == 1 && _currentRoom == 15)
2893 _charsetColor = 28;
2894 else if (a->_talkColor == 22)
2895 _charsetColor = 5;
2896 }
2897 }
2898
2899 _charsetBufPos = 0;
2900 _talkDelay = 0;
2901 _haveMsg = 1;
2902 if (_game.id == GID_FT)
2903 VAR(VAR_HAVE_MSG) = 0xFF;
2904 _haveActorSpeechMsg = (_game.id == GID_FT) ? true : (!_sound->isSoundRunning(kTalkSoundID));
2905 if (_game.id == GID_DIG || _game.id == GID_CMI) {
2906 stringWrap = _string[0].wrapping;
2907 _string[0].wrapping = true;
2908 }
2909 CHARSET_1();
2910 if (_game.id == GID_DIG || _game.id == GID_CMI) {
2911 if (_game.version == 8)
2912 VAR(VAR_HAVE_MSG) = (_string[0].no_talk_anim) ? 2 : 1;
2913 else
2914 VAR(VAR_HAVE_MSG) = 1;
2915 _string[0].wrapping = stringWrap;
2916 }
2917 }
2918 #endif
2919
actorTalk(const byte * msg)2920 void ScummEngine::actorTalk(const byte *msg) {
2921 Actor *a;
2922
2923 convertMessageToString(msg, _charsetBuffer, sizeof(_charsetBuffer));
2924
2925 // I have commented out this workaround, since it did cause another
2926 // bug (#11480). It is not okay to skip the stopTalk() calls here.
2927 // Instead, I have added two checks from LOOM DOS EGA disasm (one
2928 // below and one in CHARSET_1()).
2929 // WORKAROUND for bugs #985 and #990
2930 /*if (_game.id == GID_LOOM) {
2931 if (!*_charsetBuffer)
2932 return;
2933 }*/
2934
2935 if (_actorToPrintStrFor == 0xFF) {
2936 if (!_keepText) {
2937 stopTalk();
2938 }
2939 setTalkingActor(0xFF);
2940 } else {
2941 int oldact;
2942
2943 // WORKAROUND bug #1025
2944 if (_game.id == GID_LOOM && _roomResource == 23 &&
2945 vm.slot[_currentScript].number == 232 && _actorToPrintStrFor == 0) {
2946 _actorToPrintStrFor = 2; // Could be anything from 2 to 5. Maybe compare to original?
2947 }
2948
2949 a = derefActor(_actorToPrintStrFor, "actorTalk");
2950
2951 if (!a->isInCurrentRoom()) {
2952 oldact = 0xFF;
2953 } else {
2954 if (!_keepText) {
2955 stopTalk();
2956 }
2957 setTalkingActor(a->_number);
2958 if (_game.heversion != 0)
2959 ((ActorHE *)a)->_heTalking = true;
2960 // The second check is from LOOM DOS EGA disasm. It prevents weird speech animations
2961 // with empty strings (bug #990). The same code is present in CHARSET_1(). The FM-Towns
2962 // versions don't have such code, but I do not get the weird speech animations either.
2963 // So apparently it is not needed there.
2964 if (!_string[0].no_talk_anim && !(_game.id == GID_LOOM && _game.platform != Common::kPlatformFMTowns && !*_charsetBuffer)) {
2965 a->runActorTalkScript(a->_talkStartFrame);
2966 _useTalkAnims = true;
2967 }
2968 oldact = getTalkingActor();
2969 }
2970 if (oldact >= 0x80)
2971 return;
2972 }
2973
2974 if (_game.heversion >= 72 || getTalkingActor() > 0x7F) {
2975 if (_game.platform == Common::kPlatformNES)
2976 _charsetColor = 0; // NES MM intro color is always 0
2977 else
2978 _charsetColor = (byte)_string[0].color;
2979 } else if (_game.platform == Common::kPlatformNES) {
2980 if (_NES_lastTalkingActor != getTalkingActor())
2981 _NES_talkColor ^= 1;
2982 _NES_lastTalkingActor = getTalkingActor();
2983 _charsetColor = _NES_talkColor;
2984 } else {
2985 a = derefActor(getTalkingActor(), "actorTalk(2)");
2986 _charsetColor = a->_talkColor;
2987 }
2988 _charsetBufPos = 0;
2989 _talkDelay = 0;
2990 _haveMsg = 0xFF;
2991 VAR(VAR_HAVE_MSG) = 0xFF;
2992 if (VAR_CHARCOUNT != 0xFF)
2993 VAR(VAR_CHARCOUNT) = 0;
2994 _haveActorSpeechMsg = true;
2995 CHARSET_1();
2996 }
2997
runActorTalkScript(int f)2998 void Actor::runActorTalkScript(int f) {
2999 if (_vm->_game.version == 8 && _vm->VAR(_vm->VAR_HAVE_MSG) == 2)
3000 return;
3001
3002 if (_vm->_game.id == GID_FT && _vm->_string[0].no_talk_anim)
3003 return;
3004
3005 if (!_vm->getTalkingActor() || _room != _vm->_currentRoom || _frame == f)
3006 return;
3007
3008 if (_talkScript) {
3009 int script = _talkScript;
3010 int args[NUM_SCRIPT_LOCAL];
3011 memset(args, 0, sizeof(args));
3012 args[1] = f;
3013 args[0] = _number;
3014
3015 _vm->runScript(script, 1, 0, args);
3016 } else {
3017 startAnimActor(f);
3018 }
3019 }
3020
stopTalk()3021 void ScummEngine::stopTalk() {
3022 int act;
3023
3024 _sound->stopTalkSound();
3025
3026 _haveMsg = 0;
3027 _talkDelay = 0;
3028 _sound->_sfxMode = 0;
3029
3030 act = getTalkingActor();
3031 if (act && act < 0x80) {
3032 Actor *a = derefActor(act, "stopTalk");
3033 if ((_game.version >= 7 && !_string[0].no_talk_anim) ||
3034 (_game.version <= 6 && a->isInCurrentRoom() && _useTalkAnims)) {
3035 a->runActorTalkScript(a->_talkStopFrame);
3036 _useTalkAnims = false;
3037 }
3038 if (_game.version <= 7 && _game.heversion == 0)
3039 setTalkingActor(0xFF);
3040 if (_game.heversion != 0)
3041 ((ActorHE *)a)->_heTalking = false;
3042 }
3043
3044 if (_game.id == GID_DIG || _game.id == GID_CMI) {
3045 setTalkingActor(0);
3046 VAR(VAR_HAVE_MSG) = 0;
3047 } else if (_game.heversion >= 60) {
3048 setTalkingActor(0);
3049 }
3050
3051 _keepText = false;
3052 if (_game.version >= 7) {
3053 #ifdef ENABLE_SCUMM_7_8
3054 ((ScummEngine_v7 *)this)->clearSubtitleQueue();
3055 #endif
3056 } else {
3057 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
3058 if (_game.platform == Common::kPlatformFMTowns)
3059 towns_restoreCharsetBg();
3060 else
3061 #endif
3062 restoreCharsetBg();
3063 }
3064 }
3065
3066
3067 #pragma mark -
3068 #pragma mark --- TODO ---
3069 #pragma mark -
3070
3071
setActorCostume(int c)3072 void ActorHE::setActorCostume(int c) {
3073 if (_vm->_game.heversion >= 61 && (c == -1 || c == -2)) {
3074 _heSkipLimbs = (c == -1);
3075 _needRedraw = true;
3076 return;
3077 }
3078
3079 // Based on disassembly. It seems that high byte is not used at all, though
3080 // it is attached to all horizontally flipped object, like left eye.
3081 if (_vm->_game.heversion >= 61 && _vm->_game.heversion <= 62)
3082 c &= 0xff;
3083
3084 if (_vm->_game.features & GF_NEW_COSTUMES) {
3085 #ifdef ENABLE_HE
3086 if (_vm->_game.heversion >= 71)
3087 ((ScummEngine_v71he *)_vm)->queueAuxBlock(this);
3088 #endif
3089 _auxBlock.reset();
3090 if (_visible) {
3091 if (_vm->_game.heversion >= 60)
3092 _needRedraw = true;
3093 }
3094 }
3095
3096 Actor::setActorCostume(c);
3097
3098 if (_vm->_game.heversion >= 71 && _vm->getTalkingActor() == _number) {
3099 if (_vm->_game.heversion <= 95 || (_vm->_game.heversion >= 98 && _vm->VAR(_vm->VAR_SKIP_RESET_TALK_ACTOR) == 0)) {
3100 _vm->setTalkingActor(0);
3101 }
3102 }
3103 }
3104
setActorCostume(int c)3105 void Actor::setActorCostume(int c) {
3106 int i;
3107
3108 _costumeNeedsInit = true;
3109
3110 if (_vm->_game.features & GF_NEW_COSTUMES) {
3111 memset(_animVariable, 0, sizeof(_animVariable));
3112
3113 _costume = c;
3114 _cost.reset();
3115
3116 if (_visible) {
3117 if (_costume) {
3118 _vm->ensureResourceLoaded(rtCostume, _costume);
3119 }
3120 startAnimActor(_initFrame);
3121 }
3122 } else {
3123 if (_visible) {
3124 hideActor();
3125 _cost.reset();
3126 _costume = c;
3127 showActor();
3128 } else {
3129 _costume = c;
3130 _cost.reset();
3131 }
3132 }
3133
3134
3135 // V1 zak uses palette[] as a dynamic costume color array.
3136 if (_vm->_game.version <= 1)
3137 return;
3138
3139 if (_vm->_game.features & GF_NEW_COSTUMES) {
3140 for (i = 0; i < 256; i++)
3141 _palette[i] = 0xFF;
3142 } else if (_vm->_game.features & GF_OLD_BUNDLE) {
3143 for (i = 0; i < 16; i++)
3144 _palette[i] = i;
3145
3146 // Make stuff more visible on CGA. Based on disassembly
3147 if (_vm->_renderMode == Common::kRenderCGA && _vm->_game.version > 2) {
3148 _palette[6] = 5;
3149 _palette[7] = 15;
3150 }
3151 } else {
3152 for (i = 0; i < 32; i++)
3153 _palette[i] = 0xFF;
3154 }
3155 }
3156
3157 static const char *const v0ActorNames_English[25] = {
3158 "Syd",
3159 "Razor",
3160 "Dave",
3161 "Michael",
3162 "Bernard",
3163 "Wendy",
3164 "Jeff",
3165 "", // Radiation Suit
3166 "Dr Fred",
3167 "Nurse Edna",
3168 "Weird Ed",
3169 "Dead Cousin Ted",
3170 "Purple Tentacle",
3171 "Green Tentacle",
3172 "", // Meteor Police
3173 "Meteor",
3174 "", // Mark Eteer
3175 "", // Talkshow Host
3176 "Plant",
3177 "", // Meteor Radiation
3178 "", // Edsel (small, outro)
3179 "", // Meteor (small, intro)
3180 "Sandy", // (Lab)
3181 "", // Sandy (Cut-Scene)
3182 };
3183
3184 static const char *const v0ActorNames_German[25] = {
3185 "Syd",
3186 "Razor",
3187 "Dave",
3188 "Michael",
3189 "Bernard",
3190 "Wendy",
3191 "Jeff",
3192 "",
3193 "Dr.Fred",
3194 "Schwester Edna",
3195 "Weird Ed",
3196 "Ted",
3197 "Lila Tentakel",
3198 "Gr<nes Tentakel",
3199 "",
3200 "Meteor",
3201 "",
3202 "",
3203 "Pflanze",
3204 "",
3205 "",
3206 "",
3207 "Sandy",
3208 "",
3209 };
3210
getActorName()3211 const byte *Actor::getActorName() {
3212 const byte *ptr = NULL;
3213
3214 if (_vm->_game.version == 0) {
3215 if (_number) {
3216 switch (_vm->_language) {
3217 case Common::DE_DEU:
3218 ptr = (const byte *)v0ActorNames_German[_number - 1];
3219 break;
3220 default:
3221 ptr = (const byte *)v0ActorNames_English[_number - 1];
3222 }
3223 }
3224 } else {
3225 ptr = _vm->getResourceAddress(rtActorName, _number);
3226 }
3227
3228 if (ptr == NULL) {
3229 debugC(DEBUG_ACTORS, "Failed to find name of actor %d", _number);
3230 }
3231 return ptr;
3232 }
3233
getAnimVar(byte var) const3234 int Actor::getAnimVar(byte var) const {
3235 assertRange(0, var, 26, "getAnimVar:");
3236 return _animVariable[var];
3237 }
3238
setAnimVar(byte var,int value)3239 void Actor::setAnimVar(byte var, int value) {
3240 assertRange(0, var, 26, "setAnimVar:");
3241 _animVariable[var] = value;
3242 }
3243
remapActorPaletteColor(int color,int new_color)3244 void Actor::remapActorPaletteColor(int color, int new_color) {
3245 const byte *akos, *akpl;
3246 int akpl_size, i;
3247 byte akpl_color;
3248
3249 akos = _vm->getResourceAddress(rtCostume, _costume);
3250 if (!akos) {
3251 debugC(DEBUG_ACTORS, "Actor::remapActorPaletteColor: Can't remap actor %d, costume %d not found", _number, _costume);
3252 return;
3253 }
3254
3255 akpl = _vm->findResourceData(MKTAG('A','K','P','L'), akos);
3256 if (!akpl) {
3257 debugC(DEBUG_ACTORS, "Actor::remapActorPaletteColor: Can't remap actor %d, costume %d doesn't contain an AKPL block", _number, _costume);
3258 return;
3259 }
3260
3261 // Get the number palette entries
3262 akpl_size = _vm->getResourceDataSize(akpl);
3263
3264 for (i = 0; i < akpl_size; i++) {
3265 akpl_color = *akpl++;
3266 if (akpl_color == color) {
3267 _palette[i] = new_color;
3268 return;
3269 }
3270 }
3271 }
3272
remapActorPalette(int r_fact,int g_fact,int b_fact,int threshold)3273 void Actor::remapActorPalette(int r_fact, int g_fact, int b_fact, int threshold) {
3274 const byte *akos, *rgbs, *akpl;
3275 int akpl_size, i;
3276 int r, g, b;
3277 byte akpl_color;
3278
3279 if (!isInCurrentRoom()) {
3280 debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Actor %d not in current room", _number);
3281 return;
3282 }
3283
3284 akos = _vm->getResourceAddress(rtCostume, _costume);
3285 if (!akos) {
3286 debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d, costume %d not found", _number, _costume);
3287 return;
3288 }
3289
3290 akpl = _vm->findResourceData(MKTAG('A','K','P','L'), akos);
3291 if (!akpl) {
3292 debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d, costume %d doesn't contain an AKPL block", _number, _costume);
3293 return;
3294 }
3295
3296 // Get the number palette entries
3297 akpl_size = _vm->getResourceDataSize(akpl);
3298
3299 rgbs = _vm->findResourceData(MKTAG('R','G','B','S'), akos);
3300
3301 if (!rgbs) {
3302 debugC(DEBUG_ACTORS, "Actor::remapActorPalette: Can't remap actor %d costume %d doesn't contain an RGB block", _number, _costume);
3303 return;
3304 }
3305
3306 for (i = 0; i < akpl_size; i++) {
3307 r = *rgbs++;
3308 g = *rgbs++;
3309 b = *rgbs++;
3310
3311 akpl_color = *akpl++;
3312
3313 // allow remap of generic palette entry?
3314 if (!_shadowMode || akpl_color >= 16) {
3315 r = (r * r_fact) >> 8;
3316 g = (g * g_fact) >> 8;
3317 b = (b * b_fact) >> 8;
3318 _palette[i] = _vm->remapPaletteColor(r, g, b, threshold);
3319 }
3320 }
3321 }
3322
classChanged(int cls,bool value)3323 void Actor::classChanged(int cls, bool value) {
3324 if (cls == kObjectClassAlwaysClip)
3325 _forceClip = value;
3326 if (cls == kObjectClassIgnoreBoxes)
3327 _ignoreBoxes = value;
3328 }
3329
isInClass(int cls)3330 bool Actor::isInClass(int cls) {
3331 return _vm->getClass(_number, cls);
3332 }
3333
isPlayer()3334 bool Actor::isPlayer() {
3335 return isInClass(kObjectClassPlayer);
3336 }
3337
isPlayer()3338 bool Actor_v2::isPlayer() {
3339 // isPlayer() is not supported by v0
3340 assert(_vm->_game.version != 0);
3341 return _vm->VAR(42) <= _number && _number <= _vm->VAR(43);
3342 }
3343
setHEFlag(int bit,int set)3344 void ActorHE::setHEFlag(int bit, int set) {
3345 // Note that condition is inverted
3346 if (!set) {
3347 _heFlags |= bit;
3348 } else {
3349 _heFlags &= ~bit;
3350 }
3351 }
3352
setUserCondition(int slot,int set)3353 void ActorHE::setUserCondition(int slot, int set) {
3354 const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF;
3355 assertRange(1, slot, 32, "setUserCondition: Condition");
3356 if (set == 0) {
3357 _heCondMask &= ~(1 << (slot + 0xF));
3358 } else {
3359 _heCondMask |= 1 << (slot + 0xF);
3360 }
3361 if (_heCondMask & condMaskCode) {
3362 _heCondMask &= ~1;
3363 } else {
3364 _heCondMask |= 1;
3365 }
3366 }
3367
isUserConditionSet(int slot) const3368 bool ActorHE::isUserConditionSet(int slot) const {
3369 assertRange(1, slot, 32, "isUserConditionSet: Condition");
3370 return (_heCondMask & (1 << (slot + 0xF))) != 0;
3371 }
3372
setTalkCondition(int slot)3373 void ActorHE::setTalkCondition(int slot) {
3374 const int condMaskCode = (_vm->_game.heversion >= 85) ? 0x1FFF : 0x3FF;
3375 assertRange(1, slot, 32, "setTalkCondition: Condition");
3376 _heCondMask = (_heCondMask & ~condMaskCode) | 1;
3377 if (slot != 1) {
3378 _heCondMask |= 1 << (slot - 1);
3379 if (_heCondMask & condMaskCode) {
3380 _heCondMask &= ~1;
3381 } else {
3382 _heCondMask |= 1;
3383 }
3384 }
3385 }
3386
isTalkConditionSet(int slot) const3387 bool ActorHE::isTalkConditionSet(int slot) const {
3388 assertRange(1, slot, 32, "isTalkConditionSet: Condition");
3389 return (_heCondMask & (1 << (slot - 1))) != 0;
3390 }
3391
3392 #ifdef ENABLE_HE
preProcessAuxQueue()3393 void ScummEngine_v71he::preProcessAuxQueue() {
3394 if (!_skipProcessActors) {
3395 for (int i = 0; i < _auxBlocksNum; ++i) {
3396 AuxBlock *ab = &_auxBlocks[i];
3397 if (ab->r.top <= ab->r.bottom) {
3398 restoreBackgroundHE(ab->r);
3399 }
3400 }
3401 }
3402 _auxBlocksNum = 0;
3403 }
3404
postProcessAuxQueue()3405 void ScummEngine_v71he::postProcessAuxQueue() {
3406 if (!_skipProcessActors) {
3407 for (int i = 0; i < _auxEntriesNum; ++i) {
3408 AuxEntry *ae = &_auxEntries[i];
3409 if (ae->actorNum != -1) {
3410 ActorHE *a = (ActorHE *)derefActor(ae->actorNum, "postProcessAuxQueue");
3411 const uint8 *cost = getResourceAddress(rtCostume, a->_costume);
3412 int dy = a->_heOffsY + a->getPos().y;
3413 int dx = a->_heOffsX + a->getPos().x;
3414
3415 if (_game.heversion >= 72)
3416 dy -= a->getElevation();
3417
3418 const uint8 *akax = findResource(MKTAG('A','K','A','X'), cost);
3419 assert(akax);
3420 const uint8 *auxd = findPalInPals(akax, ae->subIndex) - _resourceHeaderSize;
3421 assert(auxd);
3422 const uint8 *frel = findResourceData(MKTAG('F','R','E','L'), auxd);
3423 if (frel) {
3424 error("unhandled FREL block");
3425 }
3426 const uint8 *disp = findResourceData(MKTAG('D','I','S','P'), auxd);
3427 if (disp) {
3428 error("unhandled DISP block");
3429 }
3430 const uint8 *axfd = findResourceData(MKTAG('A','X','F','D'), auxd);
3431 assert(axfd);
3432
3433 uint16 comp = READ_LE_UINT16(axfd);
3434 if (comp != 0) {
3435 int x = (int16)READ_LE_UINT16(axfd + 2) + dx;
3436 int y = (int16)READ_LE_UINT16(axfd + 4) + dy;
3437 int w = (int16)READ_LE_UINT16(axfd + 6);
3438 int h = (int16)READ_LE_UINT16(axfd + 8);
3439 VirtScreen *pvs = &_virtscr[kMainVirtScreen];
3440 uint8 *dst1 = pvs->getPixels(0, pvs->topline);
3441 uint8 *dst2 = pvs->getBackPixels(0, pvs->topline);
3442 switch (comp) {
3443 case 1:
3444 Wiz::copyAuxImage(dst1, dst2, axfd + 10, pvs->pitch, pvs->h, x, y, w, h, _bytesPerPixel);
3445 break;
3446 default:
3447 error("unimplemented compression type %d", comp);
3448 }
3449 }
3450 const uint8 *axur = findResourceData(MKTAG('A','X','U','R'), auxd);
3451 if (axur) {
3452 uint16 n = READ_LE_UINT16(axur); axur += 2;
3453 while (n--) {
3454 int x1 = (int16)READ_LE_UINT16(axur + 0) + dx;
3455 int y1 = (int16)READ_LE_UINT16(axur + 2) + dy;
3456 int x2 = (int16)READ_LE_UINT16(axur + 4) + dx;
3457 int y2 = (int16)READ_LE_UINT16(axur + 6) + dy;
3458 markRectAsDirty(kMainVirtScreen, x1, x2, y1, y2 + 1);
3459 axur += 8;
3460 }
3461 }
3462 const uint8 *axer = findResourceData(MKTAG('A','X','E','R'), auxd);
3463 if (axer) {
3464 a->_auxBlock.visible = true;
3465 a->_auxBlock.r.left = (int16)READ_LE_UINT16(axer + 0) + dx;
3466 a->_auxBlock.r.top = (int16)READ_LE_UINT16(axer + 2) + dy;
3467 a->_auxBlock.r.right = (int16)READ_LE_UINT16(axer + 4) + dx;
3468 a->_auxBlock.r.bottom = (int16)READ_LE_UINT16(axer + 6) + dy;
3469 adjustRect(a->_auxBlock.r);
3470 }
3471 }
3472 }
3473 }
3474 _auxEntriesNum = 0;
3475 }
3476
queueAuxBlock(ActorHE * a)3477 void ScummEngine_v71he::queueAuxBlock(ActorHE *a) {
3478 if (!a->_auxBlock.visible)
3479 return;
3480
3481 assert(_auxBlocksNum < ARRAYSIZE(_auxBlocks));
3482 _auxBlocks[_auxBlocksNum] = a->_auxBlock;
3483 ++_auxBlocksNum;
3484 }
3485
queueAuxEntry(int actorNum,int subIndex)3486 void ScummEngine_v71he::queueAuxEntry(int actorNum, int subIndex) {
3487 assert(_auxEntriesNum < ARRAYSIZE(_auxEntries));
3488 AuxEntry *ae = &_auxEntries[_auxEntriesNum];
3489 ae->actorNum = actorNum;
3490 ae->subIndex = subIndex;
3491 ++_auxEntriesNum;
3492 }
3493 #endif
3494
animateActor(int anim)3495 void Actor_v0::animateActor(int anim) {
3496 int dir = -1;
3497
3498 switch (anim) {
3499 case 0x00:
3500 case 0x04:
3501 dir = 0;
3502 break;
3503
3504 case 0x01:
3505 case 0x05:
3506 dir = 1;
3507 break;
3508
3509 case 0x02:
3510 case 0x06:
3511 dir = 2;
3512 break;
3513
3514 case 0x03:
3515 case 0x07:
3516 dir = 3;
3517 break;
3518
3519 default:
3520 break;
3521 }
3522
3523 if (isInCurrentRoom()) {
3524
3525 _costCommandNew = anim;
3526 _vm->_costumeLoader->costumeDecodeData(this, 0, 0);
3527
3528 if (dir == -1)
3529 return;
3530
3531 _facing = normalizeAngle(oldDirToNewDir(dir));
3532
3533 } else {
3534
3535 if (anim >= 4 && anim <= 7)
3536 _facing = normalizeAngle(oldDirToNewDir(dir));
3537 }
3538 }
3539
updateWalkbox()3540 byte Actor_v0::updateWalkbox() {
3541 if (_vm->checkXYInBoxBounds(_walkbox, _pos.x, _pos.y))
3542 return 0;
3543
3544 int numBoxes = _vm->getNumBoxes() - 1;
3545 for (int i = 0; i <= numBoxes; i++) {
3546 if (_vm->checkXYInBoxBounds(i, _pos.x, _pos.y) == true) {
3547 if (_walkdata.curbox == i) {
3548 setBox(i);
3549 directionUpdate();
3550
3551 _newWalkBoxEntered = true;
3552 return i;
3553 }
3554 }
3555 }
3556
3557 return kInvalidBox;
3558 }
3559
directionUpdate()3560 void Actor_v0::directionUpdate() {
3561
3562 int nextFacing = updateActorDirection(true);
3563 if (_facing != nextFacing) {
3564 // 2A89
3565 setDirection(nextFacing);
3566
3567 // Still need to turn?
3568 if (_facing != _targetFacing) {
3569 _moving |= 0x80;
3570 return;
3571 }
3572 }
3573
3574 _moving &= ~0x80;
3575 }
3576
setActorToTempPosition()3577 void Actor_v0::setActorToTempPosition() {
3578 _tmp_Pos = _pos;
3579 _pos = _tmp_NewPos;
3580 _tmp_WalkBox = _walkbox;
3581 _tmp_NewWalkBoxEntered = _newWalkBoxEntered;
3582 }
3583
setActorToOriginalPosition()3584 void Actor_v0::setActorToOriginalPosition() {
3585 _pos = _tmp_Pos;
3586 _tmp_NewPos = _tmp_Pos;
3587 _walkbox = _tmp_WalkBox;
3588 _newWalkBoxEntered = _tmp_NewWalkBoxEntered;
3589 }
3590
actorSetWalkTo()3591 void Actor_v0::actorSetWalkTo() {
3592
3593 if (_newWalkBoxEntered == false)
3594 return;
3595
3596 _newWalkBoxEntered = false;
3597
3598 int nextBox = ((ScummEngine_v0 *)_vm)->walkboxFindTarget(this, _walkdata.destbox, _walkdata.dest);
3599 if (nextBox != kInvalidBox) {
3600 _walkdata.curbox = nextBox;
3601 }
3602 }
3603
saveLoadWithSerializer(Common::Serializer & s)3604 void Actor_v0::saveLoadWithSerializer(Common::Serializer &s) {
3605 Actor::saveLoadWithSerializer(s);
3606
3607 s.syncAsByte(_costCommand, VER(84));
3608 s.skip(1, VER(84), VER(89)); // _costFrame
3609 s.syncAsByte(_miscflags, VER(84));
3610 s.syncAsByte(_speaking, VER(84));
3611 s.skip(1, VER(84), VER(89)); // _speakingPrev
3612 s.skip(1, VER(89), VER(89)); // _limbTemp
3613 s.syncAsByte(_animFrameRepeat, VER(89));
3614 s.syncArray(_limbFrameRepeatNew, 8, Common::Serializer::SByte, VER(89));
3615 s.syncArray(_limbFrameRepeat, 8, Common::Serializer::SByte, VER(90));
3616 s.syncAsSint16LE(_CurrentWalkTo.x, VER(97));
3617 s.syncAsSint16LE(_CurrentWalkTo.y, VER(97));
3618 s.syncAsSint16LE(_NewWalkTo.x, VER(97));
3619 s.syncAsSint16LE(_NewWalkTo.y, VER(97));
3620 s.syncAsSByte(_walkCountModulo, VER(97));
3621 s.syncAsByte(_newWalkBoxEntered, VER(97));
3622 s.syncAsByte(_walkDirX, VER(97));
3623 s.syncAsByte(_walkDirY, VER(97));
3624 s.syncAsByte(_walkYCountGreaterThanXCount, VER(97));
3625 s.syncAsByte(_walkXCount, VER(97));
3626 s.syncAsByte(_walkXCountInc, VER(97));
3627 s.syncAsByte(_walkYCount, VER(97));
3628 s.syncAsByte(_walkYCountInc, VER(97));
3629 s.syncAsByte(_walkMaxXYCountInc, VER(97));
3630
3631 s.syncBytes(_walkboxQueue, 16, VER(98));
3632 s.syncAsByte(_walkboxQueueIndex, VER(98));
3633
3634 // When loading, we need to ensure the limbs are restarted
3635 if (s.isLoading()) {
3636
3637 // valid costume command?
3638 if (_costCommand != 0xFF) {
3639
3640 // Do we have a walkbox queue?
3641 if (_walkboxQueueIndex < 1) {
3642 _costCommand = 0xFF;
3643
3644 // Standing Still
3645 setDirection(_facing);
3646 speakCheck();
3647
3648 } else {
3649 // Force limb direction update
3650 _facing = 0;
3651 directionUpdate();
3652
3653 // Begin walking
3654 animateActor(newDirToOldDir(_facing));
3655 }
3656 }
3657 }
3658 }
3659
saveLoadWithSerializer(Common::Serializer & s)3660 void Actor::saveLoadWithSerializer(Common::Serializer &s) {
3661 if (s.isLoading()) {
3662 // Not all actor data is saved; so when loading, we first reset
3663 // the actor, to ensure completely reproducible behavior (else,
3664 // some not saved value in the actor class can cause odd things)
3665 initActor(-1);
3666 }
3667
3668 s.syncAsSint16LE(_pos.x, VER(8));
3669 s.syncAsSint16LE(_pos.y, VER(8));
3670 s.syncAsSint16LE(_heOffsX, VER(32));
3671 s.syncAsSint16LE(_heOffsY, VER(32));
3672 s.syncAsSint16LE(_top, VER(8));
3673 s.syncAsSint16LE(_bottom, VER(8));
3674 s.syncAsSint16LE(_elevation, VER(8));
3675 s.syncAsUint16LE(_width, VER(8));
3676 s.syncAsUint16LE(_facing, VER(8));
3677 s.syncAsUint16LE(_costume, VER(8));
3678 s.syncAsByte(_room, VER(8));
3679 s.syncAsByte(_talkColor, VER(8));
3680 s.syncAsSint16LE(_talkFrequency, VER(16));
3681 s.syncAsSint16LE(_talkPan, VER(24));
3682 s.syncAsSint16LE(_talkVolume, VER(29));
3683 s.syncAsUint16LE(_boxscale, VER(34));
3684 s.syncAsByte(_scalex, VER(8));
3685 s.syncAsByte(_scaley, VER(8));
3686 s.syncAsByte(_charset, VER(8));
3687
3688 // Actor sound grew from 8 to 32 bytes and switched to uint16 in HE games
3689 s.syncArray(_sound, 8, Common::Serializer::Byte, VER(8), VER(36));
3690 s.syncArray(_sound, 32, Common::Serializer::Byte, VER(37), VER(61));
3691 s.syncArray(_sound, 32, Common::Serializer::Uint16LE, VER(62));
3692
3693 // Actor animVariable grew from 8 to 27
3694 s.syncArray(_animVariable, 8, Common::Serializer::Uint16LE, VER(8), VER(40));
3695 s.syncArray(_animVariable, 27, Common::Serializer::Uint16LE, VER(41));
3696
3697 s.syncAsUint16LE(_targetFacing, VER(8));
3698 s.syncAsByte(_moving, VER(8));
3699 s.syncAsByte(_ignoreBoxes, VER(8));
3700 s.syncAsByte(_forceClip, VER(8));
3701 s.syncAsByte(_initFrame, VER(8));
3702 s.syncAsByte(_walkFrame, VER(8));
3703 s.syncAsByte(_standFrame, VER(8));
3704 s.syncAsByte(_talkStartFrame, VER(8));
3705 s.syncAsByte(_talkStopFrame, VER(8));
3706 s.syncAsUint16LE(_speedx, VER(8));
3707 s.syncAsUint16LE(_speedy, VER(8));
3708 s.syncAsUint16LE(_cost.animCounter, VER(8));
3709 s.syncAsByte(_cost.soundCounter, VER(8));
3710 s.syncAsByte(_drawToBackBuf, VER(32));
3711 s.syncAsByte(_flip, VER(32));
3712 s.syncAsByte(_heSkipLimbs, VER(32));
3713
3714 // Actor palette grew from 64 to 256 bytes and switched to uint16 in HE games
3715 s.syncArray(_palette, 64, Common::Serializer::Byte, VER(8), VER(9));
3716 s.syncArray(_palette, 256, Common::Serializer::Byte, VER(10), VER(79));
3717 s.syncArray(_palette, 256, Common::Serializer::Uint16LE, VER(80));
3718
3719 s.skip(1, VER(8), VER(9)); // _mask
3720 s.syncAsByte(_shadowMode, VER(8));
3721 s.syncAsByte(_visible, VER(8));
3722 s.syncAsByte(_frame, VER(8));
3723 s.syncAsByte(_animSpeed, VER(8));
3724 s.syncAsByte(_animProgress, VER(8));
3725 s.syncAsByte(_walkbox, VER(8));
3726 s.syncAsByte(_needRedraw, VER(8));
3727 s.syncAsByte(_needBgReset, VER(8));
3728 s.syncAsByte(_costumeNeedsInit, VER(8));
3729 s.syncAsUint32LE(_heCondMask, VER(38));
3730 s.syncAsUint32LE(_hePaletteNum, VER(59));
3731 s.syncAsUint32LE(_heXmapNum, VER(59));
3732
3733 s.syncAsSint16LE(_talkPosY, VER(8));
3734 s.syncAsSint16LE(_talkPosX, VER(8));
3735 s.syncAsByte(_ignoreTurns, VER(8));
3736
3737 // Actor layer switched to int32 in HE games
3738 s.syncAsByte(_layer, VER(8), VER(57));
3739 s.syncAsSint32LE(_layer, VER(58));
3740
3741 s.syncAsUint16LE(_talkScript, VER(8));
3742 s.syncAsUint16LE(_walkScript, VER(8));
3743
3744 s.syncAsSint16LE(_walkdata.dest.x, VER(8));
3745 s.syncAsSint16LE(_walkdata.dest.y, VER(8));
3746 s.syncAsByte(_walkdata.destbox, VER(8));
3747 s.syncAsUint16LE(_walkdata.destdir, VER(8));
3748 s.syncAsByte(_walkdata.curbox, VER(8));
3749 s.syncAsSint16LE(_walkdata.cur.x, VER(8));
3750 s.syncAsSint16LE(_walkdata.cur.y, VER(8));
3751 s.syncAsSint16LE(_walkdata.next.x, VER(8));
3752 s.syncAsSint16LE(_walkdata.next.y, VER(8));
3753 s.syncAsSint32LE(_walkdata.deltaXFactor, VER(8));
3754 s.syncAsSint32LE(_walkdata.deltaYFactor, VER(8));
3755 s.syncAsUint16LE(_walkdata.xfrac, VER(8));
3756 s.syncAsUint16LE(_walkdata.yfrac, VER(8));
3757
3758 s.syncAsUint16LE(_walkdata.point3.x, VER(42));
3759 s.syncAsUint16LE(_walkdata.point3.y, VER(42));
3760
3761 s.syncBytes(_cost.active, 16, VER(8));
3762 s.syncAsUint16LE(_cost.stopped, VER(8));
3763 s.syncArray(_cost.curpos, 16, Common::Serializer::Uint16LE, VER(8));
3764 s.syncArray(_cost.start, 16, Common::Serializer::Uint16LE, VER(8));
3765 s.syncArray(_cost.end, 16, Common::Serializer::Uint16LE, VER(8));
3766 s.syncArray(_cost.frame, 16, Common::Serializer::Uint16LE, VER(8));
3767
3768 s.syncArray(_cost.heJumpOffsetTable, 16, Common::Serializer::Uint16LE, VER(65));
3769 s.syncArray(_cost.heJumpCountTable, 16, Common::Serializer::Uint16LE, VER(65));
3770 s.syncArray(_cost.heCondMaskTable, 16, Common::Serializer::Uint32LE, VER(65));
3771
3772 if (s.isLoading() && _vm->_game.version <= 2 && s.getVersion() < VER(70)) {
3773 _pos.x >>= V12_X_SHIFT;
3774 _pos.y >>= V12_Y_SHIFT;
3775
3776 _speedx >>= V12_X_SHIFT;
3777 _speedy >>= V12_Y_SHIFT;
3778 _elevation >>= V12_Y_SHIFT;
3779
3780 if (_walkdata.dest.x != -1) {
3781 _walkdata.dest.x >>= V12_X_SHIFT;
3782 _walkdata.dest.y >>= V12_Y_SHIFT;
3783 }
3784
3785 _walkdata.cur.x >>= V12_X_SHIFT;
3786 _walkdata.cur.y >>= V12_Y_SHIFT;
3787
3788 _walkdata.next.x >>= V12_X_SHIFT;
3789 _walkdata.next.y >>= V12_Y_SHIFT;
3790
3791 if (_walkdata.point3.x != 32000) {
3792 _walkdata.point3.x >>= V12_X_SHIFT;
3793 _walkdata.point3.y >>= V12_Y_SHIFT;
3794 }
3795
3796 setDirection(_facing);
3797 }
3798 }
3799
saveLoadWithSerializer(Common::Serializer & s)3800 void Actor_v3::saveLoadWithSerializer(Common::Serializer &s) {
3801 Actor::saveLoadWithSerializer(s);
3802
3803 int rev = (_vm->_game.version == 3 ? 101 : 102);
3804
3805 if (s.isLoading() && s.getVersion() < VER(rev)) {
3806 int diffX = _walkdata.next.x - _pos.x;
3807 int diffY = _walkdata.next.y - _pos.y;
3808
3809 if (_vm->_game.version < 3) {
3810 _stepThreshold = MAX(ABS(diffX), ABS(diffY));
3811 _walkdata.deltaXFactor = _walkdata.deltaYFactor = 1;
3812 } else {
3813 _stepX = ((ABS(diffY) / (int)_speedy) >> 1) >(ABS(diffX) / (int)_speedx) ? _speedy + 1 : _speedx;
3814 _stepThreshold = MAX(ABS(diffY) / _speedy, ABS(diffX) / _stepX);
3815 _walkdata.deltaXFactor = (int32)_stepX;
3816 _walkdata.deltaYFactor = (int32)_speedy;
3817 }
3818
3819 if (diffX < 0)
3820 _walkdata.deltaXFactor = -_walkdata.deltaXFactor;
3821 if (diffY < 0)
3822 _walkdata.deltaYFactor = -_walkdata.deltaYFactor;
3823 _walkdata.xfrac = _walkdata.xAdd = _walkdata.deltaXFactor ? diffX / _walkdata.deltaXFactor : 0;
3824 _walkdata.yfrac = _walkdata.yAdd = _walkdata.deltaYFactor ? diffY / _walkdata.deltaYFactor : 0;
3825
3826 } else {
3827 s.syncAsUint16LE(_walkdata.xAdd, VER(rev));
3828 s.syncAsUint16LE(_walkdata.yAdd, VER(rev));
3829 s.syncAsUint16LE(_stepX, VER(rev));
3830 s.syncAsUint16LE(_stepThreshold, VER(rev));
3831 }
3832 }
3833
3834 } // End of namespace Scumm
3835