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/ultima4.h"
24 #include "ultima/ultima4/controllers/camp_controller.h"
25 #include "ultima/ultima4/controllers/combat_controller.h"
26 #include "ultima/ultima4/controllers/intro_controller.h"
27 #include "ultima/ultima4/controllers/read_choice_controller.h"
28 #include "ultima/ultima4/controllers/read_dir_controller.h"
29 #include "ultima/ultima4/controllers/read_int_controller.h"
30 #include "ultima/ultima4/controllers/read_player_controller.h"
31 #include "ultima/ultima4/controllers/read_string_controller.h"
32 #include "ultima/ultima4/conversation/conversation.h"
33 #include "ultima/ultima4/core/config.h"
34 #include "ultima/ultima4/core/debugger.h"
35 #include "ultima/ultima4/core/settings.h"
36 #include "ultima/ultima4/core/utils.h"
37 #include "ultima/ultima4/events/event_handler.h"
38 #include "ultima/ultima4/filesys/savegame.h"
39 #include "ultima/ultima4/game/game.h"
40 #include "ultima/ultima4/game/armor.h"
41 #include "ultima/ultima4/game/context.h"
42 #include "ultima/ultima4/game/death.h"
43 #include "ultima/ultima4/game/item.h"
44 #include "ultima/ultima4/views/menu.h"
45 #include "ultima/ultima4/game/creature.h"
46 #include "ultima/ultima4/game/moongate.h"
47 #include "ultima/ultima4/game/names.h"
48 #include "ultima/ultima4/game/person.h"
49 #include "ultima/ultima4/game/player.h"
50 #include "ultima/ultima4/game/portal.h"
51 #include "ultima/ultima4/game/spell.h"
52 #include "ultima/ultima4/views/stats.h"
53 #include "ultima/ultima4/game/script.h"
54 #include "ultima/ultima4/game/weapon.h"
55 #include "ultima/ultima4/gfx/imagemgr.h"
56 #include "ultima/ultima4/gfx/screen.h"
57 #include "ultima/ultima4/map/city.h"
58 #include "ultima/ultima4/map/annotation.h"
59 #include "ultima/ultima4/map/dungeon.h"
60 #include "ultima/ultima4/map/direction.h"
61 #include "ultima/ultima4/map/location.h"
62 #include "ultima/ultima4/map/mapmgr.h"
63 #include "ultima/ultima4/map/movement.h"
64 #include "ultima/ultima4/map/shrine.h"
65 #include "ultima/ultima4/map/tilemap.h"
66 #include "ultima/ultima4/map/tileset.h"
67 #include "ultima/ultima4/sound/music.h"
68 #include "ultima/ultima4/sound/sound.h"
69 #include "ultima/ultima4/views/dungeonview.h"
70 #include "ultima/ultima4/meta_engine.h"
71 #include "common/savefile.h"
72 #include "common/system.h"
73 
74 namespace Ultima {
75 namespace Ultima4 {
76 
77 /*-----------------*/
78 /* Functions BEGIN */
79 
80 /* main game functions */
81 void gameAdvanceLevel(PartyMember *player);
82 void gameInnHandler();
83 void gameLostEighth(Virtue virtue);
84 void gamePartyStarving();
85 
86 void mixReagentsSuper();
87 
88 /* action functions */
89 void wearArmor(int player = -1);
90 
91 /* Functions END */
92 /*---------------*/
93 
gameSetViewMode(ViewMode newMode)94 void gameSetViewMode(ViewMode newMode) {
95 	g_context->_location->_viewMode = newMode;
96 }
97 
gameUpdateScreen()98 void gameUpdateScreen() {
99 	switch (g_context->_location->_viewMode) {
100 	case VIEW_NORMAL:
101 		g_screen->screenUpdate(&g_game->_mapArea, true, false);
102 		break;
103 	case VIEW_GEM:
104 		g_screen->screenGemUpdate();
105 		break;
106 	case VIEW_RUNE:
107 		g_screen->screenUpdate(&g_game->_mapArea, false, false);
108 		break;
109 	case VIEW_DUNGEON:
110 		g_screen->screenUpdate(&g_game->_mapArea, true, false);
111 		break;
112 	case VIEW_DEAD:
113 		g_screen->screenUpdate(&g_game->_mapArea, true, true);
114 		break;
115 	case VIEW_CODEX: /* the screen updates will be handled elsewhere */
116 		break;
117 	case VIEW_MIXTURES: /* still testing */
118 		break;
119 	default:
120 		error("invalid view mode: %d", g_context->_location->_viewMode);
121 	}
122 }
123 
gameSpellEffect(int spell,int player,Sound sound)124 void gameSpellEffect(int spell, int player, Sound sound) {
125 	int time;
126 	Spell::SpecialEffects effect = Spell::SFX_INVERT;
127 
128 	if (player >= 0)
129 		g_context->_stats->highlightPlayer(player);
130 
131 	time = settings._spellEffectSpeed * 800 / settings._gameCyclesPerSecond;
132 	soundPlay(sound, false, time);
133 
134 	///The following effect multipliers are not accurate
135 	switch (spell) {
136 	case 'g': /* gate */
137 	case 'r': /* resurrection */
138 		break;
139 	case 't': /* tremor */
140 		effect = Spell::SFX_TREMOR;
141 		break;
142 	default:
143 		/* default spell effect */
144 		break;
145 	}
146 
147 	switch (effect) {
148 	case Spell::SFX_TREMOR:
149 	case Spell::SFX_INVERT:
150 		gameUpdateScreen();
151 		g_game->_mapArea.highlight(0, 0, VIEWPORT_W * TILE_WIDTH, VIEWPORT_H * TILE_HEIGHT);
152 		g_screen->update();
153 		EventHandler::sleep(time);
154 		g_game->_mapArea.unhighlight();
155 		g_screen->update();
156 
157 		if (effect == Spell::SFX_TREMOR) {
158 			gameUpdateScreen();
159 			g_screen->update();
160 			soundPlay(SOUND_RUMBLE, false);
161 			g_screen->screenShake(8);
162 		}
163 
164 		break;
165 	default:
166 		break;
167 	}
168 }
169 
gameGetInput(int maxlen)170 Common::String gameGetInput(int maxlen) {
171 	g_screen->screenEnableCursor();
172 	g_screen->screenShowCursor();
173 #ifdef IOS_ULTIMA4
174 	U4IOS::IOSConversationHelper helper;
175 	helper.beginConversation(U4IOS::UIKeyboardTypeDefault);
176 #endif
177 
178 	return ReadStringController::get(maxlen, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
179 }
180 
gameGetPlayer(bool canBeDisabled,bool canBeActivePlayer)181 int gameGetPlayer(bool canBeDisabled, bool canBeActivePlayer) {
182 	int player;
183 	if (g_ultima->_saveGame->_members <= 1) {
184 		player = 0;
185 	} else {
186 		if (canBeActivePlayer && (g_context->_party->getActivePlayer() >= 0)) {
187 			player = g_context->_party->getActivePlayer();
188 		} else {
189 			ReadPlayerController readPlayerController;
190 			eventHandler->pushController(&readPlayerController);
191 			player = readPlayerController.waitFor();
192 		}
193 
194 		if (player == -1) {
195 			g_screen->screenMessage("None\n");
196 			return -1;
197 		}
198 	}
199 
200 	g_context->_col--;// display the selected character name, in place of the number
201 	if ((player >= 0) && (player < 8)) {
202 		g_screen->screenMessage("%s\n", g_ultima->_saveGame->_players[player]._name); //Write player's name after prompt
203 	}
204 
205 	if (!canBeDisabled && g_context->_party->member(player)->isDisabled()) {
206 		g_screen->screenMessage("%cDisabled!%c\n", FG_GREY, FG_WHITE);
207 		return -1;
208 	}
209 
210 	assertMsg(player < g_context->_party->size(), "player %d, but only %d members\n", player, g_context->_party->size());
211 	return player;
212 }
213 
gameGetDirection()214 Direction gameGetDirection() {
215 	ReadDirController dirController;
216 
217 	g_screen->screenMessage("Dir?");
218 #ifdef IOS_ULTIMA4
219 	U4IOS::IOSDirectionHelper directionPopup;
220 #endif
221 
222 	eventHandler->pushController(&dirController);
223 	Direction dir = dirController.waitFor();
224 
225 	g_screen->screenMessage("\b\b\b\b");
226 
227 	if (dir == DIR_NONE) {
228 		g_screen->screenMessage("    \n");
229 		return dir;
230 	} else {
231 		g_screen->screenMessage("%s\n", getDirectionName(dir));
232 		return dir;
233 	}
234 }
235 
fireAt(const Coords & coords,bool originAvatar)236 bool fireAt(const Coords &coords, bool originAvatar) {
237 	bool validObject = false;
238 	bool hitsAvatar = false;
239 	bool objectHit = false;
240 
241 	Object *obj = nullptr;
242 
243 
244 	MapTile tile(g_context->_location->_map->_tileSet->getByName("miss_flash")->getId());
245 	GameController::flashTile(coords, tile, 1);
246 
247 	obj = g_context->_location->_map->objectAt(coords);
248 	Creature *m = dynamic_cast<Creature *>(obj);
249 
250 	if (obj && obj->getType() == Object::CREATURE && m && m->isAttackable())
251 		validObject = true;
252 	/* See if it's an object to be destroyed (the avatar cannot destroy the balloon) */
253 	else if (obj &&
254 	         (obj->getType() == Object::UNKNOWN) &&
255 	         !(obj->getTile().getTileType()->isBalloon() && originAvatar))
256 		validObject = true;
257 
258 	/* Does the cannon hit the avatar? */
259 	if (coords == g_context->_location->_coords) {
260 		validObject = true;
261 		hitsAvatar = true;
262 	}
263 
264 	if (validObject) {
265 		/* always displays as a 'hit' though the object may not be destroyed */
266 
267 		/* Is is a pirate ship firing at US? */
268 		if (hitsAvatar) {
269 			GameController::flashTile(coords, "hit_flash", 4);
270 
271 			if (g_context->_transportContext == TRANSPORT_SHIP)
272 				gameDamageShip(-1, 10);
273 			else gameDamageParty(10, 25); /* party gets hurt between 10-25 damage */
274 		}
275 		/* inanimate objects get destroyed instantly, while creatures get a chance */
276 		else if (obj->getType() == Object::UNKNOWN) {
277 			GameController::flashTile(coords, "hit_flash", 4);
278 			g_context->_location->_map->removeObject(obj);
279 		}
280 
281 		/* only the avatar can hurt other creatures with cannon fire */
282 		else if (originAvatar) {
283 			GameController::flashTile(coords, "hit_flash", 4);
284 			if (xu4_random(4) == 0) /* reverse-engineered from u4dos */
285 				g_context->_location->_map->removeObject(obj);
286 		}
287 
288 		objectHit = true;
289 	}
290 
291 	return objectHit;
292 }
293 
gamePeerCity(int city,void * data)294 bool gamePeerCity(int city, void *data) {
295 	Map *peerMap;
296 
297 	peerMap = mapMgr->get((MapId)(city + 1));
298 
299 	if (peerMap != nullptr) {
300 		g_game->setMap(peerMap, 1, nullptr);
301 		g_context->_location->_viewMode = VIEW_GEM;
302 		g_game->_paused = true;
303 		g_game->_pausedTimer = 0;
304 
305 		g_screen->screenDisableCursor();
306 #ifdef IOS_ULTIMA4
307 		U4IOS::IOSConversationChoiceHelper continueHelper;
308 		continueHelper.updateChoices(" ");
309 		continueHelper.fullSizeChoicePanel();
310 #endif
311 		ReadChoiceController::get("\015 \033");
312 
313 		g_game->exitToParentMap();
314 		g_screen->screenEnableCursor();
315 		g_game->_paused = false;
316 
317 		return true;
318 	}
319 	return false;
320 }
321 
peer(bool useGem)322 void peer(bool useGem) {
323 	if (useGem) {
324 		if (g_ultima->_saveGame->_gems <= 0) {
325 			g_screen->screenMessage("%cPeer at What?%c\n", FG_GREY, FG_WHITE);
326 			return;
327 		}
328 
329 		g_ultima->_saveGame->_gems--;
330 		g_screen->screenMessage("Peer at a Gem!\n");
331 	}
332 
333 	g_game->_paused = true;
334 	g_game->_pausedTimer = 0;
335 	g_screen->screenDisableCursor();
336 
337 	g_context->_location->_viewMode = VIEW_GEM;
338 #ifdef IOS_ULTIMA4
339 	U4IOS::IOSConversationChoiceHelper continueHelper;
340 	continueHelper.updateChoices(" ");
341 	continueHelper.fullSizeChoicePanel();
342 #endif
343 	ReadChoiceController::get("\015 \033");
344 
345 	g_screen->screenEnableCursor();
346 	g_context->_location->_viewMode = VIEW_NORMAL;
347 	g_game->_paused = false;
348 }
349 
gameCheckHullIntegrity()350 void gameCheckHullIntegrity() {
351 	int i;
352 
353 	bool killAll = false;
354 	/* see if the ship has sunk */
355 	if ((g_context->_transportContext == TRANSPORT_SHIP) && g_ultima->_saveGame->_shipHull <= 0) {
356 		g_screen->screenMessage("\nThy ship sinks!\n\n");
357 		killAll = true;
358 	}
359 
360 
361 	if (!g_debugger->_collisionOverride && g_context->_transportContext == TRANSPORT_FOOT &&
362 	        g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS)->isSailable() &&
363 	        !g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->isShip() &&
364 	        !g_context->_location->_map->getValidMoves(g_context->_location->_coords, g_context->_party->getTransport())) {
365 		g_screen->screenMessage("\nTrapped at sea without thy ship, thou dost drown!\n\n");
366 		killAll = true;
367 	}
368 
369 	if (killAll) {
370 		for (i = 0; i < g_context->_party->size(); i++) {
371 			g_context->_party->member(i)->setHp(0);
372 			g_context->_party->member(i)->setStatus(STAT_DEAD);
373 		}
374 
375 		g_screen->update();
376 		g_death->start(5);
377 	}
378 }
379 
gameFixupObjects(Map * map)380 void gameFixupObjects(Map *map) {
381 	int i;
382 	Object *obj;
383 
384 	/* add stuff from the monster table to the map */
385 	for (i = 0; i < MONSTERTABLE_SIZE; i++) {
386 		SaveGameMonsterRecord *monster = &map->_monsterTable[i];
387 		if (monster->_prevTile != 0) {
388 			Coords coords(monster->_x, monster->_y);
389 
390 			// tile values stored in monsters.sav hardcoded to index into base tilemap
391 			MapTile tile = g_tileMaps->get("base")->translate(monster->_tile),
392 			        oldTile = g_tileMaps->get("base")->translate(monster->_prevTile);
393 
394 			if (i < MONSTERTABLE_CREATURES_SIZE) {
395 				const Creature *creature = creatureMgr->getByTile(tile);
396 				/* make sure we really have a creature */
397 				if (creature)
398 					obj = map->addCreature(creature, coords);
399 				else {
400 					warning("A non-creature object was found in the creature section of the monster table. (Tile: %s)\n", tile.getTileType()->getName().c_str());
401 					obj = map->addObject(tile, oldTile, coords);
402 				}
403 			} else {
404 				obj = map->addObject(tile, oldTile, coords);
405 			}
406 
407 			/* set the map for our object */
408 			obj->setMap(map);
409 		}
410 	}
411 }
412 
gameTimeSinceLastCommand()413 uint32 gameTimeSinceLastCommand() {
414 	return (g_system->getMillis() - g_context->_lastCommandTime) / 1000;
415 }
416 
gameCreatureAttack(Creature * m)417 void gameCreatureAttack(Creature *m) {
418 	Object *under;
419 	const Tile *ground;
420 
421 	g_screen->screenMessage("\nAttacked by %s\n", m->getName().c_str());
422 
423 	/// TODO: CHEST: Make a user option to not make chests change battlefield
424 	/// map (2 of 2)
425 	ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
426 	if (!ground->isChest()) {
427 		ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS);
428 		if ((under = g_context->_location->_map->objectAt(g_context->_location->_coords)) &&
429 		        under->getTile().getTileType()->isShip())
430 			ground = under->getTile().getTileType();
431 	}
432 
433 	CombatController *cc = new CombatController(CombatMap::mapForTile(ground, g_context->_party->getTransport().getTileType(), m));
434 	cc->init(m);
435 	cc->begin();
436 }
437 
creatureRangeAttack(const Coords & coords,Creature * m)438 bool creatureRangeAttack(const Coords &coords, Creature *m) {
439 //    int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
440 
441 	// Figure out what the ranged attack should look like
442 	MapTile tile(g_context->_location->_map->_tileSet->getByName((m && !m->getWorldrangedtile().empty()) ?
443 	             m->getWorldrangedtile() :
444 	             "hit_flash")->getId());
445 
446 	GameController::flashTile(coords, tile, 1);
447 
448 	// See if the attack hits the avatar
449 	Object *obj = g_context->_location->_map->objectAt(coords);
450 	m = dynamic_cast<Creature *>(obj);
451 
452 	// Does the attack hit the avatar?
453 	if (coords == g_context->_location->_coords) {
454 		/* always displays as a 'hit' */
455 		GameController::flashTile(coords, tile, 3);
456 
457 		/* FIXME: check actual damage from u4dos -- values here are guessed */
458 		if (g_context->_transportContext == TRANSPORT_SHIP)
459 			gameDamageShip(-1, 10);
460 		else
461 			gameDamageParty(10, 25);
462 
463 		return true;
464 	}
465 	// Destroy objects that were hit
466 	else if (obj) {
467 		if ((obj->getType() == Object::CREATURE && m && m->isAttackable()) ||
468 		        obj->getType() == Object::UNKNOWN) {
469 
470 			GameController::flashTile(coords, tile, 3);
471 			g_context->_location->_map->removeObject(obj);
472 
473 			return true;
474 		}
475 	}
476 	return false;
477 }
478 
gameGetDirectionalActionPath(int dirmask,int validDirections,const Coords & origin,int minDistance,int maxDistance,bool (* blockedPredicate)(const Tile * tile),bool includeBlocked)479 Std::vector<Coords> gameGetDirectionalActionPath(int dirmask, int validDirections, const Coords &origin, int minDistance, int maxDistance, bool (*blockedPredicate)(const Tile *tile), bool includeBlocked) {
480 	Std::vector<Coords> path;
481 	Direction dirx = DIR_NONE,
482 	          diry = DIR_NONE;
483 
484 	/* Figure out which direction the action is going */
485 	if (DIR_IN_MASK(DIR_WEST, dirmask))
486 		dirx = DIR_WEST;
487 	else if (DIR_IN_MASK(DIR_EAST, dirmask))
488 		dirx = DIR_EAST;
489 	if (DIR_IN_MASK(DIR_NORTH, dirmask))
490 		diry = DIR_NORTH;
491 	else if (DIR_IN_MASK(DIR_SOUTH, dirmask))
492 		diry = DIR_SOUTH;
493 
494 	/*
495 	 * try every tile in the given direction, up to the given range.
496 	 * Stop when the the range is exceeded, or the action is blocked.
497 	 */
498 
499 	MapCoords t_c(origin);
500 	if ((dirx <= 0 || DIR_IN_MASK(dirx, validDirections)) &&
501 	        (diry <= 0 || DIR_IN_MASK(diry, validDirections))) {
502 		for (int distance = 0; distance <= maxDistance;
503 		        distance++, t_c.move(dirx, g_context->_location->_map), t_c.move(diry, g_context->_location->_map)) {
504 
505 			if (distance >= minDistance) {
506 				/* make sure our action isn't taking us off the map */
507 				if (MAP_IS_OOB(g_context->_location->_map, t_c))
508 					break;
509 
510 				const Tile *tile = g_context->_location->_map->tileTypeAt(t_c, WITH_GROUND_OBJECTS);
511 
512 				/* should we see if the action is blocked before trying it? */
513 				if (!includeBlocked && blockedPredicate &&
514 				        !(*(blockedPredicate))(tile))
515 					break;
516 
517 				path.push_back(t_c);
518 
519 				/* see if the action was blocked only if it did not succeed */
520 				if (includeBlocked && blockedPredicate &&
521 				        !(*(blockedPredicate))(tile))
522 					break;
523 			}
524 		}
525 	}
526 
527 	return path;
528 }
529 
gameDamageParty(int minDamage,int maxDamage)530 void gameDamageParty(int minDamage, int maxDamage) {
531 	int i;
532 	int damage;
533 	int lastdmged = -1;
534 
535 	for (i = 0; i < g_context->_party->size(); i++) {
536 		if (xu4_random(2) == 0) {
537 			damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
538 			         xu4_random((maxDamage + 1) - minDamage) + minDamage :
539 			         maxDamage;
540 			g_context->_party->member(i)->applyDamage(damage);
541 			g_context->_stats->highlightPlayer(i);
542 			lastdmged = i;
543 			EventHandler::sleep(50);
544 		}
545 	}
546 
547 	g_screen->screenShake(1);
548 
549 	// Un-highlight the last player
550 	if (lastdmged != -1) g_context->_stats->highlightPlayer(lastdmged);
551 }
552 
gameDamageShip(int minDamage,int maxDamage)553 void gameDamageShip(int minDamage, int maxDamage) {
554 	int damage;
555 
556 	if (g_context->_transportContext == TRANSPORT_SHIP) {
557 		damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
558 		         xu4_random((maxDamage + 1) - minDamage) + minDamage :
559 		         maxDamage;
560 
561 		g_screen->screenShake(1);
562 
563 		g_context->_party->damageShip(damage);
564 		gameCheckHullIntegrity();
565 	}
566 }
567 
gameSetActivePlayer(int player)568 void gameSetActivePlayer(int player) {
569 	if (player == -1) {
570 		g_context->_party->setActivePlayer(-1);
571 		g_screen->screenMessage("Set Active Player: None!\n");
572 	} else if (player < g_context->_party->size()) {
573 		g_screen->screenMessage("Set Active Player: %s!\n", g_context->_party->member(player)->getName().c_str());
574 		if (g_context->_party->member(player)->isDisabled())
575 			g_screen->screenMessage("Disabled!\n");
576 		else
577 			g_context->_party->setActivePlayer(player);
578 	}
579 }
580 
gameSpawnCreature(const Creature * m)581 bool gameSpawnCreature(const Creature *m) {
582 	int t, i;
583 	const Creature *creature;
584 	MapCoords coords = g_context->_location->_coords;
585 
586 	if (g_context->_location->_context & CTX_DUNGEON) {
587 		/* FIXME: for some reason dungeon monsters aren't spawning correctly */
588 
589 		bool found = false;
590 		MapCoords new_coords;
591 
592 		for (i = 0; i < 0x20; i++) {
593 			new_coords = MapCoords(xu4_random(g_context->_location->_map->_width), xu4_random(g_context->_location->_map->_height), coords.z);
594 			const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITH_OBJECTS);
595 			if (tile->isCreatureWalkable()) {
596 				found = true;
597 				break;
598 			}
599 		}
600 
601 		if (!found)
602 			return false;
603 
604 		coords = new_coords;
605 	} else {
606 		int dx = 0,
607 		    dy = 0;
608 		bool ok = false;
609 		int tries = 0;
610 		static const int MAX_TRIES = 10;
611 
612 		while (!ok && (tries < MAX_TRIES)) {
613 			dx = 7;
614 			dy = xu4_random(7);
615 
616 			if (xu4_random(2))
617 				dx = -dx;
618 			if (xu4_random(2))
619 				dy = -dy;
620 			if (xu4_random(2)) {
621 				t = dx;
622 				dx = dy;
623 				dy = t;
624 			}
625 
626 			/* make sure we can spawn the creature there */
627 			if (m) {
628 				MapCoords new_coords = coords;
629 				new_coords.move(dx, dy, g_context->_location->_map);
630 
631 				const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITHOUT_OBJECTS);
632 				if ((m->sails() && tile->isSailable()) ||
633 				        (m->swims() && tile->isSwimable()) ||
634 				        (m->walks() && tile->isCreatureWalkable()) ||
635 				        (m->flies() && tile->isFlyable()))
636 					ok = true;
637 				else
638 					tries++;
639 			} else {
640 				ok = true;
641 			}
642 		}
643 
644 		if (ok)
645 			coords.move(dx, dy, g_context->_location->_map);
646 	}
647 
648 	/* can't spawn creatures on top of the player */
649 	if (coords == g_context->_location->_coords)
650 		return false;
651 
652 	/* figure out what creature to spawn */
653 	if (m)
654 		creature = m;
655 	else if (g_context->_location->_context & CTX_DUNGEON)
656 		creature = creatureMgr->randomForDungeon(g_context->_location->_coords.z);
657 	else
658 		creature = creatureMgr->randomForTile(g_context->_location->_map->tileTypeAt(coords, WITHOUT_OBJECTS));
659 
660 	if (creature)
661 		g_context->_location->_map->addCreature(creature, coords);
662 	return true;
663 }
664 
gameDestroyAllCreatures(void)665 void gameDestroyAllCreatures(void) {
666 	int i;
667 
668 	gameSpellEffect('t', -1, SOUND_MAGIC); /* same effect as tremor */
669 
670 	if (g_context->_location->_context & CTX_COMBAT) {
671 		// Destroy all creatures in combat
672 		for (i = 0; i < AREA_CREATURES; i++) {
673 			CombatMap *cm = getCombatMap();
674 			CreatureVector creatures = cm->getCreatures();
675 			CreatureVector::iterator obj;
676 
677 			for (obj = creatures.begin(); obj != creatures.end(); obj++) {
678 				if ((*obj)->getId() != LORDBRITISH_ID)
679 					cm->removeObject(*obj);
680 			}
681 		}
682 	} else {
683 		// Destroy all creatures on the map
684 		ObjectDeque::iterator current;
685 		Map *map = g_context->_location->_map;
686 
687 		for (current = map->_objects.begin(); current != map->_objects.end();) {
688 			Creature *m = dynamic_cast<Creature *>(*current);
689 
690 			if (m) {
691 				// The skull does not destroy Lord British
692 				if (m->getId() != LORDBRITISH_ID)
693 					current = map->removeObject(current);
694 				else
695 					current++;
696 			} else {
697 				current++;
698 			}
699 		}
700 	}
701 
702 	// Alert the guards! Really, the only one left should be LB himself :)
703 	g_context->_location->_map->alertGuards();
704 }
705 
706 // Colors assigned to reagents based on my best reading of them
707 // from the book of wisdom.  Maybe we could use BOLD to distinguish
708 // the two grey and the two red reagents.
709 const int colors[] = {
710 	FG_YELLOW, FG_GREY, FG_BLUE, FG_WHITE, FG_RED, FG_GREY, FG_GREEN, FG_RED
711 };
712 
showMixturesSuper(int page=0)713 void showMixturesSuper(int page = 0) {
714 	g_screen->screenTextColor(FG_WHITE);
715 	for (int i = 0; i < 13; i++) {
716 		char buf[4];
717 
718 		const Spell *s = g_spells->getSpell(i + 13 * page);
719 		int line = i + 8;
720 		g_screen->screenTextAt(2, line, "%s", s->_name);
721 
722 		snprintf(buf, 4, "%3d", g_ultima->_saveGame->_mixtures[i + 13 * page]);
723 		g_screen->screenTextAt(6, line, "%s", buf);
724 
725 		g_screen->screenShowChar(32, 9, line);
726 		int comp = s->_components;
727 		for (int j = 0; j < 8; j++) {
728 			g_screen->screenTextColor(colors[j]);
729 			g_screen->screenShowChar(comp & (1 << j) ? CHARSET_BULLET : ' ', 10 + j, line);
730 		}
731 		g_screen->screenTextColor(FG_WHITE);
732 
733 		snprintf(buf, 3, "%2d", s->_mp);
734 		g_screen->screenTextAt(19, line, "%s", buf);
735 	}
736 }
737 
mixReagentsSuper()738 void mixReagentsSuper() {
739 	g_screen->screenMessage("Mix reagents\n");
740 
741 	static int page = 0;
742 
743 	struct ReagentShop {
744 		const char *name;
745 		int price[6];
746 	};
747 	ReagentShop shops[] = {
748 		{ "BuccDen", {6, 7, 9, 9, 9, 1} },
749 		{ "Moonglo", {2, 5, 6, 3, 6, 9} },
750 		{ "Paws", {3, 4, 2, 8, 6, 7} },
751 		{ "SkaraBr", {2, 4, 9, 6, 4, 8} },
752 	};
753 	const int shopcount = sizeof(shops) / sizeof(shops[0]);
754 
755 	int oldlocation = g_context->_location->_viewMode;
756 	g_context->_location->_viewMode = VIEW_MIXTURES;
757 	g_screen->screenUpdate(&g_game->_mapArea, true, true);
758 
759 	g_screen->screenTextAt(16, 2, "%s", "<-Shops");
760 
761 	g_context->_stats->setView(StatsView(STATS_REAGENTS));
762 	g_screen->screenTextColor(FG_PURPLE);
763 	g_screen->screenTextAt(2, 7, "%s", "SPELL # Reagents MP");
764 
765 	for (int i = 0; i < shopcount; i++) {
766 		int line = i + 1;
767 		ReagentShop *s = &shops[i];
768 		g_screen->screenTextColor(FG_WHITE);
769 		g_screen->screenTextAt(2, line, "%s", s->name);
770 		for (int j = 0; j < 6; j++) {
771 			g_screen->screenTextColor(colors[j]);
772 			g_screen->screenShowChar('0' + s->price[j], 10 + j, line);
773 		}
774 	}
775 
776 	for (int i = 0; i < 8; i++) {
777 		g_screen->screenTextColor(colors[i]);
778 		g_screen->screenShowChar('A' + i, 10 + i, 6);
779 	}
780 
781 	bool done = false;
782 	while (!done) {
783 		showMixturesSuper(page);
784 		g_screen->screenMessage("For Spell: ");
785 
786 		int spell = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
787 		if (spell < 'a' || spell > 'z') {
788 			g_screen->screenMessage("\nDone.\n");
789 			done = true;
790 		} else {
791 			spell -= 'a';
792 			const Spell *s = g_spells->getSpell(spell);
793 			g_screen->screenMessage("%s\n", s->_name);
794 			page = (spell >= 13);
795 			showMixturesSuper(page);
796 
797 			// how many can we mix?
798 			int mixQty = 99 - g_ultima->_saveGame->_mixtures[spell];
799 			int ingQty = 99;
800 			int comp = s->_components;
801 			for (int i = 0; i < 8; i++) {
802 				if (comp & 1 << i) {
803 					int reagentQty = g_ultima->_saveGame->_reagents[i];
804 					if (reagentQty < ingQty)
805 						ingQty = reagentQty;
806 				}
807 			}
808 			g_screen->screenMessage("You can make %d.\n", (mixQty > ingQty) ? ingQty : mixQty);
809 			g_screen->screenMessage("How many? ");
810 
811 			int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
812 
813 			if (howmany == 0) {
814 				g_screen->screenMessage("\nNone mixed!\n");
815 			} else if (howmany > mixQty) {
816 				g_screen->screenMessage("\n%cYou cannot mix that much more of that spell!%c\n", FG_GREY, FG_WHITE);
817 			} else if (howmany > ingQty) {
818 				g_screen->screenMessage("\n%cYou don't have enough reagents to mix %d spells!%c\n", FG_GREY, howmany, FG_WHITE);
819 			} else {
820 				g_ultima->_saveGame->_mixtures[spell] += howmany;
821 				for (int i = 0; i < 8; i++) {
822 					if (comp & 1 << i) {
823 						g_ultima->_saveGame->_reagents[i] -= howmany;
824 					}
825 				}
826 				g_screen->screenMessage("\nSuccess!\n\n");
827 			}
828 		}
829 		g_context->_stats->setView(StatsView(STATS_REAGENTS));
830 	}
831 
832 	g_context->_location->_viewMode = oldlocation;
833 	return;
834 }
835 
836 } // End of namespace Ultima4
837 } // End of namespace Ultima
838