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