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_combat.h"
24 
25 #include "bladerunner/actor.h"
26 #include "bladerunner/audio_speech.h"
27 #include "bladerunner/bladerunner.h"
28 #include "bladerunner/combat.h"
29 #include "bladerunner/game_constants.h"
30 #include "bladerunner/game_info.h"
31 #include "bladerunner/movement_track.h"
32 #include "bladerunner/savefile.h"
33 #include "bladerunner/scene.h"
34 #include "bladerunner/scene_objects.h"
35 #include "bladerunner/script/ai_script.h"
36 #include "bladerunner/set.h"
37 #include "bladerunner/settings.h"
38 
39 namespace BladeRunner {
40 
ActorCombat(BladeRunnerEngine * vm)41 ActorCombat::ActorCombat(BladeRunnerEngine *vm) {
42 	_vm = vm;
43 	reset();
44 }
45 
~ActorCombat()46 ActorCombat::~ActorCombat() {
47 }
48 
setup()49 void ActorCombat::setup() {
50 	reset();
51 }
52 
combatOn(int actorId,int initialState,bool rangedAttackFlag,int enemyId,int waypointType,int fleeRatio,int coverRatio,int attackRatio,int damage,int range,bool unstoppable)53 void ActorCombat::combatOn(int actorId, int initialState, bool rangedAttackFlag, int enemyId, int waypointType, int fleeRatio, int coverRatio, int attackRatio, int damage, int range, bool unstoppable) {
54 	_actorId = actorId;
55 	_state = initialState;
56 	_rangedAttack = rangedAttackFlag;
57 	_enemyId = enemyId;
58 	_waypointType = waypointType;
59 	_damage = damage;
60 	_fleeRatioConst = fleeRatio;
61 	_coverRatioConst = coverRatio;
62 	_attackRatioConst = attackRatio;
63 	_fleeRatio = fleeRatio;
64 	_coverRatio = coverRatio;
65 	_attackRatio = attackRatio;
66 	_active = true;
67 	if (_rangedAttack) {
68 		_range = range;
69 	} else {
70 		_range = 300;
71 	}
72 	_unstoppable = unstoppable;
73 
74 	Actor *actor = _vm->_actors[_actorId];
75 
76 	_actorPosition = actor->getXYZ();
77 	_enemyPosition = _vm->_actors[_enemyId]->getXYZ();
78 
79 	actor->_movementTrack->flush();
80 	actor->stopWalking(false);
81 
82 	if (_enemyId == kActorMcCoy) {
83 		actor->setTarget(true);
84 	}
85 
86 	_actorHp = actor->getCurrentHP();
87 
88 	_coversWaypointCount = 0;
89 	for (int i = 0; i < (int)_vm->_gameInfo->getCoverWaypointCount(); ++i) {
90 		if (_vm->_combat->_coverWaypoints[i].type == waypointType && _vm->_combat->_coverWaypoints[i].setId == actor->getSetId()) {
91 			++_coversWaypointCount;
92 		}
93 	}
94 	if (_coversWaypointCount == 0) {
95 		_coverRatioConst = 0;
96 		_coverRatio = 0;
97 	}
98 
99 	_fleeWaypointsCount = 0;
100 	for (int i = 0; i < (int)_vm->_gameInfo->getFleeWaypointCount(); ++i) {
101 		if (_vm->_combat->_fleeWaypoints[i].type == waypointType && _vm->_combat->_fleeWaypoints[i].setId == actor->getSetId()) {
102 			++_fleeWaypointsCount;
103 		}
104 	}
105 	if (_fleeWaypointsCount == 0) {
106 		_fleeRatioConst = 0;
107 		_fleeRatio = 0;
108 	}
109 }
110 
combatOff()111 void ActorCombat::combatOff() {
112 	_active = false;
113 	reset();
114 }
115 
tick()116 void ActorCombat::tick() {
117 	static int processingCounter = 0;
118 
119 	if (!_active || processingCounter > 0) {
120 		return;
121 	}
122 
123 	Actor *actor = _vm->_actors[_actorId];
124 	Actor *enemy = _vm->_actors[_enemyId];
125 
126 	if (actor->getSetId() != enemy->getSetId()) {
127 		actor->combatModeOff();
128 		return;
129 	}
130 
131 	++processingCounter;
132 
133 	_actorPosition = actor->getXYZ();
134 	_enemyPosition = enemy->getXYZ();
135 
136 	if (_attackRatioConst >= 0) {
137 		_attackRatio = _attackRatioConst;
138 	} else {
139 		_attackRatio = calculateAttackRatio();
140 	}
141 
142 	if (_vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId) != -1) {
143 		if (_coverRatioConst >= 0) {
144 			_coverRatio = _coverRatioConst;
145 		} else {
146 			_coverRatio = calculateCoverRatio();
147 		}
148 	} else {
149 		_coverRatio = 0;
150 	}
151 
152 	if (_fleeRatioConst >= 0) {
153 		_fleeRatio = _fleeRatioConst;
154 	} else {
155 		_fleeRatio = calculateFleeRatio();
156 	}
157 
158 	float dist = actor->distanceFromActor(_enemyId);
159 	int oldState = _state;
160 
161 	if (_attackRatio < _fleeRatio || _attackRatio < _coverRatio) {
162 		if (_coverRatio >= _fleeRatio && _coverRatio >= _attackRatio) {
163 			_state = kActorCombatStateCover;
164 		} else {
165 			_state = kActorCombatStateFlee;
166 		}
167 	} else {
168 		if (_rangedAttack) {
169 			if (dist > _range) {
170 				_state = kActorCombatStateApproachRangedAttack;
171 			} else {
172 				if (actor->isObstacleBetween(_enemyPosition)) {
173 					_state = kActorCombatStateUncover;
174 				} else {
175 					_state = kActorCombatStateRangedAttack;
176 				}
177 			}
178 		} else {
179 			if (dist > 36.0f) {
180 				_state = kActorCombatStateApproachCloseAttack;
181 			} else {
182 				_state = kActorCombatStateCloseAttack;
183 			}
184 		}
185 	}
186 
187 	if (enemy->isRetired()) {
188 		_state = kActorCombatStateIdle;
189 	}
190 
191 	if (actor->getAnimationMode() == kAnimationModeHit || actor->getAnimationMode() == kAnimationModeCombatHit) {
192 		_state = kActorCombatStateIdle;
193 	} else {
194 		if (_state != oldState) {
195 			actor->stopWalking(false);
196 		}
197 	}
198 	switch (_state) {
199 	case kActorCombatStateCover:
200 		cover();
201 		break;
202 	case kActorCombatStateApproachCloseAttack:
203 		approachToCloseAttack();
204 		break;
205 	case kActorCombatStateUncover:
206 		uncover();
207 		break;
208 	case kActorCombatStateAim:
209 		aim();
210 		break;
211 	case kActorCombatStateRangedAttack:
212 		rangedAttack();
213 		break;
214 	case kActorCombatStateCloseAttack:
215 		closeAttack();
216 		break;
217 	case kActorCombatStateFlee:
218 		flee();
219 		break;
220 	case kActorCombatStateApproachRangedAttack:
221 		approachToRangedAttack();
222 		break;
223 	default:
224 		break;
225 	}
226 	--processingCounter;
227 }
228 
hitAttempt()229 void ActorCombat::hitAttempt() {
230 	Actor *actor = _vm->_actors[_actorId];
231 	Actor *enemy = _vm->_actors[_enemyId];
232 
233 	if (_enemyId == kActorMcCoy && !_vm->playerHasControl() && !_unstoppable) {
234 		return;
235 	}
236 
237 	if (actor->isRetired()) {
238 		return;
239 	}
240 
241 	int attackCoefficient = 0;
242 	if (_rangedAttack) {
243 		attackCoefficient = _rangedAttack ? getCoefficientRangedAttack() : 0;
244 	} else {
245 		attackCoefficient = getCoefficientCloseAttack();
246 	}
247 
248 	if (attackCoefficient == 0) {
249 		return;
250 	}
251 
252 	int random = _vm->_rnd.getRandomNumberRng(1, 100);
253 
254 	if (random <= attackCoefficient) {
255 		if (enemy->isWalking()) {
256 			enemy->stopWalking(true);
257 		}
258 
259 		int sentenceId = _vm->_rnd.getRandomNumberRng(0, 1) ? 9000 : 9005;
260 		if (enemy->inCombat()) {
261 			enemy->changeAnimationMode(kAnimationModeCombatHit, false);
262 		} else {
263 			enemy->changeAnimationMode(kAnimationModeHit, false);
264 		}
265 
266 		int damage = 0;
267 		if (_rangedAttack) {
268 			damage = getDamageRangedAttack(random, attackCoefficient);
269 		} else {
270 			damage = getDamageCloseAttack(random, attackCoefficient);
271 		}
272 
273 		int enemyHp = MAX(enemy->getCurrentHP() - damage, 0);
274 		enemy->setCurrentHP(enemyHp);
275 
276 		if (enemyHp <= 0) {
277 			if (!enemy->isRetired()) {
278 #if BLADERUNNER_ORIGINAL_BUGS
279 #else
280 				// make sure the dead enemy won't pick a pending movement track and re-spawn
281 				enemy->_movementTrack->flush();
282 #endif
283 				if (enemy->inCombat()) {
284 					enemy->changeAnimationMode(kAnimationModeCombatDie, false);
285 				} else {
286 					enemy->changeAnimationMode(kAnimationModeDie, false);
287 				}
288 				sentenceId = 9020;
289 			}
290 			enemy->retire(true, 6, 3, _actorId);
291 		}
292 
293 		if (_enemyId == kActorMcCoy) {
294 			sentenceId += 900;
295 		}
296 
297 		_vm->_audioSpeech->playSpeechLine(_enemyId, sentenceId, 75, enemy->soundPan(), 99);
298 	}
299 }
300 
save(SaveFileWriteStream & f)301 void ActorCombat::save(SaveFileWriteStream &f) {
302 	f.writeInt(_actorId);
303 	f.writeBool(_active);
304 	f.writeInt(_state);
305 	f.writeBool(_rangedAttack);
306 	f.writeInt(_enemyId);
307 	f.writeInt(_waypointType);
308 	f.writeInt(_damage);
309 	f.writeInt(_fleeRatio);
310 	f.writeInt(_coverRatio);
311 	f.writeInt(_attackRatio);
312 	f.writeInt(_fleeRatioConst);
313 	f.writeInt(_coverRatioConst);
314 	f.writeInt(_attackRatioConst);
315 	f.writeInt(_range);
316 	f.writeInt(_unstoppable);
317 	f.writeInt(_actorHp);
318 	f.writeInt(_fleeingTowards);
319 	f.writeVector3(_actorPosition);
320 	f.writeVector3(_enemyPosition);
321 	f.writeInt(_coversWaypointCount);
322 	f.writeInt(_fleeWaypointsCount);
323 }
324 
load(SaveFileReadStream & f)325 void ActorCombat::load(SaveFileReadStream &f) {
326 	_actorId = f.readInt();
327 	_active = f.readBool();
328 	_state = f.readInt();
329 	_rangedAttack = f.readBool();
330 	_enemyId = f.readInt();
331 	_waypointType = f.readInt();
332 	_damage = f.readInt();
333 	_fleeRatio = f.readInt();
334 	_coverRatio = f.readInt();
335 	_attackRatio = f.readInt();
336 	_fleeRatioConst = f.readInt();
337 	_coverRatioConst = f.readInt();
338 	_attackRatioConst = f.readInt();
339 	_range = f.readInt();
340 	_unstoppable = f.readInt();
341 	_actorHp = f.readInt();
342 	_fleeingTowards = f.readInt();
343 	_actorPosition = f.readVector3();
344 	_enemyPosition = f.readVector3();
345 	_coversWaypointCount = f.readInt();
346 	_fleeWaypointsCount = f.readInt();
347 }
348 
reset()349 void ActorCombat::reset() {
350 	_active              = false;
351 	_actorId             = -1;
352 	_state               = -1;
353 	_rangedAttack        = false;
354 	_enemyId             = -1;
355 	_waypointType        = -1;
356 	_damage              = 0;
357 	_fleeRatio           = -1;
358 	_coverRatio          = -1;
359 	_attackRatio         = -1;
360 	_fleeRatioConst      = -1;
361 	_coverRatioConst     = -1;
362 	_attackRatioConst    = -1;
363 	_actorHp             = 0;
364 	_range               = 300;
365 	_unstoppable         = false;
366 	_actorPosition       = Vector3(0.0f, 0.0f, 0.0f);
367 	_enemyPosition       = Vector3(0.0f, 0.0f, 0.0f);
368 	_coversWaypointCount = 0;
369 	_fleeWaypointsCount  = 0;
370 	_fleeingTowards      = -1;
371 }
372 
cover()373 void ActorCombat::cover() {
374 	Actor *actor = _vm->_actors[_actorId];
375 
376 	if (actor->isWalking()) {
377 		return;
378 	}
379 
380 	if (actor->isObstacleBetween(_enemyPosition)) {
381 		faceEnemy();
382 		return;
383 	}
384 
385 	int coverWaypointId = _vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId);
386 	if (coverWaypointId == -1) {
387 		_state = kActorCombatStateIdle;
388 	} else {
389 		actor->asyncWalkToXYZ(_vm->_combat->_coverWaypoints[coverWaypointId].position, 0, true, 0);
390 	}
391 }
392 
approachToCloseAttack()393 void ActorCombat::approachToCloseAttack() {
394 	Actor *actor = _vm->_actors[_actorId];
395 	Actor *enemy = _vm->_actors[_enemyId];
396 
397 	float dist = actor->distanceFromActor(_enemyId);
398 	if (dist > 36.0f) {
399 		if (!actor->isWalking() || enemy->isWalking()) {
400 			Vector3 target;
401 			if (findClosestPositionToEnemy(target)) {
402 				actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
403 			} else {
404 				_state = kActorCombatStateCover;
405 			}
406 		}
407 	} else {
408 		if (actor->isWalking()) {
409 			actor->stopWalking(false);
410 		}
411 		faceEnemy();
412 		_state = kActorCombatStateCloseAttack;
413 	}
414 }
415 
approachToRangedAttack()416 void ActorCombat::approachToRangedAttack() {
417 	Actor *actor = _vm->_actors[_actorId];
418 	Actor *enemy = _vm->_actors[_enemyId];
419 
420 	float dist = actor->distanceFromActor(_enemyId);
421 	if (dist > _range) {
422 		if (!actor->isWalking() || enemy->isWalking()) {
423 			Vector3 target;
424 			if (findClosestPositionToEnemy(target)) {
425 				actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
426 			} else {
427 				_state = kActorCombatStateCover;
428 			}
429 		}
430 	} else {
431 		if (actor->isWalking()) {
432 			actor->stopWalking(false);
433 		}
434 		faceEnemy();
435 		_state = kActorCombatStateRangedAttack;
436 	}
437 }
438 
uncover()439 void ActorCombat::uncover() {
440 	Actor *actor = _vm->_actors[_actorId];
441 	Actor *enemy = _vm->_actors[_enemyId];
442 
443 	if (actor->isObstacleBetween(_enemyPosition)) {
444 		actor->asyncWalkToXYZ(enemy->getXYZ(), 16, false, 0);
445 	} else {
446 		if (actor->isWalking()) {
447 			actor->stopWalking(false);
448 		}
449 		faceEnemy();
450 	}
451 }
452 
aim()453 void ActorCombat::aim() {
454 	Actor *actor = _vm->_actors[_actorId];
455 
456 	if (actor->isObstacleBetween(_enemyPosition)) {
457 		if (actor->getAnimationMode() != kAnimationModeCombatIdle) {
458 			actor->changeAnimationMode(kAnimationModeCombatIdle, false);
459 		}
460 	} else {
461 		faceEnemy();
462 		if (actor->getAnimationMode() != kAnimationModeCombatAim) {
463 			actor->changeAnimationMode(kAnimationModeCombatAim, false);
464 		}
465 	}
466 }
467 
rangedAttack()468 void ActorCombat::rangedAttack() {
469 	Actor *actor = _vm->_actors[_actorId];
470 
471 	if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > _range)) {
472 		_state = kActorCombatStateApproachRangedAttack;
473 	} else {
474 		faceEnemy();
475 		if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
476 			if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
477 				actor->changeAnimationMode(kAnimationModeCombatAttack, false);
478 			}
479 		}
480 	}
481 }
482 
closeAttack()483 void ActorCombat::closeAttack() {
484 	Actor *actor = _vm->_actors[_actorId];
485 
486 	if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > 36.0f)) {
487 		_state = kActorCombatStateApproachCloseAttack;
488 	} else {
489 		faceEnemy();
490 		if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
491 			if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
492 				actor->changeAnimationMode(kAnimationModeCombatAttack, false);
493 			}
494 		}
495 	}
496 }
497 
flee()498 void ActorCombat::flee() {
499 	Actor *actor = _vm->_actors[_actorId];
500 
501 	if (_fleeingTowards != -1 && actor->isWalking()) {
502 		Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[_fleeingTowards].position;
503 		if (distance(_actorPosition, fleeWaypointPosition) <= 12.0f) {
504 			_vm->_aiScripts->fledCombat(_actorId/*, _enemyId*/);
505 			actor->setSetId(kSetFreeSlotG);
506 			actor->combatModeOff();
507 			_fleeingTowards = -1;
508 		}
509 	} else {
510 		int fleeWaypointId = _vm->_combat->findFleeWaypoint(actor->getSetId(), _enemyId, _actorPosition);
511 		if (fleeWaypointId == -1) {
512 			_state = kActorCombatStateIdle;
513 		} else {
514 			Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[fleeWaypointId].position;
515 			actor->asyncWalkToXYZ(fleeWaypointPosition, 0, true, 0);
516 			_fleeingTowards = fleeWaypointId;
517 		}
518 	}
519 }
520 
faceEnemy()521 void ActorCombat::faceEnemy() {
522 	_vm->_actors[_actorId]->setFacing(angle_1024(_actorPosition.x, _actorPosition.z, _enemyPosition.x, _enemyPosition.z), false);
523 }
524 
getCoefficientCloseAttack() const525 int ActorCombat::getCoefficientCloseAttack() const{
526 	Actor *actor = _vm->_actors[_actorId];
527 	Actor *enemy = _vm->_actors[_enemyId];
528 
529 	float distance = actor->distanceFromActor(_enemyId);
530 
531 	if (distance > 36.0f) {
532 		return 0;
533 	}
534 
535 	int aggressiveness = 0;
536 	if (enemy->isRunning()) {
537 		aggressiveness = 11;
538 	} else if (enemy->isMoving()) {
539 		aggressiveness = 22;
540 	} else {
541 		aggressiveness = 33;
542 	}
543 
544 	aggressiveness += actor->getCombatAggressiveness() / 3;
545 
546 	int angle = abs(actor->angleTo(_enemyPosition));
547 
548 	if (angle > 128) {
549 		return false;
550 	}
551 
552 	return aggressiveness + (abs(angle - 128) / 3.7f);
553 }
554 
getCoefficientRangedAttack() const555 int ActorCombat::getCoefficientRangedAttack() const {
556 	Actor *actor = _vm->_actors[_actorId];
557 	Actor *enemy = _vm->_actors[_enemyId];
558 
559 	if (actor->isObstacleBetween(_enemyPosition)) {
560 		return 0;
561 	}
562 
563 	int distance = MIN(actor->distanceFromActor(_enemyId), 900.0f);
564 
565 	int aggressiveness = 0;
566 	if (enemy->isRunning()) {
567 		aggressiveness = 10;
568 	} else if (enemy->isMoving()) {
569 		aggressiveness = 20;
570 	} else {
571 		aggressiveness = 30;
572 	}
573 
574 	aggressiveness += actor->getCombatAggressiveness() / 5;
575 	return aggressiveness + abs((distance / 30) - 30) + actor->getIntelligence() / 5;
576 }
577 
getDamageCloseAttack(int min,int max) const578 int ActorCombat::getDamageCloseAttack(int min, int max) const {
579 	if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
580 		return _damage / 2;
581 	}
582 	if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
583 		return _damage;
584 	}
585 	return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
586 }
587 
getDamageRangedAttack(int min,int max) const588 int ActorCombat::getDamageRangedAttack(int min, int max) const {
589 	if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
590 		return _damage / 2;
591 	}
592 	if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
593 		return _damage;
594 	}
595 	return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
596 }
597 
calculateAttackRatio() const598 int ActorCombat::calculateAttackRatio() const {
599 	Actor *actor = _vm->_actors[_actorId];
600 	Actor *enemy = _vm->_actors[_enemyId];
601 
602 	int aggressivenessFactor = actor->getCombatAggressiveness();
603 	int actorHpFactor        = actor->getCurrentHP();
604 	int enemyHpFactor        = 100 - enemy->getCurrentHP();
605 	int combatFactor         = enemy->inCombat() ? 0 : 100;
606 	int angleFactor          = (100 * abs(enemy->angleTo(_actorPosition))) / 512;
607 	int distanceFactor       = 2 * (50 - MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f));
608 
609 	if (_rangedAttack) {
610 		return
611 			angleFactor          * 0.25f +
612 			combatFactor         * 0.05f +
613 			enemyHpFactor        * 0.20f +
614 			actorHpFactor        * 0.10f +
615 			aggressivenessFactor * 0.40f;
616 	} else {
617 		return
618 			distanceFactor       * 0.20f +
619 			angleFactor          * 0.10f +
620 			combatFactor         * 0.10f +
621 			enemyHpFactor        * 0.15f +
622 			actorHpFactor        * 0.15f +
623 			aggressivenessFactor * 0.30f;
624 	}
625 }
626 
calculateCoverRatio() const627 int ActorCombat::calculateCoverRatio() const {
628 	if (_coversWaypointCount == 0) {
629 		return 0;
630 	}
631 
632 	Actor *actor = _vm->_actors[_actorId];
633 	Actor *enemy = _vm->_actors[_enemyId];
634 
635 	int angleFactor          = 100 - (100 * abs(enemy->angleTo(_actorPosition))) / 512;
636 	int actorHpFactor        = 100 - actor->getCurrentHP();
637 	int enemyHpFactor        = enemy->getCurrentHP();
638 	int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
639 	int distanceFactor       = 2 * MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f);
640 
641 	if (_rangedAttack) {
642 		return
643 			angleFactor          * 0.40f +
644 			enemyHpFactor        * 0.05f +
645 			actorHpFactor        * 0.15f +
646 			aggressivenessFactor * 0.50f;
647 	} else {
648 		return
649 			distanceFactor       * 0.25f +
650 			angleFactor          * 0.20f +
651 			enemyHpFactor        * 0.05f +
652 			actorHpFactor        * 0.10f +
653 			aggressivenessFactor * 0.50f;
654 	}
655 }
656 
calculateFleeRatio() const657 int ActorCombat::calculateFleeRatio() const {
658 	if (_fleeWaypointsCount == 0) {
659 		return 0;
660 	}
661 
662 	Actor *actor = _vm->_actors[_actorId];
663 	Actor *enemy = _vm->_actors[_enemyId];
664 
665 	int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
666 	int actorHpFactor        = 100 - actor->getCurrentHP();
667 	int combatFactor         = enemy->inCombat() ? 100 : 0;
668 
669 	return
670 		combatFactor * 0.2f +
671 		actorHpFactor * 0.4f +
672 		aggressivenessFactor * 0.4f;
673 }
674 
findClosestPositionToEnemy(Vector3 & output) const675 bool ActorCombat::findClosestPositionToEnemy(Vector3 &output) const {
676 	output = Vector3();
677 
678 	Vector3 offsets[] = {
679 		Vector3(  0.0f, 0.0f, -28.0f),
680 		Vector3( 28.0f, 0.0f,   0.0f),
681 		Vector3(  0.0f, 0.0f,  28.0f),
682 		Vector3(-28.0f, 0.0f,   0.0f)
683 	};
684 
685 	float min = -1.0f;
686 
687 	for (int i = 0; i < 4; ++i) {
688 		Vector3 test = _enemyPosition + offsets[i];
689 		float dist = distance(_actorPosition, test);
690 		if ( min == -1.0f || dist < min) {
691 			if (!_vm->_sceneObjects->existsOnXZ(_actorId + kSceneObjectOffsetActors, test.x, test.z, true, true) && _vm->_scene->_set->findWalkbox(test.x, test.z) >= 0) {
692 				output = test;
693 				min = dist;
694 			}
695 		}
696 	}
697 
698 	return min >= 0.0f;
699 }
700 
701 } // End of namespace BladeRunner
702