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