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 "bladerunner/actor.h"
24 
25 #include "bladerunner/actor_clues.h"
26 #include "bladerunner/actor_combat.h"
27 #include "bladerunner/actor_walk.h"
28 #include "bladerunner/audio_speech.h"
29 #include "bladerunner/bladerunner.h"
30 #include "bladerunner/boundingbox.h"
31 #include "bladerunner/crimes_database.h"
32 #include "bladerunner/game_info.h"
33 #include "bladerunner/items.h"
34 #include "bladerunner/mouse.h"
35 #include "bladerunner/movement_track.h"
36 #include "bladerunner/savefile.h"
37 #include "bladerunner/scene.h"
38 #include "bladerunner/scene_objects.h"
39 #include "bladerunner/script/scene_script.h"
40 #include "bladerunner/script/ai_script.h"
41 #include "bladerunner/set.h"
42 #include "bladerunner/slice_animations.h"
43 #include "bladerunner/slice_renderer.h"
44 #include "bladerunner/time.h"
45 #include "bladerunner/subtitles.h"
46 #include "bladerunner/waypoints.h"
47 #include "bladerunner/zbuffer.h"
48 
49 namespace BladeRunner {
50 
Actor(BladeRunnerEngine * vm,int actorId)51 Actor::Actor(BladeRunnerEngine *vm, int actorId) {
52 	_vm = vm;
53 	_id = actorId;
54 
55 	_walkInfo      = new ActorWalk(vm);
56 	_movementTrack = new MovementTrack();
57 	_cluesLimit    = (actorId == kActorMcCoy || actorId == kActorVoiceOver) ? 4 : 2;
58 	_clues         = new ActorClues(vm, _cluesLimit);
59 	_combatInfo    = new ActorCombat(vm);
60 
61 	_friendlinessToOther.resize(_vm->_gameInfo->getActorCount());
62 
63 	setup(actorId);
64 }
65 
~Actor()66 Actor::~Actor() {
67 	delete _combatInfo;
68 	delete _clues;
69 	delete _movementTrack;
70 	delete _walkInfo;
71 }
72 
setup(int actorId)73 void Actor::setup(int actorId) {
74 	_id             = actorId;
75 	_setId          = -1;
76 
77 	_position       = Vector3(0.0, 0.0, 0.0);
78 	_facing         = 512;
79 	_targetFacing   = -1;
80 	_walkboxId      = -1;
81 
82 	_animationId    = 0;
83 	_animationFrame = 0;
84 	_fps            = 15;
85 	_frameMs        = 1000 / _fps;
86 
87 	_mustReachWalkDestination = false;	// Original's _inWalkLoop. Moved here from our constructor, since it's here in the original's init()
88 	_isMoving                 = false;
89 	_isTarget                 = false;
90 	_inCombat                 = false;
91 	_isInvisible              = false;
92 	_isImmuneToObstacles      = false;
93 	_isRetired                = false;
94 
95 	_width                    = 0;
96 	_height                   = 0;
97 	_retiredWidth             = 0;
98 	_retiredHeight            = 0;
99 	_scale                    = 1.0f;
100 
101 	_timer4RemainDefault      = 60000;
102 
103 	_movementTrackWalkingToWaypointId = -1;
104 	_movementTrackDelayOnNextWaypoint = -1;
105 
106 	for (int i = 0; i != kActorTimers; ++i) {
107 		_timersLeft[i] = 0;
108 		_timersLast[i] = _vm->_time->current();
109 	}
110 	// This was in original code.
111 	// We need to init this timer in order to kick off periodic updates for acquireCluesByRelations
112 	_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault;
113 
114 	_honesty                     = 50;
115 	_intelligence                = 50;
116 	_combatAggressiveness        = 50;
117 	_stability                   = 50;
118 
119 	_currentHP                   = 50;
120 	_maxHP                       = 50;
121 
122 	_damageAnimIfMoving          = true; // Set to true (like in original). And moved here from our constructor, since it's here in the original's init().
123 
124 	_goalNumber                  = -1;
125 
126 	_movementTrackPaused         = false;
127 	_movementTrackNextWaypointId = -1;
128 	_movementTrackNextDelay      = -1;
129 	_movementTrackNextAngle      = -1;
130 	_movementTrackNextRunning    = false;
131 
132 	_animationMode               = -1;
133 	_screenRectangle = Common::Rect(-1, -1, -1, -1);
134 
135 	_animationModeCombatIdle = kAnimationModeCombatIdle;
136 	_animationModeCombatWalk = kAnimationModeCombatWalk;
137 	_animationModeCombatRun  = kAnimationModeCombatRun;
138 
139 	int actorCount = (int)_vm->_gameInfo->getActorCount();
140 	for (int i = 0; i != actorCount; ++i)
141 		_friendlinessToOther[i] = 50;
142 
143 #if BLADERUNNER_ORIGINAL_BUGS
144 #else
145 	// if player actor was not idle and had an active _walkInfo then
146 	// upon starting a new game, the player actor would be put on the old _walkInfo
147 	_walkInfo->reset();
148 //	// delete _walkInfo and re-allocate it (a reset method would probably be better)
149 //	if (_walkInfo != nullptr) {
150 //		delete(_walkInfo);
151 //	}
152 //	_walkInfo = new ActorWalk(_vm);
153 #endif // BLADERUNNER_ORIGINAL_BUGS
154 
155 	_combatInfo->setup();
156 	_clues->removeAll();
157 	_movementTrack->flush();
158 
159 	_actorSpeed = Vector3();
160 
161 	switch (_id) {
162 		case kActorMcCoy:
163 			_sitcomRatio = 50;
164 			break;
165 
166 		case kActorGordo:
167 			_sitcomRatio = 0;
168 			break;
169 
170 		case kActorGuzza:
171 		case kActorChew:
172 		case kActorVoiceOver:
173 			_sitcomRatio = 75;
174 			break;
175 
176 		case kActorCrazylegs:
177 		case kActorBulletBob:
178 		case kActorRunciter:
179 		case kActorZuben:
180 		case kActorLeon:
181 			_sitcomRatio = 90;
182 			break;
183 
184 		case kActorGrigorian:
185 		case kActorMoraji:
186 			_sitcomRatio = 100;
187 			break;
188 
189 		default:
190 			_sitcomRatio = 33;
191 			break;
192 	}
193 }
194 
changeAnimationMode(int animationMode,bool force)195 void Actor::changeAnimationMode(int animationMode, bool force) {
196 	if (force) {
197 		_animationMode = -1;
198 	}
199 
200 	if (animationMode != _animationMode) {
201 		_vm->_aiScripts->changeAnimationMode(_id, animationMode);
202 		_animationMode = animationMode;
203 	}
204 }
205 
getFPS() const206 int Actor::getFPS() const {
207 	return _fps;
208 }
209 
setFPS(int fps)210 void Actor::setFPS(int fps) {
211 	_fps = fps;
212 
213 	if (fps == 0) { // stop actor's animation
214 		_frameMs = 0;
215 	} else if (fps == -1) { // sync actor's animation with scene animation
216 		_frameMs = -1000;
217 	} else if (fps == -2) { // set FPS to default from the model
218 		_fps = _vm->_sliceAnimations->getFPS(_animationId);
219 		_frameMs = 1000 / _fps;
220 	} else {
221 		_frameMs = 1000 / fps;
222 	}
223 }
224 
increaseFPS()225 void Actor::increaseFPS() {
226 #if BLADERUNNER_ORIGINAL_BUGS
227 	int fps = MIN(_fps + 3, 30);
228 	setFPS(fps);
229 #else
230 	int oldFps = _fps; // new aux variable
231 	int fps = MIN(_fps + 3, 30);
232 	setFPS(fps);
233 
234 	// Note: When stamina drain is disabled, McCoy returns to normal fps
235 	// (which is the default for his animations ie. 15 fps)
236 	// on his actor->tick() method, when he switches from running to walking animation
237 	// and setFPS(-2) is called
238 	if (!_vm->_disableStaminaDrain) {
239 		// Only McCoy is using the stamina timer in the game
240 		if (_id == kActorMcCoy) {
241 			if (_vm->_cutContent) {
242 				if (_fps > 20 && oldFps < _fps) {
243 					// only start the stamina timer
244 					// when McCOy's fps are more than 20 fps and purely increased
245 					// and only if the new drain interval is smaller than the previous one
246 					// the start drain interval is supposed to be slow
247 					// starting from 10 seconds and decreasing as low as 1 second
248 					// (It will barely come into play basically)
249 					int nextStaminaDrainInterval = (31 - _fps) * 1000;
250 					if (nextStaminaDrainInterval < timerLeft(kActorTimerRunningStaminaFPS)) {
251 						timerStart(kActorTimerRunningStaminaFPS, nextStaminaDrainInterval);
252 					}
253 				}
254 			} else {
255 				// just prevent any rogue state for stamina timer being 0
256 				// at any time when McCoy's fps get increased
257 				if (timerLeft(kActorTimerRunningStaminaFPS) == 0) {
258 					timerStart(kActorTimerRunningStaminaFPS, 200);
259 				}
260 			}
261 		}
262 	}
263 #endif // BLADERUNNER_ORIGINAL_BUGS
264 }
265 
timerStart(int timerId,int32 intervalMillis)266 void Actor::timerStart(int timerId, int32 intervalMillis) {
267 	assert(timerId >= 0 && timerId < kActorTimers);
268 
269 	_timersLeft[timerId] = intervalMillis;
270 	_timersLast[timerId] = _vm->_time->current();
271 }
272 
timerReset(int timerId)273 void Actor::timerReset(int timerId) {
274 	assert(timerId >= 0 && timerId < kActorTimers);
275 	_timersLeft[timerId] = 0;
276 }
277 
278 // timerLeft can be negative - This is required for
279 // the actor's animation update timer mostly (timer kActorTimerAnimationFrame)
timerLeft(int timerId)280 int32 Actor::timerLeft(int timerId) {
281 	assert(timerId >= 0 && timerId < kActorTimers);
282 	return _timersLeft[timerId];
283 }
284 
timersUpdate()285 void Actor::timersUpdate() {
286 	for (int i = 0; i < kActorTimers; ++i) {
287 		timerUpdate(i);
288 	}
289 }
290 
timerUpdate(int timerId)291 void Actor::timerUpdate(int timerId) {
292 	if (_timersLeft[timerId] == 0) {
293 		return;
294 	}
295 
296 	uint32 timeNow = _vm->_time->current();
297 	uint32 timeDiff = timeNow - _timersLast[timerId]; // unsigned difference is intentional
298 	_timersLast[timerId] = timeNow;
299 
300 	_timersLeft[timerId] = _timersLeft[timerId] - timeDiff;
301 	if (_timersLeft[timerId] <= 0) {
302 		switch (timerId) {
303 		case kActorTimerAIScriptCustomTask0:
304 			// fall through
305 		case kActorTimerAIScriptCustomTask1:
306 			// fall through
307 		case kActorTimerAIScriptCustomTask2:
308 			if (!_vm->_aiScripts->isInsideScript() && !_vm->_sceneScript->isInsideScript()) {
309 				_vm->_aiScripts->timerExpired(_id, timerId);
310 				_timersLeft[timerId] = 0;
311 			} else {
312 				_timersLeft[timerId] = 1;
313 			}
314 			break;
315 		case kActorTimerMovementTrack:
316 			_timersLeft[kActorTimerMovementTrack] = 0;
317 			if (_movementTrack->isPaused()) {
318 				_timersLeft[kActorTimerMovementTrack] = 1;
319 			} else {
320 				movementTrackNext(false);
321 			}
322 			break;
323 		case kActorTimerClueExchange:
324 			// Exchange clues between actors
325 			acquireCluesByRelations();
326 			_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault;
327 			break;
328 		case kActorTimerAnimationFrame:
329 			// Actor animation frame timer
330 			break;
331 		case kActorTimerRunningStaminaFPS:
332 			// If stamina drain is disabled then
333 			// the timer will become zero and won't get initialized again
334 			// This is better than entirely skipping updating this specific timer
335 			// which would include constantly checking for it in a frequently repeated loop
336 			// If stamina drain is re-enabled, the timer will get initialized
337 			// either:
338 			// Vanilla mode: when McCoy starts running,
339 			//               or if starting new game
340 			//               or if loading a game where the timer was stored as 0
341 			// Restored Content mode: when McCoy starts running fast enough
342 			if (!_vm->_disableStaminaDrain) {
343 				if (isRunning()) {
344 					if (_fps > 15) {
345 						int newFps = _fps - 2;
346 						if (newFps < 15) {
347 							newFps = 15;
348 						}
349 						setFPS(newFps);
350 					}
351 				}
352 #if BLADERUNNER_ORIGINAL_BUGS
353 				_timersLeft[kActorTimerRunningStaminaFPS] = 200;
354 #else
355 				if (_vm->_cutContent) {
356 					if (isRunning()) {
357 						// drain faster if closer to max fps (30), else slower
358 						_timersLeft[kActorTimerRunningStaminaFPS] = (31 - _fps) * 200;
359 					} else {
360 						// not running - stop the timer
361 						timerReset(kActorTimerRunningStaminaFPS);
362 					}
363 				} else {
364 					_timersLeft[kActorTimerRunningStaminaFPS] = 200;
365 				}
366 #endif // BLADERUNNER_ORIGINAL_BUGS
367 			}
368 			break;
369 		default:
370 			break;
371 		}
372 	}
373 }
374 
movementTrackNext(bool omitAiScript)375 void Actor::movementTrackNext(bool omitAiScript) {
376 	bool hasNextMovement;
377 	bool running;
378 	int angle;
379 	int32 delayMillis;
380 	int waypointId;
381 	Vector3 waypointPosition;
382 	bool arrived;
383 
384 	hasNextMovement = _movementTrack->next(&waypointId, &delayMillis, &angle, &running);
385 	_movementTrackNextWaypointId = waypointId;
386 	_movementTrackNextDelay = delayMillis;
387 	_movementTrackNextAngle = angle;
388 	_movementTrackNextRunning = running;
389 	if (hasNextMovement) {
390 		if (angle == -1) {
391 			angle = 0;
392 		}
393 		int waypointSetId = _vm->_waypoints->getSetId(waypointId);
394 		_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
395 		if (_setId == waypointSetId && waypointSetId == _vm->_actors[0]->_setId) {
396 			// if target waypointSetId is in same set as both the actor and McCoy then call movementTrackWaypointReached
397 			stopWalking(false);
398 			_walkInfo->setup(_id, running, _position, waypointPosition, false, &arrived);
399 
400 			_movementTrackWalkingToWaypointId = waypointId;
401 			_movementTrackDelayOnNextWaypoint = delayMillis;
402 			if (arrived) {
403 				movementTrackWaypointReached();
404 			}
405 		} else {
406 			// teleport to target waypoint's set and position anyway
407 			// and schedule next movementTrackNext() using the kActorTimerMovementTrack
408 			setSetId(waypointSetId);
409 
410 			setAtXYZ(waypointPosition, angle, true, false, false);
411 
412 			if (!delayMillis) {
413 				delayMillis = 1;
414 			}
415 			if (delayMillis > 1) {
416 				changeAnimationMode(kAnimationModeIdle, false);
417 			}
418 
419 			timerStart(kActorTimerMovementTrack, delayMillis);
420 		}
421 		//return true;
422 	} else {
423 		if (!omitAiScript) {
424 			_vm->_aiScripts->completedMovementTrack(_id);
425 		}
426 		//return false;
427 	}
428 }
429 
movementTrackPause()430 void Actor::movementTrackPause() {
431 	_movementTrack->pause();
432 	if (isWalking()) {
433 		_movementTrackPaused = true;
434 		stopWalking(false);
435 	} else {
436 		_movementTrackPaused = false;
437 	}
438 }
439 
movementTrackUnpause()440 void Actor::movementTrackUnpause() {
441 	Vector3 waypointPosition;
442 	bool arrived;
443 
444 	_movementTrack->unpause();
445 	if (_movementTrackNextWaypointId >= 0 && _movementTrackPaused) {
446 		_vm->_waypoints->getXYZ(_movementTrackNextWaypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
447 		_walkInfo->setup(_id, _movementTrackNextRunning, _position, waypointPosition, false, &arrived);
448 		_movementTrackPaused = false;
449 	}
450 }
451 
movementTrackWaypointReached()452 void Actor::movementTrackWaypointReached() {
453 	if (!_movementTrack->isPaused() && _id != kActorMcCoy) {
454 		if (_movementTrackWalkingToWaypointId >= 0 && _movementTrackDelayOnNextWaypoint >= 0) {
455 #if !BLADERUNNER_ORIGINAL_BUGS
456 			Vector3 waypointPosition;
457 			int waypointSetId = _vm->_waypoints->getSetId(_movementTrackWalkingToWaypointId);
458 			_vm->_waypoints->getXYZ(_movementTrackWalkingToWaypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
459 			if (_setId != waypointSetId || waypointSetId != _vm->_actors[0]->_setId) {
460 				// teleport to target waypoint's set and position anyway
461 				// Code similar to movementTrackNext()
462 				setSetId(waypointSetId);
463 				if (_movementTrackNextAngle == -1) {
464 					_movementTrackNextAngle = 0;
465 				}
466 				setAtXYZ(waypointPosition, _movementTrackNextAngle, true, false, false);
467 			} else {
468 				// Honor the heading defined by the AI_Movement_Track_Append_With_Facing method
469 				if (_movementTrackNextAngle >= 0) {
470 					faceHeading(_movementTrackNextAngle, true);
471 				}
472 			}
473 #endif
474 			if (!_movementTrackDelayOnNextWaypoint) {
475 				_movementTrackDelayOnNextWaypoint = 1;
476 			}
477 
478 			if (_vm->_aiScripts->reachedMovementTrackWaypoint(_id, _movementTrackWalkingToWaypointId)) {
479 				// schedule next movementTrackNext() using the kActorTimerMovementTrack
480 				int32 delay = _movementTrackDelayOnNextWaypoint;
481 				if (delay > 1) {
482 					changeAnimationMode(kAnimationModeIdle, false);
483 					delay = _movementTrackDelayOnNextWaypoint; // todo: analyze if movement is changed in some aiscript->ChangeAnimationMode?
484 				}
485 				timerStart(kActorTimerMovementTrack, delay);
486 			}
487 		}
488 		_movementTrackWalkingToWaypointId = -1;
489 		_movementTrackDelayOnNextWaypoint =  0;
490 	}
491 }
492 
setAtXYZ(const Vector3 & position,int facing,bool snapFacing,bool moving,bool retired)493 void Actor::setAtXYZ(const Vector3 &position, int facing, bool snapFacing, bool moving, bool retired) {
494 	_position = position;
495 	setFacing(facing, snapFacing);
496 
497 	if (_vm->_scene->getSetId() == _setId) {
498 		_walkboxId = _vm->_scene->_set->findWalkbox(_position.x, _position.y);
499 	} else {
500 		_walkboxId = -1;
501 	}
502 
503 	setBoundingBox(_position, retired);
504 
505 	_vm->_sceneObjects->remove(_id + kSceneObjectOffsetActors);
506 
507 	if (_vm->_scene->getSetId() == _setId) {
508 		_vm->_sceneObjects->addActor(_id + kSceneObjectOffsetActors, _bbox, _screenRectangle, true, moving, _isTarget, retired);
509 	}
510 }
511 
setAtWaypoint(int waypointId,int angle,bool moving,bool retired)512 void Actor::setAtWaypoint(int waypointId, int angle, bool moving, bool retired) {
513 	Vector3 waypointPosition;
514 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
515 	setAtXYZ(waypointPosition, angle, true, moving, retired);
516 }
517 
loopWalk(const Vector3 & destination,int proximity,bool interruptible,bool runFlag,const Vector3 & start,float targetWidth,float targetSize,bool mustReach,bool * isRunningFlag,bool async)518 bool Actor::loopWalk(const Vector3 &destination, int proximity, bool interruptible, bool runFlag, const Vector3 &start, float targetWidth, float targetSize, bool mustReach, bool *isRunningFlag, bool async) {
519 	*isRunningFlag = false;
520 
521 	if (proximity > 0) {
522 		float dist = distance(_position, destination);
523 		if (dist - targetSize <= proximity) {
524 			return false;
525 		}
526 	}
527 
528 	if (mustReach && !async && _id != kActorMcCoy && proximity <= 24) {
529 		if (distance(_vm->_playerActor->_position, destination) <= 24.0f) {
530 			_vm->_playerActor->stepAway(destination, 48.0f);
531 		}
532 	}
533 
534 	if (_id != kActorMcCoy) {
535 		interruptible = false;
536 	}
537 
538 	Vector3 destinationX(destination);
539 
540 	if (proximity > 0) {
541 		findNearestPosition(&destinationX, targetWidth, proximity, targetSize, _position, destination);
542 	}
543 
544 	bool walking = walkTo(runFlag, destinationX, mustReach);
545 
546 	if (async) {
547 		return false;
548 	}
549 
550 	if (!walking && proximity > 0) {
551 		walking = walkTo(runFlag, destination, mustReach);
552 	}
553 
554 	if (!walking) {
555 		faceXYZ(destination, false);
556 		return false;
557 	}
558 
559 	if (_id != kActorMcCoy) {
560 		_vm->_mouse->disable();
561 	}
562 
563 	if (interruptible) {
564 		_vm->_isWalkingInterruptible = true;
565 		_vm->_interruptWalking = false;
566 	} else {
567 		_vm->playerLosesControl();
568 	}
569 
570 	if (mustReach) {
571 		_mustReachWalkDestination = true;
572 	}
573 
574 	bool wasInterrupted = false;
575 	while (_walkInfo->isWalking() && _vm->_gameIsRunning) {
576 		if (_walkInfo->isRunning()) {
577 			*isRunningFlag = true;
578 		}
579 		_vm->gameTick();
580 		if (_id == kActorMcCoy && interruptible && _vm->_interruptWalking) {
581 			stopWalking(false);
582 			wasInterrupted = true;
583 		}
584 	}
585 
586 	if (mustReach) {
587 		_mustReachWalkDestination = false;
588 	}
589 
590 	if (interruptible) {
591 		_vm->_isWalkingInterruptible = false;
592 	} else {
593 		_vm->playerGainsControl();
594 	}
595 
596 #if BLADERUNNER_ORIGINAL_BUGS
597 	if (!wasInterrupted && proximity == 0 && !_vm->_playerActorIdle) {
598 		setAtXYZ(destination, _facing, true, false, false);
599 	}
600 #else
601 	if (!wasInterrupted && proximity == 0
602 	    && (_id == kActorMcCoy && !_vm->_playerActorIdle)
603 	    && !isRetired()
604 	) {
605 		setAtXYZ(destination, _facing, true, false, false);
606 	}
607 #endif // BLADERUNNER_ORIGINAL_BUGS
608 
609 	if (_id != kActorMcCoy) {
610 		_vm->_mouse->enable();
611 	}
612 
613 	return wasInterrupted;
614 }
615 
walkTo(bool runFlag,const Vector3 & destination,bool mustReach)616 bool Actor::walkTo(bool runFlag, const Vector3 &destination, bool mustReach) {
617 	bool arrived;
618 	return _walkInfo->setup(_id, runFlag, _position, destination, mustReach, &arrived);
619 }
620 
loopWalkToActor(int otherActorId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)621 bool Actor::loopWalkToActor(int otherActorId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
622 	return loopWalk(_vm->_actors[otherActorId]->_position, proximity, interruptible, runFlag, _position, 24.0f, 24.0f, mustReach, isRunningFlag, false);
623 }
624 
loopWalkToItem(int itemId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)625 bool Actor::loopWalkToItem(int itemId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
626 	float x, y, z;
627 	int width, height;
628 	_vm->_items->getXYZ(itemId, &x, &y, &z);
629 	_vm->_items->getWidthHeight(itemId, &width, &height);
630 	Vector3 itemPosition(x, y, z);
631 
632 	return loopWalk(itemPosition, proximity, interruptible, runFlag, _position, width, 24.0f, mustReach, isRunningFlag, false);
633 }
634 
loopWalkToSceneObject(const Common::String & objectName,int proximity,bool interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)635 bool Actor::loopWalkToSceneObject(const Common::String &objectName, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
636 	int sceneObject = _vm->_scene->_set->findObject(objectName);
637 	if (sceneObject < 0) {
638 		return true;
639 	}
640 
641 	BoundingBox bbox;
642 	if (!_vm->_scene->_set->objectGetBoundingBox(sceneObject, &bbox)) {
643 		return true;
644 	}
645 
646 	float x0, y0, z0, x1, y1, z1;
647 	bbox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
648 
649 	float closestDistance = distance(_position.x, _position.z, x0, z0);
650 	float closestX = x0;
651 	float closestZ = z0;
652 
653 	float d = distance(_position.x, _position.z, x1, z0);
654 	if (d < closestDistance) {
655 		closestX = x1;
656 		closestZ = z0;
657 		closestDistance = d;
658 	}
659 
660 	d = distance(_position.x, _position.z, x1, z1);
661 	if (d < closestDistance) {
662 		closestX = x1;
663 		closestZ = z1;
664 		closestDistance = d;
665 	}
666 
667 	d = distance(_position.x, _position.z, x0, z1);
668 	if (d < closestDistance) {
669 		closestX = x0;
670 		closestZ = z1;
671 	}
672 
673 	bool inWalkbox;
674 	float y = _vm->_scene->_set->getAltitudeAtXZ(closestX, closestZ, &inWalkbox);
675 	Vector3 destination(closestX, y, closestZ);
676 
677 	return loopWalk(destination, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
678 }
679 
loopWalkToWaypoint(int waypointId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)680 bool Actor::loopWalkToWaypoint(int waypointId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
681 	Vector3 waypointPosition;
682 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
683 	return loopWalk(waypointPosition, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
684 }
685 
loopWalkToXYZ(const Vector3 & destination,int proximity,bool interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)686 bool Actor::loopWalkToXYZ(const Vector3 &destination, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
687 	return loopWalk(destination, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
688 }
689 
asyncWalkToWaypoint(int waypointId,int proximity,bool runFlag,bool mustReach)690 bool Actor::asyncWalkToWaypoint(int waypointId, int proximity, bool runFlag, bool mustReach) {
691 	bool running;
692 	Vector3 waypointPosition;
693 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
694 	return loopWalk(waypointPosition, proximity, false, runFlag, _position, 0.0f, 24.0f, mustReach, &running, true);
695 }
696 
asyncWalkToXYZ(const Vector3 & destination,int proximity,bool runFlag,bool mustReach)697 void Actor::asyncWalkToXYZ(const Vector3 &destination, int proximity, bool runFlag, bool mustReach) {
698 	bool running;
699 	loopWalk(destination, proximity, false, runFlag, _position, 0.0f, 24.0f, mustReach, &running, true);
700 }
701 
run()702 void Actor::run() {
703 	_walkInfo->run(_id);
704 }
705 
tick(bool forceDraw,Common::Rect * screenRect)706 bool Actor::tick(bool forceDraw, Common::Rect *screenRect) {
707 	int32 timeLeft = 0;
708 	bool needsUpdate = false;
709 	if (_fps > 0) {
710 		// Note that when (some?) actors are retired (eg. Zuben)
711 		// their _fps is still > 0 so they will periodically set needsUpdate to true in their tick() (here)
712 		// Also, the moment an actor is retired does not necessarily means their death animation finished playing
713 		// Their death animation may finish a while later.
714 		// Thus, until it finished, their screen rectangle will be likely changing at the draw() operation.
715 		// Typically at the end of a death animation, the actor keeps updating for the same frame
716 		// (ie the last of the death animation). At that point their screen rectangle won't change at the draw() operation.
717 		timerUpdate(kActorTimerAnimationFrame);
718 		timeLeft = timerLeft(kActorTimerAnimationFrame);
719 		needsUpdate = (timeLeft <= 0);
720 	} else if (_fps == 0) {
721 		needsUpdate = false;
722 	} else if (forceDraw) {
723 		needsUpdate = true;
724 		timeLeft = 0;
725 	}
726 
727 	if (needsUpdate) {
728 		int newAnimation = 0, newFrame = 0;
729 		_vm->_aiScripts->updateAnimation(_id, &newAnimation, &newFrame);
730 
731 		assert(newFrame >= 0);
732 
733 		if (_animationId != newAnimation) {
734 			if (_fps != 0 && _fps != -1) {
735 				_animationId = newAnimation;
736 				setFPS(-2);
737 			}
738 		}
739 		_animationId = newAnimation;
740 		_animationFrame = newFrame;
741 
742 		Vector3 positionChange = _vm->_sliceAnimations->getPositionChange(_animationId);
743 		float angleChange = _vm->_sliceAnimations->getFacingChange(_animationId);
744 
745 		if (_id == kActorHysteriaPatron1) {
746 			positionChange.x = 0.0f;
747 			positionChange.y = 0.0f;
748 			positionChange.z = 0.0f;
749 		}
750 
751 		if (isWalking()) {
752 			if (0.0f <= positionChange.y) {
753 				positionChange.y = -4.0f;
754 			}
755 
756 			_targetFacing = -1;
757 
758 			bool walkInterrupted = _walkInfo->tick(_id, -positionChange.y, _mustReachWalkDestination);
759 			Vector3 pos;
760 			int facing;
761 			_walkInfo->getCurrentPosition(_id, &pos, &facing);
762 			setAtXYZ(pos, facing, false, _isMoving, false);
763 			if (walkInterrupted) {
764 				_vm->_actors[_id]->changeAnimationMode(kAnimationModeIdle);
765 				movementTrackWaypointReached();
766 				if (inCombat()) {
767 					changeAnimationMode(_animationModeCombatIdle, false);
768 				} else {
769 					changeAnimationMode(kAnimationModeIdle, false);
770 				}
771 			}
772 		} else {
773 			// actor is not walking / is idle
774 			if (angleChange != 0.0f) {
775 				int facingChange = angleChange * (512.0f / M_PI);
776 				if (facingChange != 0) {
777 					_facing = _facing - facingChange;
778 					while (_facing < 0) {
779 						_facing += 1024;
780 					}
781 
782 					while (_facing >= 1024) {
783 						_facing -= 1024;
784 					}
785 				}
786 			}
787 
788 			if (_vm->_cutContent) {
789 				// the following Generic Walker models don't have an animation Id that is idle
790 				// so we use a frame of their walking animation to show them as stopped
791 				// However, we also need to override the positionChange vector for their walking animation too
792 				if ( (_id == kActorGenwalkerA || _id == kActorGenwalkerB || _id == kActorGenwalkerC)
793 				     &&
794 				     (_animationId == 436 || _animationId == 434 || _animationId == 435 || _animationId == 422 || _animationId == 423)
795 				) {
796 					positionChange.x = 0.0f;
797 					positionChange.y = 0.0f;
798 					positionChange.z = 0.0f;
799 				}
800 			}
801 
802 			if (0.0f != positionChange.x || 0.0f != positionChange.y || 0.0f != positionChange.z) {
803 				if (_actorSpeed.x != 0.0f) {
804 					positionChange.x = positionChange.x * _actorSpeed.x;
805 				}
806 				if (_actorSpeed.y != 0.0f) {
807 					positionChange.y = positionChange.y * _actorSpeed.y;
808 				}
809 				if (_actorSpeed.z != 0.0f) {
810 					positionChange.z = positionChange.z * _actorSpeed.z;
811 				}
812 
813 				float sinx = _vm->_sinTable1024->at(_facing);
814 				float cosx = _vm->_cosTable1024->at(_facing);
815 
816 				float originalX = _position.x;
817 				float originalY = _position.y;
818 				float originalZ = _position.z;
819 
820 				// Yes, Z & Y are switched between world space and model space. X is also negated for some unknown reason (wrong dirertion for angles?)
821 
822 				_position.x = _position.x - positionChange.x * cosx - positionChange.y * sinx;
823 				_position.z = _position.z - positionChange.x * sinx + positionChange.y * cosx;
824 				_position.y = _position.y + positionChange.z;
825 
826 				if (_vm->_sceneObjects->existsOnXZ(_id + kSceneObjectOffsetActors, _position.x, _position.z, false, false) == 1 && !_isImmuneToObstacles) {
827 					_position.x = originalX;
828 					_position.y = originalY;
829 					_position.z = originalZ;
830 				}
831 				setAtXYZ(_position, _facing, true, _isMoving, _isRetired);
832 			}
833 		}
834 	}
835 
836 	bool isVisible = false;
837 	if (!_isInvisible) {
838 		// draw() will set the new screenRect for the actor
839 		// based on the current animation frame
840 		// the new screenRect may be empty, in which case draw returns false (thus isVisible will be false then).
841 		isVisible = draw(screenRect);
842 		if (isVisible) {
843 			_screenRectangle = *screenRect;
844 		}
845 	}
846 
847 #if !BLADERUNNER_ORIGINAL_BUGS
848 	// For consistency we need to init the screen rectangle and bbox for the actor's *scene object*
849 	// in a new scene (since we also reset the screen rectangle at Scene::open())
850 	// for the case of the actor not moving
851 
852 	if (_vm->_scene->getSetId() == _setId
853 	    && !_isInvisible
854 	    && _vm->_sceneObjects->findById(_id + kSceneObjectOffsetActors) != -1) {
855 		if (_vm->_sceneObjects->isEmptyScreenRectangle(_id + kSceneObjectOffsetActors)) {
856 			if (isVisible) {
857 				Vector3 pos = getPosition();
858 				int facing = getFacing();
859 				setAtXYZ(pos, facing, true, _isMoving, _isRetired);
860 			} else {
861 				resetScreenRectangleAndBbox();
862 				_vm->_sceneObjects->resetScreenRectangleAndBbox(_id + kSceneObjectOffsetActors);
863 			}
864 		} else if (_vm->_sceneObjects->compareScreenRectangle(_id + kSceneObjectOffsetActors, _screenRectangle) != 0) {
865 			if (isVisible) {
866 				// keep actor's _screenRectangle synched with sceneObject's actor's screen rectange
867 				// don't do a setAtXYZ here though
868 				_vm->_sceneObjects->synchScreenRectangle(_id + kSceneObjectOffsetActors, _screenRectangle);
869 			} else {
870 				resetScreenRectangleAndBbox();
871 				_vm->_sceneObjects->resetScreenRectangleAndBbox(_id + kSceneObjectOffsetActors);
872 			}
873 		}
874 	}
875 
876 	if ((_vm->_scene->getSetId() != _setId || _isInvisible || !isVisible)
877 	    && !_screenRectangle.isEmpty()
878 	) {
879 		resetScreenRectangleAndBbox();
880 		if (_vm->_sceneObjects->findById(_id + kSceneObjectOffsetActors) != -1
881 		    && !_vm->_sceneObjects->isEmptyScreenRectangle(_id + kSceneObjectOffsetActors)) {
882 			_vm->_sceneObjects->resetScreenRectangleAndBbox(_id + kSceneObjectOffsetActors);
883 		}
884 	}
885 #endif
886 
887 	if (needsUpdate) {
888 		// timeLeft is supposed to be negative or 0 here in the original!
889 		int32 nextFrameTime = timeLeft + _frameMs;
890 		if (nextFrameTime <= 0) {
891 			nextFrameTime = 1;
892 		}
893 		timerStart(kActorTimerAnimationFrame, nextFrameTime);
894 	}
895 	if (_targetFacing >= 0) {
896 		if (_targetFacing == _facing) {
897 			_targetFacing = -1;
898 		} else {
899 			setFacing(_targetFacing, false);
900 		}
901 	}
902 	return isVisible;
903 }
904 
tickCombat()905 void Actor::tickCombat() {
906 	if (_id != kActorMcCoy && !_isRetired && _inCombat) {
907 		_combatInfo->tick();
908 	}
909 }
910 
draw(Common::Rect * screenRect)911 bool Actor::draw(Common::Rect *screenRect) {
912 	Vector3 drawPosition(_position.x, -_position.z, _position.y + 2.0);
913 
914 #if !BLADERUNNER_ORIGINAL_BUGS
915 	// In the original game, Moraji appears to be floating above the ground a bit
916 	if (_id == kActorMoraji && _setId == kSetDR01_DR02_DR04) {
917 		drawPosition.z -= 6.0f;
918 	}
919 #endif
920 
921 	float drawAngle = M_PI - _facing * (M_PI / 512.0f);
922 	float drawScale = _scale;
923 
924 	if (_vm->_shortyMode) {
925 		drawScale = 0.7f;
926 	}
927 
928 	_vm->_sliceRenderer->drawInWorld(_animationId, _animationFrame, drawPosition, drawAngle, drawScale, _vm->_surfaceFront, _vm->_zbuffer->getData());
929 	_vm->_sliceRenderer->getScreenRectangle(screenRect, _animationId, _animationFrame, drawPosition, drawAngle, drawScale);
930 
931 	return !screenRect->isEmpty();
932 }
933 
getSetId() const934 int Actor::getSetId() const {
935 	return _setId;
936 }
937 
setSetId(int setId)938 void Actor::setSetId(int setId) {
939 	if (_setId == setId) {
940 		return;
941 	}
942 
943 	int i;
944 
945 	// leaving _setId for setId
946 	if (_setId > 0) {
947 		for (i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
948 			if (_vm->_actors[i]->_id != _id && _vm->_actors[i]->_setId == _setId) {
949 				_vm->_aiScripts->otherAgentExitedThisSet(i, _id);
950 			}
951 		}
952 	}
953 	// _setId updated to new (arrived in) setId
954 	_setId = setId;
955 	_vm->_aiScripts->enteredSet(_id, _setId);
956 	if (_setId > 0) {
957 		for (i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
958 			if (_vm->_actors[i]->_id != _id && _vm->_actors[i]->_setId == _setId) {
959 				_vm->_aiScripts->otherAgentEnteredThisSet(i, _id);
960 			}
961 		}
962 	}
963 }
964 
setFacing(int facing,bool halfOrSet)965 void Actor::setFacing(int facing, bool halfOrSet) {
966 	if (facing < 0 || facing >= 1024) {
967 		return;
968 	}
969 
970 	if (halfOrSet) {
971 		_facing = facing;
972 		return;
973 	}
974 
975 	int cw;
976 	int ccw;
977 	int offset;
978 
979 	if (facing > _facing) {
980 		cw = facing - _facing;
981 		ccw = _facing + 1024 - facing;
982 	} else {
983 		ccw = _facing - facing;
984 		cw = facing + 1024 - _facing;
985 	}
986 	if (cw < ccw) {
987 		if (cw <= 32) {
988 			offset = cw;
989 		} else {
990 			offset = cw / 2;
991 		}
992 	} else {
993 		if (ccw <= 32) {
994 			offset = -ccw;
995 		} else {
996 			offset = -ccw / 2;
997 		}
998 	}
999 
1000 	_facing += offset;
1001 
1002 	while (_facing < 0) {
1003 		_facing += 1024;
1004 	}
1005 
1006 	while (_facing >= 1024) {
1007 		_facing -= 1024;
1008 	}
1009 }
1010 
setBoundingBox(const Vector3 & position,bool retired)1011 void Actor::setBoundingBox(const Vector3 &position, bool retired) {
1012 	if (retired || _isRetired) {
1013 		_bbox.setXYZ(position.x - (_retiredWidth / 2.0f),
1014 		             position.y,
1015 		             position.z - (_retiredWidth / 2.0f),
1016 
1017 		             position.x + (_retiredWidth / 2.0f),
1018 		             position.y + _retiredHeight,
1019 		             position.z + (_retiredWidth / 2.0f));
1020 	} else {
1021 		_bbox.setXYZ(position.x - 12.0f,
1022 		             position.y + 6.0f,
1023 		             position.z - 12.0f,
1024 
1025 		             position.x + 12.0f,
1026 		             position.y + 72.0f,
1027 		             position.z + 12.0f);
1028 	}
1029 }
1030 
resetScreenRectangleAndBbox()1031 void Actor::resetScreenRectangleAndBbox() {
1032 	_screenRectangle.left   = -1;
1033 	_screenRectangle.top    = -1;
1034 	_screenRectangle.right  = -1;
1035 	_screenRectangle.bottom = -1;
1036 	_bbox.setXYZ(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
1037 }
1038 
distanceFromView(View * view) const1039 float Actor::distanceFromView(View *view) const{
1040 	float xDist = _position.x - view->_cameraPosition.x;
1041 	float zDist = _position.z + view->_cameraPosition.y; // y<->z is intentional, not a bug
1042 	return sqrt(xDist * xDist + zDist * zDist);
1043 }
1044 
isWalking() const1045 bool Actor::isWalking() const {
1046 	return _walkInfo->isWalking();
1047 }
1048 
isRunning() const1049 bool Actor::isRunning() const {
1050 	return _walkInfo->isRunning();
1051 }
1052 
stopWalking(bool value)1053 void Actor::stopWalking(bool value) {
1054 	if (value && _id == kActorMcCoy) {
1055 		_vm->_playerActorIdle = true;
1056 	}
1057 
1058 	if (isWalking()) {
1059 		_walkInfo->stop(_id, true, _animationModeCombatIdle, kAnimationModeIdle);
1060 	} else if (inCombat()) {
1061 		changeAnimationMode(_animationModeCombatIdle, false);
1062 	} else {
1063 		changeAnimationMode(kAnimationModeIdle, false);
1064 	}
1065 }
1066 
faceActor(int otherActorId,bool animate)1067 void Actor::faceActor(int otherActorId, bool animate) {
1068 	if (_setId != _vm->_scene->getSetId()) {
1069 		return;
1070 	}
1071 
1072 	Actor *otherActor = _vm->_actors[otherActorId];
1073 
1074 	if (_setId != otherActor->_setId) {
1075 		return;
1076 	}
1077 
1078 	faceXYZ(otherActor->_position, animate);
1079 }
1080 
faceObject(const Common::String & objectName,bool animate)1081 void Actor::faceObject(const Common::String &objectName, bool animate) {
1082 	int objectId = _vm->_scene->findObject(objectName);
1083 	if (objectId == -1) {
1084 		return;
1085 	}
1086 
1087 	BoundingBox boundingBox;
1088 	_vm->_scene->objectGetBoundingBox(objectId, &boundingBox);
1089 
1090 	float x0, y0, z0, x1, y1, z1;
1091 	boundingBox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
1092 
1093 	float x = (x1 + x0) / 2.0f;
1094 	float z = (z1 + z0) / 2.0f;
1095 	faceXYZ(x, y0, z, animate);
1096 }
1097 
faceItem(int itemId,bool animate)1098 void Actor::faceItem(int itemId, bool animate) {
1099 	float x, y, z;
1100 	_vm->_items->getXYZ(itemId, &x, &y, &z);
1101 	faceXYZ(x, y, z, animate);
1102 }
1103 
faceWaypoint(int waypointId,bool animate)1104 void Actor::faceWaypoint(int waypointId, bool animate) {
1105 	float x, y, z;
1106 	_vm->_waypoints->getXYZ(waypointId, &x, &y, &z);
1107 	faceXYZ(x, y, z, animate);
1108 }
1109 
faceXYZ(float x,float y,float z,bool animate)1110 void Actor::faceXYZ(float x, float y, float z, bool animate) {
1111 	if (isWalking()) {
1112 		stopWalking(false);
1113 	}
1114 	if (x == _position.x && z == _position.z) {
1115 		return;
1116 	}
1117 
1118 	int heading = angle_1024(_position.x, _position.z, x, z);
1119 	faceHeading(heading, animate);
1120 }
1121 
faceXYZ(const Vector3 & pos,bool animate)1122 void Actor::faceXYZ(const Vector3 &pos, bool animate) {
1123 	faceXYZ(pos.x, pos.y, pos.z, animate);
1124 }
1125 
faceCurrentCamera(bool animate)1126 void Actor::faceCurrentCamera(bool animate) {
1127 	faceXYZ(_vm->_view->_cameraPosition.x, _vm->_view->_cameraPosition.z, -_vm->_view->_cameraPosition.y, animate); // y<->z is intentional, not a bug
1128 }
1129 
faceHeading(int heading,bool animate)1130 void Actor::faceHeading(int heading, bool animate) {
1131 	if (heading != _facing) {
1132 		if (animate) {
1133 			_targetFacing = heading;
1134 		} else {
1135 			setFacing(heading, true);
1136 		}
1137 	}
1138 }
1139 
modifyFriendlinessToOther(int otherActorId,signed int change)1140 void Actor::modifyFriendlinessToOther(int otherActorId, signed int change) {
1141 	_friendlinessToOther[otherActorId] = CLIP(_friendlinessToOther[otherActorId] + change, 0, 100);
1142 }
1143 
setFriendlinessToOther(int otherActorId,int friendliness)1144 void Actor::setFriendlinessToOther(int otherActorId, int friendliness) {
1145 	_friendlinessToOther[otherActorId] = CLIP(friendliness, 0, 100);
1146 }
1147 
checkFriendlinessAndHonesty(int otherActorId)1148 bool Actor::checkFriendlinessAndHonesty(int otherActorId) {
1149 	int honestyDiff = 2 * _friendlinessToOther[otherActorId] - _honesty;
1150 	uint friendlinessRange;
1151 
1152 	if (honestyDiff > 30) {
1153 		friendlinessRange = 100;
1154 	} else if (honestyDiff >= 0 && honestyDiff <= 30) {
1155 		friendlinessRange = 50;
1156 	} else {
1157 		friendlinessRange = 0;
1158 	}
1159 
1160 	return _vm->_rnd.getRandomNumberRng(1, 100) <= friendlinessRange;
1161 }
1162 
setHonesty(int honesty)1163 void Actor::setHonesty(int honesty) {
1164 	_honesty = CLIP(honesty, 0, 100);
1165 }
1166 
setIntelligence(int intelligence)1167 void Actor::setIntelligence(int intelligence) {
1168 	_intelligence = CLIP(intelligence, 0, 100);
1169 }
1170 
setStability(int stability)1171 void Actor::setStability(int stability) {
1172 	_stability = CLIP(stability, 0, 100);
1173 }
1174 
setCombatAggressiveness(int combatAggressiveness)1175 void Actor::setCombatAggressiveness(int combatAggressiveness) {
1176 	_combatAggressiveness = CLIP(combatAggressiveness, 0, 100);
1177 }
1178 
setInvisible(bool isInvisible)1179 void Actor::setInvisible(bool isInvisible) {
1180 	_isInvisible = isInvisible;
1181 }
1182 
setImmunityToObstacles(bool isImmune)1183 void Actor::setImmunityToObstacles(bool isImmune) {
1184 	_isImmuneToObstacles = isImmune;
1185 }
1186 
modifyCombatAggressiveness(signed int change)1187 void Actor::modifyCombatAggressiveness(signed int change) {
1188 	_combatAggressiveness = CLIP(_combatAggressiveness + change, 0, 100);
1189 }
1190 
modifyHonesty(signed int change)1191 void Actor::modifyHonesty(signed int change) {
1192 	_honesty = CLIP(_honesty + change, 0, 100);
1193 }
1194 
modifyIntelligence(signed int change)1195 void Actor::modifyIntelligence(signed int change) {
1196 	_intelligence = CLIP(_intelligence + change, 0, 100);
1197 }
1198 
modifyStability(signed int change)1199 void Actor::modifyStability(signed int change) {
1200 	_stability = CLIP(_stability + change, 0, 100);
1201 }
1202 
setFlagDamageAnimIfMoving(bool value)1203 void Actor::setFlagDamageAnimIfMoving(bool value) {
1204 	_damageAnimIfMoving = value;
1205 }
1206 
getFlagDamageAnimIfMoving() const1207 bool Actor::getFlagDamageAnimIfMoving() const {
1208 	return _damageAnimIfMoving;
1209 }
1210 
getSitcomRatio() const1211 int Actor::getSitcomRatio() const {
1212 	return _sitcomRatio;
1213 }
1214 
retire(bool retired,int width,int height,int retiredByActorId)1215 void Actor::retire(bool retired, int width, int height, int retiredByActorId) {
1216 	_isRetired = retired;
1217 	_retiredWidth = MAX(width, 0);
1218 	_retiredHeight = MAX(height, 0);
1219 	if (_id == kActorMcCoy && _isRetired) {
1220 		_vm->playerLosesControl();
1221 		_vm->_playerDead = true;
1222 	}
1223 	if (_isRetired) {
1224 		_vm->_aiScripts->retired(_id, retiredByActorId);
1225 	}
1226 }
1227 
setTarget(bool target)1228 void Actor::setTarget(bool target) {
1229 	_isTarget = target;
1230 }
1231 
setCurrentHP(int hp)1232 void Actor::setCurrentHP(int hp) {
1233 	_currentHP = CLIP(hp, 0, 100);
1234 	if (hp > 0) {
1235 		retire(false, 0, 0, -1);
1236 	}
1237 }
1238 
setHealth(int hp,int maxHp)1239 void Actor::setHealth(int hp, int maxHp) {
1240 	if (hp > maxHp) {
1241 		hp = maxHp;
1242 	}
1243 	_currentHP = CLIP(hp,    0, 100);
1244 	_maxHP     = CLIP(maxHp, 0, 100);
1245 	if (hp > 0) {
1246 		retire(false, 0, 0, -1);
1247 	}
1248 }
1249 
modifyCurrentHP(signed int change)1250 void Actor::modifyCurrentHP(signed int change) {
1251 	_currentHP = CLIP(_currentHP + change, 0, 100);
1252 	if (_currentHP > 0) {
1253 		retire(false, 0, 0, -1);
1254 	}
1255 }
1256 
modifyMaxHP(signed int change)1257 void Actor::modifyMaxHP(signed int change) {
1258 	_maxHP = CLIP(_maxHP + change, 0, 100);
1259 }
1260 
1261 
combatModeOn(int initialState,bool rangedAttack,int enemyId,int waypointType,int animationModeCombatIdle,int animationModeCombatWalk,int animationModeCombatRun,int fleeRatio,int coverRatio,int attackRatio,int damage,int range,bool unstoppable)1262 void Actor::combatModeOn(int initialState, bool rangedAttack, int enemyId, int waypointType, int animationModeCombatIdle, int animationModeCombatWalk, int animationModeCombatRun, int fleeRatio, int coverRatio, int attackRatio, int damage, int range, bool unstoppable) {
1263 	_animationModeCombatIdle = animationModeCombatIdle;
1264 	_animationModeCombatWalk = animationModeCombatWalk;
1265 	_animationModeCombatRun = animationModeCombatRun;
1266 	_inCombat = true;
1267 	if (_id != kActorMcCoy && enemyId != -1) {
1268 		_combatInfo->combatOn(_id, initialState, rangedAttack, enemyId, waypointType, fleeRatio, coverRatio, attackRatio, damage, range, unstoppable);
1269 	}
1270 	stopWalking(false);
1271 	changeAnimationMode(_animationModeCombatIdle, false);
1272 	for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
1273 		Actor *otherActor = _vm->_actors[i];
1274 		if (i != _id && otherActor->_setId == _setId && !otherActor->_isRetired) {
1275 			_vm->_aiScripts->otherAgentEnteredCombatMode(i, _id, true);
1276 		}
1277 	}
1278 }
1279 
combatModeOff()1280 void Actor::combatModeOff() {
1281 	if (_id != kActorMcCoy) {
1282 		_combatInfo->combatOff();
1283 	}
1284 	_inCombat = false;
1285 	stopWalking(false);
1286 	changeAnimationMode(kAnimationModeIdle, false);
1287 	for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
1288 		Actor *otherActor = _vm->_actors[i];
1289 		if (i != _id && otherActor->_setId == _setId && !otherActor->_isRetired) {
1290 			_vm->_aiScripts->otherAgentEnteredCombatMode(i, _id, false);
1291 		}
1292 	}
1293 }
1294 
distanceFromActor(int otherActorId)1295 float Actor::distanceFromActor(int otherActorId) {
1296 	return (_position - _vm->_actors[otherActorId]->_position).length();
1297 }
1298 
angleTo(const Vector3 & target) const1299 int Actor::angleTo(const Vector3 &target) const {
1300 	int angle = angle_1024(_position.x, _position.z, target.x, target.z) - _facing;
1301 	if (angle < -512) {
1302 		angle += 1024;
1303 	} else if (angle > 512) {
1304 		angle -= 1024;
1305 	}
1306 	return angle;
1307 }
1308 
getX() const1309 float Actor::getX() const {
1310 	return _position.x;
1311 }
1312 
getY() const1313 float Actor::getY() const {
1314 	return _position.y;
1315 }
1316 
getZ() const1317 float Actor::getZ() const {
1318 	return _position.z;
1319 }
1320 
getXYZ() const1321 Vector3 Actor::getXYZ() const {
1322 	return _position;
1323 }
1324 
getFacing() const1325 int Actor::getFacing() const {
1326 	return _facing;
1327 }
1328 
getAnimationMode() const1329 int Actor::getAnimationMode() const {
1330 	return _animationMode;
1331 }
1332 
getAnimationId() const1333 int Actor::getAnimationId() const {
1334 	return _animationId;
1335 }
1336 
setGoal(int goalNumber)1337 void Actor::setGoal(int goalNumber) {
1338 	int oldGoalNumber = _goalNumber;
1339 	_goalNumber = goalNumber;
1340 	if (goalNumber == oldGoalNumber) {
1341 		return;
1342 	}
1343 
1344 	_vm->_aiScripts->goalChanged(_id, oldGoalNumber, goalNumber);
1345 	_vm->_sceneScript->actorChangedGoal(_id, goalNumber, oldGoalNumber, _vm->_scene->getSetId() == _setId);
1346 }
1347 
getGoal() const1348 int Actor::getGoal() const {
1349 	return _goalNumber;
1350 }
1351 
speechPlay(int sentenceId,bool voiceOver)1352 void Actor::speechPlay(int sentenceId, bool voiceOver) {
1353 	Common::String name = Common::String::format( "%02d-%04d%s.AUD", _id, sentenceId, _vm->_languageCode.c_str());
1354 
1355 	int pan = 0;
1356 	if (!voiceOver && _id != BladeRunnerEngine::kActorVoiceOver) {
1357 #if BLADERUNNER_ORIGINAL_BUGS
1358 		Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
1359 		pan = (75 * (2 *  CLIP<int>(screenPosition.x, 0, 640) - 640)) / 640; // map [0..640] to [-75..75]
1360 #else
1361 		// There are a few situations whereby
1362 		// the actor is not actually in the set when speaking,
1363 		// and the original code would result in audio playing
1364 		// from a wrong balance point (bad pan value).
1365 		// We capture these situations here and set the pan explicitly.
1366 		// Mainly, these are:
1367 		// tv news, machine voices (from PCs, Doors etc)
1368 		// dispatch calls when used as actor speech and not as ambient sounds
1369 		// phone calls (From Guzza, to Guzza, Lucy, Clovis, Dektora, Steele)
1370 		// and other special cases, where the actor is not actually in the scene.
1371 		//
1372 		// pan:: map [0..640] to [-75..75]
1373 		if ((_id == kActorNewscaster     && sentenceId >= 0    && sentenceId <= 240)
1374 		 || (_id == kActorTyrell         && sentenceId >= 430  && sentenceId <= 460)
1375 		 || (_id == kActorGuzza          && sentenceId >= 1540 && sentenceId <= 1600)
1376 		 || (_id == kActorGovernorKolvig && sentenceId >= 80   && sentenceId <= 130)) {
1377 			// MA04 TV
1378 			// x: 149 --> pan: -41
1379 			// PS05 TV
1380 			// x: 527 --> pan:  48
1381 			// These quotes only play in kSetMA04 and kSetPS05
1382 			pan = (_vm->_playerActor->getSetId() == kSetMA04) ? -41 : 48;
1383 		} else if ((_id == kActorLucy     && sentenceId >= 500  && sentenceId <= 640)
1384 		        || (_id == kActorClovis   && sentenceId >= 310  && sentenceId <= 540)
1385 		        || (_id == kActorDektora  && sentenceId >= 220  && sentenceId <= 490)
1386 		        || (_id == kActorSteele   && sentenceId >= 680  && sentenceId <= 820)
1387 		        || (_id == kActorGuzza    && sentenceId >= 0    && sentenceId <= 70)) {
1388 			// MA04 phone
1389 			// x: 351 --> pan: 7
1390 			// These quotes only play in kSetMA04
1391 			pan = 7;
1392 		} else if (_id == kActorGuzza     && sentenceId >= 1380 && sentenceId <= 1480) {
1393 			// NR02 phone (Taffy's)
1394 			// x: 300 --> pan: -5
1395 			// DR06 phone (Twin's Apartment)
1396 			// x: 565 --> pan: 57
1397 			// These quotes only play in either kSetNR02 or kSetDR06
1398 			pan = (_vm->_playerActor->getSetId() == kSetNR02) ? -5 : 57;
1399 		} else if (_id == kActorAnsweringMachine) {
1400 			if (sentenceId == 0) {
1401 				// kSetBB07 - Bradbury, Sebastian's Lab Computer   (0)
1402 				// x: 567 --> pan: 58
1403 				pan = 58;
1404 			} else if (sentenceId >= 10 && sentenceId <= 50) {
1405 				// kSetDR06 - Luther & Lance answering machine [10, 50]
1406 				// x: 278 --> pan: -11
1407 				pan = -11;
1408 			} else if (sentenceId == 60) {
1409 				// kSetDR06 - Twin's Apartment
1410 				// Restored Cut Content quote
1411 				// (Twin's Lab has a door announcer -- as heard in the video intro of Act 4 too)
1412 				// Pan will be at vidphone spot
1413 				// x: 565 --> pan: 57
1414 				pan = 57;
1415 			} else if (sentenceId >= 330 && sentenceId <= 370) {
1416 				// Mainframe terminal - x: 500 --> pan: 42
1417 				// These quotes only play in kSetPS06
1418 				pan = 42;
1419 			}
1420 			// Default pan is already set to 0 (ie. center)
1421 			// Includes Maze Scenes (kSetPS10_PS11_PS12_PS13) - quotes [280, 320]
1422 			// Also ESPER, KIA, VK, Elevator (MA06) and Spinner.
1423 		} else {
1424 			Vector3 actorScreenPosition;
1425 			switch (_id) {
1426 			case kActorLance:
1427 				// Lance does not have a model, but he is "attached" to his twin Luther
1428 				actorScreenPosition = _vm->_view->calculateScreenPosition(_vm->_actors[kActorLuther]->getPosition());
1429 				break;
1430 			case kActorDispatcher:
1431 				// kActorDispatcher does not have a model, but should be "attached" to McCoy or Steele
1432 				if (sentenceId >= 0 && sentenceId <= 40) {
1433 					// Steele's radio
1434 					actorScreenPosition = _vm->_view->calculateScreenPosition(_vm->_actors[kActorSteele]->getPosition());
1435 				} else {
1436 					// McCoy's radio
1437 					actorScreenPosition = _vm->_view->calculateScreenPosition(_vm->_playerActor->getPosition());
1438 				}
1439 				break;
1440 			case kActorOfficerLeary:
1441 				// Voice from dispatcher is attached to McCoy (coming from his radio)
1442 				if ((sentenceId >= 240 && sentenceId <= 450)
1443 				    || (sentenceId == 460 && _vm->_language == Common::DE_DEU)
1444 				    || (sentenceId >= 480 && sentenceId <= 530 && _vm->_language == Common::ES_ESP)
1445 				    || (sentenceId >= 520 && sentenceId <= 530 && _vm->_language == Common::IT_ITA)
1446 				) {
1447 					// responding to dispatch
1448 					actorScreenPosition = _vm->_view->calculateScreenPosition(_vm->_playerActor->getPosition());
1449 				} else {
1450 					actorScreenPosition = _vm->_view->calculateScreenPosition(_position);
1451 				}
1452 				break;
1453 			case kActorOfficerGrayford:
1454 				// Voice from dispatcher is attached to McCoy (coming from his radio)
1455 				if ((sentenceId >= 360 && sentenceId <= 450)
1456 				    || (sentenceId == 460 && _vm->_language == Common::DE_DEU)
1457 				    || (sentenceId >= 470 && sentenceId <= 550)
1458 				    || (sentenceId >= 560 && sentenceId <= 610 && _vm->_language == Common::ES_ESP)
1459 				) {
1460 					// responding to dispatch
1461 					actorScreenPosition = _vm->_view->calculateScreenPosition(_vm->_playerActor->getPosition());
1462 				} else {
1463 					actorScreenPosition = _vm->_view->calculateScreenPosition(_position);
1464 				}
1465 				break;
1466 			default:
1467 				actorScreenPosition = _vm->_view->calculateScreenPosition(_position);
1468 			}
1469 			pan	= (75 * (2 *  CLIP<int>(actorScreenPosition.x, 0, 640) - 640)) / 640; // map [0..640] to [-75..75]
1470 		}
1471 		// debug("actor: %d, pan: %d", _id, pan);
1472 #endif // BLADERUNNER_ORIGINAL_BUGS
1473 	}
1474 
1475 	_vm->_subtitles->loadInGameSubsText(_id, sentenceId);
1476 	_vm->_subtitles->show();
1477 
1478 	_vm->_audioSpeech->playSpeech(name, pan);
1479 }
1480 
speechStop()1481 void Actor::speechStop() {
1482 	_vm->_subtitles->hide();
1483 	_vm->_audioSpeech->stopSpeech();
1484 }
1485 
isSpeeching()1486 bool Actor::isSpeeching() {
1487 	return _vm->_audioSpeech->isPlaying();
1488 }
1489 
addClueToDatabase(int clueId,int weight,bool clueAcquired,bool unknownFlag,int fromActorId)1490 void Actor::addClueToDatabase(int clueId, int weight, bool clueAcquired, bool unknownFlag, int fromActorId) {
1491 	_clues->add(_id, clueId, weight, clueAcquired, unknownFlag, fromActorId);
1492 }
1493 
canAcquireClue(int clueId) const1494 bool Actor::canAcquireClue(int clueId) const {
1495 	return _clues->exists(clueId);
1496 }
1497 
acquireClue(int clueId,bool unknownFlag,int fromActorId)1498 void Actor::acquireClue(int clueId, bool unknownFlag, int fromActorId) {
1499 	bool hasAlready = hasClue(clueId);
1500 	_clues->acquire(clueId, unknownFlag, fromActorId);
1501 	if (!hasAlready) {
1502 		_vm->_aiScripts->receivedClue(_id, clueId, fromActorId);
1503 	}
1504 }
1505 
loseClue(int clueId)1506 void Actor::loseClue(int clueId) {
1507 	_clues->lose(clueId);
1508 }
1509 
hasClue(int clueId) const1510 bool Actor::hasClue(int clueId) const {
1511 	return _clues->isAcquired(clueId);
1512 }
1513 
1514 // This method is used exclusively for transfers from and to Mainframe.
1515 // It copies clues from this actor (_id) to a target actor (actorId).
1516 // Keep in mind that actors other than McCoy can also transfer clues to Mainframe (eg. Steele)
1517 // or retrieve from Mainframe (eg. Klein)
1518 // see: ScriptBase::Actor_Clues_Transfer_New_From_Mainframe()
1519 //      ScriptBase::Actor_Clues_Transfer_New_To_Mainframe()
1520 // In Restored Content it will skip transfering clues that are Intangible (default clue type)
1521 // since those clues do not actually show up in McCoy's KIA
copyClues(int actorId)1522 bool Actor::copyClues(int actorId) {
1523 	bool newCluesAcquired = false;
1524 	Actor *otherActor = _vm->_actors[actorId];
1525 	for (int i = 0; i < (int)_vm->_gameInfo->getClueCount(); ++i) {
1526 		int clueId = i;
1527 		if (hasClue(clueId)
1528 		    && !_clues->isPrivate(clueId)
1529 		    && (!_vm->_cutContent || _vm->_crimesDatabase->getAssetType(clueId) != kClueTypeIntangible)
1530 		    && otherActor->canAcquireClue(clueId)
1531 		    && !otherActor->hasClue(clueId)) {
1532 			int fromActorId = _id;
1533 			if (_id == BladeRunnerEngine::kActorVoiceOver) {
1534 				fromActorId = _clues->getFromActorId(clueId);
1535 			}
1536 			if (_vm->_cutContent
1537 			    && ((_id == BladeRunnerEngine::kActorVoiceOver && actorId == kActorMcCoy)
1538 			        || (_id == kActorMcCoy && actorId == BladeRunnerEngine::kActorVoiceOver) )) {
1539 				// when transfering a clue successfully between McCoy (playerActor) and Mainframe,
1540 				// we mark it as such, since if McCoy later marks it as hidden (with Bob's KIA hack)
1541 				// the player will have some indication that this clue is already on the mainframe.
1542 				// Hence manually hiding it would be pointless.
1543 				// This, however, cannot cover the case that someone else (eg. Steele) uploaded clues
1544 				// to the Mainframe, which McCoy had not yet tried to download.
1545 				// So, eg. if Steele uploaded clueA, and McCoy also somehow acquired clueA (without synching with Mainframe)
1546 				// then McCoy's KIA won't "know" that the Mainframe also has this clue, until he interacts / synchs with Mainframe
1547 				_vm->_playerActor->_clues->setSharedWithMainframe(clueId, true);
1548 			}
1549 			otherActor->acquireClue(clueId, false, fromActorId);
1550 			newCluesAcquired = true;
1551 		} else if (_vm->_cutContent
1552 		           && hasClue(clueId)
1553 		           && otherActor->hasClue(clueId)
1554 		           && _vm->_crimesDatabase->getAssetType(clueId) != kClueTypeIntangible
1555 		           && ((_id == BladeRunnerEngine::kActorVoiceOver && actorId == kActorMcCoy)
1556 		               || (_id == kActorMcCoy && actorId == BladeRunnerEngine::kActorVoiceOver) )
1557 		) {
1558 			// In Restored Content also mark clues that were not exchanged, because both parties already have them
1559 			_vm->_playerActor->_clues->setSharedWithMainframe(clueId, true);
1560 		}
1561 	}
1562 	return newCluesAcquired;
1563 }
1564 
acquireCluesByRelations()1565 void Actor::acquireCluesByRelations() {
1566 	if (_setId >= 0 && _setId != kSetFreeSlotG && _setId != _vm->_actors[0]->_setId) {
1567 		for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); ++i) {
1568 			if (i != _id && _vm->_actors[i]->_setId == _setId && i && _id
1569 					&& checkFriendlinessAndHonesty(i)
1570 					&& _vm->_actors[i]->checkFriendlinessAndHonesty(_id)) {
1571 				_clues->acquireCluesByRelations(_id, i);
1572 			}
1573 		}
1574 	}
1575 }
1576 
soundVolume() const1577 int Actor::soundVolume() const {
1578 	float dist = distanceFromView(_vm->_view);
1579 	return (35 * CLIP<int>(100 - (dist / 12), 0, 100)) / 100; // map [0..1200] to [35..0]
1580 }
1581 
1582 // overrideRange argument was added to allow for more accurate sound balance on occasion (if required)
soundPan(uint8 overrideRange) const1583 int Actor::soundPan(uint8 overrideRange) const {
1584 	Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
1585 	// By default map [0..640] to [-overrideRange..overrideRange] (default range [-35..35])
1586 	CLIP<int>(overrideRange, 35, 100);
1587 	return (overrideRange * (2 * CLIP<int>(screenPosition.x, 0, 640) - 640)) / 640;
1588 }
1589 
isObstacleBetween(const Vector3 & target)1590 bool Actor::isObstacleBetween(const Vector3 &target) {
1591 	return _vm->_sceneObjects->isObstacleBetween(_position, target, -1);
1592 }
1593 
findTargetUnderMouse(BladeRunnerEngine * vm,int mouseX,int mouseY)1594 int Actor::findTargetUnderMouse(BladeRunnerEngine *vm, int mouseX, int mouseY) {
1595 	int setId = vm->_scene->getSetId();
1596 	for (int i = 0; i < (int)vm->_gameInfo->getActorCount(); ++i) {
1597 		if (vm->_actors[i]->isTarget() && vm->_actors[i]->getSetId() == setId) {
1598 			if (vm->_actors[i]->_screenRectangle.contains(mouseX, mouseY)) {
1599 				return i;
1600 			}
1601 		}
1602 	}
1603 	return -1;
1604 }
1605 
findEmptyPositionAround(const Vector3 & startPosition,const Vector3 & targetPosition,float size,Vector3 * emptyPosition)1606 bool Actor::findEmptyPositionAround(const Vector3 &startPosition, const Vector3 &targetPosition, float size, Vector3 *emptyPosition) {
1607 	emptyPosition->x = 0.0f;
1608 	emptyPosition->y = 0.0f;
1609 	emptyPosition->z = 0.0f;
1610 
1611 	int facingLeft = angle_1024(targetPosition, startPosition);
1612 	int facingRight = facingLeft;
1613 
1614 	int facingLeftCounter = 0;
1615 	int facingRightCounter = 0;
1616 
1617 	while (true) {
1618 		float rotatedX = targetPosition.x + size * _vm->_sinTable1024->at(facingLeft);
1619 		float rotatedZ = targetPosition.z - size * _vm->_cosTable1024->at(facingLeft);
1620 
1621 		if (!_walkInfo->isXYZOccupied(rotatedX, targetPosition.y, rotatedZ, _id)) {
1622 			if (_vm->_scene->_set->findWalkbox(rotatedX, rotatedZ) >= 0) {
1623 				emptyPosition->x = rotatedX;
1624 				emptyPosition->y = targetPosition.y;
1625 				emptyPosition->z = rotatedZ;
1626 				return true;
1627 			}
1628 		} else { // looks like a bug as it might not find anything when there is no walkbox at this angle
1629 			facingLeft += 20;
1630 #if BLADERUNNER_ORIGINAL_BUGS
1631 			if (facingLeft > 1024) {
1632 				facingLeft -= 1024;
1633 			}
1634 #else
1635 			// if facingLeft + 20 == 1024 then it could cause the assertion fault
1636 			// in common/sinetables.cpp for SineTable::at(int index) -> assert((index >= 0) && (index < _nPoints))
1637 			if (facingLeft >= 1024) {
1638 				facingLeft -= 1024;
1639 			}
1640 #endif
1641 			facingLeftCounter += 20;
1642 		}
1643 
1644 		rotatedX = size * _vm->_sinTable1024->at(facingRight) + targetPosition.x;
1645 		rotatedZ = size * _vm->_cosTable1024->at(facingRight) + targetPosition.z;
1646 
1647 		if (!_walkInfo->isXYZOccupied(rotatedX, targetPosition.y, rotatedZ, _id)) {
1648 			if (_vm->_scene->_set->findWalkbox(rotatedX, rotatedZ) >= 0) {
1649 				emptyPosition->x = rotatedX;
1650 				emptyPosition->y = targetPosition.y;
1651 				emptyPosition->z = rotatedZ;
1652 				return true;
1653 			}
1654 		} else { // looks like a bug as it might not find anything when there is no walkbox at this angle
1655 			facingRight -= 20;
1656 			if (facingRight < 0) {
1657 				facingRight += 1024;
1658 			}
1659 			facingRightCounter += 20;
1660 		}
1661 
1662 		if (facingLeftCounter > 1024 && facingRightCounter > 1024) {
1663 			return false;
1664 		}
1665 	}
1666 }
1667 
findNearestPosition(Vector3 * nearestPosition,float targetWidth,int proximity,float targetSize,const Vector3 & startPosition,const Vector3 & targetPosition)1668 bool Actor::findNearestPosition(Vector3 *nearestPosition, float targetWidth, int proximity, float targetSize, const Vector3 &startPosition, const Vector3 &targetPosition) {
1669 	nearestPosition->x = 0.0f;
1670 	nearestPosition->y = 0.0f;
1671 	nearestPosition->z = 0.0f;
1672 	float size = proximity + targetSize * 0.5f + targetWidth * 0.5f;
1673 	float distance = (startPosition - targetPosition).length() - targetWidth * 0.5f - targetSize * 0.5f;
1674 	if (size < distance) {
1675 		return findEmptyPositionAround(startPosition, targetPosition, size, nearestPosition);
1676 	} else {
1677 		*nearestPosition = targetPosition;
1678 		return true;
1679 	}
1680 }
1681 
stepAway(const Vector3 & destination,float distance)1682 bool Actor::stepAway(const Vector3 &destination, float distance) {
1683 	Vector3 out;
1684 	bool running;
1685 	if (_walkInfo->findEmptyPositionAround(_id, destination, distance, out)) {
1686 		loopWalk(out, 0, false, false, _position, 0.0f, 24.0f, false, &running, false);
1687 		return true;
1688 	}
1689 	return false;
1690 }
1691 
save(SaveFileWriteStream & f)1692 void Actor::save(SaveFileWriteStream &f) {
1693 	f.writeInt(_id);
1694 	f.writeInt(_setId);
1695 	f.writeVector3(_position);
1696 	f.writeInt(_facing);
1697 	f.writeInt(_targetFacing);
1698 	f.writeInt(_timer4RemainDefault);
1699 
1700 	f.writeInt(_honesty);
1701 	f.writeInt(_intelligence);
1702 	f.writeInt(_combatAggressiveness);
1703 	f.writeInt(_stability);
1704 
1705 	f.writeInt(_goalNumber);
1706 
1707 	f.writeInt(_currentHP);
1708 	f.writeInt(_maxHP);
1709 
1710 	f.writeBool(_movementTrackPaused);
1711 	f.writeInt(_movementTrackNextWaypointId);
1712 	f.writeInt(_movementTrackNextDelay);
1713 	f.writeInt(_movementTrackNextAngle);
1714 	f.writeBool(_movementTrackNextRunning);
1715 
1716 	f.writeInt(_cluesLimit);
1717 
1718 	f.writeBool(_isMoving);
1719 	f.writeBool(_isTarget);
1720 	f.writeBool(_inCombat);
1721 	f.writeBool(_isInvisible);
1722 	f.writeBool(_isRetired);
1723 	f.writeBool(_isImmuneToObstacles);
1724 
1725 	f.writeInt(_animationMode);
1726 	f.writeInt(_fps);
1727 	f.writeInt(_frameMs);
1728 	f.writeInt(_animationId);
1729 	f.writeInt(_animationFrame);
1730 
1731 	f.writeInt(_movementTrackWalkingToWaypointId);
1732 	f.writeInt(_movementTrackDelayOnNextWaypoint);
1733 
1734 	f.writeRect(_screenRectangle);
1735 	f.writeInt(_retiredWidth);
1736 	f.writeInt(_retiredHeight);
1737 	f.writeInt(_damageAnimIfMoving);
1738 	f.writeInt(0);
1739 	f.writeInt(0);
1740 	f.writeFloat(_scale);
1741 
1742 	for (int i = 0; i < kActorTimers; ++i) {
1743 		f.writeInt(_timersLeft[i]);
1744 	}
1745 
1746 	uint32 now = _vm->_time->getPauseStart();
1747 	for (int i = 0; i < kActorTimers; ++i) {
1748 		// this effectively stores the next timeDiff to be applied to timer i (in timerUpdate)
1749 		f.writeInt(now - _timersLast[i]); // Unsigned difference is intentional
1750 	}
1751 
1752 	int actorCount = _vm->_gameInfo->getActorCount();
1753 	for (int i = 0; i != actorCount; ++i) {
1754 		f.writeInt(_friendlinessToOther[i]);
1755 	}
1756 
1757 	_clues->save(f);
1758 
1759 	_movementTrack->save(f);
1760 
1761 	_walkInfo->save(f);
1762 
1763 	f.writeBoundingBox(_bbox, false);
1764 
1765 	_combatInfo->save(f);
1766 
1767 	f.writeInt(_animationModeCombatIdle);
1768 	f.writeInt(_animationModeCombatWalk);
1769 	f.writeInt(_animationModeCombatRun);
1770 }
1771 
load(SaveFileReadStream & f)1772 void Actor::load(SaveFileReadStream &f) {
1773 	_id = f.readInt();
1774 	_setId = f.readInt();
1775 	_position = f.readVector3();
1776 	_facing = f.readInt();
1777 	_targetFacing = f.readInt();
1778 	_timer4RemainDefault = f.readUint32LE();
1779 
1780 	_honesty = f.readInt();
1781 	_intelligence = f.readInt();
1782 	_combatAggressiveness = f.readInt();
1783 	_stability = f.readInt();
1784 
1785 	_goalNumber = f.readInt();
1786 
1787 	_currentHP = f.readInt();
1788 	_maxHP = f.readInt();
1789 
1790 	_movementTrackPaused = f.readBool();
1791 	_movementTrackNextWaypointId = f.readInt();
1792 	_movementTrackNextDelay = f.readInt();
1793 	_movementTrackNextAngle = f.readInt();
1794 	_movementTrackNextRunning = f.readBool();
1795 
1796 	_cluesLimit = f.readInt();
1797 
1798 	_isMoving = f.readBool();
1799 	_isTarget = f.readBool();
1800 	_inCombat = f.readBool();
1801 	_isInvisible = f.readBool();
1802 	_isRetired = f.readBool();
1803 	_isImmuneToObstacles = f.readBool();
1804 
1805 	_animationMode = f.readInt();
1806 	_fps = f.readInt();
1807 	_frameMs = f.readInt();
1808 	_animationId = f.readInt();
1809 	_animationFrame = f.readInt();
1810 
1811 	_movementTrackWalkingToWaypointId = f.readInt();
1812 	_movementTrackDelayOnNextWaypoint = f.readInt();
1813 
1814 	_screenRectangle = f.readRect();
1815 	_retiredWidth = f.readInt();
1816 	_retiredHeight = f.readInt();
1817 	_damageAnimIfMoving = f.readInt();
1818 	f.skip(4);
1819 	f.skip(4);
1820 	_scale = f.readFloat();
1821 
1822 	for (int i = 0; i < kActorTimers; ++i) {
1823 		_timersLeft[i] = (int32)f.readUint32LE();
1824 	}
1825 
1826 	// Bugfix: Special initialization case for timer 4 (kActorTimerClueExchange) when its value is restored as 0
1827 	// This should be harmless, but will remedy any broken save-games where the timer 4 was saved as 0.
1828 	if (_timersLeft[kActorTimerClueExchange] == 0) {
1829 		_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault;
1830 	}
1831 
1832 	// Bugfix: Similar to the above
1833 	// Special initialization case for timer 6 (kActorTimerRunningStaminaFPS) when its value is restored as 0
1834 	// This was due to an original game bug and it concerns only McCoy (player's actor)
1835 	// This should be harmless, but will remedy any broken save-games where the timer 6 was saved as 0.
1836 	// Also, in restored content mode we allow this counter to be 0 and only start it
1837 	// when McCoy's FPS get increased (starts running with sufficient speed),
1838 	// so the fix is not needed for RC mode
1839 	if (!_vm->_cutContent) {
1840 		if (_id == kActorMcCoy && _timersLeft[kActorTimerRunningStaminaFPS] == 0) {
1841 			_timersLeft[kActorTimerClueExchange] = 200;
1842 		}
1843 	}
1844 
1845 	uint32 now = _vm->_time->getPauseStart();
1846 	for (int i = 0; i < kActorTimers; ++i) {
1847 		_timersLast[i] = now - f.readUint32LE(); // we require an unsigned difference here, since _timersLast is essentially keeping time
1848 	}
1849 
1850 	int actorCount = _vm->_gameInfo->getActorCount();
1851 	for (int i = 0; i != actorCount; ++i) {
1852 		_friendlinessToOther[i] = f.readInt();
1853 	}
1854 
1855 	_clues->load(f);
1856 
1857 	_movementTrack->load(f);
1858 
1859 	_walkInfo->load(f);
1860 
1861 	_bbox = f.readBoundingBox(false);
1862 
1863 	_combatInfo->load(f);
1864 
1865 	_animationModeCombatIdle = f.readInt();
1866 	_animationModeCombatWalk = f.readInt();
1867 	_animationModeCombatRun = f.readInt();
1868 }
1869 
1870 } // End of namespace BladeRunner
1871