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/creature.h"
24 #include "ultima/ultima4/game/context.h"
25 #include "ultima/ultima4/game/game.h"
26 #include "ultima/ultima4/game/player.h"
27 #include "ultima/ultima4/controllers/combat_controller.h"
28 #include "ultima/ultima4/core/config.h"
29 #include "ultima/ultima4/core/settings.h"
30 #include "ultima/ultima4/core/utils.h"
31 #include "ultima/ultima4/filesys/savegame.h"
32 #include "ultima/ultima4/gfx/screen.h"
33 #include "ultima/ultima4/gfx/textcolor.h"
34 #include "ultima/ultima4/map/location.h"
35 #include "ultima/ultima4/map/map.h"
36 #include "ultima/ultima4/map/tileset.h"
37
38 namespace Ultima {
39 namespace Ultima4 {
40
41 CreatureMgr *CreatureMgr::_instance = nullptr;
42
isCreature(Object * punknown)43 bool isCreature(Object *punknown) {
44 Creature *m;
45 if ((m = dynamic_cast<Creature *>(punknown)) != nullptr)
46 return true;
47 else
48 return false;
49 }
50
Creature(MapTile tile)51 Creature::Creature(MapTile tile) : Object(Object::CREATURE),
52 _id(0), _leader(0), _baseHp(0), _hp(0), _xp(0), _ranged(0),
53 _leavesTile(false), _mAttr(MATTR_STEALFOOD),
54 _movementAttr(MATTR_STATIONARY), _slowedType(SLOWED_BY_NOTHING),
55 _encounterSize(0), _resists(0), _spawn(0) {
56 const Creature *m = creatureMgr->getByTile(tile);
57 if (m)
58 *this = *m;
59 }
60
load(const ConfigElement & conf)61 void Creature::load(const ConfigElement &conf) {
62 uint idx;
63
64 static const struct {
65 const char *name;
66 uint mask;
67 } booleanAttributes[] = {
68 { "undead", MATTR_UNDEAD },
69 { "good", MATTR_GOOD },
70 { "swims", MATTR_WATER },
71 { "sails", MATTR_WATER },
72 { "cantattack", MATTR_NONATTACKABLE },
73 { "camouflage", MATTR_CAMOUFLAGE },
74 { "wontattack", MATTR_NOATTACK },
75 { "ambushes", MATTR_AMBUSHES },
76 { "incorporeal", MATTR_INCORPOREAL },
77 { "nochest", MATTR_NOCHEST },
78 { "divides", MATTR_DIVIDES },
79 { "forceOfNature", MATTR_FORCE_OF_NATURE }
80 };
81
82 /* steals="" */
83 static const struct {
84 const char *name;
85 uint mask;
86 } steals[] = {
87 { "food", MATTR_STEALFOOD },
88 { "gold", MATTR_STEALGOLD }
89 };
90
91 /* casts="" */
92 static const struct {
93 const char *name;
94 uint mask;
95 } casts[] = {
96 { "sleep", MATTR_CASTS_SLEEP },
97 { "negate", MATTR_NEGATE }
98 };
99
100 /* movement="" */
101 static const struct {
102 const char *name;
103 uint mask;
104 } movement[] = {
105 { "none", MATTR_STATIONARY },
106 { "wanders", MATTR_WANDERS }
107 };
108
109 /* boolean attributes that affect movement */
110 static const struct {
111 const char *name;
112 uint mask;
113 } movementBoolean[] = {
114 { "swims", MATTR_SWIMS },
115 { "sails", MATTR_SAILS },
116 { "flies", MATTR_FLIES },
117 { "teleports", MATTR_TELEPORT },
118 { "canMoveOntoCreatures", MATTR_CANMOVECREATURES },
119 { "canMoveOntoAvatar", MATTR_CANMOVEAVATAR }
120 };
121
122 static const struct {
123 const char *name;
124 TileEffect effect;
125 } effects[] = {
126 { "fire", EFFECT_FIRE },
127 { "poison", EFFECT_POISONFIELD },
128 { "sleep", EFFECT_SLEEP }
129 };
130
131 _name = conf.getString("name");
132 _id = static_cast<unsigned short>(conf.getInt("id"));
133
134 /* Get the leader if it's been included, otherwise the leader is itself */
135 _leader = static_cast<byte>(conf.getInt("leader", _id));
136
137 _xp = static_cast<unsigned short>(conf.getInt("exp"));
138 _ranged = conf.getBool("ranged");
139 setTile(g_tileSets->findTileByName(conf.getString("tile")));
140
141 setHitTile("hit_flash");
142 setMissTile("miss_flash");
143
144 _mAttr = static_cast<CreatureAttrib>(0);
145 _movementAttr = static_cast<CreatureMovementAttrib>(0);
146 _resists = 0;
147
148 /* get the encounter size */
149 _encounterSize = conf.getInt("encounterSize", 0);
150
151 /* get the base hp */
152 _baseHp = conf.getInt("basehp", 0);
153 /* adjust basehp according to battle difficulty setting */
154 if (settings._battleDiff == "Hard")
155 _baseHp *= 2;
156 if (settings._battleDiff == "Expert")
157 _baseHp *= 4;
158
159 /* get the camouflaged tile */
160 if (conf.exists("camouflageTile"))
161 _camouflageTile = conf.getString("camouflageTile");
162
163 /* get the ranged tile for world map attacks */
164 if (conf.exists("worldrangedtile"))
165 _worldRangedTile = conf.getString("worldrangedtile");
166
167 /* get ranged hit tile */
168 if (conf.exists("rangedhittile")) {
169 if (conf.getString("rangedhittile") == "random")
170 _mAttr = static_cast<CreatureAttrib>(_mAttr | MATTR_RANDOMRANGED);
171 else
172 setHitTile(conf.getString("rangedhittile"));
173 }
174
175 /* get ranged miss tile */
176 if (conf.exists("rangedmisstile")) {
177 if (conf.getString("rangedmisstile") == "random")
178 _mAttr = static_cast<CreatureAttrib>(_mAttr | MATTR_RANDOMRANGED);
179 else
180 setMissTile(conf.getString("rangedmisstile"));
181 }
182
183 /* find out if the creature leaves a tile behind on ranged attacks */
184 _leavesTile = conf.getBool("leavestile");
185
186 /* get effects that this creature is immune to */
187 for (idx = 0; idx < sizeof(effects) / sizeof(effects[0]); idx++) {
188 if (conf.getString("resists") == effects[idx].name) {
189 _resists = effects[idx].effect;
190 }
191 }
192
193 /* Load creature attributes */
194 for (idx = 0; idx < sizeof(booleanAttributes) / sizeof(booleanAttributes[0]); idx++) {
195 if (conf.getBool(booleanAttributes[idx].name)) {
196 _mAttr = static_cast<CreatureAttrib>(_mAttr | booleanAttributes[idx].mask);
197 }
198 }
199
200 /* Load boolean attributes that affect movement */
201 for (idx = 0; idx < sizeof(movementBoolean) / sizeof(movementBoolean[0]); idx++) {
202 if (conf.getBool(movementBoolean[idx].name)) {
203 _movementAttr = static_cast<CreatureMovementAttrib>(_movementAttr | movementBoolean[idx].mask);
204 }
205 }
206
207 /* steals="" */
208 for (idx = 0; idx < sizeof(steals) / sizeof(steals[0]); idx++) {
209 if (conf.getString("steals") == steals[idx].name) {
210 _mAttr = static_cast<CreatureAttrib>(_mAttr | steals[idx].mask);
211 }
212 }
213
214 /* casts="" */
215 for (idx = 0; idx < sizeof(casts) / sizeof(casts[0]); idx++) {
216 if (conf.getString("casts") == casts[idx].name) {
217 _mAttr = static_cast<CreatureAttrib>(_mAttr | casts[idx].mask);
218 }
219 }
220
221 /* movement="" */
222 for (idx = 0; idx < sizeof(movement) / sizeof(movement[0]); idx++) {
223 if (conf.getString("movement") == movement[idx].name) {
224 _movementAttr = static_cast<CreatureMovementAttrib>(_movementAttr | movement[idx].mask);
225 }
226 }
227
228 if (conf.exists("spawnsOnDeath")) {
229 _mAttr = static_cast<CreatureAttrib>(_mAttr | MATTR_SPAWNSONDEATH);
230 _spawn = static_cast<byte>(conf.getInt("spawnsOnDeath"));
231 }
232
233 /* Figure out which 'slowed' function to use */
234 _slowedType = SLOWED_BY_TILE;
235 if (sails())
236 /* sailing creatures (pirate ships) */
237 _slowedType = SLOWED_BY_WIND;
238 else if (flies() || isIncorporeal())
239 /* flying creatures (dragons, bats, etc.) and incorporeal creatures (ghosts, zorns) */
240 _slowedType = SLOWED_BY_NOTHING;
241 }
242
isAttackable() const243 bool Creature::isAttackable() const {
244 if (_mAttr & MATTR_NONATTACKABLE)
245 return false;
246 /* can't attack horse transport */
247 if (_tile.getTileType()->isHorse() && getMovementBehavior() == MOVEMENT_FIXED)
248 return false;
249 return true;
250 }
251
getDamage() const252 int Creature::getDamage() const {
253 int damage, val, x;
254 val = _baseHp;
255 x = xu4_random(val >> 2);
256 damage = (x >> 4) * 10;
257 damage += x % 10;
258 return damage;
259 }
260
setInitialHp(int points)261 int Creature::setInitialHp(int points) {
262 if (points < 0)
263 _hp = xu4_random(_baseHp) | (_baseHp / 2);
264 else
265 _hp = points;
266
267 /* make sure the creature doesn't flee initially */
268 if (_hp < 24) _hp = 24;
269
270 return _hp;
271 }
272
setRandomRanged()273 void Creature::setRandomRanged() {
274 switch (xu4_random(4)) {
275 case 0:
276 _rangedHitTile = _rangedMissTile = "poison_field";
277 break;
278 case 1:
279 _rangedHitTile = _rangedMissTile = "energy_field";
280 break;
281 case 2:
282 _rangedHitTile = _rangedMissTile = "fire_field";
283 break;
284 case 3:
285 _rangedHitTile = _rangedMissTile = "sleep_field";
286 break;
287 }
288 }
289
getState() const290 CreatureStatus Creature::getState() const {
291 int heavy_threshold, light_threshold, crit_threshold;
292
293 crit_threshold = _baseHp >> 2; /* (basehp / 4) */
294 heavy_threshold = _baseHp >> 1; /* (basehp / 2) */
295 light_threshold = crit_threshold + heavy_threshold;
296
297 if (_hp <= 0)
298 return MSTAT_DEAD;
299 else if (_hp < 24)
300 return MSTAT_FLEEING;
301 else if (_hp < crit_threshold)
302 return MSTAT_CRITICAL;
303 else if (_hp < heavy_threshold)
304 return MSTAT_HEAVILYWOUNDED;
305 else if (_hp < light_threshold)
306 return MSTAT_LIGHTLYWOUNDED;
307 else
308 return MSTAT_BARELYWOUNDED;
309
310 }
311
specialAction()312 bool Creature::specialAction() {
313 bool retval = false;
314
315 int dx = abs(g_context->_location->_coords.x - _coords.x);
316 int dy = abs(g_context->_location->_coords.y - _coords.y);
317 int mapdist = g_context->_location->_coords.distance(_coords, g_context->_location->_map);
318
319 /* find out which direction the avatar is in relation to the creature */
320 MapCoords mapcoords(_coords);
321 int dir = mapcoords.getRelativeDirection(g_context->_location->_coords, g_context->_location->_map);
322
323 //Init outside of switch
324 int broadsidesDirs = 0;
325
326 switch (_id) {
327 case LAVA_LIZARD_ID:
328 case SEA_SERPENT_ID:
329 case HYDRA_ID:
330 case DRAGON_ID:
331
332 /* A 50/50 chance they try to range attack when you're close enough
333 and not in a city
334 Note: Monsters in settlements in U3 do fire on party
335 */
336 if (mapdist <= 3 && xu4_random(2) == 0 && (g_context->_location->_context & CTX_CITY) == 0) {
337 Std::vector<Coords> path = gameGetDirectionalActionPath(dir, MASK_DIR_ALL, _coords,
338 1, 3, nullptr, false);
339 for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
340 if (creatureRangeAttack(*i, this))
341 break;
342 }
343 }
344
345 break;
346
347 case PIRATE_ID:
348
349 /* Fire cannon: Pirates only fire broadsides and only when they can hit you :) */
350 retval = true;
351 broadsidesDirs = dirGetBroadsidesDirs(_tile.getDirection());
352
353 if ((((dx == 0) && (dy <= 3)) || /* avatar is close enough and on the same column, OR */
354 ((dy == 0) && (dx <= 3))) && /* avatar is close enough and on the same row, AND */
355 ((broadsidesDirs & dir) > 0)) { /* pirate ship is firing broadsides */
356
357 // nothing (not even mountains!) can block cannonballs
358 Std::vector<Coords> path = gameGetDirectionalActionPath(dir, broadsidesDirs, _coords,
359 1, 3, nullptr, false);
360 for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
361 if (fireAt(*i, false))
362 break;
363 }
364 } else
365 retval = false;
366
367 break;
368
369 default:
370 break;
371 }
372
373 return retval;
374 }
375
specialEffect()376 bool Creature::specialEffect() {
377 Object *obj;
378 bool retval = false;
379
380 switch (_id) {
381 case STORM_ID: {
382 ObjectDeque::iterator i;
383
384 if (_coords == g_context->_location->_coords) {
385
386 /* damage the ship */
387 if (g_context->_transportContext == TRANSPORT_SHIP) {
388 /* FIXME: Check actual damage from u4dos */
389 gameDamageShip(10, 30);
390 }
391 /* anything else but balloon damages the party */
392 else if (g_context->_transportContext != TRANSPORT_BALLOON) {
393 /* FIXME: formula for twister damage is guesstimated from u4dos */
394 gameDamageParty(0, 75);
395 }
396 return true;
397 }
398
399 /* See if the storm is on top of any objects and destroy them! */
400 for (i = g_context->_location->_map->_objects.begin();
401 i != g_context->_location->_map->_objects.end();) {
402
403 obj = *i;
404 if (this != obj &&
405 obj->getCoords() == _coords) {
406 /* Converged with an object, destroy the object! */
407 i = g_context->_location->_map->removeObject(i);
408 retval = true;
409 } else i++;
410 }
411 }
412 break;
413
414 case WHIRLPOOL_ID: {
415 ObjectDeque::iterator i;
416
417 if (_coords == g_context->_location->_coords && (g_context->_transportContext == TRANSPORT_SHIP)) {
418
419 /* Deal 10 damage to the ship */
420 gameDamageShip(-1, 10);
421
422 /* Send the party to Locke Lake */
423 g_context->_location->_coords = g_context->_location->_map->getLabel("lockelake");
424
425 /* Teleport the whirlpool that sent you there far away from lockelake */
426 this->setCoords(Coords(0, 0, 0));
427 retval = true;
428 break;
429 }
430
431 /* See if the whirlpool is on top of any objects and destroy them! */
432 for (i = g_context->_location->_map->_objects.begin();
433 i != g_context->_location->_map->_objects.end();) {
434
435 obj = *i;
436
437 if (this != obj &&
438 obj->getCoords() == _coords) {
439
440 Creature *m = dynamic_cast<Creature *>(obj);
441
442 /* Make sure the object isn't a flying creature or object */
443 if (!m || (m && (m->swims() || m->sails()) && !m->flies())) {
444 /* Destroy the object it met with */
445 i = g_context->_location->_map->removeObject(i);
446 retval = true;
447 } else {
448 i++;
449 }
450 } else i++;
451 }
452 }
453
454 default:
455 break;
456 }
457
458 return retval;
459 }
460
act(CombatController * controller)461 void Creature::act(CombatController *controller) {
462 int dist;
463 CombatAction action;
464 Creature *target;
465
466 /* see if creature wakes up if it is asleep */
467 if ((getStatus() == STAT_SLEEPING) && (xu4_random(8) == 0))
468 wakeUp();
469
470 /* if the creature is still asleep, then do nothing */
471 if (getStatus() == STAT_SLEEPING)
472 return;
473
474 if (negates())
475 g_context->_aura->set(Aura::NEGATE, 2);
476
477 /*
478 * figure out what to do
479 */
480
481 // creatures who teleport do so 1/8 of the time
482 if (teleports() && xu4_random(8) == 0)
483 action = CA_TELEPORT;
484 // creatures who ranged attack do so 1/4 of the time. Make sure
485 // their ranged attack is not negated!
486 else if (_ranged != 0 && xu4_random(4) == 0 &&
487 (_rangedHitTile != "magic_flash" || (*g_context->_aura != Aura::NEGATE)))
488 action = CA_RANGED;
489 // creatures who cast sleep do so 1/4 of the time they don't ranged attack
490 else if (castsSleep() && (*g_context->_aura != Aura::NEGATE) && (xu4_random(4) == 0))
491 action = CA_CAST_SLEEP;
492 else if (getState() == MSTAT_FLEEING)
493 action = CA_FLEE;
494 // default action: attack (or move towards) closest target
495 else
496 action = CA_ATTACK;
497
498 /*
499 * now find out who to do it to
500 */
501
502 target = nearestOpponent(&dist, action == CA_RANGED);
503 if (target == nullptr)
504 return;
505
506 if (action == CA_ATTACK && dist > 1)
507 action = CA_ADVANCE;
508
509 /* let's see if the creature blends into the background, or if he appears... */
510 if (camouflages() && !hideOrShow())
511 return; /* creature is hidden -- no action! */
512
513 switch (action) {
514 case CA_ATTACK:
515 soundPlay(SOUND_NPC_ATTACK, false); // NPC_ATTACK, melee
516
517 if (controller->attackHit(this, target)) {
518 soundPlay(SOUND_PC_STRUCK, false); // PC_STRUCK, melee and ranged
519 GameController::flashTile(target->getCoords(), "hit_flash", 4);
520
521
522 if (!dealDamage(target, getDamage()))
523 target = nullptr;
524
525 if (target && isPartyMember(target)) {
526 /* steal gold if the creature steals gold */
527 if (stealsGold() && xu4_random(4) == 0) {
528 soundPlay(SOUND_ITEM_STOLEN, false); // ITEM_STOLEN, gold
529 g_context->_party->adjustGold(-(xu4_random(0x3f)));
530 }
531
532 /* steal food if the creature steals food */
533 if (stealsFood()) {
534 soundPlay(SOUND_ITEM_STOLEN, false); // ITEM_STOLEN, food
535 g_context->_party->adjustFood(-2500);
536 }
537 }
538 } else {
539 GameController::flashTile(target->getCoords(), "miss_flash", 1);
540 }
541 break;
542
543 case CA_CAST_SLEEP: {
544 g_screen->screenMessage("\nSleep!\n");
545
546 gameSpellEffect('s', -1, static_cast<Sound>(SOUND_MAGIC)); /* show the sleep spell effect */
547
548 /* Apply the sleep spell to party members still in combat */
549 if (!isPartyMember(this)) {
550 PartyMemberVector party = controller->getMap()->getPartyMembers();
551 PartyMemberVector::iterator j;
552
553 for (j = party.begin(); j != party.end(); j++) {
554 if (xu4_random(2) == 0)
555 (*j)->putToSleep();
556 }
557 }
558 break;
559 }
560
561 case CA_TELEPORT: {
562 Coords new_c;
563 bool valid = false;
564 bool firstTry = true;
565
566 while (!valid) {
567 Map *map = getMap();
568 new_c = Coords(xu4_random(map->_width), xu4_random(map->_height), g_context->_location->_coords.z);
569
570 const Tile *tile = map->tileTypeAt(new_c, WITH_OBJECTS);
571
572 if (tile->isCreatureWalkable()) {
573 /* If the tile would slow me down, try again! */
574 if (firstTry && tile->getSpeed() != FAST)
575 firstTry = false;
576 /* OK, good enough! */
577 else
578 valid = true;
579 }
580 }
581
582 /* Teleport! */
583 setCoords(new_c);
584 break;
585 }
586
587 case CA_RANGED: {
588 // if the creature has a random tile for a ranged weapon,
589 // let's switch it now!
590 if (hasRandomRanged())
591 setRandomRanged();
592
593 MapCoords m_coords = getCoords(),
594 p_coords = target->getCoords();
595
596 // figure out which direction to fire the weapon
597 int dir = m_coords.getRelativeDirection(p_coords);
598
599 soundPlay(SOUND_NPC_ATTACK, false); // NPC_ATTACK, ranged
600
601 Std::vector<Coords> path = gameGetDirectionalActionPath(dir, MASK_DIR_ALL, m_coords,
602 1, 11, &Tile::canAttackOverTile, false);
603 bool hit = false;
604 for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
605 if (controller->rangedAttack(*i, this)) {
606 hit = true;
607 break;
608 }
609 }
610 if (!hit && path.size() > 0)
611 controller->rangedMiss(path[path.size() - 1], this);
612
613 break;
614 }
615
616 case CA_FLEE:
617 case CA_ADVANCE: {
618 Map *map = getMap();
619 if (moveCombatObject(action, map, this, target->getCoords())) {
620 Coords coords = getCoords();
621
622 if (MAP_IS_OOB(map, coords)) {
623 g_screen->screenMessage("\n%c%s Flees!%c\n", FG_YELLOW, _name.c_str(), FG_WHITE);
624
625 /* Congrats, you have a heart! */
626 if (isGood())
627 g_context->_party->adjustKarma(KA_SPARED_GOOD);
628
629 map->removeObject(this);
630 }
631 }
632 break;
633 }
634 }
635 this->animateMovement();
636 }
637
addStatus(StatusType s)638 void Creature::addStatus(StatusType s) {
639 if (_status.size() && _status.back() > s) {
640 StatusType prev = _status.back();
641 _status.pop_back();
642 _status.push_back(s);
643 _status.push_back(prev);
644 } else _status.push_back(s);
645 }
646
applyTileEffect(TileEffect effect)647 void Creature::applyTileEffect(TileEffect effect) {
648 if (effect != EFFECT_NONE) {
649 gameUpdateScreen();
650
651 switch (effect) {
652 case EFFECT_SLEEP:
653 /* creature fell asleep! */
654 if ((_resists != EFFECT_SLEEP) &&
655 (xu4_random(0xFF) >= _hp))
656 putToSleep();
657 break;
658
659 case EFFECT_LAVA:
660 case EFFECT_FIRE:
661 /* deal 0 - 127 damage to the creature if it is not immune to fire damage */
662 if ((_resists != EFFECT_FIRE) && (_resists != EFFECT_LAVA))
663 applyDamage(xu4_random(0x7F), false);
664 break;
665
666 case EFFECT_POISONFIELD:
667 /* deal 0 - 127 damage to the creature if it is not immune to poison field damage */
668 if (_resists != EFFECT_POISONFIELD)
669 applyDamage(xu4_random(0x7F), false);
670 break;
671
672 case EFFECT_POISON:
673 default:
674 break;
675 }
676 }
677 }
678
getAttackBonus() const679 int Creature::getAttackBonus() const {
680 return 0;
681 }
682
getDefense() const683 int Creature::getDefense() const {
684 return 128;
685 }
686
divide()687 bool Creature::divide() {
688 Map *map = getMap();
689 int dirmask = map->getValidMoves(getCoords(), getTile());
690 Direction d = dirRandomDir(dirmask);
691
692 /* this is a game enhancement, make sure it's turned on! */
693 if (!settings._enhancements || !settings._enhancementsOptions._slimeDivides)
694 return false;
695
696 /* make sure there's a place to put the divided creature! */
697 if (d != DIR_NONE) {
698 MapCoords coords(getCoords());
699
700 g_screen->screenMessage("%s Divides!\n", _name.c_str());
701
702 /* find a spot to put our new creature */
703 coords.move(d, map);
704
705 /* create our new creature! */
706 Creature *addedCreature = map->addCreature(this, coords);
707 int dividedHp = (this->_hp + 1) / 2;
708 addedCreature->_hp = dividedHp;
709 this->_hp = dividedHp;
710 return true;
711 }
712 return false;
713 }
714
spawnOnDeath()715 bool Creature::spawnOnDeath() {
716 Map *map = getMap();
717
718 /* this is a game enhancement, make sure it's turned on! */
719 if (!settings._enhancements || !settings._enhancementsOptions._gazerSpawnsInsects)
720 return false;
721
722 /* make sure there's a place to put the divided creature! */
723 MapCoords coords(getCoords());
724
725 /* create our new creature! */
726 map->addCreature(creatureMgr->getById(_spawn), coords);
727 return true;
728 }
729
getStatus() const730 StatusType Creature::getStatus() const {
731 return _status.back();
732 }
733
isAsleep() const734 bool Creature::isAsleep() const {
735 for (StatusList::const_iterator itr = this->_status.begin();
736 itr != this->_status.end();
737 ++itr)
738 if (*itr == STAT_SLEEPING)
739 return true;
740 return false;
741 }
742
hideOrShow()743 bool Creature::hideOrShow() {
744 /* find the nearest opponent */
745 int dist;
746
747 /* ok, now we've got the nearest party member. Now, see if they're close enough */
748 if (nearestOpponent(&dist, false) != nullptr) {
749 if ((dist < 5) && !isVisible())
750 setVisible(); /* show yourself */
751 else if (dist >= 5)
752 setVisible(false); /* hide and take no action! */
753 }
754
755 return isVisible();
756 }
757
nearestOpponent(int * dist,bool ranged)758 Creature *Creature::nearestOpponent(int *dist, bool ranged) {
759 Creature *opponent = nullptr;
760 int d, leastDist = 0xFFFF;
761 ObjectDeque::iterator i;
762 bool jinx = (*g_context->_aura == Aura::JINX);
763 Map *map = getMap();
764
765 for (i = map->_objects.begin(); i != map->_objects.end(); ++i) {
766 if (!isCreature(*i))
767 continue;
768
769 bool amPlayer = isPartyMember(this);
770 bool fightingPlayer = isPartyMember(*i);
771
772 /* if a party member, find a creature. If a creature, find a party member */
773 /* if jinxed is false, find anything that isn't self */
774 if ((amPlayer != fightingPlayer) ||
775 (jinx && !amPlayer && *i != this)) {
776 MapCoords objCoords = (*i)->getCoords();
777
778 /* if ranged, get the distance using diagonals, otherwise get movement distance */
779 if (ranged)
780 d = objCoords.distance(getCoords());
781 else d = objCoords.movementDistance(getCoords());
782
783 /* skip target 50% of time if same distance */
784 if (d < leastDist || (d == leastDist && xu4_random(2) == 0)) {
785 opponent = dynamic_cast<Creature *>(*i);
786 leastDist = d;
787 }
788 }
789 }
790
791 if (opponent)
792 *dist = leastDist;
793
794 return opponent;
795 }
796
putToSleep()797 void Creature::putToSleep() {
798 if (getStatus() != STAT_DEAD) {
799 addStatus(STAT_SLEEPING);
800 setAnimated(false); /* freeze creature */
801 }
802 }
803
removeStatus(StatusType s)804 void Creature::removeStatus(StatusType s) {
805 StatusList::iterator i;
806 for (i = _status.begin(); i != _status.end();) {
807 if (*i == s)
808 i = _status.erase(i);
809 else
810 i++;
811 }
812
813 // Just to be sure, if a player is poisoned from a savegame, then they won't have
814 // a STAT_GOOD in the stack yet.
815 if (_status.empty())
816 addStatus(STAT_GOOD);
817 }
818
setStatus(StatusType s)819 void Creature::setStatus(StatusType s) {
820 _status.clear();
821 this->addStatus(s);
822 }
823
wakeUp()824 void Creature::wakeUp() {
825 removeStatus(STAT_SLEEPING);
826 setAnimated(); /* reanimate creature */
827 }
828
applyDamage(int damage,bool byplayer)829 bool Creature::applyDamage(int damage, bool byplayer) {
830 /* deal the damage */
831 if (_id != LORDBRITISH_ID)
832 AdjustValueMin(_hp, -damage, 0);
833
834 switch (getState()) {
835 case MSTAT_DEAD:
836 if (byplayer)
837 g_screen->screenMessage("%c%s Killed!%c\nExp. %d\n", FG_RED, _name.c_str(), FG_WHITE, _xp);
838 else
839 g_screen->screenMessage("%c%s Killed!%c\n", FG_RED, _name.c_str(), FG_WHITE);
840
841 /*
842 * the creature is dead; let it spawns something else on
843 * death (e.g. a gazer that spawns insects like in u5)
844 * then remove it
845 */
846 if (spawnsOnDeath())
847 spawnOnDeath();
848
849 // Remove yourself from the map
850 remove();
851 return false;
852
853 case MSTAT_FLEEING:
854 g_screen->screenMessage("%c%s Fleeing!%c\n", FG_YELLOW, _name.c_str(), FG_WHITE);
855 break;
856
857 case MSTAT_CRITICAL:
858 g_screen->screenMessage("%s Critical!\n", _name.c_str());
859 break;
860
861 case MSTAT_HEAVILYWOUNDED:
862 g_screen->screenMessage("%s Heavily Wounded!\n", _name.c_str());
863 break;
864
865 case MSTAT_LIGHTLYWOUNDED:
866 g_screen->screenMessage("%s Lightly Wounded!\n", _name.c_str());
867 break;
868
869 case MSTAT_BARELYWOUNDED:
870 g_screen->screenMessage("%s Barely Wounded!\n", _name.c_str());
871 break;
872 }
873
874 /* creature is still alive and has the chance to divide - xu4 enhancement */
875 if (divides() && xu4_random(2) == 0)
876 divide();
877
878 return true;
879 }
880
dealDamage(Creature * m,int damage)881 bool Creature::dealDamage(Creature *m, int damage) {
882 return m->applyDamage(damage, isPartyMember(this));
883 }
884
885 /**
886 * CreatureMgr class implementation
887 */
getInstance()888 CreatureMgr *CreatureMgr::getInstance() {
889 if (_instance == nullptr) {
890 _instance = new CreatureMgr();
891 _instance->loadAll();
892 }
893 return _instance;
894 }
895
loadAll()896 void CreatureMgr::loadAll() {
897 const Config *config = Config::getInstance();
898 Std::vector<ConfigElement> creatureConfs = config->getElement("creatures").getChildren();
899
900 for (Std::vector<ConfigElement>::iterator i = creatureConfs.begin(); i != creatureConfs.end(); i++) {
901 if (i->getName() != "creature")
902 continue;
903
904 Creature *m = new Creature(0);
905 m->load(*i);
906
907 /* add the creature to the list */
908 _creatures[m->getId()] = m;
909 }
910 }
911
getByTile(MapTile tile)912 Creature *CreatureMgr::getByTile(MapTile tile) {
913 CreatureMap::const_iterator i;
914
915 for (i = _creatures.begin(); i != _creatures.end(); i++) {
916 if (i->_value->getTile() == tile)
917 return i->_value;
918 }
919
920 // if (tile.id)
921 // warning("Did not find creature for tile %d", tile.id);
922 return nullptr;
923 }
924
getById(CreatureId id)925 Creature *CreatureMgr::getById(CreatureId id) {
926 CreatureMap::const_iterator i = _creatures.find(id);
927 if (i != _creatures.end())
928 return i->_value;
929 else
930 return nullptr;
931 }
932
getByName(Common::String name)933 Creature *CreatureMgr::getByName(Common::String name) {
934 CreatureMap::const_iterator i;
935 for (i = _creatures.begin(); i != _creatures.end(); i++) {
936 if (scumm_stricmp(i->_value->getName().c_str(), name.c_str()) == 0)
937 return i->_value;
938 }
939 return nullptr;
940 }
941
randomForTile(const Tile * tile)942 Creature *CreatureMgr::randomForTile(const Tile *tile) {
943 /* FIXME: this is too dependent on the tile system, and easily
944 broken when tileset changes are made. Methinks the logic
945 behind this should be moved to monsters.xml or another conf
946 file */
947
948 int era;
949 TileId randTile;
950
951 if (tile->isSailable()) {
952 randTile = _creatures.find(PIRATE_ID)->_value->getTile().getId();
953 randTile += xu4_random(7);
954 return getByTile(randTile);
955 } else if (tile->isSwimable()) {
956 randTile = _creatures.find(NIXIE_ID)->_value->getTile().getId();
957 randTile += xu4_random(5);
958 return getByTile(randTile);
959 }
960
961 if (!tile->isCreatureWalkable())
962 return nullptr;
963
964 //if (c->saveGame->_moves > 100000) // FIXME: what's 100,000 moves all about (if anything)?
965 if (g_ultima->_saveGame->_moves > 30000)
966 era = 0x0f;
967 else if (g_ultima->_saveGame->_moves > 20000)
968 era = 0x07;
969 else
970 era = 0x03;
971
972 randTile = _creatures.find(ORC_ID)->_value->getTile().getId();
973 randTile += era & xu4_random(0x10) & xu4_random(0x10);
974 return getByTile(randTile);
975 }
976
randomForDungeon(int dngLevel)977 Creature *CreatureMgr::randomForDungeon(int dngLevel) {
978 int adjustedDngLevel = dngLevel + 1;
979 size_t range = adjustedDngLevel < 5 ? 3 : 4;
980 CreatureId monster = STORM_ID + adjustedDngLevel + xu4_random(range);
981 if (monster >= MIMIC_ID)
982 ++monster;
983
984 return getById(monster);
985 }
986
randomAmbushing()987 Creature *CreatureMgr::randomAmbushing() {
988 CreatureMap::const_iterator i;
989 int numAmbushingCreatures = 0,
990 randCreature;
991
992 /* first, find out how many creatures exist that might ambush you */
993 for (i = _creatures.begin(); i != _creatures.end(); i++) {
994 if (i->_value->ambushes())
995 numAmbushingCreatures++;
996 }
997
998 if (numAmbushingCreatures > 0) {
999 /* now, randomely select one of them */
1000 randCreature = xu4_random(numAmbushingCreatures);
1001 numAmbushingCreatures = 0;
1002
1003 /* now, find the one we selected */
1004 for (i = _creatures.begin(); i != _creatures.end(); i++) {
1005 if (i->_value->ambushes()) {
1006 /* found the creature - return it! */
1007 if (numAmbushingCreatures == randCreature)
1008 return i->_value;
1009 /* move on to the next creature */
1010 else
1011 numAmbushingCreatures++;
1012 }
1013 }
1014 }
1015
1016 error("failed to find an ambushing creature");
1017 return nullptr;
1018 }
1019
1020 } // End of namespace Ultima4
1021 } // End of namespace Ultima
1022