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 "ultima/ultima4/game/player.h"
24 #include "ultima/ultima4/game/armor.h"
25 #include "ultima/ultima4/game/context.h"
26 #include "ultima/ultima4/game/game.h"
27 #include "ultima/ultima4/game/names.h"
28 #include "ultima/ultima4/game/weapon.h"
29 #include "ultima/ultima4/controllers/combat_controller.h"
30 #include "ultima/ultima4/core/debugger.h"
31 #include "ultima/ultima4/core/types.h"
32 #include "ultima/ultima4/core/utils.h"
33 #include "ultima/ultima4/map/annotation.h"
34 #include "ultima/ultima4/map/location.h"
35 #include "ultima/ultima4/map/mapmgr.h"
36 #include "ultima/ultima4/map/tilemap.h"
37 #include "ultima/ultima4/map/tileset.h"
38 
39 namespace Ultima {
40 namespace Ultima4 {
41 
isPartyMember(Object * punknown)42 bool isPartyMember(Object *punknown) {
43 	PartyMember *pm;
44 	if ((pm = dynamic_cast<PartyMember *>(punknown)) != nullptr)
45 		return true;
46 	else
47 		return false;
48 }
49 
PartyMember(Party * p,SaveGamePlayerRecord * pr)50 PartyMember::PartyMember(Party *p, SaveGamePlayerRecord *pr) :
51 	Creature(tileForClass(pr->_class)),
52 	_player(pr),
53 	_party(p) {
54 	/* FIXME: we need to rename movement behaviors */
55 	setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
56 	this->_ranged = g_weapons->get(pr->_weapon)->getRange() ? 1 : 0;
57 	setStatus(pr->_status);
58 }
59 
~PartyMember()60 PartyMember::~PartyMember() {
61 }
62 
notifyOfChange()63 void PartyMember::notifyOfChange() {
64 	if (_party) {
65 		_party->notifyOfChange(this);
66 	}
67 }
68 
translate(Std::vector<Common::String> & parts)69 Common::String PartyMember::translate(Std::vector<Common::String> &parts) {
70 	if (parts.size() == 0)
71 		return "";
72 	else if (parts.size() == 1) {
73 		if (parts[0] == "hp")
74 			return xu4_to_string(getHp());
75 		else if (parts[0] == "max_hp")
76 			return xu4_to_string(getMaxHp());
77 		else if (parts[0] == "mp")
78 			return xu4_to_string(getMp());
79 		else if (parts[0] == "max_mp")
80 			return xu4_to_string(getMaxMp());
81 		else if (parts[0] == "str")
82 			return xu4_to_string(getStr());
83 		else if (parts[0] == "dex")
84 			return xu4_to_string(getDex());
85 		else if (parts[0] == "int")
86 			return xu4_to_string(getInt());
87 		else if (parts[0] == "exp")
88 			return xu4_to_string(getExp());
89 		else if (parts[0] == "name")
90 			return getName();
91 		else if (parts[0] == "weapon")
92 			return getWeapon()->getName();
93 		else if (parts[0] == "armor")
94 			return getArmor()->getName();
95 		else if (parts[0] == "sex") {
96 			Common::String var((char)getSex());
97 			return var;
98 		} else if (parts[0] == "class")
99 			return getClassName(getClass());
100 		else if (parts[0] == "level")
101 			return xu4_to_string(getRealLevel());
102 	} else if (parts.size() == 2) {
103 		if (parts[0] == "needs") {
104 			if (parts[1] == "cure") {
105 				if (getStatus() == STAT_POISONED)
106 					return "true";
107 				else
108 					return "false";
109 			} else if (parts[1] == "heal" || parts[1] == "fullheal") {
110 				if (getHp() < getMaxHp())
111 					return "true";
112 				else
113 					return "false";
114 			} else if (parts[1] == "resurrect") {
115 				if (getStatus() == STAT_DEAD)
116 					return "true";
117 				else
118 					return "false";
119 			}
120 		}
121 	}
122 
123 	return "";
124 }
125 
getHp() const126 int PartyMember::getHp() const {
127 	return _player->_hp;
128 }
129 
getMaxMp() const130 int PartyMember::getMaxMp() const {
131 	int max_mp = -1;
132 
133 	switch (_player->_class) {
134 	case CLASS_MAGE:            /*  mage: 200% of int */
135 		max_mp = _player->_intel * 2;
136 		break;
137 
138 	case CLASS_DRUID:           /* druid: 150% of int */
139 		max_mp = _player->_intel * 3 / 2;
140 		break;
141 
142 	case CLASS_BARD:            /* bard, paladin, ranger: 100% of int */
143 	case CLASS_PALADIN:
144 	case CLASS_RANGER:
145 		max_mp = _player->_intel;
146 		break;
147 
148 	case CLASS_TINKER:          /* tinker: 50% of int */
149 		max_mp = _player->_intel / 2;
150 		break;
151 
152 	case CLASS_FIGHTER:         /* fighter, shepherd: no mp at all */
153 	case CLASS_SHEPHERD:
154 		max_mp = 0;
155 		break;
156 
157 	default:
158 		error("invalid player class: %d", _player->_class);
159 	}
160 
161 	/* mp always maxes out at 99 */
162 	if (max_mp > 99)
163 		max_mp = 99;
164 
165 	return max_mp;
166 }
167 
getWeapon() const168 const Weapon *PartyMember::getWeapon() const {
169 	return g_weapons->get(_player->_weapon);
170 }
171 
getArmor() const172 const Armor *PartyMember::getArmor() const {
173 	return g_armors->get(_player->_armor);
174 }
175 
getName() const176 Common::String PartyMember::getName() const {
177 	return _player->_name;
178 }
179 
getSex() const180 SexType PartyMember::getSex() const {
181 	return _player->_sex;
182 }
183 
getClass() const184 ClassType PartyMember::getClass() const {
185 	return _player->_class;
186 }
187 
getState() const188 CreatureStatus PartyMember::getState() const {
189 	if (getHp() <= 0)
190 		return MSTAT_DEAD;
191 	else if (getHp() < 24)
192 		return MSTAT_FLEEING;
193 	else
194 		return MSTAT_BARELYWOUNDED;
195 }
196 
getRealLevel() const197 int PartyMember::getRealLevel() const {
198 	return _player->_hpMax / 100;
199 }
200 
getMaxLevel() const201 int PartyMember::getMaxLevel() const {
202 	int level = 1;
203 	int next = 100;
204 
205 	while (_player->_xp >= next && level < 8) {
206 		level++;
207 		next <<= 1;
208 	}
209 
210 	return level;
211 }
212 
addStatus(StatusType s)213 void PartyMember::addStatus(StatusType s) {
214 	Creature::addStatus(s);
215 	_player->_status = _status.back();
216 	notifyOfChange();
217 }
218 
adjustMp(int pts)219 void PartyMember::adjustMp(int pts) {
220 	AdjustValueMax(_player->_mp, pts, getMaxMp());
221 	notifyOfChange();
222 }
223 
advanceLevel()224 void PartyMember::advanceLevel() {
225 	if (getRealLevel() == getMaxLevel())
226 		return;
227 	setStatus(STAT_GOOD);
228 	_player->_hpMax = getMaxLevel() * 100;
229 	_player->_hp = _player->_hpMax;
230 
231 	/* improve stats by 1-8 each */
232 	_player->_str   += xu4_random(8) + 1;
233 	_player->_dex   += xu4_random(8) + 1;
234 	_player->_intel += xu4_random(8) + 1;
235 
236 	if (_player->_str > 50) _player->_str = 50;
237 	if (_player->_dex > 50) _player->_dex = 50;
238 	if (_player->_intel > 50) _player->_intel = 50;
239 
240 	if (_party) {
241 		_party->setChanged();
242 		PartyEvent event(PartyEvent::ADVANCED_LEVEL, this);
243 		event._player = this;
244 		_party->notifyObservers(event);
245 	}
246 }
247 
applyEffect(TileEffect effect)248 void PartyMember::applyEffect(TileEffect effect) {
249 	if (getStatus() == STAT_DEAD)
250 		return;
251 
252 	switch (effect) {
253 	case EFFECT_NONE:
254 		break;
255 	case EFFECT_LAVA:
256 	case EFFECT_FIRE:
257 		applyDamage(16 + (xu4_random(32)));
258 
259 		/*else if (player == ALL_PLAYERS && xu4_random(2) == 0)
260 		    playerApplyDamage(&(c->saveGame->_players[i]), 10 + (xu4_random(25)));*/
261 		break;
262 	case EFFECT_SLEEP:
263 		putToSleep();
264 		break;
265 	case EFFECT_POISONFIELD:
266 	case EFFECT_POISON:
267 		if (getStatus() != STAT_POISONED) {
268 			soundPlay(SOUND_POISON_EFFECT, false);
269 			addStatus(STAT_POISONED);
270 		}
271 		break;
272 	case EFFECT_ELECTRICITY:
273 		break;
274 	default:
275 		error("invalid effect: %d", effect);
276 	}
277 
278 	if (effect != EFFECT_NONE)
279 		notifyOfChange();
280 }
281 
awardXp(int xp)282 void PartyMember::awardXp(int xp) {
283 	AdjustValueMax(_player->_xp, xp, 9999);
284 	notifyOfChange();
285 }
286 
heal(HealType type)287 bool PartyMember::heal(HealType type) {
288 	switch (type) {
289 	case HT_NONE:
290 		return true;
291 
292 	case HT_CURE:
293 		if (getStatus() != STAT_POISONED)
294 			return false;
295 		removeStatus(STAT_POISONED);
296 		break;
297 
298 	case HT_FULLHEAL:
299 		if (getStatus() == STAT_DEAD ||
300 		        _player->_hp == _player->_hpMax)
301 			return false;
302 		_player->_hp = _player->_hpMax;
303 		break;
304 
305 	case HT_RESURRECT:
306 		if (getStatus() != STAT_DEAD)
307 			return false;
308 		setStatus(STAT_GOOD);
309 		break;
310 
311 	case HT_HEAL:
312 		if (getStatus() == STAT_DEAD ||
313 		        _player->_hp == _player->_hpMax)
314 			return false;
315 
316 		_player->_hp += 75 + (xu4_random(0x100) % 0x19);
317 		break;
318 
319 	case HT_CAMPHEAL:
320 		if (getStatus() == STAT_DEAD ||
321 		        _player->_hp == _player->_hpMax)
322 			return false;
323 		_player->_hp += 99 + (xu4_random(0x100) & 0x77);
324 		break;
325 
326 	case HT_INNHEAL:
327 		if (getStatus() == STAT_DEAD ||
328 		        _player->_hp == _player->_hpMax)
329 			return false;
330 		_player->_hp += 100 + (xu4_random(50) * 2);
331 		break;
332 
333 	default:
334 		return false;
335 	}
336 
337 	if (_player->_hp > _player->_hpMax)
338 		_player->_hp = _player->_hpMax;
339 
340 	notifyOfChange();
341 
342 	return true;
343 }
344 
removeStatus(StatusType s)345 void PartyMember::removeStatus(StatusType s) {
346 	Creature::removeStatus(s);
347 	_player->_status = _status.back();
348 	notifyOfChange();
349 }
350 
setHp(int hp)351 void PartyMember::setHp(int hp) {
352 	_player->_hp = hp;
353 	notifyOfChange();
354 }
355 
setMp(int mp)356 void PartyMember::setMp(int mp) {
357 	_player->_mp = mp;
358 	notifyOfChange();
359 }
360 
setArmor(const Armor * a)361 EquipError PartyMember::setArmor(const Armor *a) {
362 	ArmorType type = a->getType();
363 
364 	if (type != ARMR_NONE && _party->_saveGame->_armor[type] < 1)
365 		return EQUIP_NONE_LEFT;
366 	if (!a->canWear(getClass()))
367 		return EQUIP_CLASS_RESTRICTED;
368 
369 	ArmorType oldArmorType = getArmor()->getType();
370 	if (oldArmorType != ARMR_NONE)
371 		_party->_saveGame->_armor[oldArmorType]++;
372 	if (type != ARMR_NONE)
373 		_party->_saveGame->_armor[type]--;
374 
375 	_player->_armor = type;
376 	notifyOfChange();
377 
378 	return EQUIP_SUCCEEDED;
379 }
380 
setWeapon(const Weapon * w)381 EquipError PartyMember::setWeapon(const Weapon *w) {
382 	WeaponType type = w->getType();
383 
384 	if (type != WEAP_HANDS && _party->_saveGame->_weapons[type] < 1)
385 		return EQUIP_NONE_LEFT;
386 	if (!w->canReady(getClass()))
387 		return EQUIP_CLASS_RESTRICTED;
388 
389 	WeaponType old = getWeapon()->getType();
390 	if (old != WEAP_HANDS)
391 		_party->_saveGame->_weapons[old]++;
392 	if (type != WEAP_HANDS)
393 		_party->_saveGame->_weapons[type]--;
394 
395 	_player->_weapon = type;
396 	notifyOfChange();
397 
398 	return EQUIP_SUCCEEDED;
399 }
400 
applyDamage(int damage,bool)401 bool PartyMember::applyDamage(int damage, bool) {
402 	int newHp = _player->_hp;
403 
404 	if (getStatus() == STAT_DEAD)
405 		return false;
406 
407 	newHp -= damage;
408 
409 	if (newHp < 0) {
410 		setStatus(STAT_DEAD);
411 		newHp = 0;
412 	}
413 
414 	_player->_hp = newHp;
415 	notifyOfChange();
416 
417 	if (isCombatMap(g_context->_location->_map) && getStatus() == STAT_DEAD) {
418 		Coords p = getCoords();
419 		Map *map = getMap();
420 
421 		assert(_party);
422 		map->_annotations->add(p, g_tileSets->findTileByName("corpse")->getId())->setTTL(_party->size() * 2);
423 
424 		{
425 			_party->setChanged();
426 			PartyEvent event(PartyEvent::PLAYER_KILLED, this);
427 			event._player = this;
428 			_party->notifyObservers(event);
429 		}
430 
431 		/* remove yourself from the map */
432 		remove();
433 		return false;
434 	}
435 
436 	return true;
437 }
438 
getAttackBonus() const439 int PartyMember::getAttackBonus() const {
440 	if (g_weapons->get(_player->_weapon)->alwaysHits() || _player->_dex >= 40)
441 		return 255;
442 	return _player->_dex;
443 }
444 
getDefense() const445 int PartyMember::getDefense() const {
446 	return g_armors->get(_player->_armor)->getDefense();
447 }
448 
dealDamage(Creature * m,int damage)449 bool PartyMember::dealDamage(Creature *m, int damage) {
450 	/* we have to record these now, because if we
451 	   kill the target, it gets destroyed */
452 	int m_xp = m->getXp();
453 
454 	if (!Creature::dealDamage(m, damage)) {
455 		/* half the time you kill an evil creature you get a karma boost */
456 		awardXp(m_xp);
457 		return false;
458 	}
459 	return true;
460 }
461 
getDamage()462 int PartyMember::getDamage() {
463 	int maxDamage;
464 
465 	maxDamage = g_weapons->get(_player->_weapon)->getDamage();
466 	maxDamage += _player->_str;
467 	if (maxDamage > 255)
468 		maxDamage = 255;
469 
470 	return xu4_random(maxDamage);
471 }
472 
getHitTile() const473 const Common::String &PartyMember::getHitTile() const {
474 	return getWeapon()->getHitTile();
475 }
476 
getMissTile() const477 const Common::String &PartyMember::getMissTile() const {
478 	return getWeapon()->getMissTile();
479 }
480 
isDead()481 bool PartyMember::isDead() {
482 	return getStatus() == STAT_DEAD;
483 }
484 
isDisabled()485 bool PartyMember::isDisabled() {
486 	return (getStatus() == STAT_GOOD ||
487 	        getStatus() == STAT_POISONED) ? false : true;
488 }
489 
loseWeapon()490 int PartyMember::loseWeapon() {
491 	int weapon = _player->_weapon;
492 
493 	notifyOfChange();
494 
495 	if (_party->_saveGame->_weapons[weapon] > 0)
496 		return (--_party->_saveGame->_weapons[weapon]) + 1;
497 	else {
498 		_player->_weapon = WEAP_HANDS;
499 		return 0;
500 	}
501 }
502 
putToSleep()503 void PartyMember::putToSleep() {
504 	if (getStatus() != STAT_DEAD) {
505 		soundPlay(SOUND_SLEEP, false);
506 		addStatus(STAT_SLEEPING);
507 		setTile(g_tileSets->findTileByName("corpse")->getId());
508 	}
509 }
510 
wakeUp()511 void PartyMember::wakeUp() {
512 	removeStatus(STAT_SLEEPING);
513 	setTile(tileForClass(getClass()));
514 }
515 
tileForClass(int klass)516 MapTile PartyMember::tileForClass(int klass) {
517 	const char *name = nullptr;
518 
519 	switch (klass) {
520 	case CLASS_MAGE:
521 		name = "mage";
522 		break;
523 	case CLASS_BARD:
524 		name = "bard";
525 		break;
526 	case CLASS_FIGHTER:
527 		name = "fighter";
528 		break;
529 	case CLASS_DRUID:
530 		name = "druid";
531 		break;
532 	case CLASS_TINKER:
533 		name = "tinker";
534 		break;
535 	case CLASS_PALADIN:
536 		name = "paladin";
537 		break;
538 	case CLASS_RANGER:
539 		name = "ranger";
540 		break;
541 	case CLASS_SHEPHERD:
542 		name = "shepherd";
543 		break;
544 	default:
545 		error("invalid class %d in tileForClass", klass);
546 	}
547 
548 	const Tile *tile = g_tileSets->get("base")->getByName(name);
549 	assertMsg(tile, "no tile found for class %d", klass);
550 	return tile->getId();
551 }
552 
553 /*-------------------------------------------------------------------*/
554 
Party(SaveGame * s)555 Party::Party(SaveGame *s) : _saveGame(s), _transport(0), _torchDuration(0), _activePlayer(-1) {
556 	MapId map = _saveGame->_positions.back()._map;
557 	if (map >= MAP_DECEIT && map <= MAP_ABYSS)
558 		_torchDuration = _saveGame->_torchDuration;
559 	for (int i = 0; i < _saveGame->_members; i++) {
560 		// add the members to the party
561 		_members.push_back(new PartyMember(this, &_saveGame->_players[i]));
562 	}
563 
564 	// set the party's transport (transport value stored in savegame
565 	// hardcoded to index into base tilemap)
566 	setTransport(g_tileMaps->get("base")->translate(_saveGame->_transport));
567 }
568 
~Party()569 Party::~Party() {
570 	for (uint idx = 0; idx < _members.size(); ++idx)
571 		delete _members[idx];
572 }
573 
notifyOfChange(PartyMember * pm,PartyEvent::Type eventType)574 void Party::notifyOfChange(PartyMember *pm, PartyEvent::Type eventType) {
575 	setChanged();
576 	PartyEvent event(eventType, pm);
577 	notifyObservers(event);
578 }
579 
translate(Std::vector<Common::String> & parts)580 Common::String Party::translate(Std::vector<Common::String> &parts) {
581 	if (parts.size() == 0)
582 		return "";
583 	else if (parts.size() == 1) {
584 		// Translate some different items for the script
585 		if (parts[0] == "transport") {
586 			if (g_context->_transportContext & TRANSPORT_FOOT)
587 				return "foot";
588 			if (g_context->_transportContext & TRANSPORT_HORSE)
589 				return "horse";
590 			if (g_context->_transportContext & TRANSPORT_SHIP)
591 				return "ship";
592 			if (g_context->_transportContext & TRANSPORT_BALLOON)
593 				return "balloon";
594 		} else if (parts[0] == "gold")
595 			return xu4_to_string(_saveGame->_gold);
596 		else if (parts[0] == "food")
597 			return xu4_to_string(_saveGame->_food);
598 		else if (parts[0] == "members")
599 			return xu4_to_string(size());
600 		else if (parts[0] == "keys")
601 			return xu4_to_string(_saveGame->_keys);
602 		else if (parts[0] == "torches")
603 			return xu4_to_string(_saveGame->_torches);
604 		else if (parts[0] == "gems")
605 			return xu4_to_string(_saveGame->_gems);
606 		else if (parts[0] == "sextants")
607 			return xu4_to_string(_saveGame->_sextants);
608 		else if (parts[0] == "food")
609 			return xu4_to_string((_saveGame->_food / 100));
610 		else if (parts[0] == "gold")
611 			return xu4_to_string(_saveGame->_gold);
612 		else if (parts[0] == "party_members")
613 			return xu4_to_string(_saveGame->_members);
614 		else if (parts[0] == "moves")
615 			return xu4_to_string(_saveGame->_moves);
616 	} else if (parts.size() >= 2) {
617 		if (parts[0].findFirstOf("member") == 0) {
618 			// Make a new parts list, but remove the first item
619 			Std::vector<Common::String> new_parts = parts;
620 			new_parts.erase(new_parts.begin());
621 
622 			// Find the member we'll be working with
623 			Common::String str = parts[0];
624 			size_t pos = str.findFirstOf("1234567890");
625 			if (pos != Common::String::npos) {
626 				str = str.substr(pos);
627 				int p_member = (int)strtol(str.c_str(), nullptr, 10);
628 
629 				// Make the party member translate its own stuff
630 				if (p_member > 0)
631 					return member(p_member - 1)->translate(new_parts);
632 			}
633 		}
634 
635 		else if (parts.size() == 2) {
636 			if (parts[0] == "weapon") {
637 				const Weapon *w = g_weapons->get(parts[1]);
638 				if (w)
639 					return xu4_to_string(_saveGame->_weapons[w->getType()]);
640 			} else if (parts[0] == "armor") {
641 				const Armor *a = g_armors->get(parts[1]);
642 				if (a)
643 					return xu4_to_string(_saveGame->_armor[a->getType()]);
644 			}
645 		}
646 	}
647 	return "";
648 }
649 
adjustFood(int food)650 void Party::adjustFood(int food) {
651 	// Check for cheat that disables party hunger
652 	if (food < 0 && g_debugger->_disableHunger)
653 		return;
654 
655 	int oldFood = _saveGame->_food;
656 	AdjustValue(_saveGame->_food, food, 999900, 0);
657 	if ((_saveGame->_food / 100) != (oldFood / 100)) {
658 		notifyOfChange();
659 	}
660 }
661 
adjustGold(int gold)662 void Party::adjustGold(int gold) {
663 	AdjustValue(_saveGame->_gold, gold, 9999, 0);
664 	notifyOfChange();
665 }
666 
adjustKarma(KarmaAction action)667 void Party::adjustKarma(KarmaAction action) {
668 	bool timeLimited = false;
669 	int v, newKarma[VIRT_MAX], maxVal[VIRT_MAX];
670 
671 	/*
672 	 * make a local copy of all virtues, and adjust it according to
673 	 * the game rules
674 	 */
675 	for (v = 0; v < VIRT_MAX; v++) {
676 		newKarma[v] = _saveGame->_karma[v] == 0 ? 100 : _saveGame->_karma[v];
677 		maxVal[v] = _saveGame->_karma[v] == 0 ? 100 : 99;
678 	}
679 
680 	switch (action) {
681 	case KA_FOUND_ITEM:
682 		AdjustValueMax(newKarma[VIRT_HONOR], 5, maxVal[VIRT_HONOR]);
683 		break;
684 	case KA_STOLE_CHEST:
685 		AdjustValueMin(newKarma[VIRT_HONESTY], -1, 1);
686 		AdjustValueMin(newKarma[VIRT_JUSTICE], -1, 1);
687 		AdjustValueMin(newKarma[VIRT_HONOR], -1, 1);
688 		break;
689 	case KA_GAVE_ALL_TO_BEGGAR:
690 	//  When donating all, you get +3 HONOR in Apple 2, but not in in U4DOS.
691 	//  TODO: Make this a configuration option.
692 	//  AdjustValueMax(newKarma[VIRT_HONOR], 3, maxVal[VIRT_HONOR]);
693 	case KA_GAVE_TO_BEGGAR:
694 		//  In U4DOS, we only get +2 COMPASSION, no HONOR or SACRIFICE even if
695 		//  donating all.
696 		timeLimited = true;
697 		AdjustValueMax(newKarma[VIRT_COMPASSION], 2, maxVal[VIRT_COMPASSION]);
698 		break;
699 	case KA_BRAGGED:
700 		AdjustValueMin(newKarma[VIRT_HUMILITY], -5, 1);
701 		break;
702 	case KA_HUMBLE:
703 		timeLimited = true;
704 		AdjustValueMax(newKarma[VIRT_HUMILITY], 10, maxVal[VIRT_HUMILITY]);
705 		break;
706 	case KA_HAWKWIND:
707 	case KA_MEDITATION:
708 		timeLimited = true;
709 		AdjustValueMax(newKarma[VIRT_SPIRITUALITY], 3, maxVal[VIRT_SPIRITUALITY]);
710 		break;
711 	case KA_BAD_MANTRA:
712 		AdjustValueMin(newKarma[VIRT_SPIRITUALITY], -3, 1);
713 		break;
714 	case KA_ATTACKED_GOOD:
715 		AdjustValueMin(newKarma[VIRT_COMPASSION], -5, 1);
716 		AdjustValueMin(newKarma[VIRT_JUSTICE], -5, 1);
717 		AdjustValueMin(newKarma[VIRT_HONOR], -5, 1);
718 		break;
719 	case KA_FLED_EVIL:
720 		AdjustValueMin(newKarma[VIRT_VALOR], -2, 1);
721 		break;
722 	case KA_HEALTHY_FLED_EVIL:
723 		AdjustValueMin(newKarma[VIRT_VALOR], -2, 1);
724 		AdjustValueMin(newKarma[VIRT_SACRIFICE], -2, 1);
725 		break;
726 	case KA_KILLED_EVIL:
727 		AdjustValueMax(newKarma[VIRT_VALOR], xu4_random(2), maxVal[VIRT_VALOR]); /* gain one valor half the time, zero the rest */
728 		break;
729 	case KA_FLED_GOOD:
730 		AdjustValueMax(newKarma[VIRT_COMPASSION], 2, maxVal[VIRT_COMPASSION]);
731 		AdjustValueMax(newKarma[VIRT_JUSTICE], 2, maxVal[VIRT_JUSTICE]);
732 		break;
733 	case KA_SPARED_GOOD:
734 		AdjustValueMax(newKarma[VIRT_COMPASSION], 1, maxVal[VIRT_COMPASSION]);
735 		AdjustValueMax(newKarma[VIRT_JUSTICE], 1, maxVal[VIRT_JUSTICE]);
736 		break;
737 	case KA_DONATED_BLOOD:
738 		AdjustValueMax(newKarma[VIRT_SACRIFICE], 5, maxVal[VIRT_SACRIFICE]);
739 		break;
740 	case KA_DIDNT_DONATE_BLOOD:
741 		AdjustValueMin(newKarma[VIRT_SACRIFICE], -5, 1);
742 		break;
743 	case KA_CHEAT_REAGENTS:
744 		AdjustValueMin(newKarma[VIRT_HONESTY], -10, 1);
745 		AdjustValueMin(newKarma[VIRT_JUSTICE], -10, 1);
746 		AdjustValueMin(newKarma[VIRT_HONOR], -10, 1);
747 		break;
748 	case KA_DIDNT_CHEAT_REAGENTS:
749 		timeLimited = true;
750 		AdjustValueMax(newKarma[VIRT_HONESTY], 2, maxVal[VIRT_HONESTY]);
751 		AdjustValueMax(newKarma[VIRT_JUSTICE], 2, maxVal[VIRT_JUSTICE]);
752 		AdjustValueMax(newKarma[VIRT_HONOR], 2, maxVal[VIRT_HONOR]);
753 		break;
754 	case KA_USED_SKULL:
755 		/* using the skull is very, very bad... */
756 		for (v = 0; v < VIRT_MAX; v++)
757 			AdjustValueMin(newKarma[v], -5, 1);
758 		break;
759 	case KA_DESTROYED_SKULL:
760 		/* ...but destroying it is very, very good */
761 		for (v = 0; v < VIRT_MAX; v++)
762 			AdjustValueMax(newKarma[v], 10, maxVal[v]);
763 		break;
764 	}
765 
766 	/*
767 	 * check if enough time has passed since last virtue award if
768 	 * action is time limited -- if not, throw away new values
769 	 */
770 	if (timeLimited) {
771 		if (((_saveGame->_moves / 16) >= 0x10000) || (((_saveGame->_moves / 16) & 0xFFFF) != _saveGame->_lastVirtue))
772 			_saveGame->_lastVirtue = (_saveGame->_moves / 16) & 0xFFFF;
773 		else
774 			return;
775 	}
776 
777 	/* something changed */
778 	notifyOfChange();
779 
780 	/*
781 	 * return to u4dos compatibility and handle losing of eighths
782 	 */
783 	for (v = 0; v < VIRT_MAX; v++) {
784 		if (maxVal[v] == 100) { /* already an avatar */
785 			if (newKarma[v] < 100) { /* but lost it */
786 				_saveGame->_karma[v] = newKarma[v];
787 				setChanged();
788 				PartyEvent event(PartyEvent::LOST_EIGHTH, 0);
789 				notifyObservers(event);
790 			} else _saveGame->_karma[v] = 0; /* return to u4dos compatibility */
791 		} else _saveGame->_karma[v] = newKarma[v];
792 	}
793 }
794 
applyEffect(TileEffect effect)795 void Party::applyEffect(TileEffect effect) {
796 	int i;
797 
798 	for (i = 0; i < size(); i++) {
799 		switch (effect) {
800 		case EFFECT_NONE:
801 		case EFFECT_ELECTRICITY:
802 			_members[i]->applyEffect(effect);
803 			break;
804 		case EFFECT_LAVA:
805 		case EFFECT_FIRE:
806 		case EFFECT_SLEEP:
807 			if (xu4_random(2) == 0)
808 				_members[i]->applyEffect(effect);
809 			break;
810 		case EFFECT_POISONFIELD:
811 		case EFFECT_POISON:
812 			if (xu4_random(5) == 0)
813 				_members[i]->applyEffect(effect);
814 			break;
815 		default:
816 			break;
817 		}
818 	}
819 }
820 
attemptElevation(Virtue virtue)821 bool Party::attemptElevation(Virtue virtue) {
822 	if (_saveGame->_karma[virtue] == 99) {
823 		_saveGame->_karma[virtue] = 0;
824 		notifyOfChange();
825 		return true;
826 	} else
827 		return false;
828 }
829 
burnTorch(int turns)830 void Party::burnTorch(int turns) {
831 	_torchDuration -= turns;
832 	if (_torchDuration <= 0)
833 		_torchDuration = 0;
834 
835 	_saveGame->_torchDuration = _torchDuration;
836 
837 	notifyOfChange();
838 }
839 
canEnterShrine(Virtue virtue)840 bool Party::canEnterShrine(Virtue virtue) {
841 	if (_saveGame->_runes & (1 << (int) virtue))
842 		return true;
843 	else
844 		return false;
845 }
846 
canPersonJoin(Common::String name,Virtue * v)847 bool Party::canPersonJoin(Common::String name, Virtue *v) {
848 	int i;
849 
850 	if (name.empty())
851 		return 0;
852 
853 	for (i = 1; i < 8; i++) {
854 		if (name == _saveGame->_players[i]._name) {
855 			if (v)
856 				*v = (Virtue) _saveGame->_players[i]._class;
857 			return true;
858 		}
859 	}
860 	return false;
861 }
862 
damageShip(uint pts)863 void Party::damageShip(uint pts) {
864 	_saveGame->_shipHull -= pts;
865 	if ((short)_saveGame->_shipHull < 0)
866 		_saveGame->_shipHull = 0;
867 
868 	notifyOfChange();
869 }
870 
donate(int quantity)871 bool Party::donate(int quantity) {
872 	if (quantity > _saveGame->_gold)
873 		return false;
874 
875 	adjustGold(-quantity);
876 	if (_saveGame->_gold > 0)
877 		adjustKarma(KA_GAVE_TO_BEGGAR);
878 	else adjustKarma(KA_GAVE_ALL_TO_BEGGAR);
879 
880 	return true;
881 }
882 
endTurn()883 void Party::endTurn() {
884 	int i;
885 
886 	_saveGame->_moves++;
887 
888 	for (i = 0; i < size(); i++) {
889 
890 		/* Handle player status (only for non-combat turns) */
891 		if ((g_context->_location->_context & CTX_NON_COMBAT) == g_context->_location->_context) {
892 
893 			/* party members eat food (also non-combat) */
894 			if (!_members[i]->isDead())
895 				adjustFood(-1);
896 
897 			switch (_members[i]->getStatus()) {
898 			case STAT_SLEEPING:
899 				if (xu4_random(5) == 0)
900 					_members[i]->wakeUp();
901 				break;
902 
903 			case STAT_POISONED:
904 				/* SOLUS
905 				 * shouldn't play poison damage sound in combat,
906 				 * yet if the PC takes damage just befor combat
907 				 * begins, the sound is played  after the combat
908 				 * screen appears
909 				 */
910 				soundPlay(SOUND_POISON_DAMAGE, false);
911 				_members[i]->applyDamage(2);
912 				break;
913 
914 			default:
915 				break;
916 			}
917 		}
918 
919 		/* regenerate magic points */
920 		if (!_members[i]->isDisabled() && _members[i]->getMp() < _members[i]->getMaxMp())
921 			_saveGame->_players[i]._mp++;
922 	}
923 
924 	/* The party is starving! */
925 	if ((_saveGame->_food == 0) && ((g_context->_location->_context & CTX_NON_COMBAT) == g_context->_location->_context)) {
926 		setChanged();
927 		PartyEvent event(PartyEvent::STARVING, 0);
928 		notifyObservers(event);
929 	}
930 
931 	/* heal ship (25% chance it is healed each turn) */
932 	if ((g_context->_location->_context == CTX_WORLDMAP) && (_saveGame->_shipHull < 50) && xu4_random(4) == 0)
933 		healShip(1);
934 }
935 
getChest()936 int Party::getChest() {
937 	int gold = xu4_random(50) + xu4_random(8) + 10;
938 	adjustGold(gold);
939 
940 	return gold;
941 }
942 
getTorchDuration() const943 int Party::getTorchDuration() const {
944 	return _torchDuration;
945 }
946 
healShip(uint pts)947 void Party::healShip(uint pts) {
948 	_saveGame->_shipHull += pts;
949 	if (_saveGame->_shipHull > 50)
950 		_saveGame->_shipHull = 50;
951 
952 	notifyOfChange();
953 }
954 
isFlying() const955 bool Party::isFlying() const {
956 	return (_saveGame->_balloonState && _torchDuration <= 0);
957 }
958 
isImmobilized()959 bool Party::isImmobilized() {
960 	int i;
961 	bool immobile = true;
962 
963 	for (i = 0; i < _saveGame->_members; i++) {
964 		if (!_members[i]->isDisabled())
965 			immobile = false;
966 	}
967 
968 	return immobile;
969 }
970 
isDead()971 bool Party::isDead() {
972 	int i;
973 	bool dead = true;
974 
975 	for (i = 0; i < _saveGame->_members; i++) {
976 		if (!_members[i]->isDead()) {
977 			dead = false;
978 		}
979 	}
980 
981 	return dead;
982 }
983 
isPersonJoined(Common::String name)984 bool Party::isPersonJoined(Common::String name) {
985 	int i;
986 
987 	if (name.empty())
988 		return false;
989 
990 	for (i = 1; i < _saveGame->_members; i++) {
991 		if (name == _saveGame->_players[i]._name)
992 			return true;
993 	}
994 	return false;
995 }
996 
join(Common::String name)997 CannotJoinError Party::join(Common::String name) {
998 	int i;
999 	SaveGamePlayerRecord tmp;
1000 
1001 	for (i = _saveGame->_members; i < 8; i++) {
1002 		if (name == _saveGame->_players[i]._name) {
1003 
1004 			/* ensure avatar is experienced enough */
1005 			if (_saveGame->_members + 1 > (_saveGame->_players[0]._hpMax / 100))
1006 				return JOIN_NOT_EXPERIENCED;
1007 
1008 			/* ensure character has enough karma */
1009 			if ((_saveGame->_karma[_saveGame->_players[i]._class] > 0) &&
1010 			        (_saveGame->_karma[_saveGame->_players[i]._class] < 40))
1011 				return JOIN_NOT_VIRTUOUS;
1012 
1013 			tmp = _saveGame->_players[_saveGame->_members];
1014 			_saveGame->_players[_saveGame->_members] = _saveGame->_players[i];
1015 			_saveGame->_players[i] = tmp;
1016 
1017 			_members.push_back(new PartyMember(this, &_saveGame->_players[_saveGame->_members++]));
1018 			setChanged();
1019 			PartyEvent event(PartyEvent::MEMBER_JOINED, _members.back());
1020 			notifyObservers(event);
1021 			return JOIN_SUCCEEDED;
1022 		}
1023 	}
1024 
1025 	return JOIN_NOT_EXPERIENCED;
1026 }
1027 
lightTorch(int duration,bool loseTorch)1028 bool Party::lightTorch(int duration, bool loseTorch) {
1029 	if (loseTorch) {
1030 		if (g_ultima->_saveGame->_torches <= 0)
1031 			return false;
1032 		g_ultima->_saveGame->_torches--;
1033 	}
1034 
1035 	_torchDuration += duration;
1036 	_saveGame->_torchDuration = _torchDuration;
1037 
1038 	notifyOfChange();
1039 
1040 	return true;
1041 }
1042 
quenchTorch()1043 void Party::quenchTorch() {
1044 	_torchDuration = _saveGame->_torchDuration = 0;
1045 
1046 	notifyOfChange();
1047 }
1048 
reviveParty()1049 void Party::reviveParty() {
1050 	int i;
1051 
1052 	for (i = 0; i < size(); i++) {
1053 		_members[i]->wakeUp();
1054 		_members[i]->setStatus(STAT_GOOD);
1055 		_saveGame->_players[i]._hp = _saveGame->_players[i]._hpMax;
1056 	}
1057 
1058 	for (i = ARMR_NONE + 1; i < ARMR_MAX; i++)
1059 		_saveGame->_armor[i] = 0;
1060 	for (i = WEAP_HANDS + 1; i < WEAP_MAX; i++)
1061 		_saveGame->_weapons[i] = 0;
1062 	_saveGame->_food = 20099;
1063 	_saveGame->_gold = 200;
1064 	setTransport(g_tileSets->findTileByName("avatar")->getId());
1065 	setChanged();
1066 	PartyEvent event(PartyEvent::PARTY_REVIVED, 0);
1067 	notifyObservers(event);
1068 }
1069 
getTransport() const1070 MapTile Party::getTransport() const {
1071 	return _transport;
1072 }
1073 
setTransport(MapTile tile)1074 void Party::setTransport(MapTile tile) {
1075 	// transport value stored in savegame hardcoded to index into base tilemap
1076 	_saveGame->_transport = g_tileMaps->get("base")->untranslate(tile);
1077 	assertMsg(_saveGame->_transport != 0, "could not generate valid savegame transport for tile with id %d\n", tile._id);
1078 
1079 	_transport = tile;
1080 
1081 	if (tile.getTileType()->isHorse())
1082 		g_context->_transportContext = TRANSPORT_HORSE;
1083 	else if (tile.getTileType()->isShip())
1084 		g_context->_transportContext = TRANSPORT_SHIP;
1085 	else if (tile.getTileType()->isBalloon())
1086 		g_context->_transportContext = TRANSPORT_BALLOON;
1087 	else g_context->_transportContext = TRANSPORT_FOOT;
1088 
1089 	notifyOfChange();
1090 }
1091 
setShipHull(int str)1092 void Party::setShipHull(int str) {
1093 	int newStr = str;
1094 	AdjustValue(newStr, 0, 99, 0);
1095 
1096 	if (_saveGame->_shipHull != newStr) {
1097 		_saveGame->_shipHull = newStr;
1098 		notifyOfChange();
1099 	}
1100 }
1101 
getDirection() const1102 Direction Party::getDirection() const {
1103 	return _transport.getDirection();
1104 }
1105 
setDirection(Direction dir)1106 void Party::setDirection(Direction dir) {
1107 	_transport.setDirection(dir);
1108 }
1109 
adjustReagent(int reagent,int amt)1110 void Party::adjustReagent(int reagent, int amt) {
1111 	int oldVal = g_ultima->_saveGame->_reagents[reagent];
1112 	AdjustValue(g_ultima->_saveGame->_reagents[reagent], amt, 99, 0);
1113 
1114 	if (oldVal != g_ultima->_saveGame->_reagents[reagent]) {
1115 		notifyOfChange();
1116 	}
1117 }
1118 
getReagent(int reagent) const1119 int Party::getReagent(int reagent) const {
1120 	return g_ultima->_saveGame->_reagents[reagent];
1121 }
1122 
getReagentPtr(int reagent) const1123 short *Party::getReagentPtr(int reagent) const {
1124 	return &g_ultima->_saveGame->_reagents[reagent];
1125 }
1126 
setActivePlayer(int p)1127 void Party::setActivePlayer(int p) {
1128 	_activePlayer = p;
1129 	setChanged();
1130 	PartyEvent event(PartyEvent::ACTIVE_PLAYER_CHANGED, _activePlayer < 0 ? 0 : _members[_activePlayer]);
1131 	notifyObservers(event);
1132 }
1133 
getActivePlayer() const1134 int Party::getActivePlayer() const {
1135 	return _activePlayer;
1136 }
1137 
swapPlayers(int p1,int p2)1138 void Party::swapPlayers(int p1, int p2) {
1139 	assertMsg(p1 < _saveGame->_members, "p1 out of range: %d", p1);
1140 	assertMsg(p2 < _saveGame->_members, "p2 out of range: %d", p2);
1141 
1142 	SaveGamePlayerRecord tmp = _saveGame->_players[p1];
1143 	_saveGame->_players[p1] = g_ultima->_saveGame->_players[p2];
1144 	g_ultima->_saveGame->_players[p2] = tmp;
1145 
1146 	syncMembers();
1147 
1148 	if (p1 == _activePlayer)
1149 		_activePlayer = p2;
1150 	else if (p2 == _activePlayer)
1151 		_activePlayer = p1;
1152 
1153 	notifyOfChange(0);
1154 }
1155 
syncMembers()1156 void Party::syncMembers() {
1157 	_members.clear();
1158 	for (int i = 0; i < _saveGame->_members; i++) {
1159 		// add the members to the party
1160 		_members.push_back(new PartyMember(this, &_saveGame->_players[i]));
1161 	}
1162 }
1163 
size() const1164 int Party::size() const {
1165 	return _members.size();
1166 }
1167 
member(int index) const1168 PartyMember *Party::member(int index) const {
1169 	return _members[index];
1170 }
1171 
1172 } // End of namespace Ultima4
1173 } // End of namespace Ultima
1174