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