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