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