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