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