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