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/combat.h"
24 
25 #include "bladerunner/actor.h"
26 #include "bladerunner/audio_speech.h"
27 #include "bladerunner/bladerunner.h"
28 #include "bladerunner/game_constants.h"
29 #include "bladerunner/game_info.h"
30 #include "bladerunner/movement_track.h"
31 #include "bladerunner/savefile.h"
32 #include "bladerunner/scene_objects.h"
33 #include "bladerunner/settings.h"
34 
35 namespace BladeRunner {
36 
Combat(BladeRunnerEngine * vm)37 Combat::Combat(BladeRunnerEngine *vm) {
38 	_vm = vm;
39 
40 	_coverWaypoints.resize(_vm->_gameInfo->getCoverWaypointCount());
41 	_fleeWaypoints.resize(_vm->_gameInfo->getFleeWaypointCount());
42 
43 	reset();
44 }
45 
~Combat()46 Combat::~Combat() {
47 }
48 
reset()49 void Combat::reset() {
50 	_active = false;
51 	_enabled = true;
52 
53 	_ammoDamage[0] = 10;
54 	_ammoDamage[1] = 20;
55 	_ammoDamage[2] = 30;
56 
57 	for (int i = 0; i < kSoundCount; ++i) {
58 		_hitSoundId[i] = -1;
59 		_missSoundId[i] = -1;
60 	}
61 }
62 
activate()63 void Combat::activate() {
64 	if (_enabled) {
65 		_vm->_playerActor->combatModeOn(-1, true, -1, -1, kAnimationModeCombatIdle, kAnimationModeCombatWalk, kAnimationModeCombatRun, -1, -1, -1, _vm->_combat->_ammoDamage[_vm->_settings->getAmmoType()], 0, false);
66 		_active = true;
67 	}
68 }
69 
deactivate()70 void Combat::deactivate() {
71 	if (_enabled) {
72 		_vm->_playerActor->combatModeOff();
73 		_active = false;
74 	}
75 }
76 
change()77 void Combat::change() {
78 	if (!_vm->_playerActor->mustReachWalkDestination() && _enabled) {
79 		if (_active) {
80 			deactivate();
81 		} else {
82 			activate();
83 		}
84 	}
85 }
86 
isActive() const87 bool Combat::isActive() const{
88 	return _active;
89 }
90 
enable()91 void Combat::enable() {
92 	_enabled = true;
93 }
94 
disable()95 void Combat::disable() {
96 	_enabled = false;
97 }
98 
setHitSound(int ammoType,int column,int soundId)99 void Combat::setHitSound(int ammoType, int column, int soundId) {
100 	_hitSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * ammoType + column] = soundId;
101 }
102 
setMissSound(int ammoType,int column,int soundId)103 void Combat::setMissSound(int ammoType, int column, int soundId) {
104 	_missSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * ammoType + column] = soundId;
105 }
106 
getHitSound() const107 int Combat::getHitSound() const {
108 	return _hitSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * _vm->_settings->getAmmoType() + _vm->_rnd.getRandomNumber(2)];
109 }
110 
getMissSound() const111 int Combat::getMissSound() const {
112 	return _missSoundId[(kSoundCount/_vm->_settings->getAmmoTypesCount()) * _vm->_settings->getAmmoType() + _vm->_rnd.getRandomNumber(2)];
113 }
114 
shoot(int actorId,Vector3 & to,int screenX)115 void Combat::shoot(int actorId, Vector3 &to, int screenX) {
116 	Actor *actor = _vm->_actors[actorId];
117 
118 	if (actor->isRetired()) {
119 		return;
120 	}
121 
122 	int sentenceId = -1;
123 
124 	/*
125 	Distance from center as a percentage:
126 	                            screenX - abs(right + left) / 2
127 	distanceFromCenter = 100 *  -------------------------------
128 	                                 abs(right - left) / 2
129 	*/
130 	const Common::Rect &rect = actor->getScreenRectangle();
131 	int distanceFromCenter = CLIP(100 * (screenX - abs((rect.right + rect.left) / 2)) / abs((rect.right - rect.left) / 2), 0, 100);
132 
133 	int damage = (100 - distanceFromCenter) * _ammoDamage[_vm->_settings->getAmmoType()] / 100;
134 
135 	int hp = MAX(actor->getCurrentHP() - damage, 0);
136 
137 	actor->setCurrentHP(hp);
138 
139 	bool setDamageAnimation = true;
140 	if (actor->isWalking() == 1 && !actor->getFlagDamageAnimIfMoving()) {
141 		setDamageAnimation = false;
142 	}
143 	if (actor->_movementTrack->hasNext() && !actor->_movementTrack->isPaused()) {
144 		setDamageAnimation = false;
145 	}
146 	if (setDamageAnimation) {
147 		if (actor->isWalking()) {
148 			actor->stopWalking(false);
149 		}
150 		if (actor->getAnimationMode() != kAnimationModeHit && actor->getAnimationMode() != kAnimationModeCombatHit) {
151 			actor->changeAnimationMode(kAnimationModeHit, false);
152 			sentenceId = _vm->_rnd.getRandomNumberRng(0, 1) ? 9000 : 9005;
153 		}
154 	}
155 
156 	if (hp <= 0) {
157 		actor->setTarget(false);
158 		if (actor->inCombat()) {
159 			actor->combatModeOff();
160 		}
161 #if BLADERUNNER_ORIGINAL_BUGS
162 #else
163 		// make sure the dead enemy won't pick a pending movement track and re-spawn
164 		actor->_movementTrack->flush();
165 #endif
166 		actor->stopWalking(false);
167 		actor->changeAnimationMode(kAnimationModeDie, false);
168 
169 		actor->retire(true, 72, 36, kActorMcCoy);
170 		actor->setAtXYZ(actor->getXYZ(), actor->getFacing(), true, false, true);
171 		_vm->_sceneObjects->setRetired(actorId + kSceneObjectOffsetActors, true);
172 
173 		sentenceId = 9020; // Bug or intended? This sentence id (death rattle) won't be used in this case since combat mode is set to off above. Probably intended, in order to use the rattle in a case by case (?)
174 	}
175 
176 	if (sentenceId >= 0 && actor->inCombat()) {
177 		_vm->_audioSpeech->playSpeechLine(actorId, sentenceId, 75, 0, 99);
178 	}
179 }
180 
findFleeWaypoint(int setId,int enemyId,const Vector3 & position) const181 int Combat::findFleeWaypoint(int setId, int enemyId, const Vector3& position) const {
182 	float min = -1.0f;
183 	int result = -1;
184 	for (int i = 0; i < (int)_fleeWaypoints.size(); ++i) {
185 		if (setId == _fleeWaypoints[i].setId) {
186 			float dist = distance(position, _fleeWaypoints[i].position);
187 			if (result == -1 || dist < min) {
188 				result = i;
189 				min = dist;
190 			}
191 		}
192 	}
193 	return result;
194 }
195 
findCoverWaypoint(int waypointType,int actorId,int enemyId) const196 int Combat::findCoverWaypoint(int waypointType, int actorId, int enemyId) const {
197 	Actor *actor = _vm->_actors[actorId];
198 	Actor *enemy = _vm->_actors[enemyId];
199 	int result = -1;
200 	float min = -1.0f;
201 	for (int i = 0; i < (int)_coverWaypoints.size(); ++i) {
202 		if (waypointType == _coverWaypoints[i].type && actor->getSetId() == _coverWaypoints[i].setId) {
203 			if (_vm->_sceneObjects->isObstacleBetween(_coverWaypoints[i].position, enemy->getXYZ(), enemyId)) {
204 				float dist = distance(_coverWaypoints[i].position, actor->getXYZ());
205 				if (result == -1 || dist < min) {
206 					result = i;
207 					min = dist;
208 				}
209 			}
210 		}
211 	}
212 	return result;
213 }
214 
save(SaveFileWriteStream & f)215 void Combat::save(SaveFileWriteStream &f) {
216 	f.writeBool(_active);
217 	f.writeBool(_enabled);
218 	for (int i = 0; i != kSoundCount; ++i) {
219 		f.writeInt(_hitSoundId[i]);
220 	}
221 	for (int i = 0; i != kSoundCount; ++i) {
222 		f.writeInt(_missSoundId[i]);
223 	}
224 }
225 
load(SaveFileReadStream & f)226 void Combat::load(SaveFileReadStream &f) {
227 	_active = f.readBool();
228 	_enabled = f.readBool();
229 	for (int i = 0; i != kSoundCount; ++i) {
230 		_hitSoundId[i] = f.readInt();
231 	}
232 	for (int i = 0; i != kSoundCount; ++i) {
233 		_missSoundId[i] = f.readInt();
234 	}
235 }
236 
237 } // End of namespace BladeRunner
238