scummvm/engines/bladerunner/actor_combat.cpp
2021-12-26 18:48:43 +01:00

701 lines
18 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "bladerunner/actor_combat.h"
#include "bladerunner/actor.h"
#include "bladerunner/audio_speech.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/combat.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/game_info.h"
#include "bladerunner/movement_track.h"
#include "bladerunner/savefile.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/script/ai_script.h"
#include "bladerunner/set.h"
#include "bladerunner/settings.h"
namespace BladeRunner {
ActorCombat::ActorCombat(BladeRunnerEngine *vm) {
_vm = vm;
reset();
}
ActorCombat::~ActorCombat() {
}
void ActorCombat::setup() {
reset();
}
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) {
_actorId = actorId;
_state = initialState;
_rangedAttack = rangedAttackFlag;
_enemyId = enemyId;
_waypointType = waypointType;
_damage = damage;
_fleeRatioConst = fleeRatio;
_coverRatioConst = coverRatio;
_attackRatioConst = attackRatio;
_fleeRatio = fleeRatio;
_coverRatio = coverRatio;
_attackRatio = attackRatio;
_active = true;
if (_rangedAttack) {
_range = range;
} else {
_range = 300;
}
_unstoppable = unstoppable;
Actor *actor = _vm->_actors[_actorId];
_actorPosition = actor->getXYZ();
_enemyPosition = _vm->_actors[_enemyId]->getXYZ();
actor->_movementTrack->flush();
actor->stopWalking(false);
if (_enemyId == kActorMcCoy) {
actor->setTarget(true);
}
_actorHp = actor->getCurrentHP();
_coversWaypointCount = 0;
for (int i = 0; i < (int)_vm->_gameInfo->getCoverWaypointCount(); ++i) {
if (_vm->_combat->_coverWaypoints[i].type == waypointType && _vm->_combat->_coverWaypoints[i].setId == actor->getSetId()) {
++_coversWaypointCount;
}
}
if (_coversWaypointCount == 0) {
_coverRatioConst = 0;
_coverRatio = 0;
}
_fleeWaypointsCount = 0;
for (int i = 0; i < (int)_vm->_gameInfo->getFleeWaypointCount(); ++i) {
if (_vm->_combat->_fleeWaypoints[i].type == waypointType && _vm->_combat->_fleeWaypoints[i].setId == actor->getSetId()) {
++_fleeWaypointsCount;
}
}
if (_fleeWaypointsCount == 0) {
_fleeRatioConst = 0;
_fleeRatio = 0;
}
}
void ActorCombat::combatOff() {
_active = false;
reset();
}
void ActorCombat::tick() {
static int processingCounter = 0;
if (!_active || processingCounter > 0) {
return;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->getSetId() != enemy->getSetId()) {
actor->combatModeOff();
return;
}
++processingCounter;
_actorPosition = actor->getXYZ();
_enemyPosition = enemy->getXYZ();
if (_attackRatioConst >= 0) {
_attackRatio = _attackRatioConst;
} else {
_attackRatio = calculateAttackRatio();
}
if (_vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId) != -1) {
if (_coverRatioConst >= 0) {
_coverRatio = _coverRatioConst;
} else {
_coverRatio = calculateCoverRatio();
}
} else {
_coverRatio = 0;
}
if (_fleeRatioConst >= 0) {
_fleeRatio = _fleeRatioConst;
} else {
_fleeRatio = calculateFleeRatio();
}
float dist = actor->distanceFromActor(_enemyId);
int oldState = _state;
if (_attackRatio < _fleeRatio || _attackRatio < _coverRatio) {
if (_coverRatio >= _fleeRatio && _coverRatio >= _attackRatio) {
_state = kActorCombatStateCover;
} else {
_state = kActorCombatStateFlee;
}
} else {
if (_rangedAttack) {
if (dist > _range) {
_state = kActorCombatStateApproachRangedAttack;
} else {
if (actor->isObstacleBetween(_enemyPosition)) {
_state = kActorCombatStateUncover;
} else {
_state = kActorCombatStateRangedAttack;
}
}
} else {
if (dist > 36.0f) {
_state = kActorCombatStateApproachCloseAttack;
} else {
_state = kActorCombatStateCloseAttack;
}
}
}
if (enemy->isRetired()) {
_state = kActorCombatStateIdle;
}
if (actor->getAnimationMode() == kAnimationModeHit || actor->getAnimationMode() == kAnimationModeCombatHit) {
_state = kActorCombatStateIdle;
} else {
if (_state != oldState) {
actor->stopWalking(false);
}
}
switch (_state) {
case kActorCombatStateCover:
cover();
break;
case kActorCombatStateApproachCloseAttack:
approachToCloseAttack();
break;
case kActorCombatStateUncover:
uncover();
break;
case kActorCombatStateAim:
aim();
break;
case kActorCombatStateRangedAttack:
rangedAttack();
break;
case kActorCombatStateCloseAttack:
closeAttack();
break;
case kActorCombatStateFlee:
flee();
break;
case kActorCombatStateApproachRangedAttack:
approachToRangedAttack();
break;
default:
break;
}
--processingCounter;
}
void ActorCombat::hitAttempt() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (_enemyId == kActorMcCoy && !_vm->playerHasControl() && !_unstoppable) {
return;
}
if (actor->isRetired()) {
return;
}
int attackCoefficient = 0;
if (_rangedAttack) {
attackCoefficient = _rangedAttack ? getCoefficientRangedAttack() : 0;
} else {
attackCoefficient = getCoefficientCloseAttack();
}
if (attackCoefficient == 0) {
return;
}
int random = _vm->_rnd.getRandomNumberRng(1, 100);
if (random <= attackCoefficient) {
if (enemy->isWalking()) {
enemy->stopWalking(true);
}
int sentenceId = _vm->_rnd.getRandomNumberRng(0, 1) ? 9000 : 9005;
if (enemy->inCombat()) {
enemy->changeAnimationMode(kAnimationModeCombatHit, false);
} else {
enemy->changeAnimationMode(kAnimationModeHit, false);
}
int damage = 0;
if (_rangedAttack) {
damage = getDamageRangedAttack(random, attackCoefficient);
} else {
damage = getDamageCloseAttack(random, attackCoefficient);
}
int enemyHp = MAX(enemy->getCurrentHP() - damage, 0);
enemy->setCurrentHP(enemyHp);
if (enemyHp <= 0) {
if (!enemy->isRetired()) {
#if BLADERUNNER_ORIGINAL_BUGS
#else
// make sure the dead enemy won't pick a pending movement track and re-spawn
enemy->_movementTrack->flush();
#endif
if (enemy->inCombat()) {
enemy->changeAnimationMode(kAnimationModeCombatDie, false);
} else {
enemy->changeAnimationMode(kAnimationModeDie, false);
}
sentenceId = 9020;
}
enemy->retire(true, 6, 3, _actorId);
}
if (_enemyId == kActorMcCoy) {
sentenceId += 900;
}
_vm->_audioSpeech->playSpeechLine(_enemyId, sentenceId, 75, enemy->soundPan(), 99);
}
}
void ActorCombat::save(SaveFileWriteStream &f) {
f.writeInt(_actorId);
f.writeBool(_active);
f.writeInt(_state);
f.writeBool(_rangedAttack);
f.writeInt(_enemyId);
f.writeInt(_waypointType);
f.writeInt(_damage);
f.writeInt(_fleeRatio);
f.writeInt(_coverRatio);
f.writeInt(_attackRatio);
f.writeInt(_fleeRatioConst);
f.writeInt(_coverRatioConst);
f.writeInt(_attackRatioConst);
f.writeInt(_range);
f.writeInt(_unstoppable);
f.writeInt(_actorHp);
f.writeInt(_fleeingTowards);
f.writeVector3(_actorPosition);
f.writeVector3(_enemyPosition);
f.writeInt(_coversWaypointCount);
f.writeInt(_fleeWaypointsCount);
}
void ActorCombat::load(SaveFileReadStream &f) {
_actorId = f.readInt();
_active = f.readBool();
_state = f.readInt();
_rangedAttack = f.readBool();
_enemyId = f.readInt();
_waypointType = f.readInt();
_damage = f.readInt();
_fleeRatio = f.readInt();
_coverRatio = f.readInt();
_attackRatio = f.readInt();
_fleeRatioConst = f.readInt();
_coverRatioConst = f.readInt();
_attackRatioConst = f.readInt();
_range = f.readInt();
_unstoppable = f.readInt();
_actorHp = f.readInt();
_fleeingTowards = f.readInt();
_actorPosition = f.readVector3();
_enemyPosition = f.readVector3();
_coversWaypointCount = f.readInt();
_fleeWaypointsCount = f.readInt();
}
void ActorCombat::reset() {
_active = false;
_actorId = -1;
_state = -1;
_rangedAttack = false;
_enemyId = -1;
_waypointType = -1;
_damage = 0;
_fleeRatio = -1;
_coverRatio = -1;
_attackRatio = -1;
_fleeRatioConst = -1;
_coverRatioConst = -1;
_attackRatioConst = -1;
_actorHp = 0;
_range = 300;
_unstoppable = false;
_actorPosition = Vector3(0.0f, 0.0f, 0.0f);
_enemyPosition = Vector3(0.0f, 0.0f, 0.0f);
_coversWaypointCount = 0;
_fleeWaypointsCount = 0;
_fleeingTowards = -1;
}
void ActorCombat::cover() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isWalking()) {
return;
}
if (actor->isObstacleBetween(_enemyPosition)) {
faceEnemy();
return;
}
int coverWaypointId = _vm->_combat->findCoverWaypoint(_waypointType, _actorId, _enemyId);
if (coverWaypointId == -1) {
_state = kActorCombatStateIdle;
} else {
actor->asyncWalkToXYZ(_vm->_combat->_coverWaypoints[coverWaypointId].position, 0, true, 0);
}
}
void ActorCombat::approachToCloseAttack() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float dist = actor->distanceFromActor(_enemyId);
if (dist > 36.0f) {
if (!actor->isWalking() || enemy->isWalking()) {
Vector3 target;
if (findClosestPositionToEnemy(target)) {
actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
} else {
_state = kActorCombatStateCover;
}
}
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
_state = kActorCombatStateCloseAttack;
}
}
void ActorCombat::approachToRangedAttack() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float dist = actor->distanceFromActor(_enemyId);
if (dist > _range) {
if (!actor->isWalking() || enemy->isWalking()) {
Vector3 target;
if (findClosestPositionToEnemy(target)) {
actor->asyncWalkToXYZ(target, 0, dist >= 240.0f, 0);
} else {
_state = kActorCombatStateCover;
}
}
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
_state = kActorCombatStateRangedAttack;
}
}
void ActorCombat::uncover() {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->isObstacleBetween(_enemyPosition)) {
actor->asyncWalkToXYZ(enemy->getXYZ(), 16, false, 0);
} else {
if (actor->isWalking()) {
actor->stopWalking(false);
}
faceEnemy();
}
}
void ActorCombat::aim() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition)) {
if (actor->getAnimationMode() != kAnimationModeCombatIdle) {
actor->changeAnimationMode(kAnimationModeCombatIdle, false);
}
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAim) {
actor->changeAnimationMode(kAnimationModeCombatAim, false);
}
}
}
void ActorCombat::rangedAttack() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > _range)) {
_state = kActorCombatStateApproachRangedAttack;
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
actor->changeAnimationMode(kAnimationModeCombatAttack, false);
}
}
}
}
void ActorCombat::closeAttack() {
Actor *actor = _vm->_actors[_actorId];
if (actor->isObstacleBetween(_enemyPosition) || (actor->distanceFromActor(_enemyId) > 36.0f)) {
_state = kActorCombatStateApproachCloseAttack;
} else {
faceEnemy();
if (actor->getAnimationMode() != kAnimationModeCombatAttack) {
if (_enemyId != kActorMcCoy || _vm->playerHasControl() || _unstoppable) {
actor->changeAnimationMode(kAnimationModeCombatAttack, false);
}
}
}
}
void ActorCombat::flee() {
Actor *actor = _vm->_actors[_actorId];
if (_fleeingTowards != -1 && actor->isWalking()) {
Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[_fleeingTowards].position;
if (distance(_actorPosition, fleeWaypointPosition) <= 12.0f) {
_vm->_aiScripts->fledCombat(_actorId/*, _enemyId*/);
actor->setSetId(kSetFreeSlotG);
actor->combatModeOff();
_fleeingTowards = -1;
}
} else {
int fleeWaypointId = _vm->_combat->findFleeWaypoint(actor->getSetId(), _enemyId, _actorPosition);
if (fleeWaypointId == -1) {
_state = kActorCombatStateIdle;
} else {
Vector3 fleeWaypointPosition = _vm->_combat->_fleeWaypoints[fleeWaypointId].position;
actor->asyncWalkToXYZ(fleeWaypointPosition, 0, true, 0);
_fleeingTowards = fleeWaypointId;
}
}
}
void ActorCombat::faceEnemy() {
_vm->_actors[_actorId]->setFacing(angle_1024(_actorPosition.x, _actorPosition.z, _enemyPosition.x, _enemyPosition.z), false);
}
int ActorCombat::getCoefficientCloseAttack() const{
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
float distance = actor->distanceFromActor(_enemyId);
if (distance > 36.0f) {
return 0;
}
int aggressiveness = 0;
if (enemy->isRunning()) {
aggressiveness = 11;
} else if (enemy->isMoving()) {
aggressiveness = 22;
} else {
aggressiveness = 33;
}
aggressiveness += actor->getCombatAggressiveness() / 3;
int angle = abs(actor->angleTo(_enemyPosition));
if (angle > 128) {
return false;
}
return aggressiveness + (abs(angle - 128) / 3.7f);
}
int ActorCombat::getCoefficientRangedAttack() const {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
if (actor->isObstacleBetween(_enemyPosition)) {
return 0;
}
int distance = MIN(actor->distanceFromActor(_enemyId), 900.0f);
int aggressiveness = 0;
if (enemy->isRunning()) {
aggressiveness = 10;
} else if (enemy->isMoving()) {
aggressiveness = 20;
} else {
aggressiveness = 30;
}
aggressiveness += actor->getCombatAggressiveness() / 5;
return aggressiveness + abs((distance / 30) - 30) + actor->getIntelligence() / 5;
}
int ActorCombat::getDamageCloseAttack(int min, int max) const {
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
return _damage / 2;
}
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
return _damage;
}
return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
}
int ActorCombat::getDamageRangedAttack(int min, int max) const {
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyEasy) {
return _damage / 2;
}
if (_enemyId == kActorMcCoy && _vm->_settings->getDifficulty() == kGameDifficultyHard) {
return _damage;
}
return ((MIN(max - min, 30) * 100.0f / 60.0f) + 50) * _damage / 100;
}
int ActorCombat::calculateAttackRatio() const {
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int aggressivenessFactor = actor->getCombatAggressiveness();
int actorHpFactor = actor->getCurrentHP();
int enemyHpFactor = 100 - enemy->getCurrentHP();
int combatFactor = enemy->inCombat() ? 0 : 100;
int angleFactor = (100 * abs(enemy->angleTo(_actorPosition))) / 512;
int distanceFactor = 2 * (50 - MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f));
if (_rangedAttack) {
return
angleFactor * 0.25f +
combatFactor * 0.05f +
enemyHpFactor * 0.20f +
actorHpFactor * 0.10f +
aggressivenessFactor * 0.40f;
} else {
return
distanceFactor * 0.20f +
angleFactor * 0.10f +
combatFactor * 0.10f +
enemyHpFactor * 0.15f +
actorHpFactor * 0.15f +
aggressivenessFactor * 0.30f;
}
}
int ActorCombat::calculateCoverRatio() const {
if (_coversWaypointCount == 0) {
return 0;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int angleFactor = 100 - (100 * abs(enemy->angleTo(_actorPosition))) / 512;
int actorHpFactor = 100 - actor->getCurrentHP();
int enemyHpFactor = enemy->getCurrentHP();
int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
int distanceFactor = 2 * MIN(actor->distanceFromActor(_enemyId) / 12.0f, 50.0f);
if (_rangedAttack) {
return
angleFactor * 0.40f +
enemyHpFactor * 0.05f +
actorHpFactor * 0.15f +
aggressivenessFactor * 0.50f;
} else {
return
distanceFactor * 0.25f +
angleFactor * 0.20f +
enemyHpFactor * 0.05f +
actorHpFactor * 0.10f +
aggressivenessFactor * 0.50f;
}
}
int ActorCombat::calculateFleeRatio() const {
if (_fleeWaypointsCount == 0) {
return 0;
}
Actor *actor = _vm->_actors[_actorId];
Actor *enemy = _vm->_actors[_enemyId];
int aggressivenessFactor = 100 - actor->getCombatAggressiveness();
int actorHpFactor = 100 - actor->getCurrentHP();
int combatFactor = enemy->inCombat() ? 100 : 0;
return
combatFactor * 0.2f +
actorHpFactor * 0.4f +
aggressivenessFactor * 0.4f;
}
bool ActorCombat::findClosestPositionToEnemy(Vector3 &output) const {
output = Vector3();
Vector3 offsets[] = {
Vector3( 0.0f, 0.0f, -28.0f),
Vector3( 28.0f, 0.0f, 0.0f),
Vector3( 0.0f, 0.0f, 28.0f),
Vector3(-28.0f, 0.0f, 0.0f)
};
float min = -1.0f;
for (int i = 0; i < 4; ++i) {
Vector3 test = _enemyPosition + offsets[i];
float dist = distance(_actorPosition, test);
if ( min == -1.0f || dist < min) {
if (!_vm->_sceneObjects->existsOnXZ(_actorId + kSceneObjectOffsetActors, test.x, test.z, true, true) && _vm->_scene->_set->findWalkbox(test.x, test.z) >= 0) {
output = test;
min = dist;
}
}
}
return min >= 0.0f;
}
} // End of namespace BladeRunner