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 "xeen/interface.h"
24 #include "xeen/dialogs/dialogs_char_info.h"
25 #include "xeen/dialogs/dialogs_control_panel.h"
26 #include "xeen/dialogs/dialogs_message.h"
27 #include "xeen/dialogs/dialogs_quick_fight.h"
28 #include "xeen/dialogs/dialogs_info.h"
29 #include "xeen/dialogs/dialogs_items.h"
30 #include "xeen/dialogs/dialogs_map.h"
31 #include "xeen/dialogs/dialogs_query.h"
32 #include "xeen/dialogs/dialogs_quests.h"
33 #include "xeen/dialogs/dialogs_quick_ref.h"
34 #include "xeen/dialogs/dialogs_spells.h"
35 #include "xeen/resources.h"
36 #include "xeen/xeen.h"
37 
38 #include "xeen/dialogs/dialogs_party.h"
39 
40 namespace Xeen {
41 
42 enum {
43 	SCENE_WINDOW = 11, SCENE_WIDTH = 216, SCENE_HEIGHT = 132
44 };
45 
PartyDrawer(XeenEngine * vm)46 PartyDrawer::PartyDrawer(XeenEngine *vm): _vm(vm) {
47 	_restoreSprites.load("restorex.icn");
48 	_hpSprites.load("hpbars.icn");
49 	_dseFace.load("dse.fac");
50 	_hiliteChar = HILIGHT_CHAR_NONE;
51 }
52 
drawParty(bool updateFlag)53 void PartyDrawer::drawParty(bool updateFlag) {
54 	Combat &combat = *_vm->_combat;
55 	Party &party = *_vm->_party;
56 	Resources &res = *_vm->_resources;
57 	Windows &windows = *_vm->_windows;
58 	bool inCombat = _vm->_mode == MODE_COMBAT;
59 	_restoreSprites.draw(0, 0, Common::Point(8, 149));
60 
61 	// Handle drawing the party faces
62 	uint partyCount = inCombat ? combat._combatParty.size() : party._activeParty.size();
63 	for (uint idx = 0; idx < partyCount; ++idx) {
64 		Character &c = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
65 		Condition charCondition = c.worstCondition();
66 		int charFrame = Res.FACE_CONDITION_FRAMES[charCondition];
67 
68 		SpriteResource *sprites = (charFrame > 4) ? &_dseFace : c._faceSprites;
69 		assert(sprites);
70 		if (charFrame > 4)
71 			charFrame -= 5;
72 
73 		sprites->draw(0, charFrame, Common::Point(Res.CHAR_FACES_X[idx], 150));
74 	}
75 
76 	for (uint idx = 0; idx < partyCount; ++idx) {
77 		Character &c = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
78 
79 		// Draw the Hp bar
80 		int maxHp = c.getMaxHP();
81 		int frame;
82 		if (c._currentHp < 1)
83 			frame = 4;
84 		else if (c._currentHp > maxHp)
85 			frame = 3;
86 		else if (c._currentHp == maxHp)
87 			frame = 0;
88 		else if (c._currentHp < (maxHp / 4))
89 			frame = 2;
90 		else
91 			frame = 1;
92 
93 		_hpSprites.draw(0, frame, Common::Point(Res.HP_BARS_X[idx], 182));
94 	}
95 
96 	if (_hiliteChar != HILIGHT_CHAR_NONE)
97 		res._globalSprites.draw(0, 8, Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));
98 
99 	if (updateFlag)
100 		windows[33].update();
101 }
102 
highlightChar(int charId)103 void PartyDrawer::highlightChar(int charId) {
104 	Resources &res = *_vm->_resources;
105 	Windows &windows = *_vm->_windows;
106 	assert(charId < MAX_ACTIVE_PARTY);
107 
108 	if (charId != _hiliteChar && _hiliteChar != HILIGHT_CHAR_DISABLED) {
109 		// Handle deselecting any previusly selected char
110 		if (_hiliteChar != HILIGHT_CHAR_NONE) {
111 			res._globalSprites.draw(0, 9 + _hiliteChar,
112 				Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));
113 		}
114 
115 		// Highlight new character
116 		res._globalSprites.draw(0, 8, Common::Point(Res.CHAR_FACES_X[charId] - 1, 149));
117 		_hiliteChar = charId;
118 		windows[33].update();
119 	}
120 }
121 
highlightChar(const Character * c)122 void PartyDrawer::highlightChar(const Character *c) {
123 	int charNum = _vm->_party->_activeParty.indexOf(*c);
124 	if (charNum != -1)
125 		highlightChar(charNum);
126 }
127 
unhighlightChar()128 void PartyDrawer::unhighlightChar() {
129 	Resources &res = *_vm->_resources;
130 	Windows &windows = *_vm->_windows;
131 
132 	if (_hiliteChar != HILIGHT_CHAR_NONE) {
133 		res._globalSprites.draw(0, _hiliteChar + 9,
134 			Common::Point(Res.CHAR_FACES_X[_hiliteChar] - 1, 149));
135 		_hiliteChar = HILIGHT_CHAR_NONE;
136 		windows[33].update();
137 	}
138 }
139 
resetHighlight()140 void PartyDrawer::resetHighlight() {
141 	_hiliteChar = HILIGHT_CHAR_NONE;
142 }
143 
144 /*------------------------------------------------------------------------*/
145 
Interface(XeenEngine * vm)146 Interface::Interface(XeenEngine *vm) : ButtonContainer(vm), InterfaceScene(vm),
147 		PartyDrawer(vm), _vm(vm) {
148 	_buttonsLoaded = false;
149 	_obscurity = OBSCURITY_NONE;
150 	_steppingFX = 0;
151 	_falling = FALL_NONE;
152 	_blessedUIFrame = 0;
153 	_powerShieldUIFrame = 0;
154 	_holyBonusUIFrame = 0;
155 	_heroismUIFrame = 0;
156 	_flipUIFrame = 0;
157 	_face1UIFrame = 0;
158 	_face2UIFrame = 0;
159 	_levitateUIFrame = 0;
160 	_spotDoorsUIFrame = 0;
161 	_dangerSenseUIFrame = 0;
162 	_face1State = _face2State = 0;
163 	_upDoorText = false;
164 	_tillMove = 0;
165 	_iconsMode = ICONS_STANDARD;
166 	Common::fill(&_charFX[0], &_charFX[MAX_ACTIVE_PARTY], 0);
167 	setWaitBounds();
168 }
169 
setup()170 void Interface::setup() {
171 	_borderSprites.load("border.icn");
172 	_spellFxSprites.load("spellfx.icn");
173 	_fecpSprites.load("fecp.brd");
174 	_blessSprites.load("bless.icn");
175 	_charPowSprites.load("charpow.icn");
176 	_uiSprites.load("inn.icn");
177 	_stdIcons.load("main.icn");
178 	_combatIcons.load("combat.icn");
179 
180 	Party &party = *_vm->_party;
181 	party.loadActiveParty();
182 	party._newDay = party._minutes < 300;
183 }
184 
startup()185 void Interface::startup() {
186 	Resources &res = *_vm->_resources;
187 
188 	animate3d();
189 	if (_vm->_map->_isOutdoors) {
190 		setIndoorsMonsters();
191 		setIndoorsObjects();
192 	} else {
193 		setOutdoorsMonsters();
194 		setOutdoorsObjects();
195 	}
196 	draw3d(false);
197 
198 	if (g_vm->getGameID() == GType_Swords)
199 		res._logoSprites.draw(1, 0, Common::Point(232, 9));
200 	else
201 		res._globalSprites.draw(1, 5, Common::Point(232, 9));
202 
203 	drawParty(false);
204 	setMainButtons();
205 
206 	_tillMove = false;
207 }
208 
mainIconsPrint()209 void Interface::mainIconsPrint() {
210 	Resources &res = *_vm->_resources;
211 	Windows &windows = *_vm->_windows;
212 	windows[38].close();
213 	windows[12].close();
214 
215 	res._globalSprites.draw(0, 7, Common::Point(232, 74));
216 	drawButtons(&windows[0]);
217 	windows[34].update();
218 }
219 
setMainButtons(IconsMode mode)220 void Interface::setMainButtons(IconsMode mode) {
221 	clearButtons();
222 	_iconsMode = mode;
223 	SpriteResource *spr = mode == ICONS_COMBAT ? &_combatIcons : &_stdIcons;
224 
225 	addButton(Common::Rect(235,  75, 259,  95),  Common::KEYCODE_s, spr);
226 	addButton(Common::Rect(260,  75, 284,  95),  Common::KEYCODE_c, spr);
227 	addButton(Common::Rect(286,  75, 310,  95),  Common::KEYCODE_r, spr);
228 	addButton(Common::Rect(235,  96, 259, 116),  Common::KEYCODE_b, spr);
229 	addButton(Common::Rect(260,  96, 284, 116),  Common::KEYCODE_d, spr);
230 	addButton(Common::Rect(286,  96, 310, 116),  Common::KEYCODE_v, spr);
231 	addButton(Common::Rect(235, 117, 259, 137),  Common::KEYCODE_m, spr);
232 	addButton(Common::Rect(260, 117, 284, 137),  Common::KEYCODE_i, spr);
233 	addButton(Common::Rect(286, 117, 310, 137),  Common::KEYCODE_q, spr);
234 	addButton(Common::Rect(109, 137, 122, 147), Common::KEYCODE_TAB, spr);
235 	addButton(Common::Rect(235, 148, 259, 168), Common::KEYCODE_LEFT, spr);
236 	addButton(Common::Rect(260, 148, 284, 168), Common::KEYCODE_UP, spr);
237 	addButton(Common::Rect(286, 148, 310, 168), Common::KEYCODE_RIGHT, spr);
238 	addButton(Common::Rect(235, 169, 259, 189), (Common::KBD_CTRL << 16) |Common::KEYCODE_LEFT, spr);
239 	addButton(Common::Rect(260, 169, 284, 189), Common::KEYCODE_DOWN, spr);
240 	addButton(Common::Rect(286, 169, 310, 189), (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT, spr);
241 	addButton(Common::Rect(236,  11, 308,  69),  Common::KEYCODE_EQUALS);
242 	addButton(Common::Rect(239,  27, 312,  37),  Common::KEYCODE_1);
243 	addButton(Common::Rect(239, 37, 312, 47), Common::KEYCODE_2);
244 	addButton(Common::Rect(239, 47, 312, 57), Common::KEYCODE_3);
245 	addPartyButtons(_vm);
246 
247 	if (mode == ICONS_COMBAT) {
248 		_buttons[0]._value = Common::KEYCODE_f;
249 		_buttons[1]._value = Common::KEYCODE_c;
250 		_buttons[2]._value = Common::KEYCODE_a;
251 		_buttons[3]._value = Common::KEYCODE_u;
252 		_buttons[4]._value = Common::KEYCODE_r;
253 		_buttons[5]._value = Common::KEYCODE_b;
254 		_buttons[6]._value = Common::KEYCODE_o;
255 		_buttons[7]._value = Common::KEYCODE_i;
256 		_buttons[16]._value = 0;
257 	}
258 }
259 
perform()260 void Interface::perform() {
261 	Combat &combat = *_vm->_combat;
262 	EventsManager &events = *_vm->_events;
263 	Map &map = *_vm->_map;
264 	Party &party = *_vm->_party;
265 	Scripts &scripts = *_vm->_scripts;
266 	Sound &sound = *_vm->_sound;
267 
268 	do {
269 		// Draw the next frame
270 		events.updateGameCounter();
271 		draw3d(true);
272 
273 		// Wait for a frame or a user event
274 		_buttonValue = 0;
275 		do {
276 			events.pollEventsAndWait();
277 			if (g_vm->shouldExit() || g_vm->isLoadPending() || party._dead)
278 				return;
279 
280 			checkEvents(g_vm);
281 		} while (!_buttonValue && events.timeElapsed() < 1);
282 	} while (!_buttonValue);
283 
284 	if (_buttonValue == Common::KEYCODE_SPACE) {
285 		int lookupId = map.mazeLookup(party._mazePosition,
286 			Res.WALL_SHIFTS[party._mazeDirection][2]);
287 
288 		bool eventsFlag = true;
289 		switch (lookupId) {
290 		case 1:
291 			if (!map._isOutdoors) {
292 				eventsFlag = !scripts.openGrate(13, 1);
293 			}
294 			break;
295 		case 6:
296 			// Open grate being closed
297 			if (!map._isOutdoors) {
298 				eventsFlag = !scripts.openGrate(9, 0);
299 			}
300 			break;
301 		case 9:
302 			// Closed grate being opened
303 			if (!map._isOutdoors) {
304 				eventsFlag = !scripts.openGrate(6, 0);
305 			}
306 			break;
307 		case 13:
308 			if (!map._isOutdoors) {
309 				eventsFlag = !scripts.openGrate(1, 1);
310 			}
311 			break;
312 		default:
313 			break;
314 		}
315 		if (eventsFlag) {
316 			scripts.checkEvents();
317 			if (_vm->shouldExit())
318 				return;
319 		} else {
320 			clearEvents();
321 		}
322 	}
323 
324 	switch (_buttonValue) {
325 	case Common::KEYCODE_TAB:
326 		// Show control panel
327 		combat._moveMonsters = false;
328 		ControlPanel::show(_vm);
329 		if (!g_vm->shouldExit() && !g_vm->_gameMode)
330 			combat._moveMonsters = true;
331 		break;
332 
333 	case Common::KEYCODE_SPACE:
334 	case Common::KEYCODE_w:
335 		// Wait one turn
336 		chargeStep();
337 		combat.moveMonsters();
338 		_upDoorText = false;
339 		_flipDefaultGround = !_flipDefaultGround;
340 		_flipGround = !_flipGround;
341 
342 		stepTime();
343 		break;
344 
345 	case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
346 	case Common::KEYCODE_KP4:
347 		if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT)) {
348 			switch (party._mazeDirection) {
349 			case DIR_NORTH:
350 				--party._mazePosition.x;
351 				break;
352 			case DIR_SOUTH:
353 				++party._mazePosition.x;
354 				break;
355 			case DIR_EAST:
356 				++party._mazePosition.y;
357 				break;
358 			case DIR_WEST:
359 				--party._mazePosition.y;
360 				break;
361 			default:
362 				break;
363 			}
364 
365 			chargeStep();
366 			_isAnimReset = true;
367 			party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
368 			_flipSky = !_flipSky;
369 			stepTime();
370 		}
371 		break;
372 
373 	case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
374 	case Common::KEYCODE_KP6:
375 		if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT)) {
376 			switch (party._mazeDirection) {
377 			case DIR_NORTH:
378 				++party._mazePosition.x;
379 				break;
380 			case DIR_SOUTH:
381 				--party._mazePosition.x;
382 				break;
383 			case DIR_EAST:
384 				--party._mazePosition.y;
385 				break;
386 			case DIR_WEST:
387 				++party._mazePosition.y;
388 				break;
389 			default:
390 				break;
391 			}
392 
393 			chargeStep();
394 			_isAnimReset = true;
395 			party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
396 			_flipSky = !_flipSky;
397 			stepTime();
398 		}
399 		break;
400 
401 	case Common::KEYCODE_LEFT:
402 	case Common::KEYCODE_KP7:
403 		party._mazeDirection = (Direction)((int)party._mazeDirection - 1);
404 		_isAnimReset = true;
405 		party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
406 		_flipSky = !_flipSky;
407 		stepTime();
408 		break;
409 
410 	case Common::KEYCODE_RIGHT:
411 	case Common::KEYCODE_KP9:
412 		party._mazeDirection = (Direction)((int)party._mazeDirection + 1);
413 		_isAnimReset = true;
414 		party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
415 		_flipSky = !_flipSky;
416 		stepTime();
417 		break;
418 
419 	case Common::KEYCODE_UP:
420 	case Common::KEYCODE_KP8:
421 		if (checkMoveDirection(Common::KEYCODE_UP)) {
422 			switch (party._mazeDirection) {
423 			case DIR_NORTH:
424 				++party._mazePosition.y;
425 				break;
426 			case DIR_SOUTH:
427 				--party._mazePosition.y;
428 				break;
429 			case DIR_EAST:
430 				++party._mazePosition.x;
431 				break;
432 			case DIR_WEST:
433 				--party._mazePosition.x;
434 				break;
435 			default:
436 				break;
437 			}
438 
439 			chargeStep();
440 			stepTime();
441 		}
442 		break;
443 
444 	case Common::KEYCODE_DOWN:
445 	case Common::KEYCODE_KP2:
446 		if (checkMoveDirection(Common::KEYCODE_DOWN)) {
447 			switch (party._mazeDirection) {
448 			case DIR_NORTH:
449 				--party._mazePosition.y;
450 				break;
451 			case DIR_SOUTH:
452 				++party._mazePosition.y;
453 				break;
454 			case DIR_EAST:
455 				--party._mazePosition.x;
456 				break;
457 			case DIR_WEST:
458 				++party._mazePosition.x;
459 				break;
460 			default:
461 				break;
462 			}
463 
464 			chargeStep();
465 			stepTime();
466 		}
467 		break;
468 
469 	case (Common::KBD_CTRL << 16) | Common::KEYCODE_DOWN:
470 		party._mazeDirection = (Direction)((int)party._mazeDirection ^ 2);
471 		_flipSky = !_flipSky;
472 		_isAnimReset = true;
473 		stepTime();
474 		break;
475 
476 	case Common::KEYCODE_F1:
477 	case Common::KEYCODE_F2:
478 	case Common::KEYCODE_F3:
479 	case Common::KEYCODE_F4:
480 	case Common::KEYCODE_F5:
481 	case Common::KEYCODE_F6:
482 		_buttonValue -= Common::KEYCODE_F1;
483 		if (_buttonValue < (int)party._activeParty.size()) {
484 			CharacterInfo::show(_vm, _buttonValue);
485 			if (party._stepped)
486 				combat.moveMonsters();
487 		}
488 		break;
489 
490 	case Common::KEYCODE_EQUALS:
491 	case Common::KEYCODE_KP_EQUALS:
492 		// Toggle minimap
493 		party._automapOn = !party._automapOn;
494 		break;
495 
496 	case Common::KEYCODE_b:
497 		chargeStep();
498 
499 		if (map.getCell(2) < map.mazeData()._difficulties._wallNoPass
500 				&& !map._isOutdoors) {
501 			switch (party._mazeDirection) {
502 			case DIR_NORTH:
503 				++party._mazePosition.y;
504 				break;
505 			case DIR_EAST:
506 				++party._mazePosition.x;
507 				break;
508 			case DIR_SOUTH:
509 				--party._mazePosition.y;
510 				break;
511 			case DIR_WEST:
512 				--party._mazePosition.x;
513 				break;
514 			default:
515 				break;
516 			}
517 			chargeStep();
518 			stepTime();
519 		} else {
520 			bash(party._mazePosition, party._mazeDirection);
521 		}
522 		break;
523 
524 	case Common::KEYCODE_c:
525 		// Cast spell
526 		if (_tillMove) {
527 			combat.moveMonsters();
528 			draw3d(true);
529 		}
530 
531 		if (CastSpell::show(_vm) != -1) {
532 			chargeStep();
533 			doStepCode();
534 		}
535 		break;
536 
537 	case Common::KEYCODE_i:
538 		// Show Info dialog
539 		combat._moveMonsters = false;
540 		InfoDialog::show(_vm);
541 		combat._moveMonsters = true;
542 		break;
543 
544 	case Common::KEYCODE_m:
545 		// Show map dialog
546 		MapDialog::show(_vm);
547 		break;
548 
549 	case Common::KEYCODE_q:
550 		// Show the quick reference dialog
551 		QuickReferenceDialog::show(_vm);
552 		break;
553 
554 	case Common::KEYCODE_r:
555 		// Rest
556 		rest();
557 		break;
558 
559 	case Common::KEYCODE_s:
560 		// Shoot
561 		if (!party.canShoot()) {
562 			sound.playFX(21);
563 		} else {
564 			if (_tillMove) {
565 				combat.moveMonsters();
566 				draw3d(true);
567 			}
568 
569 			if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
570 					|| combat._attackMonsters[2] != -1) {
571 				if ((_vm->_mode == MODE_INTERACTIVE || _vm->_mode == MODE_SLEEPING)
572 						&& !combat._monstersAttacking && !_charsShooting) {
573 					doCombat();
574 				}
575 			}
576 
577 			combat.shootRangedWeapon();
578 			chargeStep();
579 			doStepCode();
580 		}
581 		break;
582 
583 	case Common::KEYCODE_v:
584 		// Show the quests dialog
585 		Quests::show(_vm);
586 		break;
587 
588 	default:
589 		break;
590 	}
591 }
592 
chargeStep()593 void Interface::chargeStep() {
594 	if (!_vm->_party->_dead) {
595 		_vm->_party->changeTime(_vm->_map->_isOutdoors ? 10 : 1);
596 		if (_tillMove) {
597 			_vm->_combat->moveMonsters();
598 		}
599 
600 		_tillMove = 3;
601 	}
602 }
603 
stepTime()604 void Interface::stepTime() {
605 	Party &party = *_vm->_party;
606 	Sound &sound = *_vm->_sound;
607 	doStepCode();
608 
609 	if (++party._ctr24 == 24)
610 		party._ctr24 = 0;
611 
612 	if (_buttonValue != Common::KEYCODE_SPACE && _buttonValue != Common::KEYCODE_w) {
613 		_steppingFX ^= 1;
614 		sound.playFX(_steppingFX + 7);
615 	}
616 
617 	_upDoorText = false;
618 	_flipDefaultGround = !_flipDefaultGround;
619 	_flipGround = !_flipGround;
620 }
621 
doStepCode()622 void Interface::doStepCode() {
623 	Combat &combat = *_vm->_combat;
624 	Map &map = *_vm->_map;
625 	Party &party = *_vm->_party;
626 	int damage = 0;
627 
628 	party._stepped = true;
629 	_upDoorText = false;
630 
631 	map.getCell(2);
632 	int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
633 
634 	switch (surfaceId) {
635 	case SURFTYPE_SPACE:
636 		// Wheeze.. can't breathe in space! Explosive decompression, here we come
637 		party._dead = true;
638 		break;
639 	case SURFTYPE_LAVA:
640 		// It burns, it burns!
641 		damage = 100;
642 		combat._damageType = DT_FIRE;
643 		break;
644 	case SURFTYPE_SKY:
645 		// We can fly, we can.. oh wait, we can't!
646 		damage = 100;
647 		combat._damageType = DT_PHYSICAL;
648 		_falling = FALL_IN_PROGRESS;
649 		break;
650 	case SURFTYPE_DESERT:
651 		// Without navigation skills, simulate getting lost by adding extra time
652 		if (map._isOutdoors && !party.checkSkill(NAVIGATOR))
653 			party.addTime(170);
654 		break;
655 	case SURFTYPE_CLOUD:
656 		if (!party._levitateCount) {
657 			combat._damageType = DT_PHYSICAL;
658 			_falling = FALL_IN_PROGRESS;
659 			damage = 100;
660 		}
661 		break;
662 	default:
663 		break;
664 	}
665 
666 	if (_vm->getGameID() != GType_Swords && _vm->_files->_ccNum && party._gameFlags[1][118]) {
667 		_falling = FALL_NONE;
668 	} else {
669 		if (_falling != FALL_NONE)
670 			startFalling(false);
671 
672 		if ((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) {
673 			if (map._isOutdoors)
674 				map.getNewMaze();
675 		}
676 
677 		if (damage) {
678 			_flipGround = !_flipGround;
679 			draw3d(true);
680 
681 			int oldTarget = combat._combatTarget;
682 			combat._combatTarget = 0;
683 
684 			// WORKAROUND: Stepping into combat whilst on lava results in damageType being lost
685 			combat._damageType = (surfaceId == SURFTYPE_LAVA) ? DT_FIRE : DT_PHYSICAL;
686 			combat.giveCharDamage(damage, combat._damageType, 0);
687 
688 			combat._combatTarget = oldTarget;
689 			_flipGround = !_flipGround;
690 		} else if (party._dead) {
691 			draw3d(true);
692 		}
693 	}
694 }
695 
startFalling(bool flag)696 void Interface::startFalling(bool flag) {
697 	Combat &combat = *_vm->_combat;
698 	Map &map = *_vm->_map;
699 	Party &party = *_vm->_party;
700 	int ccNum = _vm->_files->_ccNum;
701 
702 	if (ccNum && party._gameFlags[1][118]) {
703 		_falling = FALL_NONE;
704 		return;
705 	}
706 
707 	_falling = FALL_NONE;
708 	draw3d(true);
709 	_falling = FALL_START;
710 	draw3d(false);
711 
712 	if (flag && (!ccNum || party._fallMaze != 0)) {
713 		party._mazeId = party._fallMaze;
714 		party._mazePosition = party._fallPosition;
715 	} else if (!ccNum) {
716 		switch (party._mazeId - 25) {
717 		case 0:
718 		case 26:
719 		case 27:
720 		case 28:
721 		case 29:
722 			party._mazeId = 24;
723 			party._mazePosition = Common::Point(11, 9);
724 			break;
725 		case 1:
726 		case 30:
727 		case 31:
728 		case 32:
729 		case 33:
730 			party._mazeId = 12;
731 			party._mazePosition = Common::Point(6, 15);
732 			break;
733 		case 2:
734 		case 34:
735 		case 35:
736 		case 36:
737 		case 37:
738 		case 51:
739 		case 52:
740 		case 53:
741 			party._mazeId = 15;
742 			party._mazePosition = Common::Point(4, 12);
743 			party._mazeDirection = DIR_SOUTH;
744 			break;
745 		case 40:
746 		case 41:
747 			party._mazeId = 14;
748 			party._mazePosition = Common::Point(8, 3);
749 			break;
750 		case 44:
751 		case 45:
752 			party._mazeId = 1;
753 			party._mazePosition = Common::Point(8, 7);
754 			party._mazeDirection = DIR_NORTH;
755 			break;
756 		case 49:
757 			party._mazeId = 12;
758 			party._mazePosition = Common::Point(11, 13);
759 			party._mazeDirection = DIR_SOUTH;
760 			break;
761 		case 57:
762 		case 58:
763 		case 59:
764 			party._mazeId = 5;
765 			party._mazePosition = Common::Point(12, 7);
766 			party._mazeDirection = DIR_NORTH;
767 			break;
768 		case 60:
769 			party._mazeId = 6;
770 			party._mazePosition = Common::Point(12, 3);
771 			party._mazeDirection = DIR_NORTH;
772 			break;
773 		default:
774 			party._mazeId = 23;
775 			party._mazePosition = Common::Point(12, 10);
776 			party._mazeDirection = DIR_NORTH;
777 			break;
778 		}
779 	} else {
780 		if (party._mazeId > 88 && party._mazeId < 114) {
781 			party._mazeId -= 88;
782 		} else {
783 			switch (party._mazeId - 25) {
784 			case 0:
785 				party._mazeId = 89;
786 				party._mazePosition = Common::Point(2, 14);
787 				break;
788 			case 1:
789 				party._mazeId = 109;
790 				party._mazePosition = Common::Point(13, 14);
791 				break;
792 			case 2:
793 				party._mazeId = 112;
794 				party._mazePosition = Common::Point(13, 3);
795 				break;
796 			case 3:
797 				party._mazeId = 92;
798 				party._mazePosition = Common::Point(2, 3);
799 				break;
800 			case 12:
801 			case 13:
802 				party._mazeId = 14;
803 				party._mazePosition = Common::Point(10, 2);
804 				break;
805 			case 16:
806 			case 17:
807 			case 18:
808 				party._mazeId = 4;
809 				party._mazePosition = Common::Point(5, 14);
810 				break;
811 			case 20:
812 			case 21:
813 			case 22:
814 				party._mazeId = 21;
815 				party._mazePosition = Common::Point(9, 11);
816 				break;
817 			case 24:
818 			case 25:
819 			case 26:
820 				party._mazeId = 1;
821 				party._mazePosition = Common::Point(10, 4);
822 				break;
823 			case 28:
824 			case 29:
825 			case 30:
826 			case 31:
827 				party._mazeId = 26;
828 				party._mazePosition = Common::Point(12, 10);
829 				break;
830 			case 32:
831 			case 33:
832 			case 34:
833 			case 35:
834 				party._mazeId = 3;
835 				party._mazePosition = Common::Point(4, 9);
836 				break;
837 			case 36:
838 			case 37:
839 			case 38:
840 			case 39:
841 				party._mazeId = 16;
842 				party._mazePosition = Common::Point(2, 7);
843 				break;
844 			case 40:
845 			case 41:
846 			case 42:
847 			case 43:
848 				party._mazeId = 23;
849 				party._mazePosition = Common::Point(10, 9);
850 				break;
851 			case 44:
852 			case 45:
853 			case 46:
854 			case 47:
855 				party._mazeId = 13;
856 				party._mazePosition = Common::Point(2, 10);
857 				break;
858 			case 103:
859 			case 104:
860 				map._loadCcNum = 0;
861 				party._mazeId = 8;
862 				party._mazePosition = Common::Point(11, 15);
863 				party._mazeDirection = DIR_NORTH;
864 				break;
865 			case 105:
866 				party._mazeId = 24;
867 				party._mazePosition = Common::Point(11, 9);
868 				break;
869 			case 106:
870 				party._mazeId = 12;
871 				party._mazePosition = Common::Point(6, 15);
872 				break;
873 			case 107:
874 				party._mazeId = 15;
875 				party._mazePosition = Common::Point(4, 12);
876 				break;
877 			default:
878 				party._mazeId = 29;
879 				party._mazePosition = Common::Point(25, 21);
880 				party._mazeDirection = DIR_NORTH;
881 				break;
882 			}
883 		}
884 	}
885 
886 	_falling = FALL_IN_PROGRESS;
887 	map.load(party._mazeId);
888 
889 	if (flag) {
890 		if (map._isOutdoors && ((party._mazePosition.x & 16) || (party._mazePosition.y & 16)))
891 			map.getNewMaze();
892 
893 		_flipGround ^= 1;
894 		draw3d(true);
895 		int oldTarget = combat._combatTarget;
896 		combat._combatTarget = 0;
897 		combat.giveCharDamage(party._fallDamage, DT_PHYSICAL, 0);
898 
899 		combat._combatTarget = oldTarget;
900 		_flipGround ^= 1;
901 	}
902 }
903 
checkMoveDirection(int key)904 bool Interface::checkMoveDirection(int key) {
905 	Debugger &debugger = *g_vm->_debugger;
906 	Map &map = *_vm->_map;
907 	Party &party = *_vm->_party;
908 	Sound &sound = *_vm->_sound;
909 
910 	// If intangibility is turned on in the debugger, allow any movement
911 	if (debugger._intangible)
912 		return true;
913 
914 	// For strafing or moving backwards, temporarily move to face the direction being checked,
915 	// since the call to getCell will the adjacent cell details in the direction being faced
916 	Direction dir = party._mazeDirection;
917 	switch (key) {
918 	case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
919 		party._mazeDirection = (party._mazeDirection == DIR_NORTH) ? DIR_WEST :
920 			(Direction)(party._mazeDirection - 1);
921 		break;
922 	case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
923 		party._mazeDirection = (party._mazeDirection == DIR_WEST) ? DIR_NORTH :
924 			(Direction)(party._mazeDirection + 1);
925 		break;
926 	case Common::KEYCODE_DOWN:
927 		party._mazeDirection = (Direction)((int)party._mazeDirection ^ 2);
928 		break;
929 	default:
930 		break;
931 	}
932 
933 	// Get next facing tile information
934 	map.getCell(7);
935 
936 	int startSurfaceId = map._currentSurfaceId;
937 	int surfaceId;
938 
939 	if (map._isOutdoors) {
940 		// Reset direction back to original facing, if it was changed for strafing checks
941 		party._mazeDirection = dir;
942 
943 		switch (map._currentWall) {
944 		case 5:
945 			if (_vm->_files->_ccNum)
946 				goto check;
947 
948 			// fall through
949 		case 0:
950 		case 2:
951 		case 4:
952 		case 8:
953 		case 11:
954 		case 13:
955 		case 14:
956 			surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
957 			if (surfaceId == SURFTYPE_WATER) {
958 				if (party.checkSkill(SWIMMING) || party._walkOnWaterActive)
959 					return true;
960 			} else if (surfaceId == SURFTYPE_DWATER) {
961 				if (party._walkOnWaterActive)
962 					return true;
963 			} else if (surfaceId != SURFTYPE_SPACE) {
964 				return true;
965 			}
966 
967 			sound.playFX(21);
968 			return false;
969 
970 		case 1:
971 		case 7:
972 		case 9:
973 		case 10:
974 		case 12:
975 			check:
976 			if (party.checkSkill(MOUNTAINEER))
977 				return true;
978 
979 			sound.playFX(21);
980 			return false;
981 
982 		default:
983 			break;
984 		}
985 	} else {
986 		surfaceId = map.getCell(2);
987 
988 		// Reset direction back to original facing, if it was changed for strafing checks
989 		party._mazeDirection = dir;
990 
991 		if (surfaceId >= map.mazeData()._difficulties._wallNoPass) {
992 			sound.playFX(46);
993 			return false;
994 		} else {
995 			if (startSurfaceId != SURFTYPE_SWAMP || party.checkSkill(SWIMMING) ||
996 					party._walkOnWaterActive) {
997 				if (_buttonValue == Common::KEYCODE_UP && _wo[107]) {
998 					_openDoor = true;
999 					sound.playFX(47);
1000 					draw3d(true);
1001 					_openDoor = false;
1002 				}
1003 				return true;
1004 			} else {
1005 				sound.playFX(46);
1006 				return false;
1007 			}
1008 		}
1009 	}
1010 
1011 	return true;
1012 }
1013 
rest()1014 void Interface::rest() {
1015 	Map &map = *_vm->_map;
1016 	Party &party = *_vm->_party;
1017 
1018 	map.cellFlagLookup(party._mazePosition);
1019 
1020 	if ((map._currentCantRest || (map.mazeData()._mazeFlags & RESTRICTION_REST))
1021 			&& _vm->_mode != MODE_INTERACTIVE2) {
1022 		ErrorScroll::show(_vm, Res.TOO_DANGEROUS_TO_REST, WT_NONFREEZED_WAIT);
1023 	} else {
1024 		// Check whether any character is in danger of dying
1025 		bool dangerFlag = false;
1026 		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
1027 			for (int attrib = MIGHT; attrib <= LUCK; ++attrib) {
1028 				if (party._activeParty[charIdx].getStat((Attribute)attrib) < 1)
1029 					dangerFlag = true;
1030 			}
1031 		}
1032 
1033 		if (dangerFlag) {
1034 			if (!Confirm::show(_vm, Res.SOME_CHARS_MAY_DIE))
1035 				return;
1036 		}
1037 
1038 		// Mark all the players as being asleep
1039 		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
1040 			party._activeParty[charIdx]._conditions[ASLEEP] = 1;
1041 		}
1042 		drawParty(true);
1043 
1044 		Mode oldMode = _vm->_mode;
1045 		_vm->_mode = MODE_SLEEPING;
1046 
1047 		if (oldMode == MODE_INTERACTIVE2) {
1048 			party.changeTime(8 * 60);
1049 		} else {
1050 			for (int idx = 0; idx < 10; ++idx) {
1051 				chargeStep();
1052 				draw3d(true);
1053 
1054 				if (_vm->_mode == MODE_INTERACTIVE) {
1055 					_vm->_mode = oldMode;
1056 					return;
1057 				}
1058 			}
1059 
1060 			party.changeTime(map._isOutdoors ? 380 : 470);
1061 		}
1062 
1063 		if (_vm->getRandomNumber(1, 20) == 1)
1064 			_vm->dream();
1065 
1066 		party.resetTemps();
1067 
1068 		// Wake up the party
1069 		bool starving = false;
1070 		int foodConsumed = 0;
1071 		for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
1072 			Character &c = party._activeParty[charIdx];
1073 			c._conditions[ASLEEP] = 0;
1074 
1075 			if (party._food == 0) {
1076 				starving = true;
1077 			} else {
1078 				party._rested = true;
1079 				Condition condition = c.worstCondition();
1080 
1081 				if (condition < DEAD || condition > ERADICATED) {
1082 					--party._food;
1083 					++foodConsumed;
1084 					party._heroism = 0;
1085 					party._holyBonus = 0;
1086 					party._powerShield = 0;
1087 					party._blessed = 0;
1088 					c._conditions[UNCONSCIOUS] = 0;
1089 					c._currentHp = c.getMaxHP();
1090 					c._currentSp = c.getMaxSP();
1091 
1092 					// WORKAROUND: Resting curing weakness only originally worked due to a bug in changeTime
1093 					// resetting WEAK if party wasn't drunk. With that resolved, we have to reset WEAK here
1094 					c._conditions[WEAK] = 0;
1095 				}
1096 			}
1097 		}
1098 
1099 		drawParty(true);
1100 		_vm->_mode = oldMode;
1101 		doStepCode();
1102 		draw3d(true);
1103 
1104 		ErrorScroll::show(_vm, Common::String::format(Res.REST_COMPLETE,
1105 			starving ? Res.PARTY_IS_STARVING : Res.HIT_SPELL_POINTS_RESTORED,
1106 			foodConsumed));
1107 		party.checkPartyDead();
1108 	}
1109 }
1110 
bash(const Common::Point & pt,Direction direction)1111 void Interface::bash(const Common::Point &pt, Direction direction) {
1112 	EventsManager &events = *_vm->_events;
1113 	Map &map = *_vm->_map;
1114 	Party &party = *_vm->_party;
1115 	Sound &sound = *_vm->_sound;
1116 	Windows &windows = *_vm->_windows;
1117 
1118 	if (map._isOutdoors)
1119 		return;
1120 
1121 	sound.playFX(31);
1122 
1123 	uint charNum1 = 0, charNum2 = 0;
1124 	for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
1125 		Character &c = party._activeParty[charIdx];
1126 		Condition condition = c.worstCondition();
1127 
1128 		if (!(condition == ASLEEP || (condition >= PARALYZED &&
1129 				condition <= ERADICATED))) {
1130 			if (charNum1) {
1131 				charNum2 = charIdx + 1;
1132 				break;
1133 			} else {
1134 				charNum1 = charIdx + 1;
1135 			}
1136 		}
1137 	}
1138 
1139 	party._activeParty[charNum1 - 1].subtractHitPoints(2);
1140 	_charPowSprites.draw(windows[0], 0,
1141 		Common::Point(Res.CHAR_FACES_X[charNum1 - 1], 150));
1142 	windows[0].update();
1143 
1144 	if (charNum2) {
1145 		party._activeParty[charNum2 - 1].subtractHitPoints(2);
1146 		_charPowSprites.draw(windows[0], 0,
1147 			Common::Point(Res.CHAR_FACES_X[charNum2 - 1], 150));
1148 		windows[0].update();
1149 	}
1150 
1151 	int cell = map.mazeLookup(Common::Point(pt.x + Res.SCREEN_POSITIONING_X[direction][7],
1152 		pt.y + Res.SCREEN_POSITIONING_Y[direction][7]), 0, 0xffff);
1153 	if (cell != INVALID_CELL) {
1154 		int v = map.getCell(2);
1155 
1156 		if (v == 7) {
1157 			++_wo[207];
1158 			++_wo[267];
1159 			++_wo[287];
1160 		} else if (v == 14) {
1161 			++_wo[267];
1162 			++_wo[287];
1163 		} else if (v == 15) {
1164 			++_wo[287];
1165 		} else {
1166 			int might = party._activeParty[charNum1 - 1].getStat(MIGHT) +
1167 				_vm->getRandomNumber(1, 30);
1168 			if (charNum2)
1169 				might += party._activeParty[charNum2 - 1].getStat(MIGHT);
1170 
1171 			int bashThreshold = (v == 9) ? map.mazeData()._difficulties._bashGrate :
1172 				map.mazeData()._difficulties._bashWall;
1173 			if (might >= bashThreshold) {
1174 				// Remove the wall on the current cell, and the reverse wall
1175 				// on the cell we're bashing through to
1176 				map.setWall(pt, direction, 3);
1177 				switch (direction) {
1178 				case DIR_NORTH:
1179 					map.setWall(Common::Point(pt.x, pt.y + 1), DIR_SOUTH, 3);
1180 					break;
1181 				case DIR_EAST:
1182 					map.setWall(Common::Point(pt.x + 1, pt.y), DIR_WEST, 3);
1183 					break;
1184 				case DIR_SOUTH:
1185 					map.setWall(Common::Point(pt.x, pt.y - 1), DIR_NORTH, 3);
1186 					break;
1187 				case DIR_WEST:
1188 					map.setWall(Common::Point(pt.x - 1, pt.y), DIR_EAST, 3);
1189 					break;
1190 				default:
1191 					break;
1192 				}
1193 			}
1194 		}
1195 	}
1196 
1197 	party.checkPartyDead();
1198 	events.ipause(2);
1199 	drawParty(true);
1200 }
1201 
draw3d(bool updateFlag,bool pauseFlag)1202 void Interface::draw3d(bool updateFlag, bool pauseFlag) {
1203 	Combat &combat = *_vm->_combat;
1204 	EventsManager &events = *_vm->_events;
1205 	Party &party = *_vm->_party;
1206 	Scripts &scripts = *_vm->_scripts;
1207 	Windows &windows = *_vm->_windows;
1208 
1209 	events.timeMark5();
1210 	if (windows[SCENE_WINDOW]._enabled)
1211 		return;
1212 
1213 	_flipUIFrame = (_flipUIFrame + 1) % 4;
1214 	if (_flipUIFrame == 0)
1215 		_flipWater = !_flipWater;
1216 	if (_tillMove && (_vm->_mode == MODE_INTERACTIVE || _vm->_mode == MODE_COMBAT) &&
1217 		!combat._monstersAttacking && combat._moveMonsters) {
1218 		if (--_tillMove == 0)
1219 			combat.moveMonsters();
1220 	}
1221 
1222 	// Draw the game scene
1223 	drawScene();
1224 
1225 	// Draw the minimap
1226 	drawMinimap();
1227 
1228 	// Handle any darkness-based oscurity
1229 	obscureScene(_obscurity);
1230 
1231 	if (_falling == FALL_IN_PROGRESS)
1232 		handleFalling();
1233 
1234 	if (_falling == FALL_START) {
1235 		setupFallSurface(true);
1236 	}
1237 
1238 	assembleBorder();
1239 
1240 	// Draw any on-screen text if flagged to do so
1241 	if (_upDoorText && combat._attackMonsters[0] == -1) {
1242 		windows[3].writeString(_screenText);
1243 	}
1244 
1245 	if (updateFlag) {
1246 		windows[1].update();
1247 		windows[3].update();
1248 	}
1249 
1250 	if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
1251 			|| combat._attackMonsters[2] != -1) {
1252 		if ((_vm->_mode == MODE_INTERACTIVE || _vm->_mode == MODE_SLEEPING) &&
1253 				!combat._monstersAttacking && !_charsShooting && combat._moveMonsters) {
1254 			doCombat();
1255 			if (scripts._eventSkipped)
1256 				scripts.checkEvents();
1257 		}
1258 	}
1259 
1260 	party._stepped = false;
1261 	if (pauseFlag)
1262 		events.ipause5(2);
1263 }
1264 
handleFalling()1265 void Interface::handleFalling() {
1266 	Party &party = *_vm->_party;
1267 	Screen &screen = *_vm->_screen;
1268 	Sound &sound = *_vm->_sound;
1269 	Windows &windows = *_vm->_windows;
1270 	Window &w = windows[3];
1271 
1272 	// Set the bottom half of the fall surface (area that is being fallen to)
1273 	setupFallSurface(false);
1274 
1275 	// Update character faces and start scream
1276 	for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
1277 		party._activeParty[idx]._faceSprites->draw(0, 4,
1278 			Common::Point(Res.CHAR_FACES_X[idx], 150));
1279 	}
1280 
1281 	windows[33].update();
1282 	sound.playFX(11);
1283 	sound.playSound("scream.voc");
1284 
1285 	// Fall down to the ground
1286 	#define YINDEX (SCENE_HEIGHT / 2)
1287 	const int Y_LIST[] = {
1288 		SCENE_HEIGHT, SCENE_HEIGHT - 5, SCENE_HEIGHT, SCENE_HEIGHT - 3, SCENE_HEIGHT
1289 	};
1290 	for (int idx = 1; idx < YINDEX + 5; ++idx) {
1291 		fall((idx < YINDEX) ? idx * 2 : Y_LIST[idx - YINDEX]);
1292 		assembleBorder();
1293 		w.update();
1294 		screen.update();
1295 		g_system->delayMillis(5);
1296 
1297 		if (idx == YINDEX) {
1298 			sound.stopSound();
1299 			sound.playSound("unnh.voc");
1300 			sound.playFX(31);
1301 		}
1302 	}
1303 
1304 	shake(10);
1305 
1306 	_falling = FALL_NONE;
1307 	drawParty(true);
1308 }
1309 
setupFallSurface(bool isTop)1310 void Interface::setupFallSurface(bool isTop) {
1311 	Window &w = (*g_vm->_windows)[SCENE_WINDOW];
1312 
1313 	if (_fallSurface.empty())
1314 		_fallSurface.create(SCENE_WIDTH, SCENE_HEIGHT * 2);
1315 	_fallSurface.blitFrom(w, w.getBounds(), Common::Point(0, isTop ? 0 : SCENE_HEIGHT));
1316 }
1317 
fall(int yp)1318 void Interface::fall(int yp) {
1319 	Window &w = (*g_vm->_windows)[SCENE_WINDOW];
1320 	w.blitFrom(_fallSurface, Common::Rect(0, yp, SCENE_WIDTH, yp + SCENE_HEIGHT), Common::Point(8, 8));
1321 }
1322 
shake(int count)1323 void Interface::shake(int count) {
1324 	Screen &screen = *g_vm->_screen;
1325 	byte b;
1326 
1327 	for (int idx = 0; idx < count * 2; ++idx) {
1328 		for (int yp = 0; yp < screen.h; ++yp) {
1329 			byte *lineP = (byte *)screen.getBasePtr(0, yp);
1330 			if (idx % 2) {
1331 				// Shift back right
1332 				b = lineP[SCREEN_WIDTH - 1];
1333 				Common::copy_backward(lineP, lineP + SCREEN_WIDTH - 1, lineP + SCREEN_WIDTH);
1334 				lineP[0] = b;
1335 			} else {
1336 				// Scroll left one pixel
1337 				b = lineP[0];
1338 				Common::copy(lineP + 1, lineP + SCREEN_WIDTH, lineP);
1339 				lineP[SCREEN_WIDTH - 1] = b;
1340 			}
1341 		}
1342 
1343 		screen.markAllDirty();
1344 		screen.update();
1345 		g_system->delayMillis(5);
1346 	}
1347 }
1348 
assembleBorder()1349 void Interface::assembleBorder() {
1350 	Combat &combat = *_vm->_combat;
1351 	Resources &res = *_vm->_resources;
1352 	Windows &windows = *_vm->_windows;
1353 
1354 	// Draw the outer frame
1355 	res._globalSprites.draw(windows[0], 0, Common::Point(8, 8));
1356 
1357 	// Draw the animating bat character on the left screen edge to indicate
1358 	// that the party is being levitated
1359 	_borderSprites.draw(windows[0], _vm->_party->_levitateCount ? _levitateUIFrame + 16 : 16,
1360 		Common::Point(0, 82));
1361 	_levitateUIFrame = (_levitateUIFrame + 1) % 12;
1362 
1363 	// Draw UI element to indicate whether can spot hidden doors
1364 	_borderSprites.draw(0,
1365 		(_thinWall && _vm->_party->checkSkill(SPOT_DOORS)) ? _spotDoorsUIFrame + 28 : 28,
1366 		Common::Point(194, 91));
1367 	_spotDoorsUIFrame = (_spotDoorsUIFrame + 1) % 12;
1368 
1369 	// Draw UI element to indicate whether can sense danger
1370 	_borderSprites.draw(0,
1371 		(combat._dangerPresent && _vm->_party->checkSkill(DANGER_SENSE)) ? _spotDoorsUIFrame + 40 : 40,
1372 		Common::Point(107, 9));
1373 	_dangerSenseUIFrame = (_dangerSenseUIFrame + 1) % 12;
1374 
1375 	// Handle the face UI elements for indicating clairvoyance status
1376 	_face1UIFrame = (_face1UIFrame + 1) % 4;
1377 	if (_face1State == 0)
1378 		_face1UIFrame += 4;
1379 	else if (_face1State == 2)
1380 		_face1UIFrame = 0;
1381 
1382 	_face2UIFrame = (_face2UIFrame + 1) % 4 + 12;
1383 	if (_face2State == 0)
1384 		_face2UIFrame -= 3;
1385 	else if (_face2State == 2)
1386 		_face2UIFrame = 8;
1387 
1388 	if (!_vm->_party->_clairvoyanceActive) {
1389 		_face1UIFrame = 0;
1390 		_face2UIFrame = 8;
1391 	}
1392 
1393 	_borderSprites.draw(0, _face1UIFrame, Common::Point(0, 32));
1394 	_borderSprites.draw(0,
1395 		windows[10]._enabled || windows[2]._enabled ? 52 : _face2UIFrame,
1396 		Common::Point(215, 32));
1397 
1398 	// Draw resistence indicators
1399 	if (!windows[10]._enabled && !windows[2]._enabled
1400 		&& !windows[38]._enabled) {
1401 		_fecpSprites.draw(0, _vm->_party->_fireResistence ? 1 : 0,
1402 			Common::Point(2, 2));
1403 		_fecpSprites.draw(0, _vm->_party->_electricityResistence ? 3 : 2,
1404 			Common::Point(219, 2));
1405 		_fecpSprites.draw(0, _vm->_party->_coldResistence ? 5 : 4,
1406 			Common::Point(2, 134));
1407 		_fecpSprites.draw(0, _vm->_party->_poisonResistence ? 7 : 6,
1408 			Common::Point(219, 134));
1409 	} else {
1410 		_fecpSprites.draw(0, _vm->_party->_fireResistence ? 9 : 8,
1411 			Common::Point(8, 8));
1412 		_fecpSprites.draw(0, _vm->_party->_electricityResistence ? 11 : 10,
1413 			Common::Point(219, 8));
1414 		_fecpSprites.draw(0, _vm->_party->_coldResistence ? 13 : 12,
1415 			Common::Point(8, 134));
1416 		_fecpSprites.draw(0, _vm->_party->_poisonResistence ? 15 : 14,
1417 			Common::Point(219, 134));
1418 	}
1419 
1420 	// Draw UI element for blessed
1421 	_blessSprites.draw(0, 16, Common::Point(33, 137));
1422 	if (_vm->_party->_blessed) {
1423 		_blessedUIFrame = (_blessedUIFrame + 1) % 4;
1424 		_blessSprites.draw(0, _blessedUIFrame, Common::Point(33, 137));
1425 	}
1426 
1427 	// Draw UI element for power shield
1428 	if (_vm->_party->_powerShield) {
1429 		_powerShieldUIFrame = (_powerShieldUIFrame + 1) % 4;
1430 		_blessSprites.draw(0, _powerShieldUIFrame + 4,
1431 			Common::Point(55, 137));
1432 	}
1433 
1434 	// Draw UI element for holy bonus
1435 	if (_vm->_party->_holyBonus) {
1436 		_holyBonusUIFrame = (_holyBonusUIFrame + 1) % 4;
1437 		_blessSprites.draw(0, _holyBonusUIFrame + 8, Common::Point(160, 137));
1438 	}
1439 
1440 	// Draw UI element for heroism
1441 	if (_vm->_party->_heroism) {
1442 		_heroismUIFrame = (_heroismUIFrame + 1) % 4;
1443 		_blessSprites.draw(0, _heroismUIFrame + 12, Common::Point(182, 137));
1444 	}
1445 
1446 	// Draw direction character if direction sense is active
1447 	if (_vm->_party->checkSkill(DIRECTION_SENSE) && !_vm->_noDirectionSense) {
1448 		const char *dirText = Res.DIRECTION_TEXT_UPPER[_vm->_party->_mazeDirection];
1449 		Common::String msg = Common::String::format("\x2\f08\x3""c\v139\t116%c\fd\x1", *dirText);
1450 		windows[0].writeString(msg);
1451 	}
1452 
1453 	// Draw view frame
1454 	if (windows[12]._enabled)
1455 		windows[12].frame();
1456 }
1457 
doCombat()1458 void Interface::doCombat() {
1459 	Combat &combat = *_vm->_combat;
1460 	EventsManager &events = *_vm->_events;
1461 	Map &map = *_vm->_map;
1462 	Party &party = *_vm->_party;
1463 	Scripts &scripts = *_vm->_scripts;
1464 	Sound &sound = *_vm->_sound;
1465 	Windows &windows = *_vm->_windows;
1466 	bool upDoorText = _upDoorText;
1467 	bool reloadMap = false;
1468 	int index = 0;
1469 
1470 	_upDoorText = false;
1471 	combat._combatMode = COMBATMODE_2;
1472 	_vm->_mode = MODE_COMBAT;
1473 
1474 	// Set the combat buttons
1475 	IconsMode oldMode = _iconsMode;
1476 	setMainButtons(ICONS_COMBAT);
1477 	mainIconsPrint();
1478 
1479 	combat._combatParty.clear();
1480 	combat.clearBlocked();
1481 	combat._pow[0]._duration = 0;
1482 	combat._pow[1]._duration = 0;
1483 	combat._pow[2]._duration = 0;
1484 	combat._monstersAttacking = false;
1485 	combat._partyRan = false;
1486 
1487 	// Set up the combat party
1488 	combat.setupCombatParty();
1489 	combat.setSpeedTable();
1490 
1491 	// Initialize arrays for character/monster states
1492 	Common::fill(&combat._charsGone[0], &combat._charsGone[PARTY_AND_MONSTERS], 0);
1493 	Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[PARTY_AND_MONSTERS], false);
1494 
1495 	combat._whosSpeed = -1;
1496 	combat._whosTurn = -1;
1497 	resetHighlight();
1498 
1499 	nextChar();
1500 
1501 	if (!party._dead) {
1502 		combat.setSpeedTable();
1503 
1504 		if (_tillMove) {
1505 			combat.moveMonsters();
1506 			draw3d(true);
1507 		}
1508 
1509 		Window &w = windows[2];
1510 		w.open();
1511 		bool breakFlag = false;
1512 
1513 		while (!_vm->shouldExit() && !breakFlag && !party._dead && _vm->_mode == MODE_COMBAT) {
1514 			// FIXME: I've had a rare issue where the loop starts with a non-party _whosTurn. Unfortunately,
1515 			// I haven't been able to consistently replicate and diagnose the problem, so for now,
1516 			// I'm simply detecting if it happens and resetting the combat round
1517 			if (combat._whosTurn >= (int)party._activeParty.size())
1518 				goto new_round;
1519 
1520 			highlightChar(combat._whosTurn);
1521 			combat.setSpeedTable();
1522 
1523 			// Write out the description of the monsters being battled
1524 			w.writeString(combat.getMonsterDescriptions());
1525 			_combatIcons.draw(0, 32, Common::Point(233, combat._attackDurationCtr * 10 + 27),
1526 				SPRFLAG_800, 0);
1527 			w.update();
1528 
1529 			// Wait for keypress
1530 			index = 0;
1531 			do {
1532 				events.updateGameCounter();
1533 				draw3d(true);
1534 
1535 				if (++index == 5 && combat._attackMonsters[0] != -1) {
1536 					MazeMonster &monster = map._mobData._monsters[combat._monster2Attack];
1537 					MonsterStruct &monsterData = *monster._monsterData;
1538 					sound.playFX(monsterData._fx);
1539 				}
1540 
1541 				do {
1542 					events.pollEventsAndWait();
1543 					checkEvents(_vm);
1544 				} while (!_vm->shouldExit() && events.timeElapsed() < 1 && !_buttonValue);
1545 			} while (!_vm->shouldExit() && !_buttonValue);
1546 			if (_vm->shouldExit())
1547 				goto exit;
1548 
1549 			switch (_buttonValue) {
1550 			case Common::KEYCODE_TAB:
1551 				// Show the control panel
1552 				if (ControlPanel::show(_vm) == 2) {
1553 					reloadMap = true;
1554 					breakFlag = true;
1555 				} else {
1556 					highlightChar(combat._whosTurn);
1557 				}
1558 				break;
1559 
1560 			case Common::KEYCODE_1:
1561 			case Common::KEYCODE_2:
1562 			case Common::KEYCODE_3:
1563 				_buttonValue -= Common::KEYCODE_1;
1564 				if (combat._attackMonsters[_buttonValue] != -1) {
1565 					combat._monster2Attack = combat._attackMonsters[_buttonValue];
1566 					combat._attackDurationCtr = _buttonValue;
1567 				}
1568 				break;
1569 
1570 			case Common::KEYCODE_a:
1571 				// Attack
1572 				combat.attack(*combat._combatParty[combat._whosTurn], RT_SINGLE);
1573 				nextChar();
1574 				break;
1575 
1576 			case Common::KEYCODE_b:
1577 				// Block
1578 				combat.block();
1579 				nextChar();
1580 				break;
1581 
1582 			case Common::KEYCODE_c: {
1583 				// Cast spell
1584 				if (CastSpell::show(_vm) != -1) {
1585 					nextChar();
1586 				} else {
1587 					highlightChar(combat._whosTurn);
1588 				}
1589 				break;
1590 			}
1591 
1592 			case Common::KEYCODE_f:
1593 				// Quick Fight
1594 				combat.quickFight();
1595 				nextChar();
1596 				break;
1597 
1598 			case Common::KEYCODE_i:
1599 				// Info dialog
1600 				InfoDialog::show(_vm);
1601 				highlightChar(combat._whosTurn);
1602 				break;
1603 
1604 			case Common::KEYCODE_o:
1605 				// Quick Fight Options
1606 				QuickFight::show(_vm, combat._combatParty[combat._whosTurn]);
1607 				highlightChar(combat._whosTurn);
1608 				break;
1609 
1610 			case Common::KEYCODE_q:
1611 				// Quick Reference dialog
1612 				QuickReferenceDialog::show(_vm);
1613 				highlightChar(combat._whosTurn);
1614 				break;
1615 
1616 			case Common::KEYCODE_r:
1617 				// Run from combat
1618 				combat.run();
1619 				nextChar();
1620 
1621 				if (_vm->_mode == MODE_INTERACTIVE) {
1622 					party._treasure._gems = 0;
1623 					party._treasure._gold = 0;
1624 					party._treasure._hasItems = false;
1625 					party.moveToRunLocation();
1626 					breakFlag = true;
1627 				}
1628 				break;
1629 
1630 			case Common::KEYCODE_u: {
1631 				int whosTurn = combat._whosTurn;
1632 				ItemsDialog::show(_vm, combat._combatParty[combat._whosTurn], ITEMMODE_COMBAT);
1633 				if (combat._whosTurn == whosTurn) {
1634 					highlightChar(combat._whosTurn);
1635 				} else {
1636 					combat._whosTurn = whosTurn;
1637 					nextChar();
1638 				}
1639 				break;
1640 			}
1641 
1642 			case Common::KEYCODE_F1:
1643 			case Common::KEYCODE_F2:
1644 			case Common::KEYCODE_F3:
1645 			case Common::KEYCODE_F4:
1646 			case Common::KEYCODE_F5:
1647 			case Common::KEYCODE_F6:
1648 				// Show character info
1649 				_buttonValue -= Common::KEYCODE_F1;
1650 				if (_buttonValue < (int)combat._combatParty.size()) {
1651 					CharacterInfo::show(_vm, _buttonValue);
1652 				}
1653 				highlightChar(combat._whosTurn);
1654 				break;
1655 
1656 			case Common::KEYCODE_LEFT:
1657 			case Common::KEYCODE_RIGHT:
1658 				// Rotate party direction left or right
1659 				if (_buttonValue == Common::KEYCODE_LEFT) {
1660 					party._mazeDirection = (party._mazeDirection == DIR_NORTH) ?
1661 						DIR_WEST : (Direction)((int)party._mazeDirection - 1);
1662 				} else {
1663 					party._mazeDirection = (party._mazeDirection == DIR_WEST) ?
1664 					DIR_NORTH : (Direction)((int)party._mazeDirection + 1);
1665 				}
1666 
1667 				_flipSky ^= 1;
1668 				if (_tillMove)
1669 					combat.moveMonsters();
1670 				party._stepped = true;
1671 				break;
1672 			}
1673 
1674 			// Handling for if the combat turn is complete
1675 			if (combat.allHaveGone()) {
1676 new_round:
1677 				Common::fill(&combat._charsGone[0], &combat._charsGone[PARTY_AND_MONSTERS], false);
1678 				combat.clearBlocked();
1679 				combat.setSpeedTable();
1680 				combat._whosTurn = -1;
1681 				combat._whosSpeed = -1;
1682 				nextChar();
1683 
1684 				for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
1685 					MazeMonster &monster = map._mobData._monsters[idx];
1686 					if (monster._spriteId == 53) {
1687 						// For Medusa sprites, their HP keeps getting reset
1688 						MonsterStruct &monsData = map._monsterData[53];
1689 						monster._hp = monsData._hp;
1690 					}
1691 				}
1692 
1693 				combat.moveMonsters();
1694 				setIndoorsMonsters();
1695 				party.changeTime(1);
1696 			}
1697 
1698 			if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
1699 					&& combat._attackMonsters[2] == -1) {
1700 				party.changeTime(1);
1701 				draw3d(true);
1702 
1703 				if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
1704 						&& combat._attackMonsters[2] == -1)
1705 					break;
1706 			}
1707 
1708 			party.checkPartyDead();
1709 		}
1710 
1711 		_vm->_mode = MODE_INTERACTIVE;
1712 		if (combat._partyRan && (combat._attackMonsters[0] != -1 ||
1713 				combat._attackMonsters[1] != -1 || combat._attackMonsters[2] != -1)) {
1714 			party.checkPartyDead();
1715 			if (!party._dead) {
1716 				party.moveToRunLocation();
1717 
1718 				for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
1719 					Character &c = *combat._combatParty[idx];
1720 					if (c.isDisabled())
1721 						c._conditions[DEAD] = 1;
1722 				}
1723 			}
1724 		}
1725 exit:
1726 		w.close();
1727 		events.clearEvents();
1728 
1729 		_vm->_mode = MODE_COMBAT;
1730 		draw3d(true);
1731 		party.giveTreasure();
1732 		_vm->_mode = MODE_INTERACTIVE;
1733 		party._stepped = true;
1734 		unhighlightChar();
1735 
1736 		combat.setupCombatParty();
1737 		drawParty(true);
1738 	}
1739 
1740 	// Restore old icons
1741 	setMainButtons(oldMode);
1742 	mainIconsPrint();
1743 	combat._monster2Attack = -1;
1744 
1745 	if (!g_vm->isLoadPending()) {
1746 		if (upDoorText) {
1747 			map.cellFlagLookup(party._mazePosition);
1748 			if (map._currentIsEvent)
1749 				scripts.checkEvents();
1750 		}
1751 
1752 		if (reloadMap) {
1753 			sound.playFX(51);
1754 			map._loadCcNum = _vm->getGameID() != GType_WorldOfXeen ? 1 : 0;
1755 			map.load(_vm->getGameID() == GType_WorldOfXeen ? 28 : 29);
1756 			party._mazeDirection = _vm->getGameID() == GType_WorldOfXeen ?
1757 				DIR_EAST : DIR_SOUTH;
1758 		}
1759 	}
1760 
1761 	combat._combatMode = COMBATMODE_INTERACTIVE;
1762 }
1763 
nextChar()1764 void Interface::nextChar() {
1765 	Combat &combat = *_vm->_combat;
1766 	Party &party = *_vm->_party;
1767 
1768 	if (combat.allHaveGone())
1769 		return;
1770 	if ((combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1 &&
1771 		combat._attackMonsters[2] == -1) || combat._combatParty.size() == 0) {
1772 		_vm->_mode = MODE_INTERACTIVE;
1773 		return;
1774 	}
1775 
1776 	// Loop for potentially multiple monsters attacking until it's time
1777 	// for one of the party's turn
1778 	for (;;) {
1779 		// Check if party is dead
1780 		party.checkPartyDead();
1781 		if (party._dead) {
1782 			_vm->_mode = MODE_INTERACTIVE;
1783 			break;
1784 		}
1785 
1786 		int idx;
1787 		for (idx = 0; idx < (int)combat._speedTable.size(); ++idx) {
1788 			if (combat._whosTurn != -1) {
1789 				combat._charsGone[combat._whosTurn] = true;
1790 			}
1791 
1792 			combat._whosSpeed = (combat._whosSpeed + 1) % combat._speedTable.size();
1793 			combat._whosTurn = combat._speedTable[combat._whosSpeed];
1794 			if (combat.allHaveGone()) {
1795 				idx = -1;
1796 				break;
1797 			}
1798 
1799 			if (combat._whosTurn < (int)combat._combatParty.size()) {
1800 				// If it's a party member, only allow them to become active if
1801 				// they're still conscious
1802 				if (combat._combatParty[combat._whosTurn]->isDisabledOrDead())
1803 					continue;
1804 			}
1805 
1806 			break;
1807 		}
1808 
1809 		if (idx == -1) {
1810 			if (!combat.charsCantAct())
1811 				return;
1812 
1813 			combat.setSpeedTable();
1814 			combat._whosTurn = -1;
1815 			combat._whosSpeed = -1;
1816 			Common::fill(&combat._charsGone[0], &combat._charsGone[PARTY_AND_MONSTERS], false);
1817 			continue;
1818 		}
1819 
1820 		if (combat._whosTurn < (int)combat._combatParty.size()) {
1821 			// It's a party character's turn now, so highlight the character
1822 			if (!combat.allHaveGone()) {
1823 				highlightChar(combat._whosTurn);
1824 			}
1825 			break;
1826 		} else {
1827 			// It's a monster's turn to attack
1828 			combat.doMonsterTurn(0);
1829 			if (!party._dead) {
1830 				party.checkPartyDead();
1831 				if (party._dead)
1832 					break;
1833 			}
1834 		}
1835 	}
1836 }
1837 
spellFX(Character * c)1838 void Interface::spellFX(Character *c) {
1839 	Combat &combat = *_vm->_combat;
1840 	EventsManager &events = *_vm->_events;
1841 	Party &party = *_vm->_party;
1842 	Sound &sound = *_vm->_sound;
1843 	Windows &windows = *_vm->_windows;
1844 
1845 	// Ensure there's no alraedy running effect for the given character
1846 	uint charIndex;
1847 	for (charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
1848 		if (&party._activeParty[charIndex] == c)
1849 			break;
1850 	}
1851 	if (charIndex == party._activeParty.size() || _charFX[charIndex])
1852 		return;
1853 
1854 	if (windows[12]._enabled)
1855 		windows[12].close();
1856 
1857 	if (combat._combatMode == COMBATMODE_2) {
1858 		for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
1859 			if (combat._combatParty[idx]->_rosterId == c->_rosterId) {
1860 				charIndex = idx;
1861 				break;
1862 			}
1863 		}
1864 	}
1865 
1866 	int tillMove = _tillMove;
1867 	_tillMove = 0;
1868 	sound.playFX(20);
1869 
1870 	for (int frameNum = 0; frameNum < 4; ++frameNum) {
1871 		events.updateGameCounter();
1872 		_spellFxSprites.draw(0, frameNum, Common::Point(
1873 			Res.CHAR_FACES_X[charIndex], 150));
1874 
1875 		if (!windows[SCENE_WINDOW]._enabled)
1876 			draw3d(false);
1877 
1878 		windows[0].update();
1879 		events.wait(windows[SCENE_WINDOW]._enabled ? 2 : 1,false);
1880 	}
1881 
1882 	drawParty(true);
1883 	_tillMove = tillMove;
1884 	++_charFX[charIndex];
1885 }
1886 
obscureScene(Obscurity obscurity)1887 void Interface::obscureScene(Obscurity obscurity) {
1888 	Screen &screen = *g_vm->_screen;
1889 	const byte *lookup;
1890 
1891 	switch (obscurity) {
1892 	case OBSCURITY_BLACK:
1893 		// Totally dark (black) background
1894 		screen.fillRect(Common::Rect(8, 8, 224, 140), 0);
1895 		break;
1896 
1897 	case OBSCURITY_1:
1898 	case OBSCURITY_2:
1899 	case OBSCURITY_3:
1900 		lookup = &Res.DARKNESS_XLAT[obscurity - 1][0];
1901 		for (int yp = 8; yp < 140; ++yp) {
1902 			byte *destP = (byte *)screen.getBasePtr(8, yp);
1903 			for (int xp = 8; xp < 224; ++xp, ++destP)
1904 				*destP = lookup[*destP];
1905 		}
1906 		break;
1907 
1908 	default:
1909 		// Full daylight, so no obscurity
1910 		break;
1911 	}
1912 }
1913 
1914 } // End of namespace Xeen
1915