1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/scummsys.h"
24 #include "common/algorithm.h"
25 #include "xeen/party.h"
26 #include "xeen/dialogs/dialogs_message.h"
27 #include "xeen/dialogs/dialogs_input.h"
28 #include "xeen/files.h"
29 #include "xeen/resources.h"
30 #include "xeen/saves.h"
31 #include "xeen/spells.h"
32 #include "xeen/xeen.h"
33
34 namespace Xeen {
35
36 /*------------------------------------------------------------------------*/
37
Roster()38 Roster::Roster() {
39 resize(TOTAL_CHARACTERS);
40
41 for (int idx = 0; idx < TOTAL_CHARACTERS; ++idx) {
42 // Set the index of the character in the roster list
43 operator[](idx)._rosterId = idx;
44
45 if (idx < XEEN_TOTAL_CHARACTERS) {
46 // Load new character resource
47 Common::String name = Common::String::format("char%02d.fac", idx + 1);
48 _charFaces[idx].load(name);
49 operator[](idx)._faceSprites = &_charFaces[idx];
50 } else {
51 operator[](idx)._faceSprites = nullptr;
52 }
53 }
54 }
55
synchronize(Common::Serializer & s)56 void Roster::synchronize(Common::Serializer &s) {
57 Party &party = *g_vm->_party;
58
59 if (s.isSaving()) {
60 // Copy out the party's characters back to the roster
61 for (uint idx = 0; idx < party._activeParty.size(); ++idx)
62 (*this)[party._activeParty[idx]._rosterId] = party._activeParty[idx];
63 }
64
65 for (uint i = 0; i < TOTAL_CHARACTERS; ++i)
66 (*this)[i].synchronize(s);
67 }
68
69 /*------------------------------------------------------------------------*/
70
Treasure()71 Treasure::Treasure() {
72 _hasItems = false;
73 _gold = _gems = 0;
74
75 _categories[0] = &_weapons[0];
76 _categories[1] = &_armor[0];
77 _categories[2] = &_accessories[0];
78 _categories[3] = &_misc[0];
79 }
80
clear()81 void Treasure::clear() {
82 for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
83 _weapons[idx].clear();
84 _armor[idx].clear();
85 _accessories[idx].clear();
86 _misc[idx].clear();
87 }
88 }
89
reset()90 void Treasure::reset() {
91 clear();
92 _hasItems = false;
93 _gold = _gems = 0;
94 }
95
96 /*------------------------------------------------------------------------*/
97
98 const int BLACKSMITH_DATA1[4][4] = {
99 { 15, 5, 5, 5 },{ 5, 10, 5, 5 },{ 0, 5, 10, 5 },{ 0, 0, 0, 5 }
100 };
101 const int BLACKSMITH_DATA2[4][4] = {
102 { 10, 5, 0, 5 },{ 10, 5, 5, 5 },{ 0, 5, 5, 10 },{ 0, 5, 10, 0 }
103 };
104
105
clear()106 void BlacksmithWares::clear() {
107 for (ItemCategory cat = CATEGORY_WEAPON; cat <= CATEGORY_MISC; cat = (ItemCategory)((int)cat + 1))
108 for (int ccNum = 0; ccNum < 2; ++ccNum)
109 for (int slot = 0; slot < 4; ++slot)
110 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx)
111 (*this)[cat][ccNum][slot][idx].clear();
112 }
113
regenerate()114 void BlacksmithWares::regenerate() {
115 Character tempChar;
116 int catCount[4];
117
118 // Clear existing blacksmith wares
119 clear();
120
121 // Wares setup for Clouds of Xeen
122 for (int slotNum = 0; slotNum < 4; ++slotNum) {
123 Common::fill(&catCount[0], &catCount[4], 0);
124
125 for (int idx2 = 0; idx2 < 4; ++idx2) {
126 for (int idx3 = 0; idx3 < BLACKSMITH_DATA1[idx2][slotNum]; ++idx3) {
127 ItemCategory itemCat = tempChar.makeItem(idx2 + 1, 0, 0);
128 if (catCount[itemCat] < 8) {
129 XeenItem &item = (*this)[itemCat][0][slotNum][catCount[itemCat]];
130 item = tempChar._items[itemCat][0];
131
132 ++catCount[itemCat];
133 }
134 }
135 }
136 }
137
138 // Wares setup for Dark Side/Swords of Xeen
139 for (int slotNum = 0; slotNum < 4; ++slotNum) {
140 Common::fill(&catCount[0], &catCount[4], 0);
141
142 for (int idx2 = 0; idx2 < 4; ++idx2) {
143 for (int idx3 = 0; idx3 < BLACKSMITH_DATA2[idx2][slotNum]; ++idx3) {
144 ItemCategory itemCat = tempChar.makeItem(idx2 + (slotNum >= 2 ? 3 : 1), 0, 0);
145 if (catCount[itemCat] < 8) {
146 XeenItem &item = (*this)[itemCat][1][slotNum][catCount[itemCat]];
147 item = tempChar._items[itemCat][0];
148
149 ++catCount[itemCat];
150 }
151 }
152 }
153 }
154 }
155
blackData2CharData(Character & c)156 void BlacksmithWares::blackData2CharData(Character &c) {
157 int ccNum = g_vm->_files->_ccNum;
158 int slotIndex = getSlotIndex();
159
160 for (ItemCategory cat = CATEGORY_WEAPON; cat <= CATEGORY_MISC; cat = (ItemCategory)((int)cat + 1))
161 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx)
162 c._items[cat][idx] = (*this)[cat][ccNum][slotIndex][idx];
163 }
164
charData2BlackData(Character & c)165 void BlacksmithWares::charData2BlackData(Character &c) {
166 int ccNum = g_vm->_files->_ccNum;
167 int slotIndex = getSlotIndex();
168
169 for (ItemCategory cat = CATEGORY_WEAPON; cat <= CATEGORY_MISC; cat = (ItemCategory)((int)cat + 1))
170 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx)
171 (*this)[cat][ccNum][slotIndex][idx] = c._items[cat][idx];
172 }
173
operator [](ItemCategory category)174 BlacksmithItems &BlacksmithWares::operator[](ItemCategory category) {
175 switch (category) {
176 case CATEGORY_WEAPON: return _weapons;
177 case CATEGORY_ARMOR: return _armor;
178 case CATEGORY_ACCESSORY: return _accessories;
179 default: return _misc;
180 }
181 }
182
getSlotIndex() const183 uint BlacksmithWares::getSlotIndex() const {
184 Party &party = *g_vm->_party;
185 int ccNum = g_vm->_files->_ccNum;
186
187 int slotIndex = 0;
188 while (slotIndex < 4 && party._mazeId != (int)Res.BLACKSMITH_MAP_IDS[ccNum][slotIndex])
189 ++slotIndex;
190 if (slotIndex == 4)
191 slotIndex = 0;
192
193 return slotIndex;
194 }
195
synchronize(Common::Serializer & s,int ccNum)196 void BlacksmithWares::synchronize(Common::Serializer &s, int ccNum) {
197 for (ItemCategory cat = CATEGORY_WEAPON; cat <= CATEGORY_MISC; cat = (ItemCategory)((int)cat + 1))
198 for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx)
199 for (int slot = 0; slot < 4; ++slot)
200 (*this)[cat][ccNum][slot][idx].synchronize(s);
201 }
202
203 /*------------------------------------------------------------------------*/
204
205 XeenEngine *Party::_vm;
206
Party(XeenEngine * vm)207 Party::Party(XeenEngine *vm) {
208 _vm = vm;
209 _mazeDirection = DIR_NORTH;
210 _mazeId = _priorMazeId = 0;
211 _levitateCount = 0;
212 _automapOn = false;
213 _wizardEyeActive = false;
214 _clairvoyanceActive = false;
215 _walkOnWaterActive = false;
216 _blessed = 0;
217 _powerShield = 0;
218 _holyBonus = 0;
219 _heroism = 0;
220 _difficulty = ADVENTURER;
221 _cloudsCompleted = false;
222 _darkSideCompleted = false;
223 _worldCompleted = false;
224 _ctr24 = 0;
225 _day = 0;
226 _year = 0;
227 _minutes = 0;
228 _food = 0;
229 _lightCount = 0;
230 _torchCount = 0;
231 _fireResistence = 0;
232 _electricityResistence = 0;
233 _coldResistence = 0;
234 _poisonResistence = 0;
235 _deathCount = 0;
236 _winCount = 0;
237 _lossCount = 0;
238 _gold = 0;
239 _gems = 0;
240 _bankGold = 0;
241 _bankGems = 0;
242 _totalTime = 0;
243 _rested = false;
244
245 Common::fill(&_gameFlags[0][0], &_gameFlags[0][256], false);
246 Common::fill(&_gameFlags[1][0], &_gameFlags[1][256], false);
247 Common::fill(&_worldFlags[0], &_worldFlags[128], false);
248 Common::fill(&_questFlags[0], &_questFlags[60], false);
249 Common::fill(&_questItems[0], &_questItems[85], 0);
250
251 for (int i = 0; i < TOTAL_CHARACTERS; ++i)
252 Common::fill(&_characterFlags[i][0], &_characterFlags[i][24], false);
253
254 _newDay = false;
255 _isNight = false;
256 _stepped = false;
257 _fallMaze = 0;
258 _fallDamage = 0;
259 _dead = false;
260
261 Character::_itemType = 0;
262 }
263
synchronize(Common::Serializer & s)264 void Party::synchronize(Common::Serializer &s) {
265 byte dummy[30];
266 Common::fill(&dummy[0], &dummy[30], 0);
267 int partyCount = _activeParty.size();
268
269 int8 partyMembers[MAX_PARTY_COUNT];
270 if (s.isSaving()) {
271 Common::fill(&partyMembers[0], &partyMembers[8], -1);
272 for (uint idx = 0; idx < _activeParty.size(); ++idx)
273 partyMembers[idx] = _activeParty[idx]._rosterId;
274 } else {
275 _activeParty.clear();
276 }
277
278 s.syncAsByte(partyCount); // Party count
279 s.syncAsByte(partyCount); // Real party count
280 for (int idx = 0; idx < MAX_PARTY_COUNT; ++idx) {
281 s.syncAsByte(partyMembers[idx]);
282 if (s.isLoading() && idx < partyCount && partyMembers[idx] != -1)
283 _activeParty.push_back(_roster[partyMembers[idx]]);
284 }
285
286 s.syncAsByte(_mazeDirection);
287 s.syncAsByte(_mazePosition.x);
288 s.syncAsByte(_mazePosition.y);
289 s.syncAsByte(_mazeId);
290
291 // Game configuration flags not used in this implementation
292 s.syncBytes(dummy, 3);
293
294 s.syncAsByte(_priorMazeId);
295 s.syncAsByte(_levitateCount);
296 s.syncAsByte(_automapOn);
297 s.syncAsByte(_wizardEyeActive);
298 s.syncAsByte(_clairvoyanceActive);
299 s.syncAsByte(_walkOnWaterActive);
300 s.syncAsByte(_blessed);
301 s.syncAsByte(_powerShield);
302 s.syncAsByte(_holyBonus);
303 s.syncAsByte(_heroism);
304 s.syncAsByte(_difficulty);
305
306 _blacksmithWares.synchronize(s, 0);
307
308 s.syncAsUint16LE(_cloudsCompleted);
309 s.syncAsUint16LE(_darkSideCompleted);
310 s.syncAsUint16LE(_worldCompleted);
311 s.syncAsUint16LE(_ctr24);
312 s.syncAsUint16LE(_day);
313 s.syncAsUint16LE(_year);
314 s.syncAsUint16LE(_minutes);
315 s.syncAsUint16LE(_food);
316 s.syncAsUint16LE(_lightCount);
317 s.syncAsUint16LE(_torchCount);
318 s.syncAsUint16LE(_fireResistence);
319 s.syncAsUint16LE(_electricityResistence);
320 s.syncAsUint16LE(_coldResistence);
321 s.syncAsUint16LE(_poisonResistence);
322 s.syncAsUint16LE(_deathCount);
323 s.syncAsUint16LE(_winCount);
324 s.syncAsUint16LE(_lossCount);
325 s.syncAsUint32LE(_gold);
326 s.syncAsUint32LE(_gems);
327 s.syncAsUint32LE(_bankGold);
328 s.syncAsUint32LE(_bankGems);
329 s.syncAsUint32LE(_totalTime);
330 s.syncAsByte(_rested);
331 File::syncBitFlags(s, &_gameFlags[0][0], &_gameFlags[0][256]);
332 File::syncBitFlags(s, &_gameFlags[1][0], &_gameFlags[1][256]);
333 File::syncBitFlags(s, &_worldFlags[0], &_worldFlags[128]);
334 File::syncBitFlags(s, &_questFlags[0], &_questFlags[60]);
335
336 for (int i = 0; i < 85; ++i)
337 s.syncAsByte(_questItems[i]);
338
339 _blacksmithWares.synchronize(s, 1);
340
341 for (int i = 0; i < TOTAL_CHARACTERS; ++i)
342 File::syncBitFlags(s, &_characterFlags[i][0], &_characterFlags[i][24]);
343 s.syncBytes(&dummy[0], 30);
344
345 if (s.isLoading())
346 _newDay = _minutes < 300;
347 _dead = false;
348 }
349
loadActiveParty()350 void Party::loadActiveParty() {
351 // No implementation needed
352 }
353
checkSkill(Skill skillId)354 bool Party::checkSkill(Skill skillId) {
355 uint total = 0;
356 for (uint i = 0; i < _activeParty.size(); ++i) {
357 if (_activeParty[i]._skills[skillId]) {
358 ++total;
359
360 switch (skillId) {
361 case MOUNTAINEER:
362 case PATHFINDER:
363 // At least two characters need skill for check to return true
364 if (total == 2)
365 return true;
366 break;
367 case CRUSADER:
368 case SWIMMING:
369 // Entire party must have skill for check to return true
370 if (total == _activeParty.size())
371 return true;
372 break;
373 default:
374 // All other skills only need to have a single player having it
375 return true;
376 }
377 }
378 }
379
380 return false;
381 }
382
isInParty(int charId)383 bool Party::isInParty(int charId) {
384 for (uint i = 0; i < _activeParty.size(); ++i) {
385 if (_activeParty[i]._rosterId == charId)
386 return true;
387 }
388
389 return false;
390 }
391
copyPartyToRoster()392 void Party::copyPartyToRoster() {
393 for (uint i = 0; i < _activeParty.size(); ++i) {
394 _roster[_activeParty[i]._rosterId] = _activeParty[i];
395 }
396 }
397
changeTime(int numMinutes)398 void Party::changeTime(int numMinutes) {
399 bool killed = false;
400
401 if (((_minutes + numMinutes) / 480) != (_minutes / 480)) {
402 for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
403 Character &player = _activeParty[idx];
404
405 if (!player._conditions[DEAD] && !player._conditions[STONED] &&
406 !player._conditions[ERADICATED]) {
407 for (int statNum = 0; statNum < TOTAL_STATS; ++statNum) {
408 int statVal = player.getStat((Attribute)statNum);
409 if (statVal < 1) {
410 player._conditions[DEAD] = 1;
411 killed = true;
412 }
413 }
414 }
415
416 // Handle heart broken condition becoming depression
417 if (player._conditions[HEART_BROKEN]) {
418 if (++player._conditions[HEART_BROKEN] > 10) {
419 player._conditions[HEART_BROKEN] = 0;
420 player._conditions[DEPRESSED] = 1;
421 }
422 }
423
424 // Handle poisoning
425 if (!player._conditions[POISONED]) {
426 if (_vm->getRandomNumber(1, 10) != 1 || !player.charSavingThrow(DT_ELECTRICAL))
427 player._conditions[POISONED] *= 2;
428 else
429 // Poison wears off
430 player._conditions[POISONED] = 0;
431 }
432
433 // Handle disease
434 if (!player._conditions[DISEASED]) {
435 if (_vm->getRandomNumber(9) != 1 || !player.charSavingThrow(DT_COLD))
436 player._conditions[DISEASED] *= 2;
437 else
438 // Disease wears off
439 player._conditions[DISEASED] = 0;
440 }
441
442 // Handle insane status
443 if (player._conditions[INSANE])
444 player._conditions[INSANE]++;
445
446 if (player._conditions[DEAD]) {
447 if (++player._conditions[DEAD] == 0)
448 player._conditions[DEAD] = -1;
449 }
450
451 if (player._conditions[STONED]) {
452 if (++player._conditions[STONED] == 0)
453 player._conditions[STONED] = -1;
454 }
455
456 if (player._conditions[ERADICATED]) {
457 if (++player._conditions[ERADICATED] == 0)
458 player._conditions[ERADICATED] = -1;
459 }
460
461 if (player._conditions[IN_LOVE]) {
462 if (++player._conditions[IN_LOVE] > 10) {
463 player._conditions[IN_LOVE] = 0;
464 player._conditions[HEART_BROKEN] = 1;
465 }
466 }
467
468 // WORKAROUND: Original incorrectly reset weakness (due to lack of sleep) even when party
469 // wasn't drunk. We now have any resetting drunkness add to, rather than replace, weakness
470 if (player._conditions[WEAK] != -1) {
471 player._conditions[WEAK] += player._conditions[DRUNK];
472 player._conditions[DRUNK] = 0;
473 }
474
475 if (player._conditions[DEPRESSED]) {
476 player._conditions[DEPRESSED] = (player._conditions[DEPRESSED] + 1) % 4;
477 }
478 }
479 }
480
481 // Increment the time
482 addTime(numMinutes);
483
484 for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
485 Character &player = _activeParty[idx];
486
487 if (player._conditions[CONFUSED] && _vm->getRandomNumber(2) == 1) {
488 if (player.charSavingThrow(DT_PHYSICAL)) {
489 player._conditions[CONFUSED] = 0;
490 } else {
491 player._conditions[CONFUSED]--;
492 }
493 }
494
495 if (player._conditions[PARALYZED] && _vm->getRandomNumber(4) == 1)
496 player._conditions[PARALYZED]--;
497 }
498
499 if (killed)
500 _vm->_interface->drawParty(true);
501
502 if (_isNight != (_minutes < (5 * 60) || _minutes >= (21 * 60)))
503 _vm->_map->loadSky();
504 }
505
addTime(int numMinutes)506 void Party::addTime(int numMinutes) {
507 int day = _day;
508 _minutes += numMinutes;
509
510 // If the total minutes has exceeded a day, move to next one
511 while (_minutes >= (24 * 60)) {
512 _minutes -= 24 * 60;
513 if (++_day >= 100) {
514 _day -= 100;
515 ++_year;
516 }
517 }
518
519 if ((_day % 10) == 1 || numMinutes > (24 * 60)) {
520 if (_day != day) {
521 resetBlacksmithWares();
522 giveBankInterest();
523 }
524 }
525
526 if (_day != day)
527 _newDay = true;
528
529 if (_newDay && _minutes >= 300) {
530 if (_vm->_mode != MODE_SCRIPT_IN_PROGRESS && _vm->_mode != MODE_INTERACTIVE7) {
531 resetTemps();
532 if (_rested || _vm->_mode == MODE_SLEEPING) {
533 _rested = false;
534 } else {
535 for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
536 if (_activeParty[idx]._conditions[WEAK] >= 0)
537 _activeParty[idx]._conditions[WEAK]++;
538 }
539
540 ErrorScroll::show(_vm, Res.THE_PARTY_NEEDS_REST, WT_NONFREEZED_WAIT);
541 }
542
543 _vm->_interface->drawParty(true);
544 }
545
546 _newDay = false;
547 }
548 }
549
resetTemps()550 void Party::resetTemps() {
551 for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
552 Character &player = _activeParty[idx];
553
554 player._magicResistence._temporary = 0;
555 player._energyResistence._temporary = 0;
556 player._poisonResistence._temporary = 0;
557 player._electricityResistence._temporary = 0;
558 player._coldResistence._temporary = 0;
559 player._fireResistence._temporary = 0;
560 player._ACTemp = 0;
561 player._level._temporary = 0;
562 player._luck._temporary = 0;
563 player._accuracy._temporary = 0;
564 player._speed._temporary = 0;
565 player._endurance._temporary = 0;
566 player._personality._temporary = 0;
567 player._intellect._temporary = 0;
568 player._might._temporary = 0;
569 }
570
571 _poisonResistence = 0;
572 _coldResistence = 0;
573 _electricityResistence = 0;
574 _fireResistence = 0;
575 _lightCount = 0;
576 _levitateCount = 0;
577 _walkOnWaterActive = false;
578 _wizardEyeActive = false;
579 _clairvoyanceActive = false;
580 _heroism = 0;
581 _holyBonus = 0;
582 _powerShield = 0;
583 _blessed = 0;
584 }
585
handleLight()586 void Party::handleLight() {
587 Interface &intf = *_vm->_interface;
588 Map &map = *_vm->_map;
589
590 if (_stepped) {
591 map.cellFlagLookup(_mazePosition);
592 if (map._currentIsDrain && _lightCount)
593 --_lightCount;
594
595 if (checkSkill(CARTOGRAPHER)) {
596 map.mazeDataCurrent()._steppedOnTiles[_mazePosition.y & 15][_mazePosition.x & 15] = true;
597 }
598 }
599
600 // Set whether the scene is completely dark or not
601 intf._obscurity = _lightCount ||
602 (map.mazeData()._mazeFlags2 & FLAG_IS_DARK) == 0 ? OBSCURITY_NONE : OBSCURITY_BLACK;
603 }
604
subtract(ConsumableType consumableId,uint amount,PartyBank whereId,MessageWaitType wait)605 int Party::subtract(ConsumableType consumableId, uint amount, PartyBank whereId, MessageWaitType wait) {
606 switch (consumableId) {
607 case CONS_GOLD:
608 // Gold
609 if (whereId) {
610 if (amount <= _bankGold) {
611 _bankGold -= amount;
612 } else {
613 notEnough(CONS_GOLD, whereId, false, wait);
614 return false;
615 }
616 } else {
617 if (amount <= _gold) {
618 _gold -= amount;
619 } else {
620 notEnough(CONS_GOLD, whereId, false, wait);
621 return false;
622 }
623 }
624 break;
625
626 case CONS_GEMS:
627 // Gems
628 if (whereId) {
629 if (amount <= _bankGems) {
630 _bankGems -= amount;
631 } else {
632 notEnough(CONS_GEMS, whereId, false, wait);
633 return false;
634 }
635 } else {
636 if (amount <= _gems) {
637 _gems -= amount;
638 } else {
639 notEnough(CONS_GEMS, whereId, false, wait);
640 return false;
641 }
642 }
643 break;
644
645 case CONS_FOOD:
646 // Food
647 if (amount > _food) {
648 _food -= amount;
649 } else {
650 notEnough(CONS_FOOD, WHERE_PARTY, 0, wait);
651 return false;
652 }
653 break;
654
655 default:
656 break;
657 }
658
659 return true;
660 }
661
notEnough(ConsumableType consumableId,PartyBank whereId,bool mode,MessageWaitType wait)662 void Party::notEnough(ConsumableType consumableId, PartyBank whereId, bool mode, MessageWaitType wait) {
663 Common::String msg = Common::String::format(
664 mode ? Res.NO_X_IN_THE_Y : Res.NOT_ENOUGH_X_IN_THE_Y,
665 Res.CONSUMABLE_NAMES[consumableId], Res.WHERE_NAMES[whereId]);
666 ErrorScroll::show(_vm, msg, wait);
667 }
668
checkPartyDead()669 void Party::checkPartyDead() {
670 Combat &combat = *_vm->_combat;
671 bool inCombat = _vm->_mode == MODE_COMBAT;
672
673 for (uint charIdx = 0; charIdx < (inCombat ? combat._combatParty.size() : _activeParty.size()); ++charIdx) {
674 Character &c = inCombat ? *combat._combatParty[charIdx] : _activeParty[charIdx];
675 Condition cond = c.worstCondition();
676 if (cond <= CONFUSED || cond == NO_CONDITION) {
677 _dead = false;
678 return;
679 }
680 }
681
682 _dead = true;
683 }
684
moveToRunLocation()685 void Party::moveToRunLocation() {
686 _mazePosition = _vm->_map->mazeData()._runPosition;
687 }
688
giveTreasure()689 void Party::giveTreasure() {
690 Combat &combat = *_vm->_combat;
691 EventsManager &events = *_vm->_events;
692 Interface &intf = *_vm->_interface;
693 Scripts &scripts = *_vm->_scripts;
694 Sound &sound = *_vm->_sound;
695 Windows &windows = *_vm->_windows;
696 Window &w = windows[10];
697
698 if (!_treasure._hasItems && !_treasure._gold && !_treasure._gems)
699 return;
700
701 bool monstersPresent = combat.areMonstersPresent();
702 if (_vm->_mode != MODE_SCRIPT_IN_PROGRESS && monstersPresent)
703 return;
704
705 combat.clearShooting();
706 intf._charsShooting = false;
707 intf.draw3d(true);
708
709 if (_treasure._gold || _treasure._gems)
710 sound.playFX(54);
711
712 events.clearEvents();
713 w.close();
714 w.open();
715 w.writeString(Common::String::format(Res.PARTY_FOUND, _treasure._gold, _treasure._gems));
716 w.update();
717
718 if (_vm->_mode != MODE_COMBAT)
719 _vm->_mode = MODE_7;
720
721 if (arePacksFull())
722 ErrorScroll::show(_vm, Res.BACKPACKS_FULL_PRESS_KEY, WT_NONFREEZED_WAIT);
723
724 for (int categoryNum = 0; categoryNum < NUM_ITEM_CATEGORIES; ++categoryNum) {
725 for (int itemNum = 0; itemNum < MAX_TREASURE_ITEMS; ++itemNum) {
726 if (arePacksFull()) {
727 if (_treasure._weapons[itemNum]._id >= XEEN_SLAYER_SWORD) {
728 // Xeen Slayer Sword, so clear a slot for it
729 _activeParty[0]._weapons[INV_ITEMS_TOTAL - 1].clear();
730 } else {
731 // Otherwise, clear all the remaining treasure items,
732 // since all the party's packs are full
733 _treasure.clear();
734 }
735 }
736
737 // If there's no treasure item to be distributed, skip to next slot
738 if (!_treasure[categoryNum][itemNum]._id)
739 continue;
740
741 int charIndex = scripts._whoWill - 1;
742 if (charIndex >= 0 && charIndex < (int)_activeParty.size()) {
743 // Check the designated character first
744 Character &c = _activeParty[charIndex];
745 if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
746 giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
747 continue;
748 }
749
750 // Fall back on checking the entire conscious party
751 for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
752 Character &ch = _activeParty[charIndex];
753 if (!ch._items[(ItemCategory)categoryNum].isFull() && !ch.isDisabledOrDead()) {
754 giveTreasureToCharacter(ch, (ItemCategory)categoryNum, itemNum);
755 break;
756 }
757 }
758 if (charIndex != (int)_activeParty.size())
759 continue;
760 }
761
762 // At this point, find an empty pack for any character, irrespective
763 // of whether the character is conscious or not
764 for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
765 Character &c = _activeParty[charIndex];
766 if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
767 giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
768 break;
769 }
770 }
771 }
772 }
773
774 w.writeString(Res.HIT_A_KEY);
775 w.update();
776 events.clearEvents();
777
778 do {
779 events.updateGameCounter();
780 intf.draw3d(true);
781
782 events.wait(1, false);
783 } while (!_vm->shouldExit() && !events.isKeyMousePressed());
784 events.clearEvents();
785
786 if (_vm->_mode != MODE_COMBAT)
787 _vm->_mode = MODE_INTERACTIVE;
788
789 w.close();
790 _gold += _treasure._gold;
791 _gems += _treasure._gems;
792 _treasure._gold = 0;
793 _treasure._gems = 0;
794
795 _treasure._hasItems = false;
796 _treasure.clear();
797 combat._combatTarget = 1;
798 }
799
arePacksFull() const800 bool Party::arePacksFull() const {
801 uint total = 0;
802 for (uint idx = 0; idx < _activeParty.size(); ++idx) {
803 const Character &c = _activeParty[idx];
804 total += (c._weapons[INV_ITEMS_TOTAL - 1].empty() ? 0 : 1)
805 + (c._armor[INV_ITEMS_TOTAL - 1].empty() ? 0 : 1)
806 + (c._accessories[INV_ITEMS_TOTAL - 1].empty() ? 0 : 1)
807 + (c._misc[INV_ITEMS_TOTAL - 1].empty() ? 0 : 1);
808 }
809
810 return total == (_activeParty.size() * NUM_ITEM_CATEGORIES);
811 }
812
giveTreasureToCharacter(Character & c,ItemCategory category,int itemIndex)813 void Party::giveTreasureToCharacter(Character &c, ItemCategory category, int itemIndex) {
814 EventsManager &events = *_vm->_events;
815 Sound &sound = *_vm->_sound;
816 Windows &windows = *_vm->_windows;
817 Window &w = windows[10];
818 XeenItem &treasureItem = _treasure._categories[category][itemIndex];
819 sound.playFX(20);
820
821 if (treasureItem._id < 82) {
822 // Copy item into the character's inventory
823 c._items[category][INV_ITEMS_TOTAL - 1] = treasureItem;
824 }
825
826 w.writeString(Res.GIVE_TREASURE_FORMATTING);
827 w.update();
828 events.ipause(5);
829
830 int index = (category == CATEGORY_MISC) ? treasureItem._material : treasureItem._id;
831 const char *itemName = XeenItem::getItemName(category, index);
832
833 if (index >= (_vm->getGameID() == GType_Swords ? 88 : 82)) {
834 // Quest item, give an extra '*' prefix
835 Common::String format = Common::String::format("\f04 * \fd%s", itemName);
836 w.writeString(Common::String::format(Res.X_FOUND_Y, c._name.c_str(), format.c_str()));
837 } else {
838 w.writeString(Common::String::format(Res.X_FOUND_Y, c._name.c_str(), itemName));
839 }
840
841 w.update();
842 c._items[category].sort();
843 events.ipause(8);
844 }
845
canShoot() const846 bool Party::canShoot() const {
847 for (uint idx = 0; idx < _activeParty.size(); ++idx) {
848 if (_activeParty[idx].hasMissileWeapon())
849 return true;
850 }
851
852 return false;
853 }
854
giveTake(int takeMode,uint takeVal,int giveMode,uint giveVal,int charIdx)855 bool Party::giveTake(int takeMode, uint takeVal, int giveMode, uint giveVal, int charIdx) {
856 Combat &combat = *_vm->_combat;
857 FileManager &files = *_vm->_files;
858 Interface &intf = *_vm->_interface;
859 Scripts &scripts = *_vm->_scripts;
860
861 if (charIdx > 7) {
862 charIdx = 7;
863 takeMode = 0;
864 }
865
866 Character &ps = _activeParty[charIdx];
867 if (takeMode && !takeVal && takeMode != 104) {
868 takeVal = howMuch();
869 if (!takeVal)
870 return true;
871
872 if (giveMode && !giveVal)
873 giveVal = takeVal;
874 } else if (takeMode == giveMode && takeVal == giveVal) {
875 if (giveVal)
876 takeVal = _vm->getRandomNumber(1, giveVal);
877 giveVal = 0;
878 giveMode = 0;
879 }
880
881 switch (takeMode) {
882 case 8:
883 combat.giveCharDamage(takeVal, scripts._nEdamageType, charIdx);
884 break;
885 case 9:
886 if (ps._hasSpells) {
887 ps._currentSp -= takeVal;
888 if (ps._currentSp < 1)
889 ps._currentSp = 0;
890 }
891 break;
892 case 10:
893 case 77:
894 ps._ACTemp -= takeVal;
895 break;
896 case 11:
897 ps._level._temporary -= takeVal;
898 break;
899 case 12:
900 ps._tempAge -= takeVal;
901 break;
902 case 13:
903 ps._skills[takeVal] = 0;
904 break;
905 case 15:
906 ps.setAward(takeVal, false);
907 break;
908 case 16:
909 ps._experience -= takeVal;
910 break;
911 case 17:
912 _poisonResistence -= takeVal;
913 break;
914 case 18:
915 ps._conditions[takeVal] = 0;
916 break;
917 case 19: {
918 SpellsCategory category = ps.getSpellsCategory();
919 assert(category != SPELLCAT_INVALID);
920
921 for (int idx = 0; idx < SPELLS_PER_CLASS; ++idx) {
922 if (Res.SPELLS_ALLOWED[category][idx] == (int)takeVal) {
923 ps._spells[idx] = false;
924 break;
925 }
926 }
927 break;
928 }
929 case 20:
930 assert(takeVal < 256);
931 _gameFlags[_vm->getGameID() == GType_Swords ? 0 : files._ccNum][takeVal] = false;
932 break;
933 case 21: {
934 const uint WEAPONS_END = _vm->getGameID() != GType_Swords ? 35 : 41;
935 const uint ARMOR_END = _vm->getGameID() != GType_Swords ? 49 : 55;
936 const uint ACCESSORIES_END = _vm->getGameID() != GType_Swords ? 60 : 66;
937 const uint MISC_END = _vm->getGameID() != GType_Swords ? 82 : 88;
938
939 if (takeVal >= MISC_END) {
940 _questItems[takeVal - MISC_END]--;
941 } else {
942 bool found = false;
943 for (int idx = 0; idx < 9; ++idx) {
944 if (takeVal < WEAPONS_END) {
945 if (ps._weapons[idx]._id == takeVal) {
946 ps._weapons[idx].clear();
947 ps._weapons.sort();
948 found = true;
949 break;
950 }
951 } else if (takeVal < ARMOR_END) {
952 if (ps._armor[idx]._id == (takeVal - WEAPONS_END)) {
953 ps._armor[idx].clear();
954 ps._armor.sort();
955 found = true;
956 break;
957 }
958 } else if (takeVal < ACCESSORIES_END) {
959 if (ps._accessories[idx]._id == (takeVal - ARMOR_END)) {
960 ps._accessories[idx].clear();
961 ps._accessories.sort();
962 found = true;
963 break;
964 }
965 } else {
966 if (ps._misc[idx]._material == (int)(takeVal - ACCESSORIES_END)) {
967 ps._misc[idx].clear();
968 ps._misc.sort();
969 found = true;
970 break;
971 }
972 }
973 }
974 if (!found)
975 return true;
976 }
977 break;
978 }
979 case 25:
980 changeTime(takeVal);
981 break;
982 case 34:
983 if (!subtract(CONS_GOLD, takeVal, WHERE_PARTY, WT_ANIMATED_WAIT))
984 return true;
985 break;
986 case 35:
987 if (!subtract(CONS_GEMS, takeVal, WHERE_PARTY, WT_ANIMATED_WAIT))
988 return true;
989 break;
990 case 37:
991 ps._might._temporary -= takeVal;
992 break;
993 case 38:
994 ps._intellect._temporary -= takeVal;
995 break;
996 case 39:
997 ps._personality._temporary -= takeVal;
998 break;
999 case 40:
1000 ps._endurance._temporary -= takeVal;
1001 break;
1002 case 41:
1003 ps._speed._temporary -= takeVal;
1004 break;
1005 case 42:
1006 ps._accuracy._temporary -= takeVal;
1007 break;
1008 case 43:
1009 ps._luck._temporary -= takeVal;
1010 break;
1011 case 45:
1012 ps._might._permanent -= takeVal;
1013 break;
1014 case 46:
1015 ps._intellect._permanent -= takeVal;
1016 break;
1017 case 47:
1018 ps._personality._permanent -= takeVal;
1019 break;
1020 case 48:
1021 ps._endurance._permanent -= takeVal;
1022 break;
1023 case 49:
1024 ps._speed._permanent -= takeVal;
1025 break;
1026 case 50:
1027 ps._accuracy._permanent -= takeVal;
1028 break;
1029 case 51:
1030 ps._luck._permanent -= takeVal;
1031 break;
1032 case 52:
1033 ps._fireResistence._permanent -= takeVal;
1034 break;
1035 case 53:
1036 ps._electricityResistence._permanent -= takeVal;
1037 break;
1038 case 54:
1039 ps._coldResistence._permanent -= takeVal;
1040 break;
1041 case 55:
1042 ps._poisonResistence._permanent -= takeVal;
1043 break;
1044 case 56:
1045 ps._energyResistence._permanent -= takeVal;
1046 break;
1047 case 57:
1048 ps._magicResistence._permanent -= takeVal;
1049 break;
1050 case 58:
1051 ps._fireResistence._temporary -= takeVal;
1052 break;
1053 case 59:
1054 ps._electricityResistence._temporary -= takeVal;
1055 break;
1056 case 60:
1057 ps._coldResistence._temporary -= takeVal;
1058 break;
1059 case 61:
1060 ps._poisonResistence._temporary -= takeVal;
1061 break;
1062 case 62:
1063 ps._energyResistence._temporary -= takeVal;
1064 break;
1065 case 63:
1066 ps._magicResistence._temporary -= takeVal;
1067 break;
1068 case 64:
1069 ps._level._permanent -= takeVal;
1070 break;
1071 case 65:
1072 if (!subtract(CONS_FOOD, takeVal, WHERE_PARTY, WT_ANIMATED_WAIT))
1073 return true;
1074 break;
1075 case 69:
1076 _levitateCount -= takeVal;
1077 break;
1078 case 70:
1079 _lightCount -= takeVal;
1080 break;
1081 case 71:
1082 _fireResistence -= takeVal;
1083 break;
1084 case 72:
1085 _electricityResistence -= takeVal;
1086 break;
1087 case 73:
1088 _coldResistence -= takeVal;
1089 break;
1090 case 74:
1091 _levitateCount -= takeVal;
1092 _lightCount -= takeVal;
1093 _fireResistence -= takeVal;
1094 _electricityResistence -= takeVal;
1095 _coldResistence -= takeVal;
1096 _poisonResistence -= takeVal;
1097 _walkOnWaterActive = false;
1098 break;
1099 case 76:
1100 subPartyTime(takeVal * 1440);
1101 break;
1102 case 79:
1103 _wizardEyeActive = false;
1104 break;
1105 case 85:
1106 _year -= takeVal;
1107 break;
1108 case 94:
1109 _walkOnWaterActive = false;
1110 break;
1111 case 103:
1112 _worldFlags[takeVal] = false;
1113 break;
1114 case 104:
1115 _questFlags[(_vm->getGameID() == GType_Swords ? 0 : files._ccNum * 30) + takeVal] = false;
1116 break;
1117 case 107:
1118 _characterFlags[ps._rosterId][takeVal] = false;
1119 break;
1120 default:
1121 break;
1122 }
1123
1124 switch (giveMode) {
1125 case 3:
1126 ps._sex = (Sex)giveVal;
1127 break;
1128 case 4:
1129 ps._race = (Race)giveVal;
1130 break;
1131 case 5:
1132 ps._class = (CharacterClass)giveVal;
1133 break;
1134 case 8:
1135 intf.spellFX(&ps);
1136 ps._currentHp += giveVal;
1137 break;
1138 case 9:
1139 ps._currentSp += giveVal;
1140 break;
1141 case 10:
1142 ps._ACTemp += giveVal;
1143 break;
1144 case 11:
1145 ps._level._temporary += giveVal;
1146 break;
1147 case 12:
1148 ps._tempAge += giveVal;
1149 break;
1150 case 13:
1151 assert(giveVal < 18);
1152 ps._skills[giveVal]++;
1153 intf.spellFX(&ps);
1154 break;
1155 case 15:
1156 ps.setAward(giveVal, true);
1157 if (giveVal != 8)
1158 intf.spellFX(&ps);
1159 break;
1160 case 16:
1161 ps._experience += giveVal;
1162 intf.spellFX(&ps);
1163 break;
1164 case 17:
1165 _poisonResistence += giveVal;
1166 break;
1167 case 18:
1168 if (giveVal == 16) {
1169 Common::fill(&ps._conditions[0], &ps._conditions[16], 0);
1170 } else if (giveVal == 6) {
1171 ps._conditions[giveVal] = 1;
1172 } else {
1173 assert(giveVal < 16);
1174 ps._conditions[giveVal]++;
1175 }
1176
1177 if (giveVal >= 13 && giveVal <= 15 && ps._currentHp > 0)
1178 ps._currentHp = 0;
1179 break;
1180 case 19: {
1181 // Give spell to character
1182 SpellsCategory category = ps.getSpellsCategory();
1183
1184 if (category != SPELLCAT_INVALID) {
1185 for (int idx = 0; idx < SPELLS_PER_CLASS; ++idx) {
1186 if (Res.SPELLS_ALLOWED[category][idx] == (int)giveVal) {
1187 ps._spells[idx] = true;
1188 intf.spellFX(&ps);
1189 break;
1190 }
1191 }
1192 }
1193 break;
1194 }
1195 case 20:
1196 assert(giveVal < 256);
1197 _gameFlags[_vm->getGameID() == GType_Swords ? 0 : files._ccNum][giveVal] = true;
1198 break;
1199 case 21: {
1200 const uint WEAPONS_END = _vm->getGameID() != GType_Swords ? 35 : 41;
1201 const uint ARMOR_END = _vm->getGameID() != GType_Swords ? 49 : 55;
1202 const uint ACCESSORIES_END = _vm->getGameID() != GType_Swords ? 60 : 66;
1203 const uint MISC_END = _vm->getGameID() != GType_Swords ? 82 : 88;
1204
1205 int idx;
1206 if (giveVal >= MISC_END) {
1207 _questItems[giveVal - MISC_END]++;
1208 }
1209 if (giveVal < WEAPONS_END || giveVal >= MISC_END) {
1210 for (idx = 0; idx < MAX_TREASURE_ITEMS && !_treasure._weapons[idx].empty(); ++idx) {}
1211 if (idx < MAX_TREASURE_ITEMS) {
1212 _treasure._weapons[idx]._id = giveVal;
1213 _treasure._hasItems = true;
1214 return false;
1215 }
1216 } else if (giveVal < ARMOR_END) {
1217 for (idx = 0; idx < MAX_TREASURE_ITEMS && !_treasure._armor[idx].empty(); ++idx) {}
1218 if (idx < MAX_TREASURE_ITEMS) {
1219 _treasure._armor[idx]._id = giveVal - WEAPONS_END;
1220 _treasure._hasItems = true;
1221 return false;
1222 }
1223 } else if (giveVal < ACCESSORIES_END) {
1224 for (idx = 0; idx < MAX_TREASURE_ITEMS && !_treasure._accessories[idx].empty(); ++idx) {}
1225 if (idx < MAX_TREASURE_ITEMS) {
1226 _treasure._accessories[idx]._id = giveVal - ARMOR_END;
1227 _treasure._hasItems = true;
1228 return false;
1229 }
1230 } else {
1231 for (idx = 0; idx < MAX_TREASURE_ITEMS && _treasure._misc[idx]._material; ++idx) {}
1232 if (idx < MAX_TREASURE_ITEMS) {
1233 _treasure._accessories[idx]._material = giveVal - ACCESSORIES_END;
1234 _treasure._hasItems = true;
1235 return false;
1236 }
1237 }
1238 return true;
1239 }
1240 case 25:
1241 subPartyTime(giveVal);
1242 intf.spellFX(&ps);
1243 break;
1244 case 34:
1245 _gold += giveVal;
1246 break;
1247 case 35:
1248 _gems += giveVal;
1249 break;
1250 case 37:
1251 ps._might._temporary = MIN(ps._might._temporary + giveVal, (uint)255);
1252 intf.spellFX(&ps);
1253 break;
1254 case 38:
1255 ps._intellect._temporary = MIN(ps._intellect._temporary + giveVal, (uint)255);
1256 intf.spellFX(&ps);
1257 break;
1258 case 39:
1259 ps._personality._temporary = MIN(ps._personality._temporary + giveVal, (uint)255);
1260 intf.spellFX(&ps);
1261 break;
1262 case 40:
1263 ps._endurance._temporary = MIN(ps._endurance._temporary + giveVal, (uint)255);
1264 intf.spellFX(&ps);
1265 break;
1266 case 41:
1267 ps._speed._temporary = MIN(ps._speed._temporary + giveVal, (uint)255);
1268 intf.spellFX(&ps);
1269 break;
1270 case 42:
1271 ps._accuracy._temporary = MIN(ps._accuracy._temporary + giveVal, (uint)255);
1272 intf.spellFX(&ps);
1273 break;
1274 case 43:
1275 ps._luck._temporary = MIN(ps._luck._temporary + giveVal, (uint)255);
1276 intf.spellFX(&ps);
1277 break;
1278 case 45:
1279 ps._might._permanent = MIN(ps._might._permanent + giveVal, (uint)255);
1280 intf.spellFX(&ps);
1281 break;
1282 case 46:
1283 ps._intellect._permanent = MIN(ps._intellect._permanent + giveVal, (uint)255);
1284 intf.spellFX(&ps);
1285 break;
1286 case 47:
1287 ps._personality._permanent = MIN(ps._personality._permanent + giveVal, (uint)255);
1288 intf.spellFX(&ps);
1289 break;
1290 case 48:
1291 ps._endurance._permanent = MIN(ps._endurance._permanent + giveVal, (uint)255);
1292 intf.spellFX(&ps);
1293 break;
1294 case 49:
1295 ps._speed._permanent = MIN(ps._speed._permanent + giveVal, (uint)255);
1296 intf.spellFX(&ps);
1297 break;
1298 case 50:
1299 ps._accuracy._permanent = MIN(ps._accuracy._permanent + giveVal, (uint)255);
1300 intf.spellFX(&ps);
1301 break;
1302 case 51:
1303 ps._luck._permanent = MIN(ps._luck._permanent + giveVal, (uint)255);
1304 intf.spellFX(&ps);
1305 break;
1306 case 52:
1307 ps._fireResistence._permanent = MIN(ps._fireResistence._permanent + giveVal, (uint)255);
1308 intf.spellFX(&ps);
1309 break;
1310 case 53:
1311 ps._electricityResistence._permanent = MIN(ps._electricityResistence._permanent + giveVal, (uint)255);
1312 intf.spellFX(&ps);
1313 break;
1314 case 54:
1315 ps._coldResistence._permanent = MIN(ps._coldResistence._permanent + giveVal, (uint)255);
1316 intf.spellFX(&ps);
1317 break;
1318 case 55:
1319 ps._poisonResistence._permanent = MIN(ps._poisonResistence._permanent + giveVal, (uint)255);
1320 intf.spellFX(&ps);
1321 break;
1322 case 56:
1323 ps._energyResistence._permanent = MIN(ps._energyResistence._permanent + giveVal, (uint)255);
1324 intf.spellFX(&ps);
1325 break;
1326 case 57:
1327 ps._magicResistence._permanent = MIN(ps._magicResistence._permanent + giveVal, (uint)255);
1328 intf.spellFX(&ps);
1329 break;
1330 case 58:
1331 ps._fireResistence._temporary = MIN(ps._fireResistence._temporary + giveVal, (uint)255);
1332 intf.spellFX(&ps);
1333 break;
1334 case 59:
1335 ps._electricityResistence._temporary = MIN(ps._electricityResistence._temporary + giveVal, (uint)255);
1336 intf.spellFX(&ps);
1337 break;
1338 case 60:
1339 ps._coldResistence._temporary = MIN(ps._coldResistence._temporary + giveVal, (uint)255);
1340 intf.spellFX(&ps);
1341 break;
1342 case 61:
1343 ps._poisonResistence._temporary = MIN(ps._poisonResistence._temporary + giveVal, (uint)255);
1344 intf.spellFX(&ps);
1345 break;
1346 case 62:
1347 ps._energyResistence._temporary = MIN(ps._energyResistence._temporary + giveVal, (uint)255);
1348 intf.spellFX(&ps);
1349 break;
1350 case 63:
1351 ps._magicResistence._temporary = MIN(ps._magicResistence._temporary + giveVal, (uint)255);
1352 intf.spellFX(&ps);
1353 break;
1354 case 64:
1355 ps._level._permanent = MIN(ps._level._permanent + giveVal, (uint)255);
1356 intf.spellFX(&ps);
1357 break;
1358 case 65:
1359 _food += giveVal;
1360 break;
1361 case 66: {
1362 Character &tempChar = _itemsCharacter;
1363 int idx = -1;
1364 if (Character::_itemType != 0) {
1365 for (idx = 0; idx < 10 && _treasure._misc[idx]._material; ++idx) {}
1366 if (idx == 10)
1367 return true;
1368 }
1369
1370 // Create the item and it's category
1371 ItemCategory itemCat = tempChar.makeItem(giveVal, 0, (idx == -1) ? 0 : 12);
1372 XeenItem &srcItem = tempChar._items[itemCat][0];
1373 XeenItem *trItems = _treasure[itemCat];
1374
1375 // Check for a free treasure slot
1376 for (idx = 0; idx < 10 && trItems[idx]._id; ++idx) {}
1377 if (idx == 10)
1378 return true;
1379
1380 // Found a free slot, so copy the created item into it
1381 trItems[idx]._material = srcItem._material;
1382 trItems[idx]._id = srcItem._id;
1383 trItems[idx]._state = srcItem._state;
1384 _treasure._hasItems = true;
1385 break;
1386 }
1387 case 69:
1388 _levitateCount += giveVal;
1389 break;
1390 case 70:
1391 _lightCount += giveVal;
1392 break;
1393 case 71:
1394 _fireResistence += giveVal;
1395 break;
1396 case 72:
1397 _electricityResistence += giveVal;
1398 break;
1399 case 73:
1400 _coldResistence += giveVal;
1401 break;
1402 case 74:
1403 _levitateCount += giveVal;
1404 _lightCount += giveVal;
1405 _fireResistence += giveVal;
1406 _electricityResistence += giveVal;
1407 _coldResistence += giveVal;
1408 _poisonResistence += giveVal;
1409 _walkOnWaterActive = false;
1410 break;
1411 case 76:
1412 addTime(giveVal * 1440);
1413 break;
1414 case 77:
1415 ps._ACTemp += giveVal;
1416 intf.spellFX(&ps);
1417 break;
1418 case 78:
1419 ps._currentHp = ps.getMaxHP();
1420 intf.spellFX(&ps);
1421 break;
1422 case 79:
1423 _wizardEyeActive = true;
1424 break;
1425 case 81:
1426 ps._currentSp = ps.getMaxSP();
1427 intf.spellFX(&ps);
1428 break;
1429 case 82:
1430 combat.giveCharDamage(giveVal, scripts._nEdamageType, charIdx);
1431 break;
1432 case 85:
1433 _year += giveVal;
1434 resetYearlyBits();
1435 resetTemps();
1436 _rested = true;
1437 break;
1438 case 94:
1439 _walkOnWaterActive = true;
1440 break;
1441 case 100:
1442 _gold += _vm->getRandomNumber(1, giveVal);
1443 break;
1444 case 103:
1445 assert(giveVal < (uint)(_vm->getGameID() == GType_Swords ? 49 : 128));
1446 _worldFlags[giveVal] = true;
1447 break;
1448 case 104:
1449 assert(giveVal < (uint)(_vm->getGameID() == GType_Swords ? 60 : 30));
1450 _questFlags[(_vm->getGameID() == GType_Swords ? 0 : files._ccNum * 30) + giveVal] = true;
1451 break;
1452 case 107:
1453 assert(giveVal < 24);
1454 _characterFlags[ps._rosterId][giveVal] = true;
1455 break;
1456 default:
1457 break;
1458 }
1459
1460 return false;
1461 }
1462
giveExt(int mode1,uint val1,int mode2,uint val2,int mode3,uint val3,int charId)1463 bool Party::giveExt(int mode1, uint val1, int mode2, uint val2, int mode3, uint val3, int charId) {
1464 Combat &combat = *g_vm->_combat;
1465 FileManager &files = *g_vm->_files;
1466 Interface &intf = *g_vm->_interface;
1467 Map &map = *g_vm->_map;
1468 Scripts &scripts = *g_vm->_scripts;
1469 Sound &sound = *g_vm->_sound;
1470
1471 // WORKAROUND: Ali Baba's chest in Dark Side requires the character in the first slot to have Lockpicking.
1472 // This is obviously a mistake, since the chest is meant to be opened via a password
1473 if (intf._objNumber != -1 && !scripts._animCounter && !(files._ccNum && _mazeId == 63 && intf._objNumber == 15)) {
1474 MazeObject &obj = map._mobData._objects[intf._objNumber];
1475 switch (obj._spriteId) {
1476 case 15:
1477 if (!files._ccNum)
1478 break;
1479 // Intentional fall-through
1480
1481 case 16:
1482 case 58:
1483 case 73: {
1484 Character &c = _activeParty[charId];
1485 obj._frame = 1;
1486
1487 if (obj._position.x != 20) {
1488 if (g_vm->getRandomNumber(1, 4) == 1) {
1489 combat.giveCharDamage(map.mazeData()._trapDamage,
1490 (DamageType)_vm->getRandomNumber(0, 6), charId);
1491 }
1492
1493 int unlockBox = map.mazeData()._difficulties._unlockBox;
1494 if ((c.getThievery() + _vm->getRandomNumber(1, 20)) >= unlockBox) {
1495 scripts._animCounter++;
1496 g_vm->_mode = MODE_7;
1497 c._experience += c.getCurrentLevel() * unlockBox * 10;
1498
1499 sound.playFX(10);
1500 intf.draw3d(true, false);
1501 Common::String msg = Common::String::format(Res.PICKS_THE_LOCK, c._name.c_str());
1502 ErrorScroll::show(g_vm, msg, WT_NONFREEZED_WAIT);
1503 } else {
1504 sound.playFX(21);
1505
1506 obj._frame = 0;
1507 scripts._animCounter = 0;
1508 Common::String msg = Common::String::format(Res.UNABLE_TO_PICK_LOCK, c._name.c_str());
1509 ErrorScroll::show(g_vm, msg, WT_NONFREEZED_WAIT);
1510
1511 scripts._animCounter = 255;
1512 return true;
1513 }
1514 }
1515 break;
1516 }
1517
1518 default:
1519 break;
1520 }
1521 }
1522
1523 for (int paramCtr = 0; paramCtr < 3; ++paramCtr) {
1524 int mode = (paramCtr == 0) ? mode1 : (paramCtr == 1 ? mode2 : mode3);
1525 int val = (paramCtr == 0) ? val1 : (paramCtr == 1 ? val2 : val3);
1526
1527 switch (mode) {
1528 case 34:
1529 _treasure._gold += val;
1530 break;
1531
1532 case 35:
1533 _treasure._gems += val;
1534 break;
1535
1536 case 66:
1537 _itemsCharacter.clear();
1538
1539 if (giveTake(0, 0, mode, val, charId))
1540 return true;
1541 break;
1542
1543 case 100:
1544 _treasure._gold += g_vm->getRandomNumber(1, val);
1545 break;
1546
1547 case 101:
1548 _treasure._gems += g_vm->getRandomNumber(1, val);
1549 break;
1550
1551 case 106:
1552 _food += g_vm->getRandomNumber(1, val);
1553 break;
1554
1555 case 67:
1556 default:
1557 if (giveTake(0, 0, mode, val, charId))
1558 return true;
1559 else if (mode == 67)
1560 return false;
1561 break;
1562 }
1563 }
1564
1565 return false;
1566 }
1567
howMuch()1568 int Party::howMuch() {
1569 return HowMuch::show(_vm);
1570 }
1571
subPartyTime(int time)1572 void Party::subPartyTime(int time) {
1573 for (_minutes -= time; _minutes < 0; _minutes += 1440) {
1574 if (--_day < 0) {
1575 _day += 100;
1576 --_year;
1577 }
1578 }
1579 }
1580
resetYearlyBits()1581 void Party::resetYearlyBits() {
1582 _gameFlags[0][55] = false;
1583 _gameFlags[0][155] = false;
1584 _gameFlags[0][222] = false;
1585 _gameFlags[0][231] = false;
1586 }
1587
resetBlacksmithWares()1588 void Party::resetBlacksmithWares() {
1589 _blacksmithWares.regenerate();
1590 }
1591
giveBankInterest()1592 void Party::giveBankInterest() {
1593 _bankGold += _bankGold / 100;
1594 _bankGems += _bankGems / 100;
1595 }
1596
getScore()1597 uint Party::getScore() {
1598 uint score = 0;
1599 for (uint idx = 0; idx < _activeParty.size(); ++idx)
1600 score += _activeParty[idx].getCurrentExperience();
1601 score = score / _activeParty.size() / 10000;
1602 score *= 100000;
1603
1604 uint time = _vm->_events->playTime() / GAME_FRAME_RATE;
1605 int minutes = (time % 3600) / 60;
1606 int hours = time / 3600;
1607
1608 score += minutes + (hours * 100);
1609 return score;
1610 }
1611
1612 } // End of namespace Xeen
1613