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/controllers/game_controller.h"
24 #include "ultima/ultima4/core/config.h"
25 #include "ultima/ultima4/core/debugger.h"
26 #include "ultima/ultima4/core/utils.h"
27 #include "ultima/ultima4/filesys/savegame.h"
28 #include "ultima/ultima4/game/game.h"
29 #include "ultima/ultima4/game/context.h"
30 #include "ultima/ultima4/game/death.h"
31 #include "ultima/ultima4/game/moongate.h"
32 #include "ultima/ultima4/views/stats.h"
33 #include "ultima/ultima4/gfx/imagemgr.h"
34 #include "ultima/ultima4/gfx/screen.h"
35 #include "ultima/ultima4/map/annotation.h"
36 #include "ultima/ultima4/map/city.h"
37 #include "ultima/ultima4/map/dungeon.h"
38 #include "ultima/ultima4/map/mapmgr.h"
39 #include "ultima/ultima4/map/shrine.h"
40 #include "ultima/ultima4/ultima4.h"
41 #include "common/system.h"
42
43 namespace Ultima {
44 namespace Ultima4 {
45
46 GameController *g_game = nullptr;
47
48 static const MouseArea MOUSE_AREAS[] = {
49 { 3, { { 8, 8 }, { 8, 184 }, { 96, 96 } }, MC_WEST, DIR_WEST },
50 { 3, { { 8, 8 }, { 184, 8 }, { 96, 96 } }, MC_NORTH, DIR_NORTH },
51 { 3, { { 184, 8 }, { 184, 184 }, { 96, 96 } }, MC_EAST, DIR_EAST },
52 { 3, { { 8, 184 }, { 184, 184 }, { 96, 96 } }, MC_SOUTH, DIR_SOUTH },
53 { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 } }, MC_NORTH, DIR_NONE }
54 };
55
GameController()56 GameController::GameController() :
57 _mapArea(BORDER_WIDTH, BORDER_HEIGHT, VIEWPORT_W, VIEWPORT_H),
58 _paused(false), _combatFinished(false), _pausedTimer(0) {
59 g_game = this;
60 }
61
initScreen()62 void GameController::initScreen() {
63 Image *screen = imageMgr->get("screen")->_image;
64
65 screen->fillRect(0, 0, screen->width(), screen->height(), 0, 0, 0);
66 g_screen->update();
67 }
68
initScreenWithoutReloadingState()69 void GameController::initScreenWithoutReloadingState() {
70 g_music->playMapMusic();
71 imageMgr->get(BKGD_BORDERS)->_image->draw(0, 0);
72 g_context->_stats->update(); /* draw the party stats */
73
74 g_screen->screenMessage("Press Alt-h for help\n");
75 g_screen->screenPrompt();
76
77 eventHandler->pushMouseAreaSet(MOUSE_AREAS);
78
79 eventHandler->setScreenUpdate(&gameUpdateScreen);
80 }
81
init()82 void GameController::init() {
83 initScreen();
84
85 // initialize the state of the global game context
86 g_context->_line = TEXT_AREA_H - 1;
87 g_context->_col = 0;
88 g_context->_stats = new StatsArea();
89 g_context->_moonPhase = 0;
90 g_context->_windDirection = DIR_NORTH;
91 g_context->_windCounter = 0;
92 g_context->_windLock = false;
93 g_context->_aura = new Aura();
94 g_context->_horseSpeed = 0;
95 g_context->_opacity = 1;
96 g_context->_lastCommandTime = g_system->getMillis();
97 g_context->_lastShip = nullptr;
98 }
99
setMap(Map * map,bool saveLocation,const Portal * portal,TurnCompleter * turnCompleter)100 void GameController::setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter) {
101 int viewMode;
102 LocationContext context;
103 int activePlayer = g_context->_party->getActivePlayer();
104 MapCoords coords;
105
106 if (!turnCompleter)
107 turnCompleter = this;
108
109 if (portal)
110 coords = portal->_start;
111 else
112 coords = MapCoords(map->_width / 2, map->_height / 2);
113
114 /* If we don't want to save the location, then just return to the previous location,
115 as there may still be ones in the stack we want to keep */
116 if (!saveLocation)
117 exitToParentMap();
118
119 switch (map->_type) {
120 case Map::WORLD:
121 context = CTX_WORLDMAP;
122 viewMode = VIEW_NORMAL;
123 break;
124 case Map::DUNGEON:
125 context = CTX_DUNGEON;
126 viewMode = VIEW_DUNGEON;
127 if (portal)
128 g_ultima->_saveGame->_orientation = DIR_EAST;
129 break;
130 case Map::COMBAT:
131 coords = MapCoords(-1, -1); /* set these to -1 just to be safe; we don't need them */
132 context = CTX_COMBAT;
133 viewMode = VIEW_NORMAL;
134 activePlayer = -1; /* different active player for combat, defaults to 'None' */
135 break;
136 case Map::SHRINE:
137 context = CTX_SHRINE;
138 viewMode = VIEW_NORMAL;
139 break;
140 case Map::CITY:
141 default:
142 context = CTX_CITY;
143 viewMode = VIEW_NORMAL;
144 break;
145 }
146 g_context->_location = new Location(coords, map, viewMode, context, turnCompleter, g_context->_location);
147 g_context->_location->addObserver(this);
148 g_context->_party->setActivePlayer(activePlayer);
149 #ifdef IOS_ULTIMA4
150 U4IOS::updateGameControllerContext(c->location->context);
151 #endif
152
153 /* now, actually set our new tileset */
154 _mapArea.setTileset(map->_tileSet);
155
156 if (isCity(map)) {
157 City *city = dynamic_cast<City *>(map);
158 assert(city);
159 city->addPeople();
160 }
161 }
162
exitToParentMap()163 int GameController::exitToParentMap() {
164 if (!g_context->_location)
165 return 0;
166
167 if (g_context->_location->_prev != nullptr) {
168 // Create the balloon for Hythloth
169 if (g_context->_location->_map->_id == MAP_HYTHLOTH)
170 createBalloon(g_context->_location->_prev->_map);
171
172 // free map info only if previous location was on a different map
173 if (g_context->_location->_prev->_map != g_context->_location->_map) {
174 g_context->_location->_map->_annotations->clear();
175 g_context->_location->_map->clearObjects();
176
177 /* quench the torch of we're on the world map */
178 if (g_context->_location->_prev->_map->isWorldMap())
179 g_context->_party->quenchTorch();
180 }
181 locationFree(&g_context->_location);
182
183 // restore the tileset to the one the current map uses
184 _mapArea.setTileset(g_context->_location->_map->_tileSet);
185 #ifdef IOS_ULTIMA4
186 U4IOS::updateGameControllerContext(c->location->context);
187 #endif
188
189 return 1;
190 }
191 return 0;
192 }
193
finishTurn()194 void GameController::finishTurn() {
195 g_context->_lastCommandTime = g_system->getMillis();
196 Creature *attacker = nullptr;
197
198 while (1) {
199
200 /* adjust food and moves */
201 g_context->_party->endTurn();
202
203 /* count down the aura, if there is one */
204 g_context->_aura->passTurn();
205
206 gameCheckHullIntegrity();
207
208 /* update party stats */
209 //c->stats->setView(STATS_PARTY_OVERVIEW);
210
211 g_screen->screenUpdate(&this->_mapArea, true, false);
212 g_screen->screenWait(1);
213
214 /* Creatures cannot spawn, move or attack while the avatar is on the balloon */
215 if (!g_context->_party->isFlying()) {
216
217 // apply effects from tile avatar is standing on
218 g_context->_party->applyEffect(g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->getEffect());
219
220 // WORKAROUND: This fixes infinite combat loop at the Shrine of Humility.
221 // I presume the original had code to show the game screen after combat
222 // without doing all the end turn logic
223 if (!_combatFinished) {
224 // Move creatures and see if something is attacking the avatar
225 attacker = g_context->_location->_map->moveObjects(g_context->_location->_coords);
226
227 // Something's attacking! Start combat!
228 if (attacker) {
229 gameCreatureAttack(attacker);
230 return;
231 }
232
233 // cleanup old creatures and spawn new ones
234 creatureCleanup();
235 checkRandomCreatures();
236 checkBridgeTrolls();
237 } else {
238 _combatFinished = false;
239 }
240 }
241
242 /* update map annotations */
243 g_context->_location->_map->_annotations->passTurn();
244
245 if (!g_context->_party->isImmobilized())
246 break;
247
248 if (g_context->_party->isDead()) {
249 g_death->start(0);
250 return;
251 } else {
252 g_screen->screenMessage("Zzzzzz\n");
253 g_screen->screenWait(4);
254 }
255 }
256
257 if (g_context->_location->_context == CTX_DUNGEON) {
258 Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
259 assert(dungeon);
260
261 if (g_context->_party->getTorchDuration() <= 0)
262 g_screen->screenMessage("It's Dark!\n");
263 else
264 g_context->_party->burnTorch();
265
266 /* handle dungeon traps */
267 if (dungeon->currentToken() == DUNGEON_TRAP) {
268 dungeonHandleTrap((TrapType)dungeon->currentSubToken());
269 // a little kludgey to have a second test for this
270 // right here. But without it you can survive an
271 // extra turn after party death and do some things
272 // that could cause a crash, like Hole up and Camp.
273 if (g_context->_party->isDead()) {
274 g_death->start(0);
275 return;
276 }
277 }
278 }
279
280
281 /* draw a prompt */
282 g_screen->screenPrompt();
283 //g_screen->screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
284 }
285
flashTile(const Coords & coords,MapTile tile,int frames)286 void GameController::flashTile(const Coords &coords, MapTile tile, int frames) {
287 g_context->_location->_map->_annotations->add(coords, tile, true);
288
289 g_screen->screenTileUpdate(&g_game->_mapArea, coords);
290
291 g_screen->screenWait(frames);
292 g_context->_location->_map->_annotations->remove(coords, tile);
293
294 g_screen->screenTileUpdate(&g_game->_mapArea, coords, false);
295 }
296
flashTile(const Coords & coords,const Common::String & tilename,int timeFactor)297 void GameController::flashTile(const Coords &coords, const Common::String &tilename, int timeFactor) {
298 Tile *tile = g_context->_location->_map->_tileSet->getByName(tilename);
299 assertMsg(tile, "no tile named '%s' found in tileset", tilename.c_str());
300 flashTile(coords, tile->getId(), timeFactor);
301 }
302
update(Party * party,PartyEvent & event)303 void GameController::update(Party *party, PartyEvent &event) {
304 int i;
305
306 switch (event._type) {
307 case PartyEvent::LOST_EIGHTH:
308 // inform a player he has lost zero or more eighths of avatarhood.
309 g_screen->screenMessage("\n %cThou hast lost\n an eighth!%c\n", FG_YELLOW, FG_WHITE);
310 break;
311 case PartyEvent::ADVANCED_LEVEL:
312 g_screen->screenMessage("\n%c%s\nThou art now Level %d%c\n", FG_YELLOW, event._player->getName().c_str(), event._player->getRealLevel(), FG_WHITE);
313 gameSpellEffect('r', -1, SOUND_MAGIC); // Same as resurrect spell
314 break;
315 case PartyEvent::STARVING:
316 g_screen->screenMessage("\n%cStarving!!!%c\n", FG_YELLOW, FG_WHITE);
317 /* FIXME: add sound effect here */
318
319 // 2 damage to each party member for starving!
320 for (i = 0; i < g_ultima->_saveGame->_members; i++)
321 g_context->_party->member(i)->applyDamage(2);
322 break;
323 default:
324 break;
325 }
326 }
327
update(Location * location,MoveEvent & event)328 void GameController::update(Location *location, MoveEvent &event) {
329 switch (location->_map->_type) {
330 case Map::DUNGEON:
331 avatarMovedInDungeon(event);
332 break;
333 case Map::COMBAT: {
334 // FIXME: let the combat controller handle it
335 CombatController *ctl = dynamic_cast<CombatController *>(eventHandler->getController());
336 assert(ctl);
337 ctl->movePartyMember(event);
338 break;
339 }
340 default:
341 avatarMoved(event);
342 break;
343 }
344 }
345
setActive()346 void GameController::setActive() {
347 // The game controller has the keybindings enabled
348 MetaEngine::setKeybindingMode(KBMODE_NORMAL);
349 }
350
keybinder(KeybindingAction action)351 void GameController::keybinder(KeybindingAction action) {
352 MetaEngine::executeAction(action);
353 }
354
mousePressed(const Common::Point & mousePos)355 bool GameController::mousePressed(const Common::Point &mousePos) {
356 const MouseArea *area = eventHandler->mouseAreaForPoint(mousePos.x, mousePos.y);
357
358 if (area) {
359 keybinder(KEYBIND_INTERACT);
360 return true;
361 }
362
363 return false;
364 }
365
initMoons()366 void GameController::initMoons() {
367 int trammelphase = g_ultima->_saveGame->_trammelPhase,
368 feluccaphase = g_ultima->_saveGame->_feluccaPhase;
369
370 assertMsg(g_context != nullptr, "Game context doesn't exist!");
371 assertMsg(g_ultima->_saveGame != nullptr, "Savegame doesn't exist!");
372 //assertMsg(mapIsWorldMap(c->location->map) && c->location->viewMode == VIEW_NORMAL, "Can only call gameInitMoons() from the world map!");
373
374 g_ultima->_saveGame->_trammelPhase = g_ultima->_saveGame->_feluccaPhase = 0;
375 g_context->_moonPhase = 0;
376
377 while ((g_ultima->_saveGame->_trammelPhase != trammelphase) ||
378 (g_ultima->_saveGame->_feluccaPhase != feluccaphase))
379 updateMoons(false);
380 }
381
updateMoons(bool showmoongates)382 void GameController::updateMoons(bool showmoongates) {
383 int realMoonPhase,
384 oldTrammel,
385 trammelSubphase;
386 const Coords *gate;
387
388 if (g_context->_location->_map->isWorldMap()) {
389 oldTrammel = g_ultima->_saveGame->_trammelPhase;
390
391 if (++g_context->_moonPhase >= MOON_PHASES * MOON_SECONDS_PER_PHASE * 4)
392 g_context->_moonPhase = 0;
393
394 trammelSubphase = g_context->_moonPhase % (MOON_SECONDS_PER_PHASE * 4 * 3);
395 realMoonPhase = (g_context->_moonPhase / (4 * MOON_SECONDS_PER_PHASE));
396
397 g_ultima->_saveGame->_trammelPhase = realMoonPhase / 3;
398 g_ultima->_saveGame->_feluccaPhase = realMoonPhase % 8;
399
400 if (g_ultima->_saveGame->_trammelPhase > 7)
401 g_ultima->_saveGame->_trammelPhase = 7;
402
403 if (showmoongates) {
404 /* update the moongates if trammel changed */
405 if (trammelSubphase == 0) {
406 gate = g_moongates->getGateCoordsForPhase(oldTrammel);
407 if (gate)
408 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
409 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
410 if (gate)
411 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
412 } else if (trammelSubphase == 1) {
413 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
414 if (gate) {
415 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
416 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
417 }
418 } else if (trammelSubphase == 2) {
419 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
420 if (gate) {
421 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
422 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
423 }
424 } else if (trammelSubphase == 3) {
425 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
426 if (gate) {
427 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
428 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
429 }
430 } else if ((trammelSubphase > 3) && (trammelSubphase < (MOON_SECONDS_PER_PHASE * 4 * 3) - 3)) {
431 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
432 if (gate) {
433 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
434 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
435 }
436 } else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 3) {
437 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
438 if (gate) {
439 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
440 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
441 }
442 } else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 2) {
443 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
444 if (gate) {
445 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
446 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
447 }
448 } else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 1) {
449 gate = g_moongates->getGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
450 if (gate) {
451 g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
452 g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
453 }
454 }
455 }
456 }
457 }
458
avatarMoved(MoveEvent & event)459 void GameController::avatarMoved(MoveEvent &event) {
460 if (event._userEvent) {
461
462 // is filterMoveMessages even used? it doesn't look like the option is hooked up in the configuration menu
463 if (!settings._filterMoveMessages) {
464 switch (g_context->_transportContext) {
465 case TRANSPORT_FOOT:
466 case TRANSPORT_HORSE:
467 g_screen->screenMessage("%s\n", getDirectionName(event._dir));
468 break;
469 case TRANSPORT_SHIP:
470 if (event._result & MOVE_TURNED)
471 g_screen->screenMessage("Turn %s!\n", getDirectionName(event._dir));
472 else if (event._result & MOVE_SLOWED)
473 g_screen->screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
474 else
475 g_screen->screenMessage("Sail %s!\n", getDirectionName(event._dir));
476 break;
477 case TRANSPORT_BALLOON:
478 g_screen->screenMessage("%cDrift Only!%c\n", FG_GREY, FG_WHITE);
479 break;
480 default:
481 error("bad transportContext %d in avatarMoved()", g_context->_transportContext);
482 }
483 }
484
485 /* movement was blocked */
486 if (event._result & MOVE_BLOCKED) {
487
488 /* if shortcuts are enabled, try them! */
489 if (settings._shortcutCommands) {
490 MapCoords new_coords = g_context->_location->_coords;
491 MapTile *tile;
492
493 new_coords.move(event._dir, g_context->_location->_map);
494 tile = g_context->_location->_map->tileAt(new_coords, WITH_OBJECTS);
495
496 if (tile->getTileType()->isDoor()) {
497 g_debugger->openAt(new_coords);
498 event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
499 } else if (tile->getTileType()->isLockedDoor()) {
500 g_debugger->jimmyAt(new_coords);
501 event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
502 } /*else if (mapPersonAt(c->location->map, new_coords) != nullptr) {
503 talkAtCoord(newx, newy, 1, nullptr);
504 event.result = MOVE_SUCCEEDED | MOVE_END_TURN;
505 }*/
506 }
507
508 /* if we're still blocked */
509 if ((event._result & MOVE_BLOCKED) && !settings._filterMoveMessages) {
510 soundPlay(SOUND_BLOCKED, false);
511 g_screen->screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
512 }
513 } else if (g_context->_transportContext == TRANSPORT_FOOT || g_context->_transportContext == TRANSPORT_HORSE) {
514 /* movement was slowed */
515 if (event._result & MOVE_SLOWED) {
516 soundPlay(SOUND_WALK_SLOWED);
517 g_screen->screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
518 } else {
519 soundPlay(SOUND_WALK_NORMAL);
520 }
521 }
522 }
523
524 /* exited map */
525 if (event._result & MOVE_EXIT_TO_PARENT) {
526 g_screen->screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
527 exitToParentMap();
528 g_music->playMapMusic();
529 }
530
531 /* things that happen while not on board the balloon */
532 if (g_context->_transportContext & ~TRANSPORT_BALLOON)
533 checkSpecialCreatures(event._dir);
534 /* things that happen while on foot or horseback */
535 if ((g_context->_transportContext & TRANSPORT_FOOT_OR_HORSE) &&
536 !(event._result & (MOVE_SLOWED | MOVE_BLOCKED))) {
537 if (checkMoongates())
538 event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_END_TURN);
539 }
540 }
541
avatarMovedInDungeon(MoveEvent & event)542 void GameController::avatarMovedInDungeon(MoveEvent &event) {
543 Direction realDir = dirNormalize((Direction)g_ultima->_saveGame->_orientation, event._dir);
544 Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
545 assert(dungeon);
546
547 if (!settings._filterMoveMessages) {
548 if (event._userEvent) {
549 if (event._result & MOVE_TURNED) {
550 if (dirRotateCCW((Direction)g_ultima->_saveGame->_orientation) == realDir)
551 g_screen->screenMessage("Turn Left\n");
552 else
553 g_screen->screenMessage("Turn Right\n");
554 } else {
555 // Show 'Advance' or 'Retreat' in dungeons
556 g_screen->screenMessage("%s\n", realDir == g_ultima->_saveGame->_orientation ? "Advance" : "Retreat");
557 }
558 }
559
560 if (event._result & MOVE_BLOCKED)
561 g_screen->screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
562 }
563
564 // If we're exiting the map, do this
565 if (event._result & MOVE_EXIT_TO_PARENT) {
566 g_screen->screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
567 exitToParentMap();
568 g_music->playMapMusic();
569 }
570
571 // Check to see if we're entering a dungeon room
572 if (event._result & MOVE_SUCCEEDED) {
573 if (dungeon->currentToken() == DUNGEON_ROOM) {
574 int room = (int)dungeon->currentSubToken(); // Get room number
575
576 /**
577 * recalculate room for the abyss -- there are 16 rooms for every 2 levels,
578 * each room marked with 0xD* where (* == room number 0-15).
579 * for levels 1 and 2, there are 16 rooms, levels 3 and 4 there are 16 rooms, etc.
580 */
581 if (g_context->_location->_map->_id == MAP_ABYSS)
582 room = (0x10 * (g_context->_location->_coords.z / 2)) + room;
583
584 Dungeon *dng = dynamic_cast<Dungeon *>(g_context->_location->_map);
585 assert(dng);
586 dng->_currentRoom = room;
587
588 // Set the map and start combat!
589 CombatController *cc = new CombatController(dng->_roomMaps[room]);
590 cc->initDungeonRoom(room, dirReverse(realDir));
591 cc->begin();
592 }
593 }
594 }
595
timerFired()596 void GameController::timerFired() {
597 if (_pausedTimer > 0) {
598 _pausedTimer--;
599 if (_pausedTimer <= 0) {
600 _pausedTimer = 0;
601 _paused = false; /* unpause the game */
602 }
603 }
604
605 if (!_paused && !_pausedTimer) {
606 if (++g_context->_windCounter >= MOON_SECONDS_PER_PHASE * 4) {
607 if (xu4_random(4) == 1 && !g_context->_windLock)
608 g_context->_windDirection = dirRandomDir(MASK_DIR_ALL);
609 g_context->_windCounter = 0;
610 }
611
612 /* balloon moves about 4 times per second */
613 if ((g_context->_transportContext == TRANSPORT_BALLOON) &&
614 g_context->_party->isFlying()) {
615 g_context->_location->move(dirReverse((Direction) g_context->_windDirection), false);
616 }
617
618 updateMoons(true);
619
620 g_screen->screenCycle();
621
622 // Check for any right-button mouse movement
623 KeybindingAction action = eventHandler->getAction();
624 if (action != KEYBIND_NONE)
625 keybinder(action);
626
627 // Do game udpates to the screen, like tile animations
628 gameUpdateScreen();
629
630 /*
631 * force pass if no commands within last 20 seconds
632 */
633 Controller *controller = eventHandler->getController();
634 if (controller != nullptr && (eventHandler->getController() == g_game ||
635 dynamic_cast<CombatController *>(eventHandler->getController()) != nullptr) &&
636 gameTimeSinceLastCommand() > 20) {
637
638 /* pass the turn, and redraw the text area so the prompt is shown */
639 MetaEngine::executeAction(KEYBIND_PASS);
640 g_screen->screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
641 }
642 }
643
644 }
645
checkSpecialCreatures(Direction dir)646 void GameController::checkSpecialCreatures(Direction dir) {
647 int i;
648 Object *obj;
649 static const struct {
650 int x, y;
651 Direction dir;
652 } pirateInfo[] = {
653 { 224, 220, DIR_EAST }, /* N'M" O'A" */
654 { 224, 228, DIR_EAST }, /* O'E" O'A" */
655 { 226, 220, DIR_EAST }, /* O'E" O'C" */
656 { 227, 228, DIR_EAST }, /* O'E" O'D" */
657 { 228, 227, DIR_SOUTH }, /* O'D" O'E" */
658 { 229, 225, DIR_SOUTH }, /* O'B" O'F" */
659 { 229, 223, DIR_NORTH }, /* N'P" O'F" */
660 { 228, 222, DIR_NORTH } /* N'O" O'E" */
661 };
662
663 /*
664 * if heading east into pirates cove (O'A" N'N"), generate pirate
665 * ships
666 */
667 if (dir == DIR_EAST &&
668 g_context->_location->_coords.x == 0xdd &&
669 g_context->_location->_coords.y == 0xe0) {
670 for (i = 0; i < 8; i++) {
671 obj = g_context->_location->_map->addCreature(creatureMgr->getById(PIRATE_ID), MapCoords(pirateInfo[i].x, pirateInfo[i].y));
672 obj->setDirection(pirateInfo[i].dir);
673 }
674 }
675
676 /*
677 * if heading south towards the shrine of humility, generate
678 * daemons unless horn has been blown
679 */
680 if (dir == DIR_SOUTH &&
681 g_context->_location->_coords.x >= 229 &&
682 g_context->_location->_coords.x < 234 &&
683 g_context->_location->_coords.y >= 212 &&
684 g_context->_location->_coords.y < 217 &&
685 *g_context->_aura != Aura::HORN) {
686 for (i = 0; i < 8; i++)
687 g_context->_location->_map->addCreature(creatureMgr->getById(DAEMON_ID), MapCoords(231, g_context->_location->_coords.y + 1, g_context->_location->_coords.z));
688 }
689 }
690
checkMoongates()691 bool GameController::checkMoongates() {
692 Coords dest;
693
694 if (g_moongates->findActiveGateAt(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase, g_context->_location->_coords, dest)) {
695
696 gameSpellEffect(-1, -1, SOUND_MOONGATE); // Default spell effect (screen inversion without 'spell' sound effects)
697
698 if (g_context->_location->_coords != dest) {
699 g_context->_location->_coords = dest;
700 gameSpellEffect(-1, -1, SOUND_MOONGATE); // Again, after arriving
701 }
702
703 if (g_moongates->isEntryToShrineOfSpirituality(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase)) {
704 Shrine *shrine_spirituality;
705
706 shrine_spirituality = dynamic_cast<Shrine *>(mapMgr->get(MAP_SHRINE_SPIRITUALITY));
707 assert(shrine_spirituality);
708
709 if (!g_context->_party->canEnterShrine(VIRT_SPIRITUALITY))
710 return true;
711
712 setMap(shrine_spirituality, 1, nullptr);
713 g_music->playMapMusic();
714
715 shrine_spirituality->enter();
716 }
717
718 return true;
719 }
720
721 return false;
722 }
723
creatureCleanup()724 void GameController::creatureCleanup() {
725 ObjectDeque::iterator i;
726 Map *map = g_context->_location->_map;
727
728 for (i = map->_objects.begin(); i != map->_objects.end();) {
729 Object *obj = *i;
730 MapCoords o_coords = obj->getCoords();
731
732 if ((obj->getType() == Object::CREATURE) && (o_coords.z == g_context->_location->_coords.z) &&
733 o_coords.distance(g_context->_location->_coords, g_context->_location->_map) > MAX_CREATURE_DISTANCE) {
734
735 /* delete the object and remove it from the map */
736 i = map->removeObject(i);
737 } else {
738 i++;
739 }
740 }
741 }
742
checkRandomCreatures()743 void GameController::checkRandomCreatures() {
744 int canSpawnHere = g_context->_location->_map->isWorldMap() || g_context->_location->_context & CTX_DUNGEON;
745 #ifdef IOS_ULTIMA4
746 int spawnDivisor = c->location->context & CTX_DUNGEON ? (53 - (c->location->coords.z << 2)) : 53;
747 #else
748 int spawnDivisor = g_context->_location->_context & CTX_DUNGEON ? (32 - (g_context->_location->_coords.z << 2)) : 32;
749 #endif
750
751 /* If there are too many creatures already,
752 or we're not on the world map, don't worry about it! */
753 if (!canSpawnHere ||
754 g_context->_location->_map->getNumberOfCreatures() >= MAX_CREATURES_ON_MAP ||
755 xu4_random(spawnDivisor) != 0)
756 return;
757
758 // If combat is turned off, then don't spawn any creator
759 if (g_debugger->_disableCombat)
760 return;
761
762 gameSpawnCreature(nullptr);
763 }
764
checkBridgeTrolls()765 void GameController::checkBridgeTrolls() {
766 const Tile *bridge = g_context->_location->_map->_tileSet->getByName("bridge");
767 if (!bridge)
768 return;
769
770 // TODO: CHEST: Make a user option to not make chests block bridge trolls
771 if (!g_context->_location->_map->isWorldMap() ||
772 g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)->_id != bridge->getId() ||
773 xu4_random(8) != 0)
774 return;
775
776 g_screen->screenMessage("\nBridge Trolls!\n");
777
778 Creature *m = g_context->_location->_map->addCreature(creatureMgr->getById(TROLL_ID), g_context->_location->_coords);
779 CombatController *cc = new CombatController(MAP_BRIDGE_CON);
780 cc->init(m);
781 cc->begin();
782 }
783
createBalloon(Map * map)784 bool GameController::createBalloon(Map *map) {
785 ObjectDeque::iterator i;
786
787 /* see if the balloon has already been created (and not destroyed) */
788 for (i = map->_objects.begin(); i != map->_objects.end(); i++) {
789 Object *obj = *i;
790 if (obj->getTile().getTileType()->isBalloon())
791 return false;
792 }
793
794 const Tile *balloon = map->_tileSet->getByName("balloon");
795 assertMsg(balloon, "no balloon tile found in tileset");
796 map->addObject(balloon->getId(), balloon->getId(), map->getLabel("balloon"));
797 return true;
798 }
799
attack(Direction dir)800 void GameController::attack(Direction dir) {
801 g_screen->screenMessage("Attack: ");
802 if (g_context->_party->isFlying()) {
803 g_screen->screenMessage("\n%cDrift only!%c\n", FG_GREY, FG_WHITE);
804 return;
805 }
806
807 if (dir == DIR_NONE)
808 dir = gameGetDirection();
809
810 if (dir == DIR_NONE) {
811 g_screen->screenMessage("\n");
812 return;
813 }
814
815 Std::vector<Coords> path = gameGetDirectionalActionPath(
816 MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
817 1, 1, nullptr, true);
818 for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
819 if (attackAt(*i))
820 return;
821 }
822
823 g_screen->screenMessage("%cNothing to Attack!%c\n", FG_GREY, FG_WHITE);
824 }
825
attackAt(const Coords & coords)826 bool GameController::attackAt(const Coords &coords) {
827 Object *under;
828 const Tile *ground;
829 Creature *m;
830
831 m = dynamic_cast<Creature *>(g_context->_location->_map->objectAt(coords));
832 // Nothing attackable: move on to next tile
833 if (m == nullptr || !m->isAttackable())
834 return false;
835
836 // Attack successful
837 /// TODO: CHEST: Make a user option to not make chests change battlefield
838 /// map (1 of 2)
839 ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
840 if (!ground->isChest()) {
841 ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS);
842 if ((under = g_context->_location->_map->objectAt(g_context->_location->_coords)) &&
843 under->getTile().getTileType()->isShip())
844 ground = under->getTile().getTileType();
845 }
846
847 // You're attacking a townsperson! Alert the guards!
848 if ((m->getType() == Object::PERSON) && (m->getMovementBehavior() != MOVEMENT_ATTACK_AVATAR))
849 g_context->_location->_map->alertGuards();
850
851 // Not good karma to be killing the innocent. Bad avatar!
852 if (m->isGood() || /* attacking a good creature */
853 /* attacking a docile (although possibly evil) person in town */
854 ((m->getType() == Object::PERSON) && (m->getMovementBehavior() != MOVEMENT_ATTACK_AVATAR)))
855 g_context->_party->adjustKarma(KA_ATTACKED_GOOD);
856
857 CombatController *cc = new CombatController(CombatMap::mapForTile(ground, g_context->_party->getTransport().getTileType(), m));
858 cc->init(m);
859 cc->begin();
860 return false;
861 }
862
863 } // End of namespace Ultima4
864 } // End of namespace Ultima
865