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 "common/algorithm.h"
24 #include "common/rect.h"
25 #include "xeen/character.h"
26 #include "xeen/combat.h"
27 #include "xeen/interface.h"
28 #include "xeen/xeen.h"
29
30 namespace Xeen {
31
32 #define GRID_SIZE (7 * 7)
33
34 static const int MONSTER_GRID_X[GRID_SIZE] = {
35 1, 1, 1, 0, -1, -1, -1,
36 1, 1, 1, 0, -1, -1, -1,
37 1, 1, 1, 0, -1, -1, -1,
38 1, 1, 1, 0, -1, -1, -1,
39 1, 1, 1, 0, -1, -1, -1,
40 1, 1, 1, 0, -1, -1, -1,
41 1, 1, 1, 0, -1, -1, -1,
42 };
43
44 static const int MONSTER_GRID_Y[GRID_SIZE] = {
45 0, 0, 0, -1, 0, 0, 0,
46 0, 0, 0, -1, 0, 0, 0,
47 0, 0, 0, -1, 0, 0, 0,
48 0, 0, 0, 0, 0, 0, 0,
49 0, 0, 0, +1, 0, 0, 0,
50 0, 0, 0, +1, 0, 0, 0,
51 0, 0, 0, +1, 0, 0, 0,
52 };
53
54 static const int MONSTER_GRID3[GRID_SIZE] = {
55 -1, -1, -1, -1, -1, -1, -1,
56 -1, -1, -1, -1, -1, -1, -1,
57 -1, -1, -1, -1, -1, -1, -1,
58 +1, +1, +1, 0, -1, -1, -1,
59 +1, +1, +1, +1, +1, +1, +1,
60 +1, +1, +1, +1, +1, +1, +1,
61 +1, +1, +1, +1, +1, +1, +1,
62 };
63
64 static const int MONSTER_GRID_BITINDEX1[GRID_SIZE] = {
65 1, 1, 1, 2, 3, 3, 3,
66 1, 1, 1, 2, 3, 3, 3,
67 1, 1, 1, 2, 3, 3, 3,
68 1, 1, 1, 0, 3, 3, 3,
69 1, 1, 1, 0, 3, 3, 3,
70 1, 1, 1, 0, 3, 3, 3,
71 1, 1, 1, 0, 3, 3, 3,
72 };
73
74 static const int MONSTER_GRID_BITINDEX2[GRID_SIZE] = {
75 2, 2, 2, 2, 2, 2, 2,
76 2, 2, 2, 2, 2, 2, 2,
77 2, 2, 2, 2, 2, 2, 2,
78 1, 1, 1, 0, 3, 3, 3,
79 0, 0, 0, 0, 0, 0, 0,
80 0, 0, 0, 0, 0, 0, 0,
81 0, 0, 0, 0, 0, 0, 0,
82 };
83
84 static const int ATTACK_TYPE_FX[23] = {
85 49, 18, 13, 14, 15, 17, 16, 0, 6, 1, 2, 3,
86 4, 5, 4, 9, 27, 29, 44, 51, 53, 61, 71
87 };
88
89 static const PowType MONSTER_SHOOT_POW[7] = {
90 POW_MAGIC_ARROW, POW_SPARKLES, POW_FIREBALL,
91 POW_MEGAVOLTS, POW_COLD_RAY, POW_SPRAY, POW_ENERGY_BLAST
92 };
93
94 static const int COMBAT_SHOOTING[4] = { 1, 1, 2, 3 };
95
96 static const int DAMAGE_TYPE_EFFECTS[19] = {
97 3, 10, 4, 11, 1, 2, 5, 9, 5, 14, 5, 14, 10, 8, 3, 9, 2, 2, 3
98 };
99
100 static const int POW_WEAPON_VOCS[35] = {
101 0, 5, 4, 5, 5, 5, 5, 2, 4, 5, 3, 5, 4, 2, 3, 2, 2, 4, 5, 5,
102 5, 5, 5, 1, 3, 2, 5, 1, 1, 1, 0, 0, 0, 2, 2
103 };
104
105 static const int MONSTER_ITEM_RANGES[7] = { 10, 20, 50, 100, 100, 100, 0 };
106
107 #define monsterSavingThrow(MONINDEX) (_vm->getRandomNumber(1, 50 + (MONINDEX)) <= (MONINDEX))
108
109 /*------------------------------------------------------------------------*/
110
Combat(XeenEngine * vm)111 Combat::Combat(XeenEngine *vm): _vm(vm), _missVoc("miss.voc") {
112 Common::fill(&_attackMonsters[0], &_attackMonsters[26], 0);
113 Common::fill(&_shootingRow[0], &_shootingRow[MAX_PARTY_COUNT], 0);
114 Common::fill(&_monsterMap[0][0], &_monsterMap[31][32], 0);
115 Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
116 Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
117 Common::fill(&_gmonHit[0], &_gmonHit[36], 0);
118 Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], 0);
119 _globalCombat = 0;
120 _whosTurn = -1;
121 _itemFlag = false;
122 _monstersAttacking = false;
123 _combatMode = COMBATMODE_STARTUP;
124 _attackDurationCtr = 0;
125 _partyRan = false;
126 _monster2Attack = -1;
127 _whosSpeed = 0;
128 _damageType = DT_PHYSICAL;
129 _oldCharacter = nullptr;
130 _shootType = ST_0;
131 _monsterDamage = 0;
132 _weaponDamage = 0;
133 _weaponDie = _weaponDice = 0;
134 _weaponElemMaterial = 0;
135 _attackWeapon = nullptr;
136 _attackWeaponId = 0;
137 _hitChanceBonus = 0;
138 _dangerPresent = false;
139 _moveMonsters = false;
140 _rangeType = RT_SINGLE;
141 _combatTarget = 0;
142 }
143
clearAttackers()144 void Combat::clearAttackers() {
145 Common::fill(&_attackMonsters[0], &_attackMonsters[ATTACK_MONSTERS_COUNT], -1);
146 }
147
clearBlocked()148 void Combat::clearBlocked() {
149 Common::fill(_charsBlocked, _charsBlocked + PARTY_AND_MONSTERS, false);
150 }
151
clearShooting()152 void Combat::clearShooting() {
153 Common::fill(_shootingRow, _shootingRow + MAX_PARTY_COUNT, 0);
154 }
155
giveCharDamage(int damage,DamageType attackType,int charIndex)156 void Combat::giveCharDamage(int damage, DamageType attackType, int charIndex) {
157 EventsManager &events = *_vm->_events;
158 Interface &intf = *_vm->_interface;
159 Party &party = *_vm->_party;
160 Sound &sound = *_vm->_sound;
161 Windows &windows = *_vm->_windows;
162 int endIndex = charIndex + 1;
163 int selectedIndex = 0;
164 bool breakFlag = false;
165
166 windows.closeAll();
167
168 int idx = (int)party._activeParty.size();
169 if (_combatTarget == 2) {
170 for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
171 Character &c = party._activeParty[idx];
172 Condition condition = c.worstCondition();
173
174 if (!(condition >= UNCONSCIOUS && condition <= ERADICATED)) {
175 if (!charIndex) {
176 charIndex = idx + 1;
177 } else {
178 selectedIndex = idx + 1;
179 --charIndex;
180 break;
181 }
182 }
183 }
184 }
185 if (idx == (int)party._activeParty.size()) {
186 if (!_combatTarget)
187 charIndex = 0;
188 }
189
190 for (;;) {
191 for (; charIndex < (_combatTarget ? endIndex : (int)party._activeParty.size()); ++charIndex) {
192 Character &c = party._activeParty[charIndex];
193 c._conditions[ASLEEP] = 0; // Force attacked character to be awake
194
195 int frame = 0, fx = 0;
196 switch (attackType) {
197 case DT_PHYSICAL:
198 fx = 29;
199 break;
200 case DT_MAGICAL:
201 frame = 6;
202 fx = 27;
203 break;
204 case DT_FIRE:
205 damage -= party._fireResistence;
206 frame = 1;
207 fx = 22;
208 break;
209 case DT_ELECTRICAL:
210 damage -= party._electricityResistence;
211 frame = 2;
212 fx = 23;
213 break;
214 case DT_COLD:
215 damage -= party._coldResistence;
216 frame = 3;
217 fx = 24;
218 break;
219 case DT_POISON:
220 damage -= party._poisonResistence;
221 frame = 4;
222 fx = 26;
223 break;
224 case DT_ENERGY:
225 frame = 5;
226 fx = 25;
227 break;
228 case DT_SLEEP:
229 fx = 38;
230 break;
231 default:
232 break;
233 }
234
235 // All attack types other than physical allow for saving
236 // throws to reduce the damage
237 if (attackType != DT_PHYSICAL) {
238 while (c.charSavingThrow(attackType) && damage > 0)
239 damage /= 2;
240 }
241
242 // Draw the attack effect on the character sprite
243 sound.playFX(fx);
244 intf._charPowSprites.draw(0, frame, Common::Point(Res.CHAR_FACES_X[charIndex], 150));
245 windows[33].update();
246
247 // Reduce damage if power shield active, and set it zero
248 // if the damage amount has become negative.. you wouldn't
249 // want attacks healing the characters
250 if (party._powerShield)
251 damage -= party._powerShield;
252 if (damage < 0)
253 damage = 0;
254
255 if (attackType == DT_SLEEP) {
256 damage = c._currentHp;
257 c._conditions[DEAD] = 1;
258 }
259
260 // Subtract the hit points from the character
261 c.subtractHitPoints(damage);
262 if (selectedIndex)
263 break;
264 }
265
266 // Break check and if not, move to other index
267 if (!selectedIndex || breakFlag)
268 break;
269
270 charIndex = selectedIndex - 1;
271 breakFlag = true;
272 }
273
274 // WORKAROUND: Flag a script in progress when pausing to prevent any pending combat starting prematurely
275 Mode oldMode = _vm->_mode;
276 _vm->_mode = MODE_SCRIPT_IN_PROGRESS;
277 events.ipause(5);
278 _vm->_mode = oldMode;
279
280 intf.drawParty(true);
281 party.checkPartyDead();
282 }
283
doCharDamage(Character & c,int charNum,int monsterDataIndex)284 void Combat::doCharDamage(Character &c, int charNum, int monsterDataIndex) {
285 Debugger &debugger = *g_vm->_debugger;
286 EventsManager &events = *_vm->_events;
287 Interface &intf = *_vm->_interface;
288 Map &map = *_vm->_map;
289 Party &party = *_vm->_party;
290 Sound &sound = *_vm->_sound;
291 Windows &windows = *_vm->_windows;
292 MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
293
294 // Attacked characters are automatically woken up
295 c._conditions[ASLEEP] = 0;
296
297 // Figure out the damage amount
298 int damage = 0;
299 for (int idx = 0; idx < monsterData._strikes; ++idx)
300 damage += _vm->getRandomNumber(1, monsterData._dmgPerStrike);
301
302
303 int fx = 29, frame = 0;
304 if (monsterData._attackType != DT_PHYSICAL) {
305 if (c.charSavingThrow(monsterData._attackType))
306 damage /= 2;
307
308 switch (monsterData._attackType) {
309 case DT_MAGICAL:
310 frame = 6;
311 fx = 27;
312 break;
313 case DT_FIRE:
314 damage -= party._fireResistence;
315 frame = 1;
316 fx = 22;
317 break;
318 case DT_ELECTRICAL:
319 damage -= party._electricityResistence;
320 frame = 2;
321 fx = 23;
322 break;
323 case DT_COLD:
324 damage -= party._coldResistence;
325 frame = 3;
326 fx = 24;
327 break;
328 case DT_POISON:
329 damage -= party._poisonResistence;
330 frame = 4;
331 fx = 26;
332 break;
333 case DT_ENERGY:
334 frame = 5;
335 fx = 25;
336 break;
337 default:
338 break;
339 }
340
341 while (damage > 0 && c.charSavingThrow(monsterData._attackType))
342 damage /= 2;
343 }
344
345 sound.playFX(fx);
346 intf._charPowSprites.draw(0, frame, Common::Point(Res.CHAR_FACES_X[charNum], 150));
347 windows[33].update();
348
349 damage = MAX(damage - party._powerShield, 0);
350 if (damage > 0 && monsterData._specialAttack && !c.charSavingThrow(DT_PHYSICAL)) {
351 switch (monsterData._specialAttack) {
352 case SA_POISON:
353 if (!++c._conditions[POISONED])
354 c._conditions[POISONED] = -1;
355 sound.playFX(26);
356 break;
357 case SA_DISEASE:
358 if (!++c._conditions[DISEASED])
359 c._conditions[DISEASED] = -1;
360 sound.playFX(26);
361 break;
362 case SA_INSANE:
363 if (!++c._conditions[INSANE])
364 c._conditions[INSANE] = -1;
365 sound.playFX(28);
366 break;
367 case SA_SLEEP:
368 if (!++c._conditions[ASLEEP])
369 c._conditions[ASLEEP] = -1;
370 sound.playFX(36);
371 break;
372 case SA_CURSEITEM:
373 c._items.curseUncurse(true);
374 sound.playFX(37);
375 break;
376 case SA_DRAINSP:
377 c._currentSp = 0;
378 sound.playFX(37);
379 break;
380 case SA_CURSE:
381 if (!++c._conditions[CURSED])
382 c._conditions[CURSED] = -1;
383 sound.playFX(37);
384 break;
385 case SA_PARALYZE:
386 if (!++c._conditions[PARALYZED])
387 c._conditions[PARALYZED] = -1;
388 sound.playFX(37);
389 break;
390 case SA_UNCONSCIOUS:
391 if (!++c._conditions[UNCONSCIOUS])
392 c._conditions[UNCONSCIOUS] = -1;
393 sound.playFX(37);
394 break;
395 case SA_CONFUSE:
396 if (!++c._conditions[CONFUSED])
397 c._conditions[CONFUSED] = -1;
398 sound.playFX(28);
399 break;
400 case SA_BREAKWEAPON:
401 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
402 XeenItem &weapon = c._weapons[idx];
403 if (weapon._id < XEEN_SLAYER_SWORD && weapon._id != 0 && weapon._frame != 0) {
404 weapon._state._broken = true;
405 // WORKAROUND: For consistency, we don't de-equip broken items
406 //weapon._frame = 0;
407 }
408 }
409 sound.playFX(37);
410 break;
411 case SA_WEAKEN:
412 if (!++c._conditions[WEAK])
413 c._conditions[WEAK] = -1;
414 sound.playFX(36);
415 break;
416 case SA_ERADICATE:
417 if (!++c._conditions[ERADICATED])
418 c._conditions[ERADICATED] = -1;
419 c._items.breakAllItems();
420 sound.playFX(37);
421
422 if (c._currentHp > 0)
423 c._currentHp = 0;
424 break;
425 case SA_AGING:
426 ++c._tempAge;
427 sound.playFX(37);
428 break;
429 case SA_DEATH:
430 if (!++c._conditions[DEAD])
431 c._conditions[DEAD] = -1;
432 sound.playFX(38);
433 if (c._currentHp > 0)
434 c._currentHp = 0;
435 break;
436 case SA_STONE:
437 if (!++c._conditions[STONED])
438 c._conditions[STONED] = -1;
439 sound.playFX(38);
440 if (c._currentHp > 0)
441 c._currentHp = 0;
442 break;
443
444 default:
445 break;
446 }
447 }
448
449 if (debugger._invincible)
450 // Invincibility mode is on, so reset conditions that were set
451 c.clearConditions();
452 else
453 // Standard gameplay, deal out the damage
454 c.subtractHitPoints(damage);
455
456 events.ipause(2);
457 intf.drawParty(true);
458 }
459
moveMonsters()460 void Combat::moveMonsters() {
461 Interface &intf = *_vm->_interface;
462 Map &map = *_vm->_map;
463 Party &party = *_vm->_party;
464
465 if (!_moveMonsters)
466 return;
467
468 intf._tillMove = 0;
469 if (intf._charsShooting)
470 return;
471
472 Common::fill(&_monsterMap[0][0], &_monsterMap[31][32], 0);
473 Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
474 Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
475 Common::fill(&_gmonHit[0], &_gmonHit[36], -1);
476 _dangerPresent = false;
477
478 for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
479 MazeMonster &monster = map._mobData._monsters[idx];
480
481 // WORKAROUND: Original only checked on y, but some monsters have an invalid X instead
482 if ((uint)monster._position.x < 32 && (uint)monster._position.y < 32) {
483 assert((uint)monster._position.x < 32);
484 _monsterMap[monster._position.y][monster._position.x]++;
485 }
486 }
487
488 for (int loopNum = 0; loopNum < 2; ++loopNum) {
489 int arrIndex = -1;
490 for (int yDiff = 3; yDiff >= -3; --yDiff) {
491 for (int xDiff = -3; xDiff <= 3; ++xDiff) {
492 Common::Point pt = party._mazePosition + Common::Point(xDiff, yDiff);
493 ++arrIndex;
494
495 for (int idx = 0; idx < (int)map._mobData._monsters.size(); ++idx) {
496 MazeMonster &monster = map._mobData._monsters[idx];
497 MonsterStruct &monsterData = *monster._monsterData;
498
499 if (pt == monster._position) {
500 _dangerPresent = true;
501 if ((monster._isAttacking || _vm->_mode == MODE_SLEEPING)
502 && !_monsterMoved[idx]) {
503 if (party._mazePosition.x == pt.x || party._mazePosition.y == pt.y) {
504 // Check for range attacks
505 if (monsterData._rangeAttack && !_rangeAttacking[idx]
506 && _attackMonsters[0] != idx && _attackMonsters[1] != idx
507 && _attackMonsters[2] != idx && monster._damageType == DT_PHYSICAL) {
508 // Setup monster for attacking
509 setupMonsterAttack(monster._spriteId, pt);
510 _rangeAttacking[idx] = true;
511 }
512 }
513
514 switch (party._mazeDirection) {
515 case DIR_NORTH:
516 case DIR_SOUTH:
517 if (canMonsterMove(pt, Res.MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
518 MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
519 // Move the monster
520 moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
521 } else {
522 if (canMonsterMove(pt, Res.MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
523 arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
524 arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
525 idx)) {
526 if (arrIndex >= 21 && arrIndex <= 27) {
527 moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
528 } else {
529 moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
530 }
531 }
532 }
533 break;
534
535 case DIR_EAST:
536 case DIR_WEST:
537 if (canMonsterMove(pt, Res.MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
538 arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
539 arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
540 idx)) {
541 if (arrIndex >= 21 && arrIndex <= 27) {
542 moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
543 } else {
544 moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
545 }
546 } else if (canMonsterMove(pt, Res.MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
547 MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
548 moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
549 }
550
551 default:
552 break;
553 }
554 }
555 }
556 }
557 }
558 }
559 }
560
561 monsterOvercome();
562 if (_monstersAttacking)
563 monstersAttack();
564 }
565
monstersAttack()566 void Combat::monstersAttack() {
567 EventsManager &events = *_vm->_events;
568 Interface &intf = *_vm->_interface;
569 Map &map = *_vm->_map;
570 Party &party = *_vm->_party;
571 Sound &sound = *_vm->_sound;
572 PowType powNum = POW_INVALID;
573 MonsterStruct *monsterData = nullptr;
574 OutdoorDrawList &outdoorList = intf._outdoorList;
575 IndoorDrawList &indoorList = intf._indoorList;
576
577 for (int idx = 0; idx < 36; ++idx) {
578 if (_gmonHit[idx] != -1) {
579 monsterData = &map._monsterData[_gmonHit[idx]];
580 powNum = MONSTER_SHOOT_POW[monsterData->_attackType];
581 if (powNum != POW_MAGIC_ARROW)
582 break;
583 }
584 }
585
586 _powSprites.load(Common::String::format("pow%d.icn", (int)powNum));
587 sound.playFX(ATTACK_TYPE_FX[monsterData->_attackType]);
588
589 for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
590 if (!_shootingRow[charNum])
591 continue;
592
593 if (map._isOutdoors) {
594 outdoorList._attackImgs1[charNum]._scale = 3;
595 outdoorList._attackImgs2[charNum]._scale = 7;
596 outdoorList._attackImgs3[charNum]._scale = 11;
597 outdoorList._attackImgs4[charNum]._scale = 15;
598 outdoorList._attackImgs1[charNum]._sprites = nullptr;
599 outdoorList._attackImgs2[charNum]._sprites = nullptr;
600 outdoorList._attackImgs3[charNum]._sprites = nullptr;
601 outdoorList._attackImgs4[charNum]._sprites = nullptr;
602
603 switch (_shootingRow[charNum]) {
604 case 1:
605 outdoorList._attackImgs1[charNum]._sprites = &_powSprites;
606 break;
607 case 2:
608 outdoorList._attackImgs2[charNum]._sprites = &_powSprites;
609 break;
610 default:
611 outdoorList._attackImgs3[charNum]._sprites = &_powSprites;
612 break;
613 }
614 } else {
615 indoorList._attackImgs1[charNum]._scale = 3;
616 indoorList._attackImgs2[charNum]._scale = 7;
617 indoorList._attackImgs3[charNum]._scale = 11;
618 indoorList._attackImgs4[charNum]._scale = 15;
619 indoorList._attackImgs1[charNum]._sprites = nullptr;
620 indoorList._attackImgs2[charNum]._sprites = nullptr;
621 indoorList._attackImgs3[charNum]._sprites = nullptr;
622 indoorList._attackImgs4[charNum]._sprites = nullptr;
623
624 switch (_shootingRow[charNum]) {
625 case 1:
626 indoorList._attackImgs1[charNum]._sprites = &_powSprites;
627 break;
628 case 2:
629 indoorList._attackImgs2[charNum]._sprites = &_powSprites;
630 break;
631 default:
632 indoorList._attackImgs3[charNum]._sprites = &_powSprites;
633 break;
634 }
635 }
636 }
637
638 // Wait whilst the attacking effect is done
639 do {
640 intf.draw3d(true);
641 events.pollEventsAndWait();
642 } while (!_vm->shouldExit() && intf._isAttacking);
643
644 endAttack();
645
646 if (_vm->_mode != MODE_COMBAT) {
647 // Combat wasn't previously active, but it is now. Set up
648 // the combat party from the currently active party
649 setupCombatParty();
650 }
651
652 for (int idx = 0; idx < 36; ++idx) {
653 if (_gmonHit[idx] != -1)
654 doMonsterTurn(_gmonHit[idx]);
655 }
656
657 _monstersAttacking = false;
658
659 if (_vm->_mode == MODE_SLEEPING) {
660 for (uint charNum = 0; charNum < party._activeParty.size(); ++charNum) {
661 Condition condition = party._activeParty[charNum].worstCondition();
662
663 if (condition == DEPRESSED || condition == CONFUSED || condition == NO_CONDITION) {
664 _vm->_mode = MODE_INTERACTIVE;
665 break;
666 }
667 }
668 }
669 }
670
setupMonsterAttack(int monsterDataIndex,const Common::Point & pt)671 void Combat::setupMonsterAttack(int monsterDataIndex, const Common::Point &pt) {
672 Party &party = *_vm->_party;
673
674 for (int idx = 0; idx < 36; ++idx) {
675 if (_gmonHit[idx] == -1) {
676 int result = stopAttack(pt - party._mazePosition);
677 if (result) {
678 _monstersAttacking = true;
679 _gmonHit[idx] = monsterDataIndex;
680
681 if (result != 1) {
682 for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
683 if (!_shootingRow[charNum]) {
684 _shootingRow[charNum] = COMBAT_SHOOTING[result - 1];
685 break;
686 }
687 }
688 }
689 }
690
691 break;
692 }
693 }
694 }
695
canMonsterMove(const Common::Point & pt,int wallShift,int xDiff,int yDiff,int monsterId)696 bool Combat::canMonsterMove(const Common::Point &pt, int wallShift, int xDiff, int yDiff, int monsterId) {
697 Map &map = *_vm->_map;
698 MazeMonster &monster = map._mobData._monsters[monsterId];
699 MonsterStruct &monsterData = *monster._monsterData;
700
701 Common::Point tempPos = pt;
702 if (map._isOutdoors) {
703 tempPos += Common::Point(xDiff, yDiff);
704 wallShift = 4;
705 }
706 int v = map.mazeLookup(tempPos, wallShift);
707
708 if (!map._isOutdoors) {
709 return v <= map.mazeData()._difficulties._wallNoPass;
710 } else {
711 SurfaceType surfaceType;
712 switch (v) {
713 case 0:
714 case 2:
715 case 3:
716 case 4:
717 case 5:
718 case 6:
719 case 8:
720 case 11:
721 case 13:
722 case 14:
723 surfaceType = (SurfaceType)map.mazeData()._surfaceTypes[map._currentSurfaceId];
724 if (surfaceType == SURFTYPE_WATER || surfaceType == SURFTYPE_DWATER) {
725 return monsterData._flying || monster._spriteId == 59;
726 } else if (surfaceType == SURFTYPE_SPACE) {
727 return monsterData._flying;
728 } else {
729 return _vm->_files->_ccNum || monster._spriteId != 59;
730 }
731 default:
732 return v <= map.mazeData()._difficulties._wallNoPass;
733 }
734 }
735 }
736
moveMonster(int monsterId,const Common::Point & moveDelta)737 void Combat::moveMonster(int monsterId, const Common::Point &moveDelta) {
738 Map &map = *_vm->_map;
739 MazeMonster &monster = map._mobData._monsters[monsterId];
740 Common::Point newPos = monster._position + moveDelta;
741
742 // FIXME: Monster moved outside mapping area. Which shouldn't happen, so ignore the move if it does
743 if ((uint)newPos.x >= 32 || (uint)newPos.y >= 32)
744 return;
745
746 if (_monsterMap[newPos.y][newPos.x] < 3 && monster._damageType == DT_PHYSICAL && _moveMonsters) {
747 // Adjust monster's position
748 ++_monsterMap[newPos.y][newPos.x];
749 --_monsterMap[monster._position.y][monster._position.x];
750 monster._position = newPos;
751 _monsterMoved[monsterId] = true;
752 }
753 }
754
endAttack()755 void Combat::endAttack() {
756 Interface &intf = *_vm->_interface;
757 Map &map = *_vm->_map;
758 Party &party = *_vm->_party;
759 intf._isAttacking = false;
760 IndoorDrawList &indoorList = intf._indoorList;
761 OutdoorDrawList &outdoorList = intf._outdoorList;
762
763 for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
764 if (map._isOutdoors) {
765 outdoorList._attackImgs1[idx]._scale = 0;
766 outdoorList._attackImgs2[idx]._scale = 0;
767 outdoorList._attackImgs3[idx]._scale = 0;
768 outdoorList._attackImgs4[idx]._scale = 0;
769 outdoorList._attackImgs1[idx]._sprites = nullptr;
770 outdoorList._attackImgs2[idx]._sprites = nullptr;
771 outdoorList._attackImgs3[idx]._sprites = nullptr;
772 outdoorList._attackImgs4[idx]._sprites = nullptr;
773 } else {
774 indoorList._attackImgs1[idx]._scale = 0;
775 indoorList._attackImgs2[idx]._scale = 0;
776 indoorList._attackImgs3[idx]._scale = 0;
777 indoorList._attackImgs4[idx]._scale = 0;
778 indoorList._attackImgs1[idx]._sprites = nullptr;
779 indoorList._attackImgs2[idx]._sprites = nullptr;
780 indoorList._attackImgs3[idx]._sprites = nullptr;
781 indoorList._attackImgs4[idx]._sprites = nullptr;
782 }
783 }
784
785 clearShooting();
786 }
787
monsterOvercome()788 void Combat::monsterOvercome() {
789 Map &map = *_vm->_map;
790
791 for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
792 MazeMonster &monster = map._mobData._monsters[idx];
793 int dataIndex = monster._spriteId;
794
795 if (monster._damageType != DT_PHYSICAL && monster._damageType != DT_DRAGONSLEEP) {
796 // Do a saving throw for monster
797 if (dataIndex <= _vm->getRandomNumber(1, dataIndex + 50))
798 monster._damageType = DT_PHYSICAL;
799 }
800 }
801 }
802
doMonsterTurn(int monsterId)803 void Combat::doMonsterTurn(int monsterId) {
804 Interface &intf = *_vm->_interface;
805 Map &map = *_vm->_map;
806 Party &party = *_vm->_party;
807 Sound &sound = *_vm->_sound;
808
809 if (!_monstersAttacking) {
810 int monsterIndex;
811 switch (_whosTurn - _combatParty.size()) {
812 case 0:
813 monsterIndex = _attackMonsters[0];
814 intf._indoorList[156]._scale = 0;
815 break;
816 case 1:
817 monsterIndex = _attackMonsters[1];
818 intf._indoorList[150]._scale = 0;
819 break;
820 case 2:
821 default:
822 monsterIndex = _attackMonsters[2];
823 intf._indoorList[153]._scale = 0;
824 }
825
826 assert(monsterIndex != -1);
827 MazeMonster &monster = map._mobData._monsters[monsterIndex];
828 MonsterStruct &monsterData = *monster._monsterData;
829 if (monster._damageType != DT_PHYSICAL)
830 return;
831
832 monster._frame = 8;
833 monster._postAttackDelay = 3;
834 monster._field9 = 0;
835 intf.draw3d(true);
836 intf.draw3d(true);
837
838 sound.playSound(Common::String::format("%s.voc", monsterData._attackVoc.c_str()));
839 monsterId = monster._spriteId;
840 }
841
842 MonsterStruct &monsterData = map._monsterData[monsterId];
843 for (int attackNum = 0; attackNum < monsterData._numberOfAttacks; ++attackNum) {
844 int charNum = -1;
845 bool isHated = false;
846
847 if (monsterData._hatesClass != CLASS_PALADIN) {
848 if (monsterData._hatesClass == HATES_PARTY) {
849 // Monster hates entire party, even the disabled/dead
850 for (uint idx = 0; idx < _combatParty.size(); ++idx) {
851 doMonsterTurn(monsterId, idx);
852 }
853
854 // Move onto monster's next attack (if any)
855 continue;
856 }
857
858 for (uint charIndex = 0; charIndex < _combatParty.size(); ++charIndex) {
859 Character &c = *_combatParty[charIndex];
860 Condition cond = c.worstCondition();
861 if (cond >= PARALYZED && cond <= ERADICATED)
862 continue;
863
864 switch (monsterData._hatesClass) {
865 case CLASS_KNIGHT:
866 case CLASS_ARCHER:
867 case CLASS_CLERIC:
868 case CLASS_SORCERER:
869 case CLASS_ROBBER:
870 case CLASS_NINJA:
871 case CLASS_BARBARIAN:
872 case CLASS_DRUID:
873 case CLASS_RANGER:
874 isHated = c._class == monsterData._hatesClass;
875 break;
876 case HATES_DWARF:
877 isHated = c._race == DWARF;
878 break;
879 default:
880 break;
881 }
882
883 if (isHated) {
884 charNum = charIndex;
885 break;
886 }
887 }
888 }
889
890 if (!isHated) {
891 // No particularly hated foe, so pick a random character to start with
892 // Note: Original had a whole switch statement depending on party size, that boiled down to
893 // picking a random character in all cases anyway
894 charNum = _vm->getRandomNumber(0, _combatParty.size() - 1);
895 }
896
897 // If the chosen character is already disabled, we need to pick a still able body character
898 // from the remainder of the combat party
899 Condition cond = _combatParty[charNum]->worstCondition();
900 if (cond >= PARALYZED && cond <= ERADICATED) {
901 Common::Array<int> ableChars;
902
903 for (uint idx = 0; idx < _combatParty.size(); ++idx) {
904 switch (_combatParty[idx]->worstCondition()) {
905 case PARALYZED:
906 case UNCONSCIOUS:
907 case DEAD:
908 case STONED:
909 case ERADICATED:
910 break;
911 default:
912 ableChars.push_back(idx);
913 break;
914 }
915 }
916
917 if (ableChars.size() == 0) {
918 party._dead = true;
919 _vm->_mode = MODE_INTERACTIVE;
920 return;
921 }
922
923 charNum = ableChars[_vm->getRandomNumber(0, ableChars.size() - 1)];
924 }
925
926 doMonsterTurn(monsterId, charNum);
927 }
928
929 intf.drawParty(true);
930 }
931
doMonsterTurn(int monsterId,int charNum)932 void Combat::doMonsterTurn(int monsterId, int charNum) {
933 Map &map = *_vm->_map;
934 Sound &sound = *_vm->_sound;
935 MonsterStruct &monsterData = map._monsterData[monsterId];
936 Character &c = *_combatParty[charNum];
937
938 if (monsterData._attackType != DT_PHYSICAL || c._conditions[ASLEEP]) {
939 doCharDamage(c, charNum, monsterId);
940 } else {
941 int v = _vm->getRandomNumber(1, 20);
942 if (v == 1) {
943 // Critical Save
944 sound.playFX(6);
945 } else {
946 if (v == 20)
947 // Critical failure
948 doCharDamage(c, charNum, monsterId);
949 v += monsterData._hitChance / 4 + _vm->getRandomNumber(1,
950 monsterData._hitChance);
951
952 int ac = c.getArmorClass() + (!_charsBlocked[charNum] ? 10 :
953 c.getCurrentLevel() / 2 + 15);
954 if (ac > v) {
955 sound.playFX(6);
956 } else {
957 doCharDamage(c, charNum, monsterId);
958 }
959 }
960 }
961 }
962
stopAttack(const Common::Point & diffPt)963 int Combat::stopAttack(const Common::Point &diffPt) {
964 Map &map = *_vm->_map;
965 Party &party = *_vm->_party;
966 Direction dir = party._mazeDirection;
967 const Common::Point &mazePos = party._mazePosition;
968
969 if (map._isOutdoors) {
970 if (diffPt.x > 0) {
971 for (int x = 1; x <= diffPt.x; ++x) {
972 int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
973 if (v)
974 return 0;
975 }
976 return (dir == DIR_EAST) ? diffPt.x + 1 : 1;
977
978 } else if (diffPt.x < 0) {
979 for (int x = diffPt.x; x < 0; ++x) {
980 int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 4);
981 switch (v) {
982 case 0:
983 case 2:
984 case 4:
985 case 5:
986 case 8:
987 case 11:
988 case 13:
989 case 14:
990 break;
991 default:
992 return 0;
993 }
994 }
995 return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
996
997 } else if (diffPt.y <= 0) {
998 for (int y = diffPt.y; y < 0; ++y) {
999 int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
1000 switch (v) {
1001 case 0:
1002 case 2:
1003 case 4:
1004 case 5:
1005 case 8:
1006 case 11:
1007 case 13:
1008 case 14:
1009 break;
1010 default:
1011 return 0;
1012 }
1013 }
1014 return party._mazeDirection == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
1015
1016 } else {
1017 for (int y = 1; y <= diffPt.y; ++y) {
1018 int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
1019 switch (v) {
1020 case 0:
1021 case 2:
1022 case 4:
1023 case 5:
1024 case 8:
1025 case 11:
1026 case 13:
1027 case 14:
1028 break;
1029 default:
1030 return 0;
1031 }
1032 }
1033 return dir == DIR_NORTH ? diffPt.y + 1 : 1;
1034 }
1035 } else {
1036 // Indoors
1037 if (diffPt.x > 0) {
1038 for (int x = 1; x <= diffPt.x; ++x) {
1039 int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
1040 if (v)
1041 return 0;
1042 }
1043 return dir == DIR_EAST ? diffPt.x + 1 : 1;
1044
1045 } else if (diffPt.x < 0) {
1046 for (int x = diffPt.x; x < 0; ++x) {
1047 int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 0x800);
1048 if (v)
1049 return 0;
1050 }
1051 return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
1052
1053 } else if (diffPt.y <= 0) {
1054 for (int y = diffPt.y; y < 0; ++y) {
1055 int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x8000);
1056 if (v)
1057 return 0;
1058 }
1059 return dir == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
1060
1061 } else {
1062 for (int y = 1; y <= diffPt.y; ++y) {
1063 int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x80);
1064 if (v)
1065 return 0;
1066 }
1067 return dir == DIR_NORTH ? diffPt.y + 1 : 1;
1068 }
1069 }
1070 }
1071
setupCombatParty()1072 void Combat::setupCombatParty() {
1073 Party &party = *_vm->_party;
1074
1075 _combatParty.clear();
1076 for (uint idx = 0; idx < party._activeParty.size(); ++idx)
1077 _combatParty.push_back(&party._activeParty[idx]);
1078 }
1079
setSpeedTable()1080 void Combat::setSpeedTable() {
1081 Map &map = *_vm->_map;
1082 Common::Array<int> charSpeeds;
1083 bool hasSpeed = _whosSpeed != -1;
1084 int oldSpeed = hasSpeed && _whosSpeed < (int)_speedTable.size() ? _speedTable[_whosSpeed] : 0;
1085
1086 // Set up speeds for party members
1087 int maxSpeed = 0;
1088 for (uint charNum = 0; charNum < _combatParty.size(); ++charNum) {
1089 Character &c = *_combatParty[charNum];
1090 charSpeeds.push_back(c.getStat(SPEED));
1091
1092 maxSpeed = MAX(charSpeeds[charNum], maxSpeed);
1093 }
1094
1095 // Add in speeds of attacking monsters
1096 for (int monsterNum = 0; monsterNum < 3; ++monsterNum) {
1097 if (_attackMonsters[monsterNum] != -1) {
1098 MazeMonster &monster = map._mobData._monsters[_attackMonsters[monsterNum]];
1099 MonsterStruct &monsterData = *monster._monsterData;
1100 charSpeeds.push_back(monsterData._speed);
1101
1102 maxSpeed = MAX(maxSpeed, monsterData._speed);
1103 } else {
1104 charSpeeds.push_back(0);
1105 }
1106 }
1107
1108 // Populate the _speedTable list with the character/monster indexes
1109 // in order of attacking speed
1110 _speedTable.clear();
1111 for (; maxSpeed > 0; --maxSpeed) {
1112 for (uint idx = 0; idx < charSpeeds.size(); ++idx) {
1113 if (charSpeeds[idx] == maxSpeed)
1114 _speedTable.push_back(idx);
1115 }
1116 }
1117
1118 if (hasSpeed) {
1119 if (_speedTable.empty()) {
1120 _whosSpeed = 0;
1121 } else if (_whosSpeed >= (int)_speedTable.size() || _speedTable[_whosSpeed] != oldSpeed) {
1122 for (_whosSpeed = 0; _whosSpeed < (int)_speedTable.size(); ++_whosSpeed) {
1123 if (oldSpeed == _speedTable[_whosSpeed])
1124 break;
1125 }
1126
1127 if (_whosSpeed == (int)charSpeeds.size())
1128 error("Could not reset next speedy character. Beep beep.");
1129 }
1130 }
1131 }
1132
allHaveGone() const1133 bool Combat::allHaveGone() const {
1134 int monsCount = (_attackMonsters[0] != -1 ? 1 : 0)
1135 + (_attackMonsters[1] != -1 ? 1 : 0)
1136 + (_attackMonsters[2] != -1 ? 1 : 0);
1137
1138 for (uint idx = 0; idx < (_combatParty.size() + monsCount); ++idx) {
1139 if (!_charsGone[idx]) {
1140 if (idx >= _combatParty.size()) {
1141 return false;
1142 } else {
1143 Condition condition = _combatParty[idx]->worstCondition();
1144 if (condition < PARALYZED || condition == NO_CONDITION)
1145 return false;
1146 }
1147 }
1148 }
1149
1150 return true;
1151 }
1152
charsCantAct() const1153 bool Combat::charsCantAct() const {
1154 for (uint idx = 0; idx < _combatParty.size(); ++idx) {
1155 if (!_combatParty[idx]->isDisabledOrDead())
1156 return false;
1157 }
1158
1159 return true;
1160 }
1161
getMonsterDescriptions()1162 Common::String Combat::getMonsterDescriptions() {
1163 Map &map = *_vm->_map;
1164 Common::String lines[3];
1165
1166 // Get names of monsters attacking, if any
1167 for (int idx = 0; idx < 3; ++idx) {
1168 if (_attackMonsters[idx] != -1) {
1169 MazeMonster &monster = map._mobData._monsters[_attackMonsters[idx]];
1170 MonsterStruct &monsterData = *monster._monsterData;
1171 int textColor = monster.getTextColor();
1172
1173 Common::String format = "\n\v020\f%2u%s\fd";
1174 format.setChar('2' + idx, 3);
1175 lines[idx] = Common::String::format(format.c_str(), textColor,
1176 monsterData._name.c_str());
1177 }
1178 }
1179
1180 if (_attackDurationCtr == 2 && _attackMonsters[2] != -1) {
1181 _monster2Attack = _attackMonsters[2];
1182 } else if (_attackDurationCtr == 1 && _attackMonsters[1] != -1) {
1183 _monster2Attack = _attackMonsters[1];
1184 } else {
1185 _monster2Attack = _attackMonsters[0];
1186 _attackDurationCtr = 0;
1187 }
1188
1189 return Common::String::format(Res.COMBAT_DETAILS, lines[0].c_str(),
1190 lines[1].c_str(), lines[2].c_str());
1191 }
1192
attack(Character & c,RangeType rangeType)1193 void Combat::attack(Character &c, RangeType rangeType) {
1194 Interface &intf = *_vm->_interface;
1195 Map &map = *_vm->_map;
1196 Party &party = *_vm->_party;
1197 int damage = _monsterDamage;
1198
1199 if (_monster2Attack == -1)
1200 return;
1201
1202 MazeMonster &monster = map._mobData._monsters[_monster2Attack];
1203 int monsterDataIndex = monster._spriteId;
1204 MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
1205
1206 if (rangeType != RT_SINGLE) {
1207 if (_shootType != ST_1 || _damageType == DT_MAGIC_ARROW) {
1208 if (!monsterData._magicResistence || monsterData._magicResistence <=
1209 _vm->getRandomNumber(1, 100 + _oldCharacter->getCurrentLevel())) {
1210 if (_monsterDamage != 0) {
1211 attack2(damage, rangeType);
1212 } else {
1213 switch (_damageType) {
1214 case DT_SLEEP:
1215 if (monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID) {
1216 if (_vm->getRandomNumber(1, 50 + monsterDataIndex) > monsterDataIndex)
1217 monster._damageType = DT_SLEEP;
1218 }
1219 break;
1220 case DT_FINGEROFDEATH:
1221 if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
1222 && !monsterSavingThrow(monsterDataIndex)) {
1223 damage = MIN(monster._hp, 50);
1224 attack2(damage, RT_ALL);
1225 }
1226 break;
1227 case DT_HOLYWORD:
1228 if (monsterData._monsterType == MONSTER_UNDEAD) {
1229 attack2(monster._hp, RT_ALL);
1230 }
1231 break;
1232 case DT_MASS_DISTORTION:
1233 attack2(MAX(monster._hp / 2, 1), RT_ALL);
1234 break;
1235 case DT_UNDEAD:
1236 if (monsterData._monsterType == MONSTER_UNDEAD)
1237 damage = 25;
1238 else
1239 rangeType = RT_ALL;
1240 attack2(damage, rangeType);
1241 break;
1242 case DT_BEASTMASTER:
1243 if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
1244 && !monsterSavingThrow(monsterDataIndex)) {
1245 monster._damageType = DT_BEASTMASTER;
1246 }
1247 break;
1248 case DT_DRAGONSLEEP:
1249 if (monsterData._monsterType == MONSTER_DRAGON && !monsterSavingThrow(monsterDataIndex))
1250 monster._damageType = DT_DRAGONSLEEP;
1251 break;
1252 case DT_GOLEMSTOPPER:
1253 if (monsterData._monsterType == MONSTER_GOLEM) {
1254 attack2(100, rangeType);
1255 }
1256 break;
1257 case DT_HYPNOTIZE:
1258 if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
1259 && !monsterSavingThrow(monsterDataIndex)) {
1260 monster._damageType = _damageType;
1261 }
1262 break;
1263 case DT_INSECT_SPRAY:
1264 if (monsterData._monsterType == MONSTER_INSECT) {
1265 attack2(25, rangeType);
1266 }
1267 break;
1268 case DT_MAGIC_ARROW:
1269 attack2(8, rangeType);
1270 break;
1271 default:
1272 break;
1273 }
1274 }
1275 }
1276 } else {
1277 _pow.resetElementals();
1278 damage = 0;
1279
1280 for (uint charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
1281 Character &ch = party._activeParty[charIndex];
1282
1283 if (_shootingRow[charIndex] && !_missedShot[charIndex]) {
1284 if (!hitMonster(ch, rangeType)) {
1285 ++_missedShot[charIndex];
1286 } else {
1287 damage = _monsterDamage ? _monsterDamage : _weaponDamage;
1288 _shootingRow[charIndex] = 0;
1289 attack2(damage, RT_HIT);
1290
1291 if (map._isOutdoors) {
1292 intf._outdoorList._attackImgs1[charIndex]._scale = 0;
1293 intf._outdoorList._attackImgs1[charIndex]._sprites = nullptr;
1294 intf._outdoorList._attackImgs2[charIndex]._scale = 0;
1295 intf._outdoorList._attackImgs2[charIndex]._sprites = nullptr;
1296 intf._outdoorList._attackImgs3[charIndex]._scale = 0;
1297 intf._outdoorList._attackImgs3[charIndex]._sprites = nullptr;
1298 intf._outdoorList._attackImgs4[charIndex]._scale = 0;
1299 intf._outdoorList._attackImgs4[charIndex]._sprites = nullptr;
1300 } else {
1301 intf._indoorList._attackImgs1[charIndex]._scale = 0;
1302 intf._indoorList._attackImgs1[charIndex]._sprites = nullptr;
1303 intf._indoorList._attackImgs2[charIndex]._scale = 0;
1304 intf._indoorList._attackImgs2[charIndex]._sprites = nullptr;
1305 intf._indoorList._attackImgs3[charIndex]._scale = 0;
1306 intf._indoorList._attackImgs3[charIndex]._sprites = nullptr;
1307 intf._indoorList._attackImgs4[charIndex]._scale = 0;
1308 intf._indoorList._attackImgs4[charIndex]._sprites = nullptr;
1309 }
1310
1311 if (_monster2Attack == -1)
1312 return;
1313 }
1314 }
1315 }
1316 }
1317 } else {
1318 _damageType = DT_PHYSICAL;
1319 int divisor = 0;
1320 switch (c._class) {
1321 case CLASS_BARBARIAN:
1322 divisor = 4;
1323 break;
1324 case CLASS_KNIGHT:
1325 case CLASS_NINJA:
1326 divisor = 5;
1327 break;
1328 case CLASS_PALADIN:
1329 case CLASS_ARCHER:
1330 case CLASS_ROBBER:
1331 case CLASS_RANGER:
1332 divisor = 6;
1333 break;
1334 case CLASS_CLERIC:
1335 case CLASS_DRUID:
1336 divisor = 7;
1337 break;
1338 case CLASS_SORCERER:
1339 divisor = 8;
1340 break;
1341 default:
1342 error("Invalid class");
1343 }
1344
1345 int numberOfAttacks = c.getCurrentLevel() / divisor + 1;
1346 damage = 0;
1347
1348 while (numberOfAttacks-- > 0) {
1349 if (hitMonster(c, RT_SINGLE))
1350 damage += getMonsterDamage(c);
1351 }
1352
1353 for (int itemIndex = 0; itemIndex < INV_ITEMS_TOTAL; ++itemIndex) {
1354 XeenItem &weapon = c._weapons[itemIndex];
1355 if (weapon.isEquipped()) {
1356 switch (weapon._state._counter) {
1357 case EFFECTIVE_DRAGON:
1358 if (monsterData._monsterType == MONSTER_DRAGON)
1359 damage *= 3;
1360 break;
1361 case EFFECTIVE_UNDEAD :
1362 if (monsterData._monsterType == MONSTER_UNDEAD)
1363 damage *= 3;
1364 break;
1365 case EFFECTIVE_GOLEM:
1366 if (monsterData._monsterType == MONSTER_GOLEM)
1367 damage *= 3;
1368 break;
1369 case EFFECTIVE_INSECT:
1370 if (monsterData._monsterType == MONSTER_INSECT)
1371 damage *= 3;
1372 break;
1373 case EFFEctIVE_MONSTERS:
1374 if (monsterData._monsterType == MONSTER_MONSTERS)
1375 damage *= 3;
1376 break;
1377 case EFFECTIVE_ANIMAL:
1378 if (monsterData._monsterType == MONSTER_ANIMAL)
1379 damage *= 3;
1380 break;
1381 default:
1382 break;
1383 }
1384 }
1385 }
1386
1387 attack2(damage, rangeType);
1388 }
1389
1390 setSpeedTable();
1391 }
1392
attack2(int damage,RangeType rangeType)1393 void Combat::attack2(int damage, RangeType rangeType) {
1394 Debugger &debugger = *_vm->_debugger;
1395 Interface &intf = *_vm->_interface;
1396 Map &map = *_vm->_map;
1397 Party &party = *_vm->_party;
1398 Sound &sound = *_vm->_sound;
1399 int ccNum = _vm->_files->_ccNum;
1400 MazeMonster &monster = map._mobData._monsters[_monster2Attack];
1401 MonsterStruct &monsterData = *monster._monsterData;
1402 bool monsterDied = false;
1403
1404 if (!ccNum && damage && rangeType != RT_SINGLE && monster._spriteId == 89)
1405 damage = 0;
1406 if (debugger._superStrength)
1407 damage = 10000;
1408
1409 if (!damage) {
1410 sound.playSound(_missVoc, 1);
1411 sound.playFX(6);
1412 } else {
1413 if (!ccNum && monster._spriteId == 89)
1414 damage += 100;
1415 if (monster._damageType == DT_SLEEP || monster._damageType == DT_DRAGONSLEEP)
1416 monster._damageType = DT_PHYSICAL;
1417
1418 if ((rangeType == RT_SINGLE || _damageType == DT_PHYSICAL) && _attackWeaponId < XEEN_SLAYER_SWORD) {
1419 if (monsterData._phsyicalResistence != 0) {
1420 if (monsterData._phsyicalResistence == 100) {
1421 // Completely immune to the damage
1422 damage = 0;
1423 } else {
1424 // Reduce the damage based on physical resistance
1425 damage = damage * (100 - monsterData._phsyicalResistence) / 100;
1426 }
1427 }
1428 }
1429
1430 if (damage) {
1431 _pow[_attackDurationCtr]._duration = 3;
1432 _pow[_attackDurationCtr]._active = _damageType == DT_PHYSICAL && (rangeType == RT_HIT || rangeType == RT_SINGLE);
1433 monster._frame = 11;
1434 monster._postAttackDelay = 5;
1435 }
1436
1437 int monsterResist = getMonsterResistence(rangeType);
1438 damage += monsterResist;
1439 if (monsterResist > 0) {
1440 _pow[_attackDurationCtr]._elemFrame = XeenItem::getElementalCategory(_weaponElemMaterial);
1441 _pow[_attackDurationCtr]._elemScale = getDamageScale(monsterResist);
1442 } else if (rangeType != RT_HIT) {
1443 _pow[_attackDurationCtr]._elemFrame = 0;
1444 }
1445
1446 if (rangeType != RT_SINGLE && rangeType != RT_HIT) {
1447 monster._effect2 = DAMAGE_TYPE_EFFECTS[_damageType];
1448 monster._effect1 = 0;
1449 }
1450
1451 if (rangeType != RT_SINGLE && monsterSavingThrow(monster._spriteId)) {
1452 switch (_damageType) {
1453 case DT_FINGEROFDEATH:
1454 case DT_MASS_DISTORTION:
1455 damage = 5;
1456 break;
1457 case DT_SLEEP:
1458 case DT_HOLYWORD:
1459 case DT_UNDEAD:
1460 case DT_BEASTMASTER:
1461 case DT_DRAGONSLEEP:
1462 case DT_GOLEMSTOPPER:
1463 case DT_HYPNOTIZE:
1464 case DT_INSECT_SPRAY:
1465 case DT_MAGIC_ARROW:
1466 break;
1467 default:
1468 damage /= 2;
1469 break;
1470 }
1471 }
1472
1473 if (damage < 1) {
1474 sound.playSound(_missVoc, 1);
1475 sound.playFX(6);
1476 } else {
1477 _pow[_attackDurationCtr]._scale = getDamageScale(damage);
1478 intf.draw3d(true);
1479
1480 sound.stopSound();
1481 int powNum = (_attackWeaponId > XEEN_SLAYER_SWORD) ? 0 : POW_WEAPON_VOCS[_attackWeaponId];
1482 File powVoc(Common::String::format("pow%d.voc", powNum));
1483 sound.playFX(60 + powNum);
1484 sound.playSound(powVoc, 1);
1485
1486 if (monster._hp > damage) {
1487 monster._hp -= damage;
1488 } else {
1489 monster._hp = 0;
1490 monsterDied = true;
1491 }
1492 }
1493 }
1494
1495 intf.draw3d(true);
1496
1497 if (monsterDied) {
1498 if (!ccNum) {
1499 if (_monster2Attack == 20 && party._mazeId == 41)
1500 party._gameFlags[0][11] = true;
1501 if (_monster2Attack == 8 && party._mazeId == 78) {
1502 party._gameFlags[0][60] = true;
1503 party._questFlags[23] = false;
1504
1505 for (uint idx = 0; idx < party._activeParty.size(); ++idx)
1506 party._activeParty[idx].setAward(42, true);
1507 }
1508 if (_monster2Attack == 27 && party._mazeId == 29)
1509 party._gameFlags[0][104] = true;
1510 }
1511
1512 giveExperience(monsterData._experience);
1513
1514 if (party._mazeId != 85) {
1515 party._treasure._gold += monsterData._gold;
1516 party._treasure._gems += monsterData._gems;
1517
1518 if (!ccNum && monster._spriteId == 89) {
1519 // Xeen's Scepter of Temporal Distortion
1520 party._treasure._weapons[0]._id = 90;
1521 party._treasure._weapons[0]._material = 0;
1522 party._treasure._weapons[0]._state.clear();
1523 party._treasure._hasItems = true;
1524 party._questItems[8]++;
1525 }
1526
1527 int itemDrop = monsterData._itemDrop;
1528 if (itemDrop) {
1529 if (MONSTER_ITEM_RANGES[itemDrop - 1] >= _vm->getRandomNumber(1, 100)) {
1530 Character tempChar;
1531 int category = tempChar.makeItem(itemDrop, 0, 0);
1532
1533 switch (category) {
1534 case CATEGORY_WEAPON:
1535 for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
1536 if (party._treasure._weapons[idx].empty()) {
1537 party._treasure._weapons[idx] = tempChar._weapons[0];
1538 party._treasure._hasItems = true;
1539 break;
1540 }
1541 }
1542 break;
1543 case CATEGORY_ARMOR:
1544 for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
1545 if (party._treasure._armor[idx].empty()) {
1546 party._treasure._armor[idx] = tempChar._armor[0];
1547 party._treasure._hasItems = true;
1548 break;
1549 }
1550 }
1551 break;
1552 case CATEGORY_ACCESSORY:
1553 for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
1554 if (party._treasure._accessories[idx].empty()) {
1555 party._treasure._accessories[idx] = tempChar._accessories[0];
1556 party._treasure._hasItems = true;
1557 break;
1558 }
1559 }
1560 break;
1561 case CATEGORY_MISC:
1562 for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
1563 if (party._treasure._accessories[idx].empty()) {
1564 party._treasure._accessories[idx] = tempChar._accessories[0];
1565 party._treasure._hasItems = true;
1566 break;
1567 }
1568 }
1569 break;
1570 default:
1571 break;
1572 }
1573 }
1574 }
1575 }
1576
1577 monster._position = Common::Point(0x80, 0x80);
1578 _pow[_attackDurationCtr]._duration = 0;
1579 _monster2Attack = -1;
1580 intf.draw3d(true);
1581
1582 if (_attackMonsters[0] != -1) {
1583 _monster2Attack = _attackMonsters[0];
1584 _attackDurationCtr = 0;
1585 }
1586 }
1587 }
1588
block()1589 void Combat::block() {
1590 _charsBlocked[_whosTurn] = true;
1591 }
1592
quickFight()1593 void Combat::quickFight() {
1594 Spells &spells = *_vm->_spells;
1595 Character *c = _combatParty[_whosTurn];
1596
1597 switch (c->_quickOption) {
1598 case QUICK_ATTACK:
1599 attack(*c, RT_SINGLE);
1600 break;
1601 case QUICK_SPELL:
1602 if (c->_currentSpell != -1) {
1603 spells.castSpell(c, (MagicSpell)Res.SPELLS_ALLOWED[c->getSpellsCategory()][c->_currentSpell]);
1604 }
1605 break;
1606 case QUICK_BLOCK:
1607 block();
1608 break;
1609 case QUICK_RUN:
1610 run();
1611 break;
1612 default:
1613 break;
1614 }
1615 }
1616
run()1617 void Combat::run() {
1618 Map &map = *_vm->_map;
1619 Sound &sound = *_vm->_sound;
1620
1621 if (_vm->getRandomNumber(1, 100) < map.mazeData()._difficulties._chance2Run) {
1622 // Remove the character from the combat party
1623 _combatParty.remove_at(_whosTurn);
1624 setSpeedTable();
1625 --_whosSpeed;
1626 _whosTurn = -1;
1627 _partyRan = true;
1628 sound.playFX(51);
1629 }
1630 }
1631
hitMonster(Character & c,RangeType rangeType)1632 bool Combat::hitMonster(Character &c, RangeType rangeType) {
1633 Map &map = *_vm->_map;
1634 getWeaponDamage(c, rangeType);
1635 int chance = c.statBonus(c.getStat(ACCURACY)) + _hitChanceBonus;
1636 int divisor = 0;
1637
1638 switch (c._class) {
1639 case CLASS_PALADIN :
1640 case CLASS_ARCHER:
1641 case CLASS_ROBBER:
1642 case CLASS_NINJA:
1643 case CLASS_RANGER:
1644 divisor = 2;
1645 break;
1646 case CLASS_CLERIC:
1647 case CLASS_DRUID:
1648 divisor = 3;
1649 break;
1650 case CLASS_SORCERER:
1651 divisor = 4;
1652 break;
1653 case CLASS_KNIGHT:
1654 case CLASS_BARBARIAN:
1655 default:
1656 divisor = 1;
1657 break;
1658 }
1659
1660 chance += c.getCurrentLevel() / divisor;
1661 chance -= c._conditions[CURSED];
1662
1663 // Add on a random amount
1664 int v;
1665 do {
1666 v = _vm->getRandomNumber(1, 20);
1667 chance += v;
1668 } while (v == 20);
1669
1670 assert(_monster2Attack != -1);
1671 MazeMonster &monster = map._mobData._monsters[_monster2Attack];
1672 MonsterStruct &monsterData = *monster._monsterData;
1673
1674 if (monster._damageType != DT_PHYSICAL)
1675 chance += 20;
1676
1677 return chance >= (monsterData._armorClass + 10);
1678 }
1679
getWeaponDamage(Character & c,RangeType rangeType)1680 void Combat::getWeaponDamage(Character &c, RangeType rangeType) {
1681 Party &party = *_vm->_party;
1682 _attackWeapon = nullptr;
1683 _weaponDie = _weaponDice = 0;
1684 _weaponDamage = 0;
1685 _hitChanceBonus = 0;
1686 _weaponElemMaterial = 0;
1687
1688 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
1689 XeenItem &weapon = c._weapons[idx];
1690 bool flag;
1691 if (rangeType != RT_SINGLE) {
1692 flag = weapon._frame == 4;
1693 } else {
1694 flag = weapon._frame == 1 || weapon._frame == 13;
1695 }
1696
1697 if (flag) {
1698 if (!weapon.isBad()) {
1699 _attackWeapon = &weapon;
1700
1701 if (weapon._material < 37) {
1702 _weaponElemMaterial = weapon._material;
1703 } else if (weapon._material < 59) {
1704 _hitChanceBonus = Res.METAL_DAMAGE_PERCENT[weapon._material - 37];
1705 _weaponDamage = Res.METAL_DAMAGE[weapon._material - 37];
1706 }
1707 }
1708
1709 _hitChanceBonus += party._heroism;
1710 _attackWeaponId = weapon._id;
1711 _weaponDice = Res.WEAPON_DAMAGE_BASE[_attackWeaponId];
1712 _weaponDie = Res.WEAPON_DAMAGE_MULTIPLIER[_attackWeaponId];
1713
1714 for (int diceIdx = 0; diceIdx < _weaponDice; ++diceIdx)
1715 _weaponDamage += _vm->getRandomNumber(1, _weaponDie);
1716 }
1717 }
1718
1719 if (_weaponDamage < 1)
1720 _weaponDamage = 0;
1721 if (party._difficulty == ADVENTURER) {
1722 _hitChanceBonus += 5;
1723 _weaponDamage *= 3;
1724 }
1725 }
1726
getMonsterDamage(Character & c)1727 int Combat::getMonsterDamage(Character &c) {
1728 return MAX(c.statBonus(c.getStat(MIGHT)) + _weaponDamage, 1);
1729 }
1730
getDamageScale(int v)1731 int Combat::getDamageScale(int v) {
1732 if (v < 10)
1733 return 5;
1734 else if (v < 100)
1735 return 0;
1736 else
1737 return 0x8000;
1738 }
1739
getMonsterResistence(RangeType rangeType)1740 int Combat::getMonsterResistence(RangeType rangeType) {
1741 Map &map = *_vm->_map;
1742 assert(_monster2Attack != -1);
1743 MazeMonster &monster = map._mobData._monsters[_monster2Attack];
1744 MonsterStruct &monsterData = *monster._monsterData;
1745 int resistence = 0, damage = 0;
1746
1747 if (rangeType != RT_SINGLE && rangeType != RT_HIT) {
1748 switch (_damageType) {
1749 case DT_PHYSICAL:
1750 resistence = monsterData._phsyicalResistence;
1751 break;
1752 case DT_MAGICAL:
1753 resistence = monsterData._magicResistence;
1754 break;
1755 case DT_FIRE:
1756 resistence = monsterData._fireResistence;
1757 break;
1758 case DT_ELECTRICAL:
1759 resistence = monsterData._electricityResistence;
1760 break;
1761 case DT_COLD:
1762 resistence = monsterData._coldResistence;
1763 break;
1764 case DT_POISON:
1765 resistence = monsterData._poisonResistence;
1766 break;
1767 case DT_ENERGY:
1768 resistence = monsterData._energyResistence;
1769 break;
1770 default:
1771 break;
1772 }
1773 } else {
1774 int material = _weaponElemMaterial;
1775 damage = Res.ELEMENTAL_DAMAGE[material];
1776
1777 if (material != 0) {
1778 if (material < 9)
1779 resistence = monsterData._fireResistence;
1780 else if (material < 16)
1781 resistence = monsterData._electricityResistence;
1782 else if (material < 21)
1783 resistence = monsterData._coldResistence;
1784 else if (material < 26)
1785 resistence = monsterData._poisonResistence;
1786 else if (material < 34)
1787 resistence = monsterData._energyResistence;
1788 else
1789 resistence = monsterData._magicResistence;
1790 }
1791 }
1792
1793 if (resistence != 0) {
1794 if (resistence == 100)
1795 return 0;
1796 else
1797 return ((100 - resistence) * damage) / 100;
1798 }
1799
1800 return damage;
1801 }
1802
giveExperience(int experience)1803 void Combat::giveExperience(int experience) {
1804 Party &party = *_vm->_party;
1805 bool inCombat = _vm->_mode == MODE_COMBAT;
1806 int count = 0;
1807
1808 // Two loops: first to figure out how many active characters there are,
1809 // and the second to distribute the experience between them
1810 for (int loopNum = 0; loopNum < 2; ++loopNum) {
1811 for (uint charIndex = 0; charIndex < (inCombat ? _combatParty.size() :
1812 party._activeParty.size()); ++charIndex) {
1813 Character &c = inCombat ? *_combatParty[charIndex] : party._activeParty[charIndex];
1814 Condition condition = c.worstCondition();
1815
1816 if (condition != DEAD && condition != STONED && condition != ERADICATED) {
1817 if (loopNum == 0) {
1818 ++count;
1819 } else {
1820 int exp = experience / count;
1821 if (c._level._permanent < 15 && _vm->getGameID() != GType_Clouds)
1822 exp *= 2;
1823 c._experience += exp;
1824 }
1825 }
1826 }
1827 }
1828 }
1829
rangedAttack(PowType powNum)1830 void Combat::rangedAttack(PowType powNum) {
1831 Interface &intf = *_vm->_interface;
1832 Map &map = *_vm->_map;
1833 Party &party = *_vm->_party;
1834 Sound &sound = *_vm->_sound;
1835
1836 if (_damageType == DT_POISON_VOLLEY) {
1837 _damageType = DT_POISON;
1838 _shootType = ST_1;
1839 Common::fill(&_shootingRow[0], &_shootingRow[MAX_ACTIVE_PARTY], 1);
1840 } else if (powNum == POW_ARROW) {
1841 _shootType = ST_1;
1842 bool flag = false;
1843
1844 if (_damageType == DT_PHYSICAL) {
1845 for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
1846 Character &c = party._activeParty[idx];
1847 if (c.hasMissileWeapon()) {
1848 _shootingRow[idx] = 1;
1849 flag = true;
1850 }
1851 }
1852 } else {
1853 _shootingRow[0] = 1;
1854 flag = true;
1855 }
1856
1857 if (!flag) {
1858 sound.playFX(21);
1859 return;
1860 }
1861
1862 sound.playFX(49);
1863 } else {
1864 _shootingRow[0] = 1;
1865 _shootType = ST_0;
1866 }
1867
1868 intf._charsShooting = true;
1869 _powSprites.load(Common::String::format("pow%d.icn", (int)powNum));
1870 int attackDurationCtr = _attackDurationCtr;
1871 int monster2Attack = _monster2Attack;
1872 bool attackedFlag = false;
1873
1874 Common::Array<int> attackMonsters;
1875 for (int idx = 0; idx < 3; ++idx) {
1876 if (_attackMonsters[idx] != -1)
1877 attackMonsters.push_back(_attackMonsters[idx]);
1878 }
1879
1880 _attackDurationCtr = -1;
1881 if (_monster2Attack != -1) {
1882 _attackDurationCtr = attackDurationCtr - 1;
1883 if (attackMonsters.empty())
1884 attackMonsters.resize(1);
1885 attackMonsters[0] = monster2Attack;
1886 }
1887
1888 for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
1889 if (_shootingRow[idx]) {
1890 if (map._isOutdoors) {
1891 intf._outdoorList._attackImgs1[idx]._scale = 0;
1892 intf._outdoorList._attackImgs2[idx]._scale = 4;
1893 intf._outdoorList._attackImgs3[idx]._scale = 8;
1894 intf._outdoorList._attackImgs4[idx]._scale = 12;
1895 intf._outdoorList._attackImgs1[idx]._sprites = &_powSprites;
1896 intf._outdoorList._attackImgs2[idx]._sprites = nullptr;
1897 intf._outdoorList._attackImgs3[idx]._sprites = nullptr;
1898 intf._outdoorList._attackImgs4[idx]._sprites = nullptr;
1899 } else {
1900 intf._indoorList._attackImgs1[idx]._scale = 0;
1901 intf._indoorList._attackImgs2[idx]._scale = 4;
1902 intf._indoorList._attackImgs3[idx]._scale = 8;
1903 intf._indoorList._attackImgs4[idx]._scale = 12;
1904 intf._indoorList._attackImgs1[idx]._sprites = &_powSprites;
1905 intf._indoorList._attackImgs2[idx]._sprites = nullptr;
1906 intf._indoorList._attackImgs3[idx]._sprites = nullptr;
1907 intf._indoorList._attackImgs4[idx]._sprites = nullptr;
1908 }
1909 }
1910 }
1911
1912 intf.draw3d(true);
1913
1914 // Iterate through the three possible monster positions in the first row
1915 for (uint monIdx = 0; monIdx < 3; ++monIdx) {
1916 ++_attackDurationCtr;
1917
1918 if (monIdx < attackMonsters.size()) {
1919 Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], false);
1920 _monster2Attack = attackMonsters[monIdx];
1921 attack(*_oldCharacter, RT_GROUP);
1922 attackedFlag = true;
1923
1924 if (_rangeType == RT_SINGLE)
1925 // Only single shot, so exit now that the attack is done
1926 goto finished;
1927 }
1928 }
1929
1930 if (attackedFlag && _rangeType == RT_GROUP)
1931 // Finished group attack, so exit
1932 goto finished;
1933
1934 if (map._isOutdoors) {
1935 map.getCell(7);
1936 switch (map._currentWall) {
1937 case 1:
1938 case 3:
1939 case 6:
1940 case 7:
1941 case 9:
1942 case 10:
1943 case 12:
1944 sound.playFX(46);
1945 goto finished;
1946 default:
1947 break;
1948 }
1949 } else {
1950 int cell = map.getCell(2);
1951 if (cell >= map.mazeData()._difficulties._wallNoPass) {
1952 sound.playFX(46);
1953 goto finished;
1954 }
1955 }
1956 if (!intf._isAttacking)
1957 goto finished;
1958
1959 intf.draw3d(true);
1960
1961 // Start handling second teir of monsters in the back
1962 attackMonsters.clear();
1963 for (uint idx = 3; idx < 6; ++idx) {
1964 if (_attackMonsters[idx] != -1)
1965 attackMonsters.push_back(_attackMonsters[idx]);
1966 }
1967
1968 // Iterate through the three possible monster positions in the second row
1969 for (uint monIdx = 0; monIdx < 3; ++monIdx) {
1970 ++_attackDurationCtr;
1971
1972 if (monIdx < attackMonsters.size()) {
1973 Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], false);
1974 _monster2Attack = attackMonsters[monIdx];
1975 attack(*_oldCharacter, RT_GROUP);
1976 attackedFlag = true;
1977
1978 if (_rangeType == RT_SINGLE)
1979 // Only single shot, so exit now that the attack is done
1980 goto finished;
1981 }
1982 }
1983
1984 if (attackedFlag && _rangeType == RT_GROUP)
1985 // Finished group attack, so exit
1986 goto finished;
1987
1988 if (map._isOutdoors) {
1989 map.getCell(14);
1990 switch (map._currentWall) {
1991 case 1:
1992 case 3:
1993 case 6:
1994 case 7:
1995 case 9:
1996 case 10:
1997 case 12:
1998 sound.playFX(46);
1999 goto finished;
2000 default:
2001 break;
2002 }
2003 } else {
2004 int cell = map.getCell(7);
2005 if (cell >= map.mazeData()._difficulties._wallNoPass) {
2006 sound.playFX(46);
2007 goto finished;
2008 }
2009 }
2010 if (!intf._isAttacking)
2011 goto finished;
2012
2013 intf.draw3d(true);
2014
2015 // Start handling third teir of monsters in the back
2016 attackMonsters.clear();
2017 for (uint idx = 6; idx < 9; ++idx) {
2018 if (_attackMonsters[idx] != -1)
2019 attackMonsters.push_back(_attackMonsters[idx]);
2020 }
2021
2022 // Iterate through the three possible monster positions in the third row
2023 for (uint monIdx = 0; monIdx < 3; ++monIdx) {
2024 ++_attackDurationCtr;
2025
2026 if (monIdx < attackMonsters.size()) {
2027 Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], false);
2028 _monster2Attack = attackMonsters[monIdx];
2029 attack(*_oldCharacter, RT_GROUP);
2030 attackedFlag = true;
2031
2032 if (_rangeType == RT_SINGLE)
2033 // Only single shot, so exit now that the attack is done
2034 goto finished;
2035 }
2036 }
2037
2038 if (attackedFlag && _rangeType == RT_GROUP)
2039 // Finished group attack, so exit
2040 goto finished;
2041
2042 if (map._isOutdoors) {
2043 map.getCell(27);
2044 switch (map._currentWall) {
2045 case 1:
2046 case 3:
2047 case 6:
2048 case 7:
2049 case 9:
2050 case 10:
2051 case 12:
2052 sound.playFX(46);
2053 goto finished;
2054 default:
2055 break;
2056 }
2057 } else {
2058 int cell = map.getCell(14);
2059 if (cell >= map.mazeData()._difficulties._wallNoPass) {
2060 sound.playFX(46);
2061 goto finished;
2062 }
2063 }
2064 if (!intf._isAttacking)
2065 goto finished;
2066
2067 intf.draw3d(true);
2068
2069 // Fourth tier
2070 attackMonsters.clear();
2071 for (uint idx = 9; idx < 12; ++idx) {
2072 if (_attackMonsters[idx] != -1)
2073 attackMonsters.push_back(_attackMonsters[idx]);
2074 }
2075
2076 // Iterate through the three possible monster positions in the fourth row
2077 for (uint monIdx = 0; monIdx < 3; ++monIdx) {
2078 ++_attackDurationCtr;
2079
2080 if (monIdx < attackMonsters.size()) {
2081 Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], false);
2082 _monster2Attack = attackMonsters[monIdx];
2083 attack(*_oldCharacter, RT_GROUP);
2084 attackedFlag = true;
2085
2086 if (_rangeType == RT_SINGLE)
2087 // Only single shot, so exit now that the attack is done
2088 goto finished;
2089 }
2090 }
2091
2092 if (!(attackedFlag && _rangeType == RT_GROUP))
2093 goto done;
2094
2095 finished:
2096 endAttack();
2097
2098 done:
2099 clearShooting();
2100 _monster2Attack = monster2Attack;
2101 _attackDurationCtr = attackDurationCtr;
2102 party.giveTreasure();
2103 }
2104
shootRangedWeapon()2105 void Combat::shootRangedWeapon() {
2106 _rangeType = RT_ALL;
2107 _damageType = DT_PHYSICAL;
2108 rangedAttack(POW_ARROW);
2109 }
2110
areMonstersPresent() const2111 bool Combat::areMonstersPresent() const {
2112 for (int idx = 0; idx < 26; ++idx) {
2113 if (_attackMonsters[idx] != -1)
2114 return true;
2115 }
2116
2117 return false;
2118 }
2119
reset()2120 void Combat::reset() {
2121 clearShooting();
2122 setupCombatParty();
2123
2124 _combatMode = COMBATMODE_INTERACTIVE;
2125 _monster2Attack = -1;
2126 }
2127
2128 } // End of namespace Xeen
2129