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/game_info.h"
32 #include "bladerunner/items.h"
33 #include "bladerunner/mouse.h"
34 #include "bladerunner/movement_track.h"
35 #include "bladerunner/savefile.h"
36 #include "bladerunner/scene.h"
37 #include "bladerunner/scene_objects.h"
38 #include "bladerunner/script/scene_script.h"
39 #include "bladerunner/script/ai_script.h"
40 #include "bladerunner/set.h"
41 #include "bladerunner/slice_animations.h"
42 #include "bladerunner/slice_renderer.h"
43 #include "bladerunner/time.h"
44 #include "bladerunner/subtitles.h"
45 #include "bladerunner/waypoints.h"
46 #include "bladerunner/zbuffer.h"
47 
48 namespace BladeRunner {
49 
Actor(BladeRunnerEngine * vm,int actorId)50 Actor::Actor(BladeRunnerEngine *vm, int actorId) {
51 	_vm = vm;
52 	_id = actorId;
53 
54 	_walkInfo      = new ActorWalk(vm);
55 	_movementTrack = new MovementTrack();
56 	_cluesLimit    = (actorId == kActorMcCoy || actorId == kActorVoiceOver) ? 4 : 2;
57 	_clues         = new ActorClues(vm, _cluesLimit);
58 	_combatInfo    = new ActorCombat(vm);
59 
60 	_friendlinessToOther.resize(_vm->_gameInfo->getActorCount());
61 
62 	setup(actorId);
63 }
64 
~Actor()65 Actor::~Actor() {
66 	delete _combatInfo;
67 	delete _clues;
68 	delete _movementTrack;
69 	delete _walkInfo;
70 }
71 
setup(int actorId)72 void Actor::setup(int actorId) {
73 	_id             = actorId;
74 	_setId          = -1;
75 
76 	_position       = Vector3(0.0, 0.0, 0.0);
77 	_facing         = 512;
78 	_targetFacing   = -1;
79 	_walkboxId      = -1;
80 
81 	_animationId    = 0;
82 	_animationFrame = 0;
83 	_fps            = 15;
84 	_frameMs        = 1000 / _fps;
85 
86 	_mustReachWalkDestination = false;	// Original's _inWalkLoop. Moved here from our constructor, since it's here in the original's init()
87 	_isMoving                 = false;
88 	_isTarget                 = false;
89 	_inCombat                 = false;
90 	_isInvisible              = false;
91 	_isImmuneToObstacles      = false;
92 	_isRetired                = false;
93 
94 	_width                    = 0;
95 	_height                   = 0;
96 	_retiredWidth             = 0;
97 	_retiredHeight            = 0;
98 	_scale                    = 1.0f;
99 
100 	_timer4RemainDefault      = 60000u;
101 
102 	_movementTrackWalkingToWaypointId = -1;
103 	_movementTrackDelayOnNextWaypoint = -1;
104 
105 	for (int i = 0; i != kActorTimers; ++i) {
106 		_timersLeft[i] = 0u;
107 		_timersLast[i] = _vm->_time->current();
108 	}
109 	_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault; // This was in original code. We need to init this timer in order to kick off periodic updates for acquireCluesByRelations
110 
111 	_honesty                     = 50;
112 	_intelligence                = 50;
113 	_combatAggressiveness        = 50;
114 	_stability                   = 50;
115 
116 	_currentHP                   = 50;
117 	_maxHP                       = 50;
118 
119 	_damageAnimIfMoving          = true; // Set to true (like in original). And moved here from our constructor, since it's here in the original's init().
120 
121 	_goalNumber                  = -1;
122 
123 	_movementTrackPaused         = false;
124 	_movementTrackNextWaypointId = -1;
125 	_movementTrackNextDelay      = -1;
126 	_movementTrackNextAngle      = -1;
127 	_movementTrackNextRunning    = false;
128 
129 	_animationMode               = -1;
130 	_screenRectangle = Common::Rect(-1, -1, -1, -1);
131 
132 	_animationModeCombatIdle = kAnimationModeCombatIdle;
133 	_animationModeCombatWalk = kAnimationModeCombatWalk;
134 	_animationModeCombatRun  = kAnimationModeCombatRun;
135 
136 	int actorCount = (int)_vm->_gameInfo->getActorCount();
137 	for (int i = 0; i != actorCount; ++i)
138 		_friendlinessToOther[i] = 50;
139 
140 #if BLADERUNNER_ORIGINAL_BUGS
141 #else
142 	// if player actor was not idle and had an active _walkInfo then
143 	// upon starting a new game, the player actor would be put on the old _walkInfo
144 	_walkInfo->reset();
145 //	// delete _walkInfo and re-allocate it (a reset method would probably be better)
146 //	if (_walkInfo != nullptr) {
147 //		delete(_walkInfo);
148 //	}
149 //	_walkInfo = new ActorWalk(_vm);
150 #endif // BLADERUNNER_ORIGINAL_BUGS
151 
152 	_combatInfo->setup();
153 	_clues->removeAll();
154 	_movementTrack->flush();
155 
156 	_actorSpeed = Vector3();
157 
158 	switch (_id) {
159 		case kActorMcCoy:
160 			_sitcomRatio = 50;
161 			break;
162 
163 		case kActorGordo:
164 			_sitcomRatio = 0;
165 			break;
166 
167 		case kActorGuzza:
168 		case kActorChew:
169 		case kActorVoiceOver:
170 			_sitcomRatio = 75;
171 			break;
172 
173 		case kActorCrazylegs:
174 		case kActorBulletBob:
175 		case kActorRunciter:
176 		case kActorZuben:
177 		case kActorLeon:
178 			_sitcomRatio = 90;
179 			break;
180 
181 		case kActorGrigorian:
182 		case kActorMoraji:
183 			_sitcomRatio = 100;
184 			break;
185 
186 		default:
187 			_sitcomRatio = 33;
188 			break;
189 	}
190 }
191 
changeAnimationMode(int animationMode,bool force)192 void Actor::changeAnimationMode(int animationMode, bool force) {
193 	if (force) {
194 		_animationMode = -1;
195 	}
196 
197 	if (animationMode != _animationMode) {
198 		_vm->_aiScripts->changeAnimationMode(_id, animationMode);
199 		_animationMode = animationMode;
200 	}
201 }
202 
setFPS(int fps)203 void Actor::setFPS(int fps) {
204 	_fps = fps;
205 
206 	if (fps == 0) { // stop actor's animation
207 		_frameMs = 0;
208 	} else if (fps == -1) { // sync actor's animation with scene animation
209 		_frameMs = -1000;
210 	} else if (fps == -2) { // set FPS to default from the model
211 		_fps = _vm->_sliceAnimations->getFPS(_animationId);
212 		_frameMs = 1000 / _fps;
213 	} else {
214 		_frameMs = 1000 / fps;
215 	}
216 }
217 
increaseFPS()218 void Actor::increaseFPS() {
219 	int fps = MIN(_fps + 3, 30);
220 	setFPS(fps);
221 }
222 
timerStart(int timerId,int32 interval)223 void Actor::timerStart(int timerId, int32 interval) {
224 	assert(timerId >= 0 && timerId < kActorTimers);
225 	if (interval < 0 ) {
226 		interval = 0;
227 	}
228 	_timersLeft[timerId] = (uint32)interval;
229 	_timersLast[timerId] = _vm->_time->current();
230 }
231 
timerReset(int timerId)232 void Actor::timerReset(int timerId) {
233 	assert(timerId >= 0 && timerId < kActorTimers);
234 	_timersLeft[timerId] = 0u;
235 }
236 
timerLeft(int timerId)237 uint32 Actor::timerLeft(int timerId) {
238 	assert(timerId >= 0 && timerId < kActorTimers);
239 	return _timersLeft[timerId];
240 }
241 
timersUpdate()242 void Actor::timersUpdate() {
243 	for (int i = 0; i <= 6; i++) {
244 		timerUpdate(i);
245 	}
246 }
247 
timerUpdate(int timerId)248 void Actor::timerUpdate(int timerId) {
249 	if (_timersLeft[timerId] == 0u) {
250 		return;
251 	}
252 
253 	uint32 timeNow = _vm->_time->current();
254 	uint32 timeDiff = timeNow - _timersLast[timerId]; // unsigned difference is intentional
255 	_timersLast[timerId] = timeNow;
256 	// new check is safe-guard against old saves, _timersLeft should never have very big value anyway
257 	if ((int32)_timersLeft[timerId] < 0 ) {
258 		// this will make it 0u in the next command below
259 		_timersLeft[timerId] = 0u;
260 	}
261 	_timersLeft[timerId] = (_timersLeft[timerId] < timeDiff) ? 0u : (_timersLeft[timerId] - timeDiff);
262 
263 	if (_timersLeft[timerId] == 0u) { // original check was <= 0
264 		switch (timerId) {
265 		case kActorTimerAIScriptCustomTask0:
266 			// fall through
267 		case kActorTimerAIScriptCustomTask1:
268 			// fall through
269 		case kActorTimerAIScriptCustomTask2:
270 			if (!_vm->_aiScripts->isInsideScript() && !_vm->_sceneScript->isInsideScript()) {
271 				_vm->_aiScripts->timerExpired(_id, timerId);
272 				_timersLeft[timerId] = 0u;
273 			} else {
274 				_timersLeft[timerId] = 1u;
275 			}
276 			break;
277 		case kActorTimerMovementTrack:
278 			_timersLeft[kActorTimerMovementTrack] = 0u;
279 			if (_movementTrack->isPaused()) {
280 				_timersLeft[kActorTimerMovementTrack] = 1u;
281 			} else {
282 				movementTrackNext(false);
283 			}
284 			break;
285 		case kActorTimerClueExchange:
286 			// Exchange clues between actors
287 			acquireCluesByRelations();
288 			_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault;
289 			break;
290 		case kActorTimerAnimationFrame:
291 			// Actor animation frame timer
292 			break;
293 		case kActorTimerRunningStaminaFPS:
294 			if (isRunning()) {
295 				if (_fps > 15) {
296 					int newFps = _fps - 2;
297 					if (newFps < 15) {
298 						newFps = 15;
299 					}
300 					setFPS(newFps);
301 				}
302 			}
303 			_timersLeft[kActorTimerRunningStaminaFPS] = 200u;
304 			break;
305 		}
306 	}
307 }
308 
movementTrackNext(bool omitAiScript)309 void Actor::movementTrackNext(bool omitAiScript) {
310 	bool hasNextMovement;
311 	bool running;
312 	int angle;
313 	int32 delay;
314 	int waypointId;
315 	Vector3 waypointPosition;
316 	bool arrived;
317 
318 	hasNextMovement = _movementTrack->next(&waypointId, &delay, &angle, &running);
319 	_movementTrackNextWaypointId = waypointId;
320 	_movementTrackNextDelay = delay;
321 	_movementTrackNextAngle = angle;
322 	_movementTrackNextRunning = running;
323 	if (hasNextMovement) {
324 		if (angle == -1) {
325 			angle = 0;
326 		}
327 		int waypointSetId = _vm->_waypoints->getSetId(waypointId);
328 		_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
329 		if (_setId == waypointSetId && waypointSetId == _vm->_actors[0]->_setId) {
330 			stopWalking(false);
331 			_walkInfo->setup(_id, running, _position, waypointPosition, false, &arrived);
332 
333 			_movementTrackWalkingToWaypointId = waypointId;
334 			_movementTrackDelayOnNextWaypoint = delay;
335 			if (arrived) {
336 				movementTrackWaypointReached();
337 			}
338 		} else {
339 			setSetId(waypointSetId);
340 			setAtXYZ(waypointPosition, angle, true, false, false);
341 
342 			if (!delay) {
343 				delay = 1;
344 			}
345 			if (delay > 1) {
346 				changeAnimationMode(kAnimationModeIdle, false);
347 			}
348 			timerStart(kActorTimerMovementTrack, delay);
349 		}
350 		//return true;
351 	} else {
352 		if (!omitAiScript) {
353 			_vm->_aiScripts->completedMovementTrack(_id);
354 		}
355 		//return false;
356 	}
357 }
358 
movementTrackPause()359 void Actor::movementTrackPause() {
360 	_movementTrack->pause();
361 	if (isWalking()) {
362 		_movementTrackPaused = true;
363 		stopWalking(false);
364 	} else {
365 		_movementTrackPaused = false;
366 	}
367 }
368 
movementTrackUnpause()369 void Actor::movementTrackUnpause() {
370 	Vector3 waypointPosition;
371 	bool arrived;
372 
373 	_movementTrack->unpause();
374 	if (_movementTrackNextWaypointId >= 0 && _movementTrackPaused) {
375 		_vm->_waypoints->getXYZ(_movementTrackNextWaypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
376 		_walkInfo->setup(_id, _movementTrackNextRunning, _position, waypointPosition, false, &arrived);
377 		_movementTrackPaused = false;
378 	}
379 }
380 
movementTrackWaypointReached()381 void Actor::movementTrackWaypointReached() {
382 	if (!_movementTrack->isPaused() && _id != kActorMcCoy) {
383 		if (_movementTrackWalkingToWaypointId >= 0 && _movementTrackDelayOnNextWaypoint >= 0) {
384 			if (!_movementTrackDelayOnNextWaypoint) {
385 				_movementTrackDelayOnNextWaypoint = 1;
386 			}
387 #if !BLADERUNNER_ORIGINAL_BUGS
388 			// Honor the heading defined by the AI_Movement_Track_Append_With_Facing method
389 			if (_movementTrackNextAngle >= 0) {
390 				faceHeading(_movementTrackNextAngle, true);
391 			}
392 #endif
393 			if (_vm->_aiScripts->reachedMovementTrackWaypoint(_id, _movementTrackWalkingToWaypointId)) {
394 				int32 delay = _movementTrackDelayOnNextWaypoint;
395 				if (delay > 1) {
396 					changeAnimationMode(kAnimationModeIdle, false);
397 					delay = _movementTrackDelayOnNextWaypoint; // todo: analyze if movement is changed in some aiscript->ChangeAnimationMode?
398 				}
399 				timerStart(kActorTimerMovementTrack, delay);
400 			}
401 		}
402 		_movementTrackWalkingToWaypointId = -1;
403 		_movementTrackDelayOnNextWaypoint =  0;
404 	}
405 }
406 
setAtXYZ(const Vector3 & position,int facing,bool snapFacing,bool moving,bool retired)407 void Actor::setAtXYZ(const Vector3 &position, int facing, bool snapFacing, bool moving, bool retired) {
408 	_position = position;
409 	setFacing(facing, snapFacing);
410 
411 	if (_vm->_scene->getSetId() == _setId) {
412 		_walkboxId = _vm->_scene->_set->findWalkbox(_position.x, _position.y);
413 	} else {
414 		_walkboxId = -1;
415 	}
416 
417 	setBoundingBox(_position, retired);
418 
419 	_vm->_sceneObjects->remove(_id + kSceneObjectOffsetActors);
420 
421 	if (_vm->_scene->getSetId() == _setId) {
422 		_vm->_sceneObjects->addActor(_id + kSceneObjectOffsetActors, _bbox, _screenRectangle, true, moving, _isTarget, retired);
423 	}
424 }
425 
setAtWaypoint(int waypointId,int angle,int moving,bool retired)426 void Actor::setAtWaypoint(int waypointId, int angle, int moving, bool retired) {
427 	Vector3 waypointPosition;
428 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
429 	setAtXYZ(waypointPosition, angle, true, moving, retired);
430 }
431 
loopWalk(const Vector3 & destination,int proximity,bool interruptible,bool runFlag,const Vector3 & start,float targetWidth,float targetSize,bool mustReach,bool * isRunningFlag,bool async)432 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) {
433 	*isRunningFlag = false;
434 
435 	if (proximity > 0) {
436 		float dist = distance(_position, destination);
437 		if (dist - targetSize <= proximity) {
438 			return false;
439 		}
440 	}
441 
442 	if (mustReach && !async && _id != kActorMcCoy && proximity <= 24) {
443 		if (distance(_vm->_playerActor->_position, destination) <= 24.0f) {
444 			_vm->_playerActor->stepAway(destination, 48.0f);
445 		}
446 	}
447 
448 	if (_id != kActorMcCoy) {
449 		interruptible = false;
450 	}
451 
452 	Vector3 destinationX(destination);
453 
454 	if (proximity > 0) {
455 		findNearestPosition(&destinationX, targetWidth, proximity, targetSize, _position, destination);
456 	}
457 
458 	bool walking = walkTo(runFlag, destinationX, mustReach);
459 
460 	if (async) {
461 		return false;
462 	}
463 
464 	if (!walking && proximity > 0) {
465 		walking = walkTo(runFlag, destination, mustReach);
466 	}
467 
468 	if (!walking) {
469 		faceXYZ(destination, false);
470 		return false;
471 	}
472 
473 	if (_id != kActorMcCoy) {
474 		_vm->_mouse->disable();
475 	}
476 
477 	if (interruptible) {
478 		_vm->_isWalkingInterruptible = true;
479 		_vm->_interruptWalking = false;
480 	} else {
481 		_vm->playerLosesControl();
482 	}
483 
484 	if (mustReach) {
485 		_mustReachWalkDestination = true;
486 	}
487 
488 	bool wasInterrupted = false;
489 	while (_walkInfo->isWalking() && _vm->_gameIsRunning) {
490 		if (_walkInfo->isRunning()) {
491 			*isRunningFlag = true;
492 		}
493 		_vm->gameTick();
494 		if (_id == kActorMcCoy && interruptible && _vm->_interruptWalking) {
495 			stopWalking(false);
496 			wasInterrupted = true;
497 		}
498 	}
499 
500 	if (mustReach) {
501 		_mustReachWalkDestination = false;
502 	}
503 
504 	if (interruptible) {
505 		_vm->_isWalkingInterruptible = false;
506 	} else {
507 		_vm->playerGainsControl();
508 	}
509 
510 #if BLADERUNNER_ORIGINAL_BUGS
511 	if (!wasInterrupted && proximity == 0 && !_vm->_playerActorIdle) {
512 		setAtXYZ(destination, _facing, true, false, false);
513 	}
514 #else
515 	if (!wasInterrupted && proximity == 0
516 	    && (_id == kActorMcCoy && !_vm->_playerActorIdle)
517 	    && !isRetired()
518 	) {
519 		setAtXYZ(destination, _facing, true, false, false);
520 	}
521 #endif // BLADERUNNER_ORIGINAL_BUGS
522 
523 	if (_id != kActorMcCoy) {
524 		_vm->_mouse->enable();
525 	}
526 
527 	return wasInterrupted;
528 }
529 
walkTo(bool runFlag,const Vector3 & destination,bool mustReach)530 bool Actor::walkTo(bool runFlag, const Vector3 &destination, bool mustReach) {
531 	bool arrived;
532 	return _walkInfo->setup(_id, runFlag, _position, destination, mustReach, &arrived);
533 }
534 
loopWalkToActor(int otherActorId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)535 bool Actor::loopWalkToActor(int otherActorId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
536 	return loopWalk(_vm->_actors[otherActorId]->_position, proximity, interruptible, runFlag, _position, 24.0f, 24.0f, mustReach, isRunningFlag, false);
537 }
538 
loopWalkToItem(int itemId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)539 bool Actor::loopWalkToItem(int itemId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
540 	float x, y, z;
541 	int width, height;
542 	_vm->_items->getXYZ(itemId, &x, &y, &z);
543 	_vm->_items->getWidthHeight(itemId, &width, &height);
544 	Vector3 itemPosition(x, y, z);
545 
546 	return loopWalk(itemPosition, proximity, interruptible, runFlag, _position, width, 24.0f, mustReach, isRunningFlag, false);
547 }
548 
loopWalkToSceneObject(const Common::String & objectName,int proximity,bool interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)549 bool Actor::loopWalkToSceneObject(const Common::String &objectName, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
550 	int sceneObject = _vm->_scene->_set->findObject(objectName);
551 	if (sceneObject < 0) {
552 		return true;
553 	}
554 
555 	BoundingBox bbox;
556 	if (!_vm->_scene->_set->objectGetBoundingBox(sceneObject, &bbox)) {
557 		return true;
558 	}
559 
560 	float x0, y0, z0, x1, y1, z1;
561 	bbox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
562 
563 	float closestDistance = distance(_position.x, _position.z, x0, z0);
564 	float closestX = x0;
565 	float closestZ = z0;
566 
567 	float d = distance(_position.x, _position.z, x1, z0);
568 	if (d < closestDistance) {
569 		closestX = x1;
570 		closestZ = z0;
571 		closestDistance = d;
572 	}
573 
574 	d = distance(_position.x, _position.z, x1, z1);
575 	if (d < closestDistance) {
576 		closestX = x1;
577 		closestZ = z1;
578 		closestDistance = d;
579 	}
580 
581 	d = distance(_position.x, _position.z, x0, z1);
582 	if (d < closestDistance) {
583 		closestX = x0;
584 		closestZ = z1;
585 	}
586 
587 	bool inWalkbox;
588 	float y = _vm->_scene->_set->getAltitudeAtXZ(closestX, closestZ, &inWalkbox);
589 	Vector3 destination(closestX, y, closestZ);
590 
591 	return loopWalk(destination, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
592 }
593 
loopWalkToWaypoint(int waypointId,int proximity,int interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)594 bool Actor::loopWalkToWaypoint(int waypointId, int proximity, int interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
595 	Vector3 waypointPosition;
596 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
597 	return loopWalk(waypointPosition, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
598 }
599 
loopWalkToXYZ(const Vector3 & destination,int proximity,bool interruptible,bool runFlag,bool mustReach,bool * isRunningFlag)600 bool Actor::loopWalkToXYZ(const Vector3 &destination, int proximity, bool interruptible, bool runFlag, bool mustReach, bool *isRunningFlag) {
601 	return loopWalk(destination, proximity, interruptible, runFlag, _position, 0.0f, 24.0f, mustReach, isRunningFlag, false);
602 }
603 
asyncWalkToWaypoint(int waypointId,int proximity,bool runFlag,bool mustReach)604 bool Actor::asyncWalkToWaypoint(int waypointId, int proximity, bool runFlag, bool mustReach) {
605 	bool running;
606 	Vector3 waypointPosition;
607 	_vm->_waypoints->getXYZ(waypointId, &waypointPosition.x, &waypointPosition.y, &waypointPosition.z);
608 	return loopWalk(waypointPosition, proximity, false, runFlag, _position, 0.0f, 24.0f, mustReach, &running, true);
609 }
610 
asyncWalkToXYZ(const Vector3 & destination,int proximity,bool runFlag,bool mustReach)611 void Actor::asyncWalkToXYZ(const Vector3 &destination, int proximity, bool runFlag, bool mustReach) {
612 	bool running;
613 	loopWalk(destination, proximity, false, runFlag, _position, 0.0f, 24.0f, mustReach, &running, true);
614 }
615 
run()616 void Actor::run() {
617 	_walkInfo->run(_id);
618 }
619 
tick(bool forceDraw,Common::Rect * screenRect)620 bool Actor::tick(bool forceDraw, Common::Rect *screenRect) {
621 	uint32 timeLeft = 0u;
622 	bool needsUpdate = false;
623 	if (_fps > 0) {
624 		timerUpdate(kActorTimerAnimationFrame);
625 		timeLeft = timerLeft(kActorTimerAnimationFrame);
626 		needsUpdate = (timeLeft == 0); // original was <= 0, with timeLeft int
627 	} else if (_fps == 0) {
628 		needsUpdate = false;
629 	} else if (forceDraw) {
630 		needsUpdate = true;
631 		timeLeft = 0u;
632 	}
633 
634 	if (needsUpdate) {
635 		int newAnimation = 0, newFrame = 0;
636 		_vm->_aiScripts->updateAnimation(_id, &newAnimation, &newFrame);
637 
638 		assert(newFrame >= 0);
639 
640 		if (_animationId != newAnimation) {
641 			if (_fps != 0 && _fps != -1) {
642 				_animationId = newAnimation;
643 				setFPS(-2);
644 			}
645 		}
646 		_animationId = newAnimation;
647 		_animationFrame = newFrame;
648 
649 		Vector3 positionChange = _vm->_sliceAnimations->getPositionChange(_animationId);
650 		float angleChange = _vm->_sliceAnimations->getFacingChange(_animationId);
651 
652 		if (_id == kActorHysteriaPatron1) {
653 			positionChange.x = 0.0f;
654 			positionChange.y = 0.0f;
655 			positionChange.z = 0.0f;
656 		}
657 
658 		if (isWalking()) {
659 			if (0.0f <= positionChange.y) {
660 				positionChange.y = -4.0f;
661 			}
662 
663 			_targetFacing = -1;
664 
665 			bool walkInterrupted = _walkInfo->tick(_id, -positionChange.y, _mustReachWalkDestination);
666 			Vector3 pos;
667 			int facing;
668 			_walkInfo->getCurrentPosition(_id, &pos, &facing);
669 			setAtXYZ(pos, facing, false, _isMoving, false);
670 			if (walkInterrupted) {
671 				_vm->_actors[_id]->changeAnimationMode(kAnimationModeIdle);
672 				movementTrackWaypointReached();
673 				if (inCombat()) {
674 					changeAnimationMode(_animationModeCombatIdle, false);
675 				} else {
676 					changeAnimationMode(kAnimationModeIdle, false);
677 				}
678 			}
679 		} else {
680 			// actor is not walking / is idle
681 			if (angleChange != 0.0f) {
682 				int facingChange = angleChange * (512.0f / M_PI);
683 				if (facingChange != 0) {
684 					_facing = _facing - facingChange;
685 					while (_facing < 0) {
686 						_facing += 1024;
687 					}
688 
689 					while (_facing >= 1024) {
690 						_facing -= 1024;
691 					}
692 				}
693 			}
694 
695 			if (_vm->_cutContent) {
696 				// the following Generic Walker models don't have an animation Id that is idle
697 				// so we use a frame of their walking animation to show them as stopped
698 				// However, we also need to override the positionChange vector for their walking animation too
699 				if ( (_id == kActorGenwalkerA || _id == kActorGenwalkerB || _id == kActorGenwalkerC)
700 				     &&
701 				     (_animationId == 436 || _animationId == 434 || _animationId == 435 || _animationId == 422 || _animationId == 423)
702 				) {
703 					positionChange.x = 0.0f;
704 					positionChange.y = 0.0f;
705 					positionChange.z = 0.0f;
706 				}
707 			}
708 
709 			if (0.0f != positionChange.x || 0.0f != positionChange.y || 0.0f != positionChange.z) {
710 				if (_actorSpeed.x != 0.0f) {
711 					positionChange.x = positionChange.x * _actorSpeed.x;
712 				}
713 				if (_actorSpeed.y != 0.0f) {
714 					positionChange.y = positionChange.y * _actorSpeed.y;
715 				}
716 				if (_actorSpeed.z != 0.0f) {
717 					positionChange.z = positionChange.z * _actorSpeed.z;
718 				}
719 
720 				float sinx = _vm->_sinTable1024->at(_facing);
721 				float cosx = _vm->_cosTable1024->at(_facing);
722 
723 				float originalX = _position.x;
724 				float originalY = _position.y;
725 				float originalZ = _position.z;
726 
727 				// Yes, Z & Y are switched between world space and model space. X is also negated for some unknown reason (wrong dirertion for angles?)
728 
729 				_position.x = _position.x - positionChange.x * cosx - positionChange.y * sinx;
730 				_position.z = _position.z - positionChange.x * sinx + positionChange.y * cosx;
731 				_position.y = _position.y + positionChange.z;
732 
733 				if (_vm->_sceneObjects->existsOnXZ(_id + kSceneObjectOffsetActors, _position.x, _position.z, false, false) == 1 && !_isImmuneToObstacles) {
734 					_position.x = originalX;
735 					_position.y = originalY;
736 					_position.z = originalZ;
737 				}
738 				setAtXYZ(_position, _facing, true, _isMoving, _isRetired);
739 			}
740 		}
741 	}
742 
743 	bool isVisible = false;
744 	if (!_isInvisible) {
745 		isVisible = draw(screenRect);
746 		if (isVisible) {
747 			_screenRectangle = *screenRect;
748 		}
749 	}
750 
751 	if (needsUpdate) {
752 		int nextFrameTime = (int)(timeLeft + _frameMs); // Should be ok
753 		if (nextFrameTime <= 0) {
754 			nextFrameTime = 1;
755 		}
756 		timerStart(kActorTimerAnimationFrame, nextFrameTime);
757 	}
758 	if (_targetFacing >= 0) {
759 		if (_targetFacing == _facing) {
760 			_targetFacing = -1;
761 		} else {
762 			setFacing(_targetFacing, false);
763 		}
764 	}
765 	return isVisible;
766 }
767 
tickCombat()768 void Actor::tickCombat() {
769 	if (_id != kActorMcCoy && !_isRetired && _inCombat) {
770 		_combatInfo->tick();
771 	}
772 }
773 
draw(Common::Rect * screenRect)774 bool Actor::draw(Common::Rect *screenRect) {
775 	Vector3 drawPosition(_position.x, -_position.z, _position.y + 2.0);
776 
777 #if !BLADERUNNER_ORIGINAL_BUGS
778 	// In the original game, Moraji appears to be floating above the ground a bit
779 	if (_id == kActorMoraji && _setId == kSetDR01_DR02_DR04) {
780 		drawPosition.z -= 6.0f;
781 	}
782 #endif
783 
784 	float drawAngle = M_PI - _facing * (M_PI / 512.0f);
785 	float drawScale = _scale;
786 
787 	if (_vm->_shortyMode) {
788 		drawScale = 0.7f;
789 	}
790 
791 	_vm->_sliceRenderer->drawInWorld(_animationId, _animationFrame, drawPosition, drawAngle, drawScale, _vm->_surfaceFront, _vm->_zbuffer->getData());
792 	_vm->_sliceRenderer->getScreenRectangle(screenRect, _animationId, _animationFrame, drawPosition, drawAngle, drawScale);
793 
794 	return !screenRect->isEmpty();
795 }
796 
getSetId() const797 int Actor::getSetId() const {
798 	return _setId;
799 }
800 
setSetId(int setId)801 void Actor::setSetId(int setId) {
802 	if (_setId == setId) {
803 		return;
804 	}
805 
806 	int i;
807 
808 	if (_setId > 0) {
809 		for (i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) {
810 			if (_vm->_actors[i]->_id != _id && _vm->_actors[i]->_setId == _setId) {
811 				_vm->_aiScripts->otherAgentExitedThisScene(i, _id);
812 			}
813 		}
814 	}
815 	_setId = setId;
816 	_vm->_aiScripts->enteredScene(_id, _setId);
817 	if (_setId > 0) {
818 		for (i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) {
819 			if (_vm->_actors[i]->_id != _id && _vm->_actors[i]->_setId == _setId) {
820 				_vm->_aiScripts->otherAgentEnteredThisScene(i, _id);
821 			}
822 		}
823 	}
824 }
825 
setFacing(int facing,bool halfOrSet)826 void Actor::setFacing(int facing, bool halfOrSet) {
827 	if (facing < 0 || facing >= 1024) {
828 		return;
829 	}
830 
831 	if (halfOrSet) {
832 		_facing = facing;
833 		return;
834 	}
835 
836 	int cw;
837 	int ccw;
838 	int offset;
839 
840 	if (facing > _facing) {
841 		cw = facing - _facing;
842 		ccw = _facing + 1024 - facing;
843 	} else {
844 		ccw = _facing - facing;
845 		cw = facing + 1024 - _facing;
846 	}
847 	if (cw < ccw) {
848 		if (cw <= 32) {
849 			offset = cw;
850 		} else {
851 			offset = cw / 2;
852 		}
853 	} else {
854 		if (ccw <= 32) {
855 			offset = -ccw;
856 		} else {
857 			offset = -ccw / 2;
858 		}
859 	}
860 
861 	_facing += offset;
862 
863 	while (_facing < 0) {
864 		_facing += 1024;
865 	}
866 
867 	while (_facing >= 1024) {
868 		_facing -= 1024;
869 	}
870 }
871 
setBoundingBox(const Vector3 & position,bool retired)872 void Actor::setBoundingBox(const Vector3 &position, bool retired) {
873 	if (retired) {
874 		_bbox.setXYZ(position.x - (_retiredWidth / 2.0f),
875 		             position.y,
876 		             position.z - (_retiredWidth / 2.0f),
877 
878 		             position.x + (_retiredWidth / 2.0f),
879 		             position.y + _retiredHeight,
880 		             position.z + (_retiredWidth / 2.0f));
881 	} else {
882 		_bbox.setXYZ(position.x - 12.0f,
883 		             position.y + 6.0f,
884 		             position.z - 12.0f,
885 
886 		             position.x + 12.0f,
887 		             position.y + 72.0f,
888 		             position.z + 12.0f);
889 	}
890 }
891 
distanceFromView(View * view) const892 float Actor::distanceFromView(View *view) const{
893 	float xDist = _position.x - view->_cameraPosition.x;
894 	float zDist = _position.z + view->_cameraPosition.y; // y<->z is intentional, not a bug
895 	return sqrt(xDist * xDist + zDist * zDist);
896 }
897 
isWalking() const898 bool Actor::isWalking() const {
899 	return _walkInfo->isWalking();
900 }
901 
isRunning() const902 bool Actor::isRunning() const {
903 	return _walkInfo->isRunning();
904 }
905 
stopWalking(bool value)906 void Actor::stopWalking(bool value) {
907 	if (value && _id == kActorMcCoy) {
908 		_vm->_playerActorIdle = true;
909 	}
910 
911 	if (isWalking()) {
912 		_walkInfo->stop(_id, true, _animationModeCombatIdle, kAnimationModeIdle);
913 	} else if (inCombat()) {
914 		changeAnimationMode(_animationModeCombatIdle, false);
915 	} else {
916 		changeAnimationMode(kAnimationModeIdle, false);
917 	}
918 }
919 
faceActor(int otherActorId,bool animate)920 void Actor::faceActor(int otherActorId, bool animate) {
921 	if (_setId != _vm->_scene->getSetId()) {
922 		return;
923 	}
924 
925 	Actor *otherActor = _vm->_actors[otherActorId];
926 
927 	if (_setId != otherActor->_setId) {
928 		return;
929 	}
930 
931 	faceXYZ(otherActor->_position, animate);
932 }
933 
faceObject(const Common::String & objectName,bool animate)934 void Actor::faceObject(const Common::String &objectName, bool animate) {
935 	int objectId = _vm->_scene->findObject(objectName);
936 	if (objectId == -1) {
937 		return;
938 	}
939 
940 	BoundingBox boundingBox;
941 	_vm->_scene->objectGetBoundingBox(objectId, &boundingBox);
942 
943 	float x0, y0, z0, x1, y1, z1;
944 	boundingBox.getXYZ(&x0, &y0, &z0, &x1, &y1, &z1);
945 
946 	float x = (x1 + x0) / 2.0f;
947 	float z = (z1 + z0) / 2.0f;
948 	faceXYZ(x, y0, z, animate);
949 }
950 
faceItem(int itemId,bool animate)951 void Actor::faceItem(int itemId, bool animate) {
952 	float x, y, z;
953 	_vm->_items->getXYZ(itemId, &x, &y, &z);
954 	faceXYZ(x, y, z, animate);
955 }
956 
faceWaypoint(int waypointId,bool animate)957 void Actor::faceWaypoint(int waypointId, bool animate) {
958 	float x, y, z;
959 	_vm->_waypoints->getXYZ(waypointId, &x, &y, &z);
960 	faceXYZ(x, y, z, animate);
961 }
962 
faceXYZ(float x,float y,float z,bool animate)963 void Actor::faceXYZ(float x, float y, float z, bool animate) {
964 	if (isWalking()) {
965 		stopWalking(false);
966 	}
967 	if (x == _position.x && z == _position.z) {
968 		return;
969 	}
970 
971 	int heading = angle_1024(_position.x, _position.z, x, z);
972 	faceHeading(heading, animate);
973 }
974 
faceXYZ(const Vector3 & pos,bool animate)975 void Actor::faceXYZ(const Vector3 &pos, bool animate) {
976 	faceXYZ(pos.x, pos.y, pos.z, animate);
977 }
978 
faceCurrentCamera(bool animate)979 void Actor::faceCurrentCamera(bool animate) {
980 	faceXYZ(_vm->_view->_cameraPosition.x, _vm->_view->_cameraPosition.z, -_vm->_view->_cameraPosition.y, animate); // y<->z is intentional, not a bug
981 }
982 
faceHeading(int heading,bool animate)983 void Actor::faceHeading(int heading, bool animate) {
984 	if (heading != _facing) {
985 		if (animate) {
986 			_targetFacing = heading;
987 		} else {
988 			setFacing(heading, true);
989 		}
990 	}
991 }
992 
modifyFriendlinessToOther(int otherActorId,signed int change)993 void Actor::modifyFriendlinessToOther(int otherActorId, signed int change) {
994 	_friendlinessToOther[otherActorId] = CLIP(_friendlinessToOther[otherActorId] + change, 0, 100);
995 }
996 
setFriendlinessToOther(int otherActorId,int friendliness)997 void Actor::setFriendlinessToOther(int otherActorId, int friendliness) {
998 	_friendlinessToOther[otherActorId] = CLIP(friendliness, 0, 100);
999 }
1000 
checkFriendlinessAndHonesty(int otherActorId)1001 bool Actor::checkFriendlinessAndHonesty(int otherActorId) {
1002 	int honestyDiff = 2 * _friendlinessToOther[otherActorId] - _honesty;
1003 	uint friendlinessRange;
1004 
1005 	if (honestyDiff > 30) {
1006 		friendlinessRange = 100;
1007 	} else if (honestyDiff >= 0 && honestyDiff <= 30) {
1008 		friendlinessRange = 50;
1009 	} else {
1010 		friendlinessRange = 0;
1011 	}
1012 
1013 	return _vm->_rnd.getRandomNumberRng(1, 100) <= friendlinessRange;
1014 }
1015 
setHonesty(int honesty)1016 void Actor::setHonesty(int honesty) {
1017 	_honesty = CLIP(honesty, 0, 100);
1018 }
1019 
setIntelligence(int intelligence)1020 void Actor::setIntelligence(int intelligence) {
1021 	_intelligence = CLIP(intelligence, 0, 100);
1022 }
1023 
setStability(int stability)1024 void Actor::setStability(int stability) {
1025 	_stability = CLIP(stability, 0, 100);
1026 }
1027 
setCombatAggressiveness(int combatAggressiveness)1028 void Actor::setCombatAggressiveness(int combatAggressiveness) {
1029 	_combatAggressiveness = CLIP(combatAggressiveness, 0, 100);
1030 }
1031 
setInvisible(bool isInvisible)1032 void Actor::setInvisible(bool isInvisible) {
1033 	_isInvisible = isInvisible;
1034 }
1035 
setImmunityToObstacles(bool isImmune)1036 void Actor::setImmunityToObstacles(bool isImmune) {
1037 	_isImmuneToObstacles = isImmune;
1038 }
1039 
modifyCombatAggressiveness(signed int change)1040 void Actor::modifyCombatAggressiveness(signed int change) {
1041 	_combatAggressiveness = CLIP(_combatAggressiveness + change, 0, 100);
1042 }
1043 
modifyHonesty(signed int change)1044 void Actor::modifyHonesty(signed int change) {
1045 	_honesty = CLIP(_honesty + change, 0, 100);
1046 }
1047 
modifyIntelligence(signed int change)1048 void Actor::modifyIntelligence(signed int change) {
1049 	_intelligence = CLIP(_intelligence + change, 0, 100);
1050 }
1051 
modifyStability(signed int change)1052 void Actor::modifyStability(signed int change) {
1053 	_stability = CLIP(_stability + change, 0, 100);
1054 }
1055 
setFlagDamageAnimIfMoving(bool value)1056 void Actor::setFlagDamageAnimIfMoving(bool value) {
1057 	_damageAnimIfMoving = value;
1058 }
1059 
getFlagDamageAnimIfMoving() const1060 bool Actor::getFlagDamageAnimIfMoving() const {
1061 	return _damageAnimIfMoving;
1062 }
1063 
getSitcomRatio() const1064 int Actor::getSitcomRatio() const {
1065 	return _sitcomRatio;
1066 }
1067 
retire(bool retired,int width,int height,int retiredByActorId)1068 void Actor::retire(bool retired, int width, int height, int retiredByActorId) {
1069 	_isRetired = retired;
1070 	_retiredWidth = MAX(width, 0);
1071 	_retiredHeight = MAX(height, 0);
1072 	if (_id == kActorMcCoy && _isRetired) {
1073 		_vm->playerLosesControl();
1074 		_vm->_playerDead = true;
1075 	}
1076 	if (_isRetired) {
1077 		_vm->_aiScripts->retired(_id, retiredByActorId);
1078 	}
1079 }
1080 
setTarget(bool target)1081 void Actor::setTarget(bool target) {
1082 	_isTarget = target;
1083 }
1084 
setCurrentHP(int hp)1085 void Actor::setCurrentHP(int hp) {
1086 	_currentHP = CLIP(hp, 0, 100);
1087 	if (hp > 0) {
1088 		retire(false, 0, 0, -1);
1089 	}
1090 }
1091 
setHealth(int hp,int maxHp)1092 void Actor::setHealth(int hp, int maxHp) {
1093 	if (hp > maxHp) {
1094 		hp = maxHp;
1095 	}
1096 	_currentHP = CLIP(hp,    0, 100);
1097 	_maxHP     = CLIP(maxHp, 0, 100);
1098 	if (hp > 0) {
1099 		retire(false, 0, 0, -1);
1100 	}
1101 }
1102 
modifyCurrentHP(signed int change)1103 void Actor::modifyCurrentHP(signed int change) {
1104 	_currentHP = CLIP(_currentHP + change, 0, 100);
1105 	if (_currentHP > 0) {
1106 		retire(false, 0, 0, -1);
1107 	}
1108 }
1109 
modifyMaxHP(signed int change)1110 void Actor::modifyMaxHP(signed int change) {
1111 	_maxHP = CLIP(_maxHP + change, 0, 100);
1112 }
1113 
1114 
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)1115 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) {
1116 	_animationModeCombatIdle = animationModeCombatIdle;
1117 	_animationModeCombatWalk = animationModeCombatWalk;
1118 	_animationModeCombatRun = animationModeCombatRun;
1119 	_inCombat = true;
1120 	if (_id != kActorMcCoy && enemyId != -1) {
1121 		_combatInfo->combatOn(_id, initialState, rangedAttack, enemyId, waypointType, fleeRatio, coverRatio, attackRatio, damage, range, unstoppable);
1122 	}
1123 	stopWalking(false);
1124 	changeAnimationMode(_animationModeCombatIdle, false);
1125 	for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) {
1126 		Actor *otherActor = _vm->_actors[i];
1127 		if (i != _id && otherActor->_setId == _setId && !otherActor->_isRetired) {
1128 			_vm->_aiScripts->otherAgentEnteredCombatMode(i, _id, true);
1129 		}
1130 	}
1131 }
1132 
combatModeOff()1133 void Actor::combatModeOff() {
1134 	if (_id != kActorMcCoy) {
1135 		_combatInfo->combatOff();
1136 	}
1137 	_inCombat = false;
1138 	stopWalking(false);
1139 	changeAnimationMode(kAnimationModeIdle, false);
1140 	for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) {
1141 		Actor *otherActor = _vm->_actors[i];
1142 		if (i != _id && otherActor->_setId == _setId && !otherActor->_isRetired) {
1143 			_vm->_aiScripts->otherAgentEnteredCombatMode(i, _id, false);
1144 		}
1145 	}
1146 }
1147 
distanceFromActor(int otherActorId)1148 float Actor::distanceFromActor(int otherActorId) {
1149 	return (_position - _vm->_actors[otherActorId]->_position).length();
1150 }
1151 
angleTo(const Vector3 & target) const1152 int Actor::angleTo(const Vector3 &target) const {
1153 	int angle = angle_1024(_position.x, _position.z, target.x, target.z) - _facing;
1154 	if (angle < -512) {
1155 		angle += 1024;
1156 	} else if (angle > 512) {
1157 		angle -= 1024;
1158 	}
1159 	return angle;
1160 }
1161 
getX() const1162 float Actor::getX() const {
1163 	return _position.x;
1164 }
1165 
getY() const1166 float Actor::getY() const {
1167 	return _position.y;
1168 }
1169 
getZ() const1170 float Actor::getZ() const {
1171 	return _position.z;
1172 }
1173 
getXYZ() const1174 Vector3 Actor::getXYZ() const {
1175 	return _position;
1176 }
1177 
getFacing() const1178 int Actor::getFacing() const {
1179 	return _facing;
1180 }
1181 
getAnimationMode() const1182 int Actor::getAnimationMode() const {
1183 	return _animationMode;
1184 }
1185 
getAnimationId() const1186 int Actor::getAnimationId() const {
1187 	return _animationId;
1188 }
1189 
setGoal(int goalNumber)1190 void Actor::setGoal(int goalNumber) {
1191 	int oldGoalNumber = _goalNumber;
1192 	_goalNumber = goalNumber;
1193 	if (goalNumber == oldGoalNumber) {
1194 		return;
1195 	}
1196 
1197 	_vm->_aiScripts->goalChanged(_id, oldGoalNumber, goalNumber);
1198 	_vm->_sceneScript->actorChangedGoal(_id, goalNumber, oldGoalNumber, _vm->_scene->getSetId() == _setId);
1199 }
1200 
getGoal() const1201 int Actor::getGoal() const {
1202 	return _goalNumber;
1203 }
1204 
speechPlay(int sentenceId,bool voiceOver)1205 void Actor::speechPlay(int sentenceId, bool voiceOver) {
1206 	Common::String name = Common::String::format( "%02d-%04d%s.AUD", _id, sentenceId, _vm->_languageCode.c_str());
1207 
1208 	int pan = 0;
1209 	if (!voiceOver && _id != BladeRunnerEngine::kActorVoiceOver) {
1210 		Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
1211 		pan = (75 * (2 *  CLIP<int>(screenPosition.x, 0, 640) - 640)) / 640; // map [0..640] to [-75..75]
1212 	}
1213 
1214 	_vm->_subtitles->loadInGameSubsText(_id, sentenceId);
1215 	_vm->_subtitles->show();
1216 
1217 	_vm->_audioSpeech->playSpeech(name, pan);
1218 }
1219 
speechStop()1220 void Actor::speechStop() {
1221 	_vm->_subtitles->hide();
1222 	_vm->_audioSpeech->stopSpeech();
1223 }
1224 
isSpeeching()1225 bool Actor::isSpeeching() {
1226 	return _vm->_audioSpeech->isPlaying();
1227 }
1228 
addClueToDatabase(int clueId,int weight,bool clueAcquired,bool unknownFlag,int fromActorId)1229 void Actor::addClueToDatabase(int clueId, int weight, bool clueAcquired, bool unknownFlag, int fromActorId) {
1230 	_clues->add(_id, clueId, weight, clueAcquired, unknownFlag, fromActorId);
1231 }
1232 
canAcquireClue(int clueId) const1233 bool Actor::canAcquireClue(int clueId) const {
1234 	return _clues->exists(clueId);
1235 }
1236 
acquireClue(int clueId,bool unknownFlag,int fromActorId)1237 void Actor::acquireClue(int clueId, bool unknownFlag, int fromActorId) {
1238 	bool hasAlready = hasClue(clueId);
1239 	_clues->acquire(clueId, unknownFlag, fromActorId);
1240 	if (!hasAlready) {
1241 		_vm->_aiScripts->receivedClue(_id, clueId, fromActorId);
1242 	}
1243 }
1244 
loseClue(int clueId)1245 void Actor::loseClue(int clueId) {
1246 	_clues->lose(clueId);
1247 }
1248 
hasClue(int clueId) const1249 bool Actor::hasClue(int clueId) const {
1250 	return _clues->isAcquired(clueId);
1251 }
1252 
copyClues(int actorId)1253 bool Actor::copyClues(int actorId) {
1254 	bool newCluesAcquired = false;
1255 	Actor *otherActor = _vm->_actors[actorId];
1256 	for (int i = 0; i < (int)_vm->_gameInfo->getClueCount(); i++) {
1257 		int clueId = i;
1258 		if (hasClue(clueId) && !_clues->isPrivate(clueId) && otherActor->canAcquireClue(clueId) && !otherActor->hasClue(clueId)) {
1259 			int fromActorId = _id;
1260 			if (_id == BladeRunnerEngine::kActorVoiceOver) {
1261 				fromActorId = _clues->getFromActorId(clueId);
1262 			}
1263 			otherActor->acquireClue(clueId, false, fromActorId);
1264 			newCluesAcquired = true;
1265 		}
1266 	}
1267 	return newCluesAcquired;
1268 }
1269 
acquireCluesByRelations()1270 void Actor::acquireCluesByRelations() {
1271 	if (_setId >= 0 && _setId != kSetFreeSlotG && _setId != _vm->_actors[0]->_setId) {
1272 		for (int i = 0; i < (int)_vm->_gameInfo->getActorCount(); i++) {
1273 			if (i != _id && _vm->_actors[i]->_setId == _setId && i && _id
1274 					&& checkFriendlinessAndHonesty(i)
1275 					&& _vm->_actors[i]->checkFriendlinessAndHonesty(_id)) {
1276 				_clues->acquireCluesByRelations(_id, i);
1277 			}
1278 		}
1279 	}
1280 }
1281 
soundVolume() const1282 int Actor::soundVolume() const {
1283 	float dist = distanceFromView(_vm->_view);
1284 	return (35 * CLIP<int>(100 - (dist / 12), 0, 100)) / 100; // map [0..1200] to [35..0]
1285 }
1286 
soundPan() const1287 int Actor::soundPan() const {
1288 	Vector3 screenPosition = _vm->_view->calculateScreenPosition(_position);
1289 	return (35 * (2 * CLIP<int>(screenPosition.x, 0, 640) - 640)) / 640; // map [0..640] to [-35..35]
1290 }
1291 
isObstacleBetween(const Vector3 & target)1292 bool Actor::isObstacleBetween(const Vector3 &target) {
1293 	return _vm->_sceneObjects->isObstacleBetween(_position, target, -1);
1294 }
1295 
findTargetUnderMouse(BladeRunnerEngine * vm,int mouseX,int mouseY)1296 int Actor::findTargetUnderMouse(BladeRunnerEngine *vm, int mouseX, int mouseY) {
1297 	int setId = vm->_scene->getSetId();
1298 	for (int i = 0; i < (int)vm->_gameInfo->getActorCount(); ++i) {
1299 		if (vm->_actors[i]->isTarget() && vm->_actors[i]->getSetId() == setId) {
1300 			if (vm->_actors[i]->_screenRectangle.contains(mouseX, mouseY)) {
1301 				return i;
1302 			}
1303 		}
1304 	}
1305 	return -1;
1306 }
1307 
findEmptyPositionAround(const Vector3 & startPosition,const Vector3 & targetPosition,float size,Vector3 * emptyPosition)1308 bool Actor::findEmptyPositionAround(const Vector3 &startPosition, const Vector3 &targetPosition, float size, Vector3 *emptyPosition) {
1309 	emptyPosition->x = 0.0f;
1310 	emptyPosition->y = 0.0f;
1311 	emptyPosition->z = 0.0f;
1312 
1313 	int facingLeft = angle_1024(targetPosition, startPosition);
1314 	int facingRight = facingLeft;
1315 
1316 	int facingLeftCounter = 0;
1317 	int facingRightCounter = 0;
1318 
1319 	while (true) {
1320 		float rotatedX = targetPosition.x + size * _vm->_sinTable1024->at(facingLeft);
1321 		float rotatedZ = targetPosition.z - size * _vm->_cosTable1024->at(facingLeft);
1322 
1323 		if (!_walkInfo->isXYZOccupied(rotatedX, targetPosition.y, rotatedZ, _id)) {
1324 			if (_vm->_scene->_set->findWalkbox(rotatedX, rotatedZ) >= 0) {
1325 				emptyPosition->x = rotatedX;
1326 				emptyPosition->y = targetPosition.y;
1327 				emptyPosition->z = rotatedZ;
1328 				return true;
1329 			}
1330 		} else { // looks like a bug as it might not find anything when there is no walkbox at this angle
1331 			facingLeft += 20;
1332 #if BLADERUNNER_ORIGINAL_BUGS
1333 			if (facingLeft > 1024) {
1334 				facingLeft -= 1024;
1335 			}
1336 #else
1337 			// if facingLeft + 20 == 1024 then it could cause the assertion fault
1338 			// in common/sinetables.cpp for SineTable::at(int index) -> assert((index >= 0) && (index < _nPoints))
1339 			if (facingLeft >= 1024) {
1340 				facingLeft -= 1024;
1341 			}
1342 #endif
1343 			facingLeftCounter += 20;
1344 		}
1345 
1346 		rotatedX = size * _vm->_sinTable1024->at(facingRight) + targetPosition.x;
1347 		rotatedZ = size * _vm->_cosTable1024->at(facingRight) + targetPosition.z;
1348 
1349 		if (!_walkInfo->isXYZOccupied(rotatedX, targetPosition.y, rotatedZ, _id)) {
1350 			if (_vm->_scene->_set->findWalkbox(rotatedX, rotatedZ) >= 0) {
1351 				emptyPosition->x = rotatedX;
1352 				emptyPosition->y = targetPosition.y;
1353 				emptyPosition->z = rotatedZ;
1354 				return true;
1355 			}
1356 		} else { // looks like a bug as it might not find anything when there is no walkbox at this angle
1357 			facingRight -= 20;
1358 			if (facingRight < 0) {
1359 				facingRight += 1024;
1360 			}
1361 			facingRightCounter += 20;
1362 		}
1363 
1364 		if (facingLeftCounter > 1024 && facingRightCounter > 1024) {
1365 			return false;
1366 		}
1367 	}
1368 }
1369 
findNearestPosition(Vector3 * nearestPosition,float targetWidth,int proximity,float targetSize,const Vector3 & startPosition,const Vector3 & targetPosition)1370 bool Actor::findNearestPosition(Vector3 *nearestPosition, float targetWidth, int proximity, float targetSize, const Vector3 &startPosition, const Vector3 &targetPosition) {
1371 	nearestPosition->x = 0.0f;
1372 	nearestPosition->y = 0.0f;
1373 	nearestPosition->z = 0.0f;
1374 	float size = proximity + targetSize * 0.5f + targetWidth * 0.5f;
1375 	float distance = (startPosition - targetPosition).length() - targetWidth * 0.5f - targetSize * 0.5f;
1376 	if (size < distance) {
1377 		return findEmptyPositionAround(startPosition, targetPosition, size, nearestPosition);
1378 	} else {
1379 		*nearestPosition = targetPosition;
1380 		return true;
1381 	}
1382 }
1383 
stepAway(const Vector3 & destination,float distance)1384 bool Actor::stepAway(const Vector3 &destination, float distance) {
1385 	Vector3 out;
1386 	bool running;
1387 	if (_walkInfo->findEmptyPositionAround(_id, destination, distance, out)) {
1388 		loopWalk(out, 0, false, false, _position, 0.0f, 24.0f, false, &running, false);
1389 		return true;
1390 	}
1391 	return false;
1392 }
1393 
save(SaveFileWriteStream & f)1394 void Actor::save(SaveFileWriteStream &f) {
1395 	f.writeInt(_id);
1396 	f.writeInt(_setId);
1397 	f.writeVector3(_position);
1398 	f.writeInt(_facing);
1399 	f.writeInt(_targetFacing);
1400 	f.writeInt(_timer4RemainDefault);
1401 
1402 	f.writeInt(_honesty);
1403 	f.writeInt(_intelligence);
1404 	f.writeInt(_combatAggressiveness);
1405 	f.writeInt(_stability);
1406 
1407 	f.writeInt(_goalNumber);
1408 
1409 	f.writeInt(_currentHP);
1410 	f.writeInt(_maxHP);
1411 
1412 	f.writeBool(_movementTrackPaused);
1413 	f.writeInt(_movementTrackNextWaypointId);
1414 	f.writeInt(_movementTrackNextDelay);
1415 	f.writeInt(_movementTrackNextAngle);
1416 	f.writeBool(_movementTrackNextRunning);
1417 
1418 	f.writeInt(_cluesLimit);
1419 
1420 	f.writeBool(_isMoving);
1421 	f.writeBool(_isTarget);
1422 	f.writeBool(_inCombat);
1423 	f.writeBool(_isInvisible);
1424 	f.writeBool(_isRetired);
1425 	f.writeBool(_isImmuneToObstacles);
1426 
1427 	f.writeInt(_animationMode);
1428 	f.writeInt(_fps);
1429 	f.writeInt(_frameMs);
1430 	f.writeInt(_animationId);
1431 	f.writeInt(_animationFrame);
1432 
1433 	f.writeInt(_movementTrackWalkingToWaypointId);
1434 	f.writeInt(_movementTrackDelayOnNextWaypoint);
1435 
1436 	f.writeRect(_screenRectangle);
1437 	f.writeInt(_retiredWidth);
1438 	f.writeInt(_retiredHeight);
1439 	f.writeInt(_damageAnimIfMoving);
1440 	f.writeInt(0);
1441 	f.writeInt(0);
1442 	f.writeFloat(_scale);
1443 
1444 	for (int i = 0; i < kActorTimers; ++i) {
1445 		f.writeInt(_timersLeft[i]);
1446 	}
1447 
1448 	uint32 now = _vm->_time->getPauseStart();
1449 	for (int i = 0; i < kActorTimers; ++i) {
1450 		// this effectively stores the next timeDiff to be applied to timer i (in timerUpdate)
1451 		f.writeInt(now - _timersLast[i]); // Unsigned difference is intentional
1452 	}
1453 
1454 	int actorCount = _vm->_gameInfo->getActorCount();
1455 	for (int i = 0; i != actorCount; ++i) {
1456 		f.writeInt(_friendlinessToOther[i]);
1457 	}
1458 
1459 	_clues->save(f);
1460 
1461 	_movementTrack->save(f);
1462 
1463 	_walkInfo->save(f);
1464 
1465 	f.writeBoundingBox(_bbox, false);
1466 
1467 	_combatInfo->save(f);
1468 
1469 	f.writeInt(_animationModeCombatIdle);
1470 	f.writeInt(_animationModeCombatWalk);
1471 	f.writeInt(_animationModeCombatRun);
1472 }
1473 
load(SaveFileReadStream & f)1474 void Actor::load(SaveFileReadStream &f) {
1475 	_id = f.readInt();
1476 	_setId = f.readInt();
1477 	_position = f.readVector3();
1478 	_facing = f.readInt();
1479 	_targetFacing = f.readInt();
1480 	_timer4RemainDefault = f.readUint32LE();
1481 
1482 	_honesty = f.readInt();
1483 	_intelligence = f.readInt();
1484 	_combatAggressiveness = f.readInt();
1485 	_stability = f.readInt();
1486 
1487 	_goalNumber = f.readInt();
1488 
1489 	_currentHP = f.readInt();
1490 	_maxHP = f.readInt();
1491 
1492 	_movementTrackPaused = f.readBool();
1493 	_movementTrackNextWaypointId = f.readInt();
1494 	_movementTrackNextDelay = f.readInt();
1495 	_movementTrackNextAngle = f.readInt();
1496 	_movementTrackNextRunning = f.readBool();
1497 
1498 	_cluesLimit = f.readInt();
1499 
1500 	_isMoving = f.readBool();
1501 	_isTarget = f.readBool();
1502 	_inCombat = f.readBool();
1503 	_isInvisible = f.readBool();
1504 	_isRetired = f.readBool();
1505 	_isImmuneToObstacles = f.readBool();
1506 
1507 	_animationMode = f.readInt();
1508 	_fps = f.readInt();
1509 	_frameMs = f.readInt();
1510 	_animationId = f.readInt();
1511 	_animationFrame = f.readInt();
1512 
1513 	_movementTrackWalkingToWaypointId = f.readInt();
1514 	_movementTrackDelayOnNextWaypoint = f.readInt();
1515 
1516 	_screenRectangle = f.readRect();
1517 	_retiredWidth = f.readInt();
1518 	_retiredHeight = f.readInt();
1519 	_damageAnimIfMoving = f.readInt();
1520 	f.skip(4);
1521 	f.skip(4);
1522 	_scale = f.readFloat();
1523 
1524 	for (int i = 0; i < kActorTimers; ++i) {
1525 		_timersLeft[i] = f.readUint32LE();
1526 	}
1527 	// Bugfix: Special initialization case for timer 4 (kActorTimerClueExchange) when its value is restored as 0
1528 	// This should be harmless, but will remedy any broken save-games where the timer 4 was saved as 0.
1529 	if (_timersLeft[kActorTimerClueExchange] == 0u) {
1530 		_timersLeft[kActorTimerClueExchange] = _timer4RemainDefault;
1531 	}
1532 
1533 	uint32 now = _vm->_time->getPauseStart();
1534 	for (int i = 0; i < kActorTimers; ++i) {
1535 		_timersLast[i] = now - f.readUint32LE(); // we require an unsigned difference here, since _timersLast is essentially keeping time
1536 	}
1537 
1538 	int actorCount = _vm->_gameInfo->getActorCount();
1539 	for (int i = 0; i != actorCount; ++i) {
1540 		_friendlinessToOther[i] = f.readInt();
1541 	}
1542 
1543 	_clues->load(f);
1544 
1545 	_movementTrack->load(f);
1546 
1547 	_walkInfo->load(f);
1548 
1549 	_bbox = f.readBoundingBox(false);
1550 
1551 	_combatInfo->load(f);
1552 
1553 	_animationModeCombatIdle = f.readInt();
1554 	_animationModeCombatWalk = f.readInt();
1555 	_animationModeCombatRun = f.readInt();
1556 }
1557 
1558 } // End of namespace BladeRunner
1559