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