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