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