[Scummvm-git-logs] scummvm master -> 7aae5d45c9c40d5aa8c9f6e5cc0bc4c58eed09c5

dreammaster paulfgilbert at gmail.com
Fri Apr 17 02:31:08 UTC 2020


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
c4d188249c ULTIMA4: Shifted abyss jump to debugger command
4e1a461b53 ULTIMA4: Hook up bulk of the cheat actions to keybinder
e0607343af ULTIMA4: Split up controllers into their own files
7aae5d45c9 ULTIMA4: Moving more controller classes to controllers/


Commit: c4d188249c271ec66146903127ef7df0d64af161
    https://github.com/scummvm/scummvm/commit/c4d188249c271ec66146903127ef7df0d64af161
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-16T19:27:48-07:00

Commit Message:
ULTIMA4: Shifted abyss jump to debugger command

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.cpp


diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index 3643a0bccc..f2b9915d96 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -79,6 +79,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("yell", WRAP_METHOD(Debugger, cmdYell));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
+	registerCmd("abyss", WRAP_METHOD(Debugger, cmdAbyss));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
 	registerCmd("companions", WRAP_METHOD(Debugger, cmdCompanions));
 	registerCmd("destroy", WRAP_METHOD(Debugger, cmdDestroy));
@@ -1074,6 +1075,23 @@ bool Debugger::cmd3d(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdAbyss(int argc, const char **argv) {
+	// first teleport to the abyss
+	g_context->_location->_coords.x = 0xe9;
+	g_context->_location->_coords.y = 0xe9;
+	g_game->setMap(mapMgr->get(MAP_ABYSS), 1, NULL);
+
+	// then to the final altar
+	g_context->_location->_coords.x = 7;
+	g_context->_location->_coords.y = 7;
+	g_context->_location->_coords.z = 7;
+	g_ultima->_saveGame->_orientation = DIR_NORTH;
+	g_context->_party->lightTorch(100, false);
+
+	cmdIgnite(0, nullptr);
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdCollisions(int argc, const char **argv) {
 	_collisionOverride = !_collisionOverride;
 	print("Collision detection %s",
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index e1cddf4f6a..3bc7ec6c21 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -235,6 +235,11 @@ private:
 	 */
 	bool cmd3d(int argc, const char **argv);
 
+	/**
+	 * Teleports to the Abyss final altar
+	 */
+	bool cmdAbyss(int argc, const char **argv);
+
 	/**
 	 * Collision detection on/off
 	 */
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index fdfa57eb0f..ff82a024e6 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -600,25 +600,6 @@ bool GameController::keyPressed(int key) {
 
 	if ((g_context->_location->_context & CTX_DUNGEON) && strchr("abefjlotxy", key))
 		screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
-	else
-		switch (key) {
-		case 'c' + U4_ALT:
-			if (settings._debug && g_context->_location->_map->isWorldMap()) {
-				/* first teleport to the abyss */
-				g_context->_location->_coords.x = 0xe9;
-				g_context->_location->_coords.y = 0xe9;
-				setMap(mapMgr->get(MAP_ABYSS), 1, NULL);
-				/* then to the final altar */
-				g_context->_location->_coords.x = 7;
-				g_context->_location->_coords.y = 7;
-				g_context->_location->_coords.z = 7;
-			}
-			break;
-
-		default:
-			valid = false;
-			break;
-		}
 
 	if (valid && endTurn) {
 		if (eventHandler->getController() == g_game)


Commit: 4e1a461b532ca56765e2c24dfac00b3b3e3d4260
    https://github.com/scummvm/scummvm/commit/4e1a461b532ca56765e2c24dfac00b3b3e3d4260
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-16T19:27:48-07:00

Commit Message:
ULTIMA4: Hook up bulk of the cheat actions to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/meta_engine.cpp
    engines/ultima/ultima4/meta_engine.h


diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index f2b9915d96..1cfb1585d7 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -92,6 +92,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("help", WRAP_METHOD(Debugger, cmdHelp));
 	registerCmd("items", WRAP_METHOD(Debugger, cmdItems));
 	registerCmd("karma", WRAP_METHOD(Debugger, cmdKarma));
+	registerCmd("leave", WRAP_METHOD(Debugger, cmdLeave));
 	registerCmd("location", WRAP_METHOD(Debugger, cmdLocation));
 	registerCmd("mixtures", WRAP_METHOD(Debugger, cmdMixtures));
 	registerCmd("moon", WRAP_METHOD(Debugger, cmdMoon));
@@ -218,7 +219,7 @@ bool Debugger::cmdAttack(int argc, const char **argv) {
 
 	printN("Attack: ");
 	if (g_context->_party->isFlying()) {
-		screenMessage("\n%cDrift only!%c\n", FG_GREY, FG_WHITE);
+		print("\n%cDrift only!%c", FG_GREY, FG_WHITE);
 		return isDebuggerActive();
 	}
 
@@ -300,7 +301,7 @@ bool Debugger::cmdCastSpell(int argc, const char **argv) {
 	if (spell == -1)
 		return isDebuggerActive();
 
-	screenMessage("%s!\n", spellGetName(spell)); //Prints spell name at prompt
+	print("%s!", spellGetName(spell)); //Prints spell name at prompt
 
 	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
 
@@ -317,7 +318,7 @@ bool Debugger::cmdCastSpell(int argc, const char **argv) {
 		break;
 
 	case Spell::PARAM_PHASE: {
-		screenMessage("To Phase: ");
+		printN("To Phase: ");
 #ifdef IOS
 		U4IOS::IOSConversationChoiceHelper choiceController;
 		choiceController.fullSizeChoicePanel();
@@ -615,7 +616,7 @@ bool Debugger::cmdIgnite(int argc, const char **argv) {
 }
 
 bool Debugger::cmdJimmy(int argc, const char **argv) {
-	screenMessage("Jimmy: ");
+	printN("Jimmy: ");
 	Direction dir = gameGetDirection();
 
 	if (dir == DIR_NONE)
@@ -1121,7 +1122,7 @@ bool Debugger::cmdDestroy(int argc, const char **argv) {
 		print("destroy <direction>");
 		return isDebuggerActive();
 	} else {
-		screenMessage("Destroy Object\nDir: ");
+		printN("Destroy Object\nDir: ");
 		dir = gameGetDirection();
 	}
 
@@ -1226,9 +1227,9 @@ bool Debugger::cmdGoto(int argc, const char **argv) {
 		print("teleport <destination name>");
 		return true;
 	} else {
-		screenMessage("Goto: ");
+		printN("Goto: ");
 		dest = gameGetInput(32);
-		screenMessage("\n");
+		print("");
 	}
 
 	dest.toLowercase();
@@ -1240,7 +1241,7 @@ bool Debugger::cmdGoto(int argc, const char **argv) {
 		destNameLower.toLowercase();
 
 		if (destNameLower.find(dest) != Common::String::npos) {
-			screenMessage("\n%s\n", mapMgr->get(destid)->getName().c_str());
+			print("\n%s", mapMgr->get(destid)->getName().c_str());
 			g_context->_location->_coords = g_context->_location->_map->_portals[p]->_coords;
 			found = true;
 			break;
@@ -1270,12 +1271,12 @@ bool Debugger::cmdGoto(int argc, const char **argv) {
 
 bool Debugger::cmdHelp(int argc, const char **argv) {
 	if (!isDebuggerActive()) {
-		screenMessage("Help!\n");
+		print("Help!");
 		screenPrompt();
 	}
 
-	/* Help! send me to Lord British (who conveniently is right around where you are)! */
-	g_game->setMap(mapMgr->get(100), 1, NULL);
+	// Help! send me to Lord British
+	g_game->setMap(mapMgr->get(100), 1, nullptr);
 	g_context->_location->_coords.x = 19;
 	g_context->_location->_coords.y = 8;
 	g_context->_location->_coords.z = 0;
@@ -1356,7 +1357,7 @@ bool Debugger::cmdMixtures(int argc, const char **argv) {
 	for (int i = 0; i < SPELL_MAX; i++)
 		g_ultima->_saveGame->_mixtures[i] = 99;
 
-	screenMessage("All mixtures given");
+	print("All mixtures given");
 	return isDebuggerActive();
 }
 
@@ -1382,7 +1383,7 @@ bool Debugger::cmdMoon(int argc, const char **argv) {
 
 bool Debugger::cmdOpacity(int argc, const char **argv) {
 	g_context->_opacity = !g_context->_opacity;
-	screenMessage("Opacity is %s", g_context->_opacity ? "on" : "off");
+	print("Opacity is %s", g_context->_opacity ? "on" : "off");
 	return isDebuggerActive();
 }
 
@@ -1420,8 +1421,8 @@ bool Debugger::cmdSummon(int argc, const char **argv) {
 		print("summon <creature name>");
 		return true;
 	} else {
-		screenMessage("Summon!\n");
-		screenMessage("What?\n");
+		print("Summon!");
+		print("What?");
 		creature = gameGetInput();
 	}
 
@@ -1485,13 +1486,13 @@ bool Debugger::cmdTransport(int argc, const char **argv) {
 	} else if (isDebuggerActive()) {
 		dir = DIR_NONE;
 	} else {
-		screenMessage("%s\n", tile->getName().c_str());
+		print("%s", tile->getName().c_str());
 
 		// Get the direction in which to create the transport
 		ReadDirController readDir;
 		eventHandler->pushController(&readDir);
 
-		screenMessage("Dir: ");
+		printN("Dir: ");
 		dir = readDir.waitFor();
 	}
 
@@ -1558,7 +1559,7 @@ bool Debugger::cmdVirtue(int argc, const char **argv) {
 			g_ultima->_saveGame->_karma[i] = 0;
 
 		g_context->_stats->update();
-		screenMessage("Full virtues");
+		print("Full virtues");
 	} else {
 		int virtue = strToInt(argv[1]);
 
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index cee8d0af2e..39d58b1ce2 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -92,6 +92,24 @@ static const KeybindingRecord PARTY_KEYS[] = {
 };
 
 static const KeybindingRecord CHEAT_KEYS[] = {
+	{ KEYBIND_CHEAT_COLLISIONS, "CHEAT-COLLISIONS", "Toggle Collision Handling", "collisions", "A+c", nullptr },
+	{ KEYBIND_CHEAT_DESTROY, "CHEAT-DESTROY", "Destroy Object", "destroy", "A+d", nullptr },
+	{ KEYBIND_CHEAT_EQUIPMENT, "CHEAT-EQUIPMENT", "Full Equipment", "equipment", "A+e", nullptr },
+	{ KEYBIND_CHEAT_GOTO, "CHEAT-GOTO", "Goto location", "goto", "A+g", nullptr },
+	{ KEYBIND_CHEAT_HELP, "CHEAT-HELP", "Help - Teleport to Lord British", "goto", "A+h", nullptr },
+	{ KEYBIND_CHEAT_ITEMS, "CHEAT-ITEMS", "Give Items", "items", "A+i", nullptr },
+	{ KEYBIND_CHEAT_KARMA, "CHEAT-KARMA", "List Karma", "karma", "A+k", nullptr },
+	{ KEYBIND_CHEAT_LEAVE, "CHEAT-LEAVE", "Leave Location", "leave", "A+l", nullptr },
+	{ KEYBIND_CHEAT_MIXTURES, "CHEAT-MIXTURES", "Give Mixtures", "mixtures", "A+m", nullptr },
+	{ KEYBIND_CHEAT_PARTY, "CHEAT-PARTY", "Full Party", "companions", "A+p", nullptr },
+	{ KEYBIND_CHEAT_REAGENTS, "CHEAT-REAGENTS", "Give Reagents", "reagents", "A+r", nullptr },
+	{ KEYBIND_CHEAT_STATS, "CHEAT-STATS", "Full Stats", "fullstats", "A+s", nullptr },
+	{ KEYBIND_CHEAT_TRANSPORT, "CHEAT-TRANSPORT", "Create Transport", "transport", "A+t", nullptr },
+	{ KEYBIND_CHEAT_UP, "CHEAT-UP", "Up Level", "up", "A+UP", nullptr },
+	{ KEYBIND_CHEAT_DOWN, "CHEAT-DOWN", "Down Level", "down", "A+DOWN", nullptr },
+	{ KEYBIND_CHEAT_VIRTUE, "CHEAT-VIRTUE", "Grant Virtue", "virtue", "A+v", nullptr },
+	{ KEYBIND_CHEAT_WIND, "CHEAT-WIND", "Change Wind", "wind", "A+w", nullptr },
+
 	{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
 };
 
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index 46f711a306..512a7b5092 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -44,6 +44,13 @@ enum KeybindingAction {
 	KEYBIND_PARTY4, KEYBIND_PARTY5, KEYBIND_PARTY6, KEYBIND_PARTY7,
 	KEYBIND_PARTY8,
 
+	KEYBIND_CHEAT_COLLISIONS, KEYBIND_CHEAT_DESTROY,
+	KEYBIND_CHEAT_EQUIPMENT, KEYBIND_CHEAT_GOTO, KEYBIND_CHEAT_HELP,
+	KEYBIND_CHEAT_ITEMS, KEYBIND_CHEAT_KARMA, KEYBIND_CHEAT_LEAVE,
+	KEYBIND_CHEAT_MIXTURES, KEYBIND_CHEAT_PARTY, KEYBIND_CHEAT_REAGENTS,
+	KEYBIND_CHEAT_STATS, KEYBIND_CHEAT_TRANSPORT, KEYBIND_CHEAT_UP,
+	KEYBIND_CHEAT_DOWN, KEYBIND_CHEAT_VIRTUE, KEYBIND_CHEAT_WIND,
+
 	KEYBIND_NONE
 };
 


Commit: e0607343af8f2698f5ad6079af4c1f8fc9825060
    https://github.com/scummvm/scummvm/commit/e0607343af8f2698f5ad6079af4c1f8fc9825060
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-16T19:27:48-07:00

Commit Message:
ULTIMA4: Split up controllers into their own files

Changed paths:
  A engines/ultima/ultima4/controllers/alpha_action_controller.cpp
  A engines/ultima/ultima4/controllers/alpha_action_controller.h
  A engines/ultima/ultima4/controllers/controller.cpp
  A engines/ultima/ultima4/controllers/controller.h
  A engines/ultima/ultima4/controllers/game_controller.cpp
  A engines/ultima/ultima4/controllers/game_controller.h
  A engines/ultima/ultima4/controllers/key_handler_controller.cpp
  A engines/ultima/ultima4/controllers/key_handler_controller.h
  A engines/ultima/ultima4/controllers/read_choice_controller.cpp
  A engines/ultima/ultima4/controllers/read_choice_controller.h
  A engines/ultima/ultima4/controllers/read_dir_controller.cpp
  A engines/ultima/ultima4/controllers/read_dir_controller.h
  A engines/ultima/ultima4/controllers/read_int_controller.cpp
  A engines/ultima/ultima4/controllers/read_int_controller.h
  A engines/ultima/ultima4/controllers/read_player_controller.cpp
  A engines/ultima/ultima4/controllers/read_player_controller.h
  A engines/ultima/ultima4/controllers/read_string_controller.cpp
  A engines/ultima/ultima4/controllers/read_string_controller.h
  A engines/ultima/ultima4/controllers/wait_controller.cpp
  A engines/ultima/ultima4/controllers/wait_controller.h
  A engines/ultima/ultima4/controllers/ztats_controller.cpp
  A engines/ultima/ultima4/controllers/ztats_controller.h
  R engines/ultima/ultima4/events/controller.cpp
  R engines/ultima/ultima4/events/controller.h
    engines/ultima/module.mk
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/events/event.cpp
    engines/ultima/ultima4/events/event.h
    engines/ultima/ultima4/events/event_scummvm.cpp
    engines/ultima/ultima4/game/death.cpp
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/game.h
    engines/ultima/ultima4/game/intro.cpp
    engines/ultima/ultima4/game/intro.h
    engines/ultima/ultima4/game/item.cpp
    engines/ultima/ultima4/game/person.cpp
    engines/ultima/ultima4/map/combat.cpp
    engines/ultima/ultima4/map/combat.h
    engines/ultima/ultima4/map/shrine.cpp
    engines/ultima/ultima4/meta_engine.cpp


diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index a3834e9d8b..d7c91d2381 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -134,6 +134,17 @@ MODULE_OBJS := \
 	ultima1/widgets/urban_widget.o \
 	ultima1/widgets/wench.o \
 	ultima1/game.o \
+	ultima4/controllers/alpha_action_controller.o \
+	ultima4/controllers/controller.o \
+	ultima4/controllers/game_controller.o \
+	ultima4/controllers/key_handler_controller.o \
+	ultima4/controllers/read_choice_controller.o \
+	ultima4/controllers/read_dir_controller.o \
+	ultima4/controllers/read_int_controller.o \
+	ultima4/controllers/read_player_controller.o \
+	ultima4/controllers/read_string_controller.o \
+	ultima4/controllers/wait_controller.o \
+	ultima4/controllers/ztats_controller.o \
 	ultima4/conversation/conversation.o \
 	ultima4/conversation/dialogueloader.o \
 	ultima4/conversation/dialogueloader_hw.o \
@@ -148,7 +159,6 @@ MODULE_OBJS := \
 	ultima4/core/error.o \
 	ultima4/core/settings.o \
 	ultima4/core/utils.o \
-	ultima4/events/controller.o \
 	ultima4/events/event.o \
 	ultima4/events/event_scummvm.o \
 	ultima4/events/timed_event_mgr.o \
diff --git a/engines/ultima/ultima4/controllers/alpha_action_controller.cpp b/engines/ultima/ultima4/controllers/alpha_action_controller.cpp
new file mode 100644
index 0000000000..48d62058be
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/alpha_action_controller.cpp
@@ -0,0 +1,59 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/alpha_action_controller.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/gfx/screen.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+bool AlphaActionController::keyPressed(int key) {
+	if (Common::isLower(key))
+		key = toupper(key);
+
+	if (key >= 'A' && key <= toupper(_lastValidLetter)) {
+		_value = key - 'A';
+		doneWaiting();
+	} else if (key == U4_SPACE || key == U4_ESC || key == U4_ENTER) {
+		screenMessage("\n");
+		_value = -1;
+		doneWaiting();
+	} else {
+		screenMessage("\n%s", _prompt.c_str());
+		g_screen->update();
+		return KeyHandler::defaultHandler(key, NULL);
+	}
+	return true;
+}
+
+int AlphaActionController::get(char lastValidLetter, const Common::String &prompt, EventHandler *eh) {
+	if (!eh)
+		eh = eventHandler;
+
+	AlphaActionController ctrl(lastValidLetter, prompt);
+	eh->pushController(&ctrl);
+	return ctrl.waitFor();
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/alpha_action_controller.h b/engines/ultima/ultima4/controllers/alpha_action_controller.h
new file mode 100644
index 0000000000..90efd64793
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/alpha_action_controller.h
@@ -0,0 +1,52 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_ALPHA_ACTION_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_ALPHA_ACTION_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/events/event.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to handle input for commands requiring a letter
+ * argument in the range 'a' - lastValidLetter.
+ */
+class AlphaActionController : public WaitableController<int> {
+public:
+	AlphaActionController(char letter, const Common::String &p) : _lastValidLetter(letter), _prompt(p) {
+	}
+	bool keyPressed(int key) override;
+
+	static int get(char lastValidLetter, const Common::String &prompt, EventHandler *eh = NULL);
+
+private:
+	char _lastValidLetter;
+	Common::String _prompt;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/events/controller.cpp b/engines/ultima/ultima4/controllers/controller.cpp
similarity index 97%
rename from engines/ultima/ultima4/events/controller.cpp
rename to engines/ultima/ultima4/controllers/controller.cpp
index d5bafa1e8a..50f086848a 100644
--- a/engines/ultima/ultima4/events/controller.cpp
+++ b/engines/ultima/ultima4/controllers/controller.cpp
@@ -20,7 +20,7 @@
  *
  */
 
-#include "ultima/ultima4/events/controller.h"
+#include "ultima/ultima4/controllers/controller.h"
 #include "ultima/ultima4/events/event.h"
 
 namespace Ultima {
diff --git a/engines/ultima/ultima4/events/controller.h b/engines/ultima/ultima4/controllers/controller.h
similarity index 94%
rename from engines/ultima/ultima4/events/controller.h
rename to engines/ultima/ultima4/controllers/controller.h
index 726cc7d683..012f367c89 100644
--- a/engines/ultima/ultima4/events/controller.h
+++ b/engines/ultima/ultima4/controllers/controller.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef ULTIMA4_CONTROLLER_H
-#define ULTIMA4_CONTROLLER_H
+#ifndef ULTIMA4_CONTROLLERS_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_CONTROLLER_H
 
 #include "ultima/ultima4/meta_engine.h"
 
@@ -113,6 +113,13 @@ private:
 	bool _exitWhenDone;
 };
 
+class TurnCompleter {
+public:
+	virtual ~TurnCompleter() {
+	}
+	virtual void finishTurn() = 0;
+};
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima4/controllers/game_controller.cpp b/engines/ultima/ultima4/controllers/game_controller.cpp
new file mode 100644
index 0000000000..e6d8f57c29
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/game_controller.cpp
@@ -0,0 +1,842 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/game_controller.h"
+#include "ultima/ultima4/core/config.h"
+#include "ultima/ultima4/core/debugger.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/death.h"
+#include "ultima/ultima4/game/moongate.h"
+#include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/gfx/imagemgr.h"
+#include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/map/annotation.h"
+#include "ultima/ultima4/map/city.h"
+#include "ultima/ultima4/map/dungeon.h"
+#include "ultima/ultima4/map/mapmgr.h"
+#include "ultima/ultima4/map/shrine.h"
+#include "ultima/ultima4/ultima4.h"
+#include "common/system.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+using namespace std;
+
+GameController *g_game = NULL;
+
+static const MouseArea MOUSE_AREAS[] = {
+	{ 3, { { 8, 8 }, { 8, 184 }, { 96, 96 } }, MC_WEST, { U4_ENTER, 0, U4_LEFT } },
+	{ 3, { { 8, 8 }, { 184, 8 }, { 96, 96 } }, MC_NORTH, { U4_ENTER, 0, U4_UP }  },
+	{ 3, { { 184, 8 }, { 184, 184 }, { 96, 96 } }, MC_EAST, { U4_ENTER, 0, U4_RIGHT } },
+	{ 3, { { 8, 184 }, { 184, 184 }, { 96, 96 } }, MC_SOUTH, { U4_ENTER, 0, U4_DOWN } },
+	{ 0, { { 0, 0 }, { 0, 0 }, { 0, 0 } }, MC_NORTH, { 0, 0, 0 } }
+};
+
+GameController::GameController() : _mapArea(BORDER_WIDTH, BORDER_HEIGHT, VIEWPORT_W, VIEWPORT_H), _paused(false), _pausedTimer(0) {
+	g_game = this;
+}
+
+void GameController::initScreen() {
+	Image *screen = imageMgr->get("screen")->_image;
+
+	screen->fillRect(0, 0, screen->width(), screen->height(), 0, 0, 0);
+	g_screen->update();
+}
+
+void GameController::initScreenWithoutReloadingState() {
+	g_music->play();
+	imageMgr->get(BKGD_BORDERS)->_image->draw(0, 0);
+	g_context->_stats->update(); /* draw the party stats */
+
+	screenMessage("Press Alt-h for help\n");
+	screenPrompt();
+
+	eventHandler->pushMouseAreaSet(MOUSE_AREAS);
+
+	eventHandler->setScreenUpdate(&gameUpdateScreen);
+}
+
+void GameController::init() {
+	initScreen();
+
+	// initialize the global game context, conversation and game state variables
+	g_context = new Context();
+	g_context->_line = TEXT_AREA_H - 1;
+	g_context->col = 0;
+	g_context->_stats = new StatsArea();
+	g_context->_moonPhase = 0;
+	g_context->_windDirection = DIR_NORTH;
+	g_context->_windCounter = 0;
+	g_context->_windLock = false;
+	g_context->_aura = new Aura();
+	g_context->_horseSpeed = 0;
+	g_context->_opacity = 1;
+	g_context->_lastCommandTime = g_system->getMillis();
+	g_context->_lastShip = NULL;
+}
+
+void GameController::setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter) {
+	int viewMode;
+	LocationContext context;
+	int activePlayer = g_context->_party->getActivePlayer();
+	MapCoords coords;
+
+	if (!turnCompleter)
+		turnCompleter = this;
+
+	if (portal)
+		coords = portal->_start;
+	else
+		coords = MapCoords(map->_width / 2, map->_height / 2);
+
+	/* If we don't want to save the location, then just return to the previous location,
+	   as there may still be ones in the stack we want to keep */
+	if (!saveLocation)
+		exitToParentMap();
+
+	switch (map->_type) {
+	case Map::WORLD:
+		context = CTX_WORLDMAP;
+		viewMode = VIEW_NORMAL;
+		break;
+	case Map::DUNGEON:
+		context = CTX_DUNGEON;
+		viewMode = VIEW_DUNGEON;
+		if (portal)
+			g_ultima->_saveGame->_orientation = DIR_EAST;
+		break;
+	case Map::COMBAT:
+		coords = MapCoords(-1, -1); /* set these to -1 just to be safe; we don't need them */
+		context = CTX_COMBAT;
+		viewMode = VIEW_NORMAL;
+		activePlayer = -1; /* different active player for combat, defaults to 'None' */
+		break;
+	case Map::SHRINE:
+		context = CTX_SHRINE;
+		viewMode = VIEW_NORMAL;
+		break;
+	case Map::CITY:
+	default:
+		context = CTX_CITY;
+		viewMode = VIEW_NORMAL;
+		break;
+	}
+	g_context->_location = new Location(coords, map, viewMode, context, turnCompleter, g_context->_location);
+	g_context->_location->addObserver(this);
+	g_context->_party->setActivePlayer(activePlayer);
+#ifdef IOS
+	U4IOS::updateGameControllerContext(c->location->context);
+#endif
+
+	/* now, actually set our new tileset */
+	_mapArea.setTileset(map->_tileset);
+
+	if (isCity(map)) {
+		City *city = dynamic_cast<City *>(map);
+		city->addPeople();
+	}
+}
+
+int GameController::exitToParentMap() {
+	if (!g_context->_location)
+		return 0;
+
+	if (g_context->_location->_prev != NULL) {
+		// Create the balloon for Hythloth
+		if (g_context->_location->_map->_id == MAP_HYTHLOTH)
+			createBalloon(g_context->_location->_prev->_map);
+
+		// free map info only if previous location was on a different map
+		if (g_context->_location->_prev->_map != g_context->_location->_map) {
+			g_context->_location->_map->_annotations->clear();
+			g_context->_location->_map->clearObjects();
+
+			/* quench the torch of we're on the world map */
+			if (g_context->_location->_prev->_map->isWorldMap())
+				g_context->_party->quenchTorch();
+		}
+		locationFree(&g_context->_location);
+
+		// restore the tileset to the one the current map uses
+		_mapArea.setTileset(g_context->_location->_map->_tileset);
+#ifdef IOS
+		U4IOS::updateGameControllerContext(c->location->context);
+#endif
+
+		return 1;
+	}
+	return 0;
+}
+
+void GameController::finishTurn() {
+	g_context->_lastCommandTime = g_system->getMillis();
+	Creature *attacker = NULL;
+
+	while (1) {
+
+		/* adjust food and moves */
+		g_context->_party->endTurn();
+
+		/* count down the aura, if there is one */
+		g_context->_aura->passTurn();
+
+		gameCheckHullIntegrity();
+
+		/* update party stats */
+		//c->stats->setView(STATS_PARTY_OVERVIEW);
+
+		screenUpdate(&this->_mapArea, true, false);
+		screenWait(1);
+
+		/* Creatures cannot spawn, move or attack while the avatar is on the balloon */
+		if (!g_context->_party->isFlying()) {
+
+			// apply effects from tile avatar is standing on
+			g_context->_party->applyEffect(g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->getEffect());
+
+			// Move creatures and see if something is attacking the avatar
+			attacker = g_context->_location->_map->moveObjects(g_context->_location->_coords);
+
+			// Something's attacking!  Start combat!
+			if (attacker) {
+				gameCreatureAttack(attacker);
+				return;
+			}
+
+			// cleanup old creatures and spawn new ones
+			creatureCleanup();
+			checkRandomCreatures();
+			checkBridgeTrolls();
+		}
+
+		/* update map annotations */
+		g_context->_location->_map->_annotations->passTurn();
+
+		if (!g_context->_party->isImmobilized())
+			break;
+
+		if (g_context->_party->isDead()) {
+			deathStart(0);
+			return;
+		} else {
+			screenMessage("Zzzzzz\n");
+			screenWait(4);
+		}
+	}
+
+	if (g_context->_location->_context == CTX_DUNGEON) {
+		Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
+		if (g_context->_party->getTorchDuration() <= 0)
+			screenMessage("It's Dark!\n");
+		else g_context->_party->burnTorch();
+
+		/* handle dungeon traps */
+		if (dungeon->currentToken() == DUNGEON_TRAP) {
+			dungeonHandleTrap((TrapType)dungeon->currentSubToken());
+			// a little kludgey to have a second test for this
+			// right here.  But without it you can survive an
+			// extra turn after party death and do some things
+			// that could cause a crash, like Hole up and Camp.
+			if (g_context->_party->isDead()) {
+				deathStart(0);
+				return;
+			}
+		}
+	}
+
+
+	/* draw a prompt */
+	screenPrompt();
+	//screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
+}
+
+void GameController::flashTile(const Coords &coords, MapTile tile, int frames) {
+	g_context->_location->_map->_annotations->add(coords, tile, true);
+
+	screenTileUpdate(&g_game->_mapArea, coords);
+
+	screenWait(frames);
+	g_context->_location->_map->_annotations->remove(coords, tile);
+
+	screenTileUpdate(&g_game->_mapArea, coords, false);
+}
+
+void GameController::flashTile(const Coords &coords, const Common::String &tilename, int timeFactor) {
+	Tile *tile = g_context->_location->_map->_tileset->getByName(tilename);
+	ASSERT(tile, "no tile named '%s' found in tileset", tilename.c_str());
+	flashTile(coords, tile->getId(), timeFactor);
+}
+
+void GameController::update(Party *party, PartyEvent &event) {
+	int i;
+
+	switch (event._type) {
+	case PartyEvent::LOST_EIGHTH:
+		// inform a player he has lost zero or more eighths of avatarhood.
+		screenMessage("\n %cThou hast lost\n  an eighth!%c\n", FG_YELLOW, FG_WHITE);
+		break;
+	case PartyEvent::ADVANCED_LEVEL:
+		screenMessage("\n%c%s\nThou art now Level %d%c\n", FG_YELLOW, event._player->getName().c_str(), event._player->getRealLevel(), FG_WHITE);
+		gameSpellEffect('r', -1, SOUND_MAGIC); // Same as resurrect spell
+		break;
+	case PartyEvent::STARVING:
+		screenMessage("\n%cStarving!!!%c\n", FG_YELLOW, FG_WHITE);
+		/* FIXME: add sound effect here */
+
+		// 2 damage to each party member for starving!
+		for (i = 0; i < g_ultima->_saveGame->_members; i++)
+			g_context->_party->member(i)->applyDamage(2);
+		break;
+	default:
+		break;
+	}
+}
+
+void GameController::update(Location *location, MoveEvent &event) {
+	switch (location->_map->_type) {
+	case Map::DUNGEON:
+		avatarMovedInDungeon(event);
+		break;
+	case Map::COMBAT:
+		// FIXME: let the combat controller handle it
+		dynamic_cast<CombatController *>(eventHandler->getController())->movePartyMember(event);
+		break;
+	default:
+		avatarMoved(event);
+		break;
+	}
+}
+
+void GameController::keybinder(KeybindingAction action) {
+	MetaEngine::executeAction(action);
+}
+
+bool GameController::keyPressed(int key) {
+	bool valid = true;
+	int endTurn = 1;
+	Object *obj;
+	MapTile *tile;
+
+	/* Translate context-sensitive action key into a useful command */
+	if (key == U4_ENTER && settings._enhancements && settings._enhancementsOptions._smartEnterKey) {
+		/* Attempt to guess based on the character's surroundings etc, what
+		   action they want */
+
+		/* Do they want to board something? */
+		if (g_context->_transportContext == TRANSPORT_FOOT) {
+			obj = g_context->_location->_map->objectAt(g_context->_location->_coords);
+			if (obj && (obj->getTile().getTileType()->isShip() ||
+			            obj->getTile().getTileType()->isHorse() ||
+			            obj->getTile().getTileType()->isBalloon()))
+				key = 'b';
+		}
+		/* Klimb/Descend Balloon */
+		else if (g_context->_transportContext == TRANSPORT_BALLOON) {
+			if (g_context->_party->isFlying())
+				key = 'd';
+			else {
+#ifdef IOS
+				U4IOS::IOSSuperButtonHelper superHelper;
+				key = ReadChoiceController::get("xk \033\n");
+#else
+				key = 'k';
+#endif
+			}
+		}
+		/* X-it transport */
+		else key = 'x';
+
+		/* Klimb? */
+		if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_KLIMB) != NULL))
+			key = 'k';
+		/* Descend? */
+		else if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_DESCEND) != NULL))
+			key = 'd';
+
+		if (g_context->_location->_context == CTX_DUNGEON) {
+			Dungeon *dungeon = static_cast<Dungeon *>(g_context->_location->_map);
+			bool up = dungeon->ladderUpAt(g_context->_location->_coords);
+			bool down = dungeon->ladderDownAt(g_context->_location->_coords);
+			if (up && down) {
+#ifdef IOS
+				U4IOS::IOSClimbHelper climbHelper;
+				key = ReadChoiceController::get("kd \033\n");
+#else
+				key = 'k'; // This is consistent with the previous code. Ideally, I would have a UI here as well.
+#endif
+			} else if (up) {
+				key = 'k';
+			} else {
+				key = 'd';
+			}
+		}
+
+		/* Enter? */
+		if (g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_ENTER) != NULL)
+			key = 'e';
+
+		/* Get Chest? */
+		if (!g_context->_party->isFlying()) {
+			tile = g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
+
+			if (tile->getTileType()->isChest()) key = 'g';
+		}
+
+		/* None of these? Default to search */
+		if (key == U4_ENTER) key = 's';
+	}
+
+	if ((g_context->_location->_context & CTX_DUNGEON) && strchr("abefjlotxy", key))
+		screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
+
+	if (valid && endTurn) {
+		if (eventHandler->getController() == g_game)
+			g_context->_location->_turnCompleter->finishTurn();
+	} else if (!endTurn) {
+		/* if our turn did not end, then manually redraw the text prompt */
+		screenPrompt();
+	}
+
+	return valid || KeyHandler::defaultHandler(key, NULL);
+}
+
+void GameController::initMoons() {
+	int trammelphase = g_ultima->_saveGame->_trammelPhase,
+	    feluccaphase = g_ultima->_saveGame->_feluccaPhase;
+
+	ASSERT(g_context != NULL, "Game context doesn't exist!");
+	ASSERT(g_ultima->_saveGame != NULL, "Savegame doesn't exist!");
+	//ASSERT(mapIsWorldMap(c->location->map) && c->location->viewMode == VIEW_NORMAL, "Can only call gameInitMoons() from the world map!");
+
+	g_ultima->_saveGame->_trammelPhase = g_ultima->_saveGame->_feluccaPhase = 0;
+	g_context->_moonPhase = 0;
+
+	while ((g_ultima->_saveGame->_trammelPhase != trammelphase) ||
+	        (g_ultima->_saveGame->_feluccaPhase != feluccaphase))
+		updateMoons(false);
+}
+
+void GameController::updateMoons(bool showmoongates) {
+	int realMoonPhase,
+	    oldTrammel,
+	    trammelSubphase;
+	const Coords *gate;
+
+	if (g_context->_location->_map->isWorldMap()) {
+		oldTrammel = g_ultima->_saveGame->_trammelPhase;
+
+		if (++g_context->_moonPhase >= MOON_PHASES * MOON_SECONDS_PER_PHASE * 4)
+			g_context->_moonPhase = 0;
+
+		trammelSubphase = g_context->_moonPhase % (MOON_SECONDS_PER_PHASE * 4 * 3);
+		realMoonPhase = (g_context->_moonPhase / (4 * MOON_SECONDS_PER_PHASE));
+
+		g_ultima->_saveGame->_trammelPhase = realMoonPhase / 3;
+		g_ultima->_saveGame->_feluccaPhase = realMoonPhase % 8;
+
+		if (g_ultima->_saveGame->_trammelPhase > 7)
+			g_ultima->_saveGame->_trammelPhase = 7;
+
+		if (showmoongates) {
+			/* update the moongates if trammel changed */
+			if (trammelSubphase == 0) {
+				gate = moongateGetGateCoordsForPhase(oldTrammel);
+				if (gate)
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate)
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
+			} else if (trammelSubphase == 1) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
+				}
+			} else if (trammelSubphase == 2) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
+				}
+			} else if (trammelSubphase == 3) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
+				}
+			} else if ((trammelSubphase > 3) && (trammelSubphase < (MOON_SECONDS_PER_PHASE * 4 * 3) - 3)) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
+				}
+			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 3) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
+				}
+			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 2) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
+				}
+			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 1) {
+				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
+				if (gate) {
+					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
+					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
+				}
+			}
+		}
+	}
+}
+
+void GameController::avatarMoved(MoveEvent &event) {
+	if (event._userEvent) {
+
+		// is filterMoveMessages even used?  it doesn't look like the option is hooked up in the configuration menu
+		if (!settings._filterMoveMessages) {
+			switch (g_context->_transportContext) {
+			case TRANSPORT_FOOT:
+			case TRANSPORT_HORSE:
+				screenMessage("%s\n", getDirectionName(event._dir));
+				break;
+			case TRANSPORT_SHIP:
+				if (event._result & MOVE_TURNED)
+					screenMessage("Turn %s!\n", getDirectionName(event._dir));
+				else if (event._result & MOVE_SLOWED)
+					screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
+				else
+					screenMessage("Sail %s!\n", getDirectionName(event._dir));
+				break;
+			case TRANSPORT_BALLOON:
+				screenMessage("%cDrift Only!%c\n", FG_GREY, FG_WHITE);
+				break;
+			default:
+				error("bad transportContext %d in avatarMoved()", g_context->_transportContext);
+			}
+		}
+
+		/* movement was blocked */
+		if (event._result & MOVE_BLOCKED) {
+
+			/* if shortcuts are enabled, try them! */
+			if (settings._shortcutCommands) {
+				MapCoords new_coords = g_context->_location->_coords;
+				MapTile *tile;
+
+				new_coords.move(event._dir, g_context->_location->_map);
+				tile = g_context->_location->_map->tileAt(new_coords, WITH_OBJECTS);
+
+				if (tile->getTileType()->isDoor()) {
+					g_debugger->openAt(new_coords);
+					event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
+				} else if (tile->getTileType()->isLockedDoor()) {
+					g_debugger->jimmyAt(new_coords);
+					event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
+				} /*else if (mapPersonAt(c->location->map, new_coords) != NULL) {
+                    talkAtCoord(newx, newy, 1, NULL);
+                    event.result = MOVE_SUCCEEDED | MOVE_END_TURN;
+                    }*/
+			}
+
+			/* if we're still blocked */
+			if ((event._result & MOVE_BLOCKED) && !settings._filterMoveMessages) {
+				soundPlay(SOUND_BLOCKED, false);
+				screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
+			}
+		} else if (g_context->_transportContext == TRANSPORT_FOOT || g_context->_transportContext == TRANSPORT_HORSE) {
+			/* movement was slowed */
+			if (event._result & MOVE_SLOWED) {
+				soundPlay(SOUND_WALK_SLOWED);
+				screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
+			} else {
+				soundPlay(SOUND_WALK_NORMAL);
+			}
+		}
+	}
+
+	/* exited map */
+	if (event._result & MOVE_EXIT_TO_PARENT) {
+		screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
+		exitToParentMap();
+		g_music->play();
+	}
+
+	/* things that happen while not on board the balloon */
+	if (g_context->_transportContext & ~TRANSPORT_BALLOON)
+		checkSpecialCreatures(event._dir);
+	/* things that happen while on foot or horseback */
+	if ((g_context->_transportContext & TRANSPORT_FOOT_OR_HORSE) &&
+	        !(event._result & (MOVE_SLOWED | MOVE_BLOCKED))) {
+		if (checkMoongates())
+			event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_END_TURN);
+	}
+}
+
+void GameController::avatarMovedInDungeon(MoveEvent &event) {
+	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
+	Direction realDir = dirNormalize((Direction)g_ultima->_saveGame->_orientation, event._dir);
+
+	if (!settings._filterMoveMessages) {
+		if (event._userEvent) {
+			if (event._result & MOVE_TURNED) {
+				if (dirRotateCCW((Direction)g_ultima->_saveGame->_orientation) == realDir)
+					screenMessage("Turn Left\n");
+				else screenMessage("Turn Right\n");
+			}
+			/* show 'Advance' or 'Retreat' in dungeons */
+			else screenMessage("%s\n", realDir == g_ultima->_saveGame->_orientation ? "Advance" : "Retreat");
+		}
+
+		if (event._result & MOVE_BLOCKED)
+			screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
+	}
+
+	/* if we're exiting the map, do this */
+	if (event._result & MOVE_EXIT_TO_PARENT) {
+		screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
+		exitToParentMap();
+		g_music->play();
+	}
+
+	/* check to see if we're entering a dungeon room */
+	if (event._result & MOVE_SUCCEEDED) {
+		if (dungeon->currentToken() == DUNGEON_ROOM) {
+			int room = (int)dungeon->currentSubToken(); /* get room number */
+
+			/**
+			 * recalculate room for the abyss -- there are 16 rooms for every 2 levels,
+			 * each room marked with 0xD* where (* == room number 0-15).
+			 * for levels 1 and 2, there are 16 rooms, levels 3 and 4 there are 16 rooms, etc.
+			 */
+			if (g_context->_location->_map->_id == MAP_ABYSS)
+				room = (0x10 * (g_context->_location->_coords.z / 2)) + room;
+
+			Dungeon *dng = dynamic_cast<Dungeon *>(g_context->_location->_map);
+			dng->_currentRoom = room;
+
+			/* set the map and start combat! */
+			CombatController *cc = new CombatController(dng->_roomMaps[room]);
+			cc->initDungeonRoom(room, dirReverse(realDir));
+			cc->begin();
+		}
+	}
+}
+
+void GameController::timerFired() {
+	if (_pausedTimer > 0) {
+		_pausedTimer--;
+		if (_pausedTimer <= 0) {
+			_pausedTimer = 0;
+			_paused = false; /* unpause the game */
+		}
+	}
+
+	if (!_paused && !_pausedTimer) {
+		if (++g_context->_windCounter >= MOON_SECONDS_PER_PHASE * 4) {
+			if (xu4_random(4) == 1 && !g_context->_windLock)
+				g_context->_windDirection = dirRandomDir(MASK_DIR_ALL);
+			g_context->_windCounter = 0;
+		}
+
+		/* balloon moves about 4 times per second */
+		if ((g_context->_transportContext == TRANSPORT_BALLOON) &&
+		        g_context->_party->isFlying()) {
+			g_context->_location->move(dirReverse((Direction) g_context->_windDirection), false);
+		}
+
+		updateMoons(true);
+
+		screenCycle();
+
+		/*
+		 * force pass if no commands within last 20 seconds
+		 */
+		Controller *controller = eventHandler->getController();
+		if (controller != NULL && (eventHandler->getController() == g_game ||
+			dynamic_cast<CombatController *>(eventHandler->getController()) != NULL) &&
+			gameTimeSinceLastCommand() > 20) {
+
+			/* pass the turn, and redraw the text area so the prompt is shown */
+			MetaEngine::executeAction(KEYBIND_PASS);
+			screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
+		}
+	}
+
+}
+
+void GameController::checkSpecialCreatures(Direction dir) {
+	int i;
+	Object *obj;
+	static const struct {
+		int x, y;
+		Direction dir;
+	} pirateInfo[] = {
+		{ 224, 220, DIR_EAST }, /* N'M" O'A" */
+		{ 224, 228, DIR_EAST }, /* O'E" O'A" */
+		{ 226, 220, DIR_EAST }, /* O'E" O'C" */
+		{ 227, 228, DIR_EAST }, /* O'E" O'D" */
+		{ 228, 227, DIR_SOUTH }, /* O'D" O'E" */
+		{ 229, 225, DIR_SOUTH }, /* O'B" O'F" */
+		{ 229, 223, DIR_NORTH }, /* N'P" O'F" */
+		{ 228, 222, DIR_NORTH } /* N'O" O'E" */
+	};
+
+	/*
+	 * if heading east into pirates cove (O'A" N'N"), generate pirate
+	 * ships
+	 */
+	if (dir == DIR_EAST &&
+	        g_context->_location->_coords.x == 0xdd &&
+	        g_context->_location->_coords.y == 0xe0) {
+		for (i = 0; i < 8; i++) {
+			obj = g_context->_location->_map->addCreature(creatureMgr->getById(PIRATE_ID), MapCoords(pirateInfo[i].x, pirateInfo[i].y));
+			obj->setDirection(pirateInfo[i].dir);
+		}
+	}
+
+	/*
+	 * if heading south towards the shrine of humility, generate
+	 * daemons unless horn has been blown
+	 */
+	if (dir == DIR_SOUTH &&
+	        g_context->_location->_coords.x >= 229 &&
+	        g_context->_location->_coords.x < 234 &&
+	        g_context->_location->_coords.y >= 212 &&
+	        g_context->_location->_coords.y < 217 &&
+	        *g_context->_aura != Aura::HORN) {
+		for (i = 0; i < 8; i++)
+			g_context->_location->_map->addCreature(creatureMgr->getById(DAEMON_ID), MapCoords(231, g_context->_location->_coords.y + 1, g_context->_location->_coords.z));
+	}
+}
+
+bool GameController::checkMoongates() {
+	Coords dest;
+
+	if (moongateFindActiveGateAt(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase, g_context->_location->_coords, dest)) {
+
+		gameSpellEffect(-1, -1, SOUND_MOONGATE); // Default spell effect (screen inversion without 'spell' sound effects)
+
+		if (g_context->_location->_coords != dest) {
+			g_context->_location->_coords = dest;
+			gameSpellEffect(-1, -1, SOUND_MOONGATE); // Again, after arriving
+		}
+
+		if (moongateIsEntryToShrineOfSpirituality(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase)) {
+			Shrine *shrine_spirituality;
+
+			shrine_spirituality = dynamic_cast<Shrine *>(mapMgr->get(MAP_SHRINE_SPIRITUALITY));
+
+			if (!g_context->_party->canEnterShrine(VIRT_SPIRITUALITY))
+				return true;
+
+			setMap(shrine_spirituality, 1, NULL);
+			g_music->play();
+
+			shrine_spirituality->enter();
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+void GameController::creatureCleanup() {
+	ObjectDeque::iterator i;
+	Map *map = g_context->_location->_map;
+
+	for (i = map->_objects.begin(); i != map->_objects.end();) {
+		Object *obj = *i;
+		MapCoords o_coords = obj->getCoords();
+
+		if ((obj->getType() == Object::CREATURE) && (o_coords.z == g_context->_location->_coords.z) &&
+		        o_coords.distance(g_context->_location->_coords, g_context->_location->_map) > MAX_CREATURE_DISTANCE) {
+
+			/* delete the object and remove it from the map */
+			i = map->removeObject(i);
+		} else i++;
+	}
+}
+
+void GameController::checkRandomCreatures() {
+	int canSpawnHere = g_context->_location->_map->isWorldMap() || g_context->_location->_context & CTX_DUNGEON;
+#ifdef IOS
+	int spawnDivisor = c->location->context & CTX_DUNGEON ? (53 - (c->location->coords.z << 2)) : 53;
+#else
+	int spawnDivisor = g_context->_location->_context & CTX_DUNGEON ? (32 - (g_context->_location->_coords.z << 2)) : 32;
+#endif
+
+	/* If there are too many creatures already,
+	   or we're not on the world map, don't worry about it! */
+	if (!canSpawnHere ||
+	        g_context->_location->_map->getNumberOfCreatures() >= MAX_CREATURES_ON_MAP ||
+	        xu4_random(spawnDivisor) != 0)
+		return;
+
+	gameSpawnCreature(NULL);
+}
+
+void GameController::checkBridgeTrolls() {
+	const Tile *bridge = g_context->_location->_map->_tileset->getByName("bridge");
+	if (!bridge)
+		return;
+
+	// TODO: CHEST: Make a user option to not make chests block bridge trolls
+	if (!g_context->_location->_map->isWorldMap() ||
+	        g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)->_id != bridge->getId() ||
+	        xu4_random(8) != 0)
+		return;
+
+	screenMessage("\nBridge Trolls!\n");
+
+	Creature *m = g_context->_location->_map->addCreature(creatureMgr->getById(TROLL_ID), g_context->_location->_coords);
+	CombatController *cc = new CombatController(MAP_BRIDGE_CON);
+	cc->init(m);
+	cc->begin();
+}
+
+bool GameController::createBalloon(Map *map) {
+	ObjectDeque::iterator i;
+
+	/* see if the balloon has already been created (and not destroyed) */
+	for (i = map->_objects.begin(); i != map->_objects.end(); i++) {
+		Object *obj = *i;
+		if (obj->getTile().getTileType()->isBalloon())
+			return false;
+	}
+
+	const Tile *balloon = map->_tileset->getByName("balloon");
+	ASSERT(balloon, "no balloon tile found in tileset");
+	map->addObject(balloon->getId(), balloon->getId(), map->getLabel("balloon"));
+	return true;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/game_controller.h b/engines/ultima/ultima4/controllers/game_controller.h
new file mode 100644
index 0000000000..6ba364b1ec
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/game_controller.h
@@ -0,0 +1,174 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_GAME_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_GAME_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/core/coords.h"
+#include "ultima/ultima4/core/observer.h"
+#include "ultima/ultima4/game/portal.h"
+#include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/map/location.h"
+#include "ultima/ultima4/map/tileview.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * The main game controller that handles basic game flow and keypresses.
+ *
+ * @todo
+ *  <ul>
+ *      <li>separate the dungeon specific stuff into another class (subclass?)</li>
+ *  </ul>
+ */
+class GameController : public Controller, public Observer<Party *, PartyEvent &>, public Observer<Location *, MoveEvent &>,
+	public TurnCompleter {
+public:
+	GameController();
+
+	/* controller functions */
+
+	/**
+	 * Keybinder actions
+	 */
+	void keybinder(KeybindingAction action) override;
+
+	/**
+	 * The main key handler for the game.  Interpretes each key as a
+	 * command - 'a' for attack, 't' for talk, etc.
+	 */
+	bool keyPressed(int key) override;
+
+	/**
+	 * This function is called every quarter second.
+	 */
+	void timerFired() override;
+
+	/* main game functions */
+	void init();
+	void initScreen();
+	void initScreenWithoutReloadingState();
+	void setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter = NULL);
+
+	/**
+	 * Exits the current map and location and returns to its parent location
+	 * This restores all relevant information from the previous location,
+	 * such as the map, map position, etc. (such as exiting a city)
+	 **/
+	int exitToParentMap();
+
+	/**
+	 * Terminates a game turn.  This performs the post-turn housekeeping
+	 * tasks like adjusting the party's food, incrementing the number of
+	 * moves, etc.
+	 */
+	void finishTurn() override;
+
+	/**
+	 * Provide feedback to user after a party event happens.
+	 */
+	void update(Party *party, PartyEvent &event) override;
+
+	/**
+	 * Provide feedback to user after a movement event happens.
+	 */
+	void update(Location *location, MoveEvent &event) override;
+
+	/**
+	 * Initializes the moon state according to the savegame file. This method of
+	 * initializing the moons (rather than just setting them directly) is necessary
+	 * to make sure trammel and felucca stay in sync
+	 */
+	void initMoons();
+
+	/**
+	 * Updates the phases of the moons and shows
+	 * the visual moongates on the map, if desired
+	 */
+	void updateMoons(bool showmoongates);
+
+	/**
+	 * Show an attack flash at x, y on the current map.
+	 * This is used for 'being hit' or 'being missed'
+	 * by weapons, cannon fire, spells, etc.
+	 */
+	static void flashTile(const Coords &coords, MapTile tile, int timeFactor);
+
+	static void flashTile(const Coords &coords, const Common::String &tilename, int timeFactor);
+	static void doScreenAnimationsWhilePausing(int timeFactor);
+
+	TileView _mapArea;
+	bool _paused;
+	int _pausedTimer;
+
+private:
+	/**
+	 * Handles feedback after avatar moved during normal 3rd-person view.
+	 */
+	void avatarMoved(MoveEvent &event);
+
+	/**
+	 * Handles feedback after moving the avatar in the 3-d dungeon view.
+	 */
+	void avatarMovedInDungeon(MoveEvent &event);
+
+	/**
+	 * Removes creatures from the current map if they are too far away from the avatar
+	 */
+	void creatureCleanup();
+
+	/**
+	 * Handles trolls under bridges
+	 */
+	void checkBridgeTrolls();
+
+	/**
+	 * Checks creature conditions and spawns new creatures if necessary
+	 */
+	void checkRandomCreatures();
+
+	/**
+	 * Checks for valid conditions and handles
+	 * special creatures guarding the entrance to the
+	 * abyss and to the shrine of spirituality
+	 */
+	void checkSpecialCreatures(Direction dir);
+
+	/**
+	 * Checks for and handles when the avatar steps on a moongate
+	 */
+	bool checkMoongates();
+
+	/**
+	 * Creates the balloon near Hythloth, but only if the balloon doesn't already exists somewhere
+	 */
+	bool createBalloon(Map *map);
+};
+
+extern GameController *g_game;
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/key_handler_controller.cpp b/engines/ultima/ultima4/controllers/key_handler_controller.cpp
new file mode 100644
index 0000000000..1f8925b78d
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/key_handler_controller.cpp
@@ -0,0 +1,140 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/key_handler_controller.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/ultima4.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+
+KeyHandler::KeyHandler(Callback func, void *d, bool asyncronous) :
+	_handler(func),
+	_async(asyncronous),
+	_data(d) {
+}
+
+/**
+ * Sets the key-repeat characteristics of the keyboard.
+ */
+int KeyHandler::setKeyRepeat(int delay, int interval) {
+#ifdef TODO
+	return SDL_EnableKeyRepeat(delay, interval);
+#else
+	return 0;
+#endif
+}
+
+bool KeyHandler::globalHandler(int key) {
+	switch (key) {
+#if defined(MACOSX)
+	case U4_META + 'q': /* Cmd+q */
+	case U4_META + 'x': /* Cmd+x */
+#endif
+	case U4_ALT + 'x': /* Alt+x */
+#if defined(WIN32)
+	case U4_ALT + U4_FKEY + 3:
+#endif
+		g_ultima->quitGame();
+		EventHandler::end();
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool KeyHandler::defaultHandler(int key, void *data) {
+	bool valid = true;
+
+	switch (key) {
+	case '`':
+		if (g_context && g_context->_location)
+			debug(1, "x = %d, y = %d, level = %d, tile = %d (%s)\n", g_context->_location->_coords.x, g_context->_location->_coords.y, g_context->_location->_coords.z, g_context->_location->_map->translateToRawTileIndex(*g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)), g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_OBJECTS)->getName().c_str());
+		break;
+	default:
+		valid = false;
+		break;
+	}
+
+	return valid;
+}
+
+bool KeyHandler::ignoreKeys(int key, void *data) {
+	return true;
+}
+
+bool KeyHandler::handle(int key) {
+	bool processed = false;
+	if (!isKeyIgnored(key)) {
+		processed = globalHandler(key);
+		if (!processed)
+			processed = _handler(key, _data);
+	}
+
+	return processed;
+}
+
+bool KeyHandler::isKeyIgnored(int key) {
+	switch (key) {
+	case U4_RIGHT_SHIFT:
+	case U4_LEFT_SHIFT:
+	case U4_RIGHT_CTRL:
+	case U4_LEFT_CTRL:
+	case U4_RIGHT_ALT:
+	case U4_LEFT_ALT:
+	case U4_RIGHT_META:
+	case U4_LEFT_META:
+	case U4_TAB:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool KeyHandler::operator==(Callback cb) const {
+	return (_handler == cb) ? true : false;
+}
+
+/*-------------------------------------------------------------------*/
+
+KeyHandlerController::KeyHandlerController(KeyHandler *handler) {
+	this->_handler = handler;
+}
+
+KeyHandlerController::~KeyHandlerController() {
+	delete _handler;
+}
+
+bool KeyHandlerController::keyPressed(int key) {
+	ASSERT(_handler != NULL, "key handler must be initialized");
+	return _handler->handle(key);
+}
+
+KeyHandler *KeyHandlerController::getKeyHandler() {
+	return _handler;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/key_handler_controller.h b/engines/ultima/ultima4/controllers/key_handler_controller.h
new file mode 100644
index 0000000000..f9f47b57cf
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/key_handler_controller.h
@@ -0,0 +1,123 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_KEY_HANDLER_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_KEY_HANDLER_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A class for handling keystrokes.
+ */
+class KeyHandler {
+public:
+	virtual ~KeyHandler() {}
+
+	/* Typedefs */
+	typedef bool (*Callback)(int, void *);
+
+	/** Additional information to be passed as data param for read buffer key handler */
+	typedef struct ReadBuffer {
+		int (*_handleBuffer)(Common::String *);
+		Common::String *_buffer;
+		int _bufferLen;
+		int _screenX, _screenY;
+	} ReadBuffer;
+
+	/** Additional information to be passed as data param for get choice key handler */
+	typedef struct GetChoice {
+		Common::String _choices;
+		int (*_handleChoice)(int);
+	} GetChoice;
+
+	/* Constructors */
+	KeyHandler(Callback func, void *data = NULL, bool asyncronous = true);
+
+	/* Static functions */
+	static int setKeyRepeat(int delay, int interval);
+
+	/**
+	 * Handles any and all keystrokes.
+	 * Generally used to exit the application, switch applications,
+	 * minimize, maximize, etc.
+	 */
+	static bool globalHandler(int key);
+
+	/* Static default key handler functions */
+	/**
+	 * A default key handler that should be valid everywhere
+	 */
+	static bool defaultHandler(int key, void *data);
+
+	/**
+	 * A key handler that ignores keypresses
+	 */
+	static bool ignoreKeys(int key, void *data);
+
+	/* Operators */
+	bool operator==(Callback cb) const;
+
+	/* Member functions */
+	/**
+	 * Handles a keypress.
+	 * First it makes sure the key combination is not ignored
+	 * by the current key handler. Then, it passes the keypress
+	 * through the global key handler. If the global handler
+	 * does not process the keystroke, then the key handler
+	 * handles it itself by calling its handler callback function.
+	 */
+	bool handle(int key);
+
+	/**
+	 * Returns true if the key or key combination is always ignored by xu4
+	 */
+	virtual bool isKeyIgnored(int key);
+
+protected:
+	Callback _handler;
+	bool _async;
+	void *_data;
+};
+
+/**
+ * A controller that wraps a keyhander function.  Keyhandlers are
+ * deprecated -- please use a controller instead.
+ */
+class KeyHandlerController : public Controller {
+public:
+	KeyHandlerController(KeyHandler *handler);
+	~KeyHandlerController();
+
+	bool keyPressed(int key) override;
+	KeyHandler *getKeyHandler();
+
+private:
+	KeyHandler *_handler;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/read_choice_controller.cpp b/engines/ultima/ultima4/controllers/read_choice_controller.cpp
new file mode 100644
index 0000000000..105df96473
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_choice_controller.cpp
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+
+ReadChoiceController::ReadChoiceController(const Common::String &choices) {
+	_choices = choices;
+}
+
+bool ReadChoiceController::keyPressed(int key) {
+	// Common::isUpper() accepts 1-byte characters, yet the modifier keys
+	// (ALT, SHIFT, ETC) produce values beyond 255
+	if ((key <= 0x7F) && (Common::isUpper(key)))
+		key = tolower(key);
+
+	_value = key;
+
+	if (_choices.empty() || _choices.findFirstOf(_value) < _choices.size()) {
+		// If the value is printable, display it
+		if (!Common::isSpace(key))
+			screenMessage("%c", toupper(key));
+		doneWaiting();
+		return true;
+	}
+
+	return false;
+}
+
+char ReadChoiceController::get(const Common::String &choices, EventHandler *eh) {
+	if (!eh)
+		eh = eventHandler;
+
+	ReadChoiceController ctrl(choices);
+	eh->pushController(&ctrl);
+	return ctrl.waitFor();
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/read_choice_controller.h b/engines/ultima/ultima4/controllers/read_choice_controller.h
new file mode 100644
index 0000000000..4410e2df3e
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_choice_controller.h
@@ -0,0 +1,49 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_READ_CHOICE_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_READ_CHOICE_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/events/event.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to read a single key from a provided list.
+ */
+class ReadChoiceController : public WaitableController<int> {
+public:
+	ReadChoiceController(const Common::String &choices);
+	bool keyPressed(int key) override;
+
+	static char get(const Common::String &choices, EventHandler *eh = NULL);
+
+protected:
+	Common::String _choices;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/read_dir_controller.cpp b/engines/ultima/ultima4/controllers/read_dir_controller.cpp
new file mode 100644
index 0000000000..11bf12f568
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_dir_controller.cpp
@@ -0,0 +1,76 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/read_dir_controller.h"
+#include "ultima/ultima4/map/direction.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+
+ReadDirController::ReadDirController() {
+	_value = DIR_NONE;
+}
+
+void ReadDirController::keybinder(KeybindingAction action) {
+	switch (action) {
+	case KEYBIND_UP:
+		_value = DIR_NORTH;
+		break;
+	case KEYBIND_DOWN:
+		_value = DIR_SOUTH;
+		break;
+	case KEYBIND_LEFT:
+		_value = DIR_WEST;
+		break;
+	case KEYBIND_RIGHT:
+		_value = DIR_EAST;
+		break;
+	case KEYBIND_PASS:
+		_value = DIR_NONE;
+		doneWaiting();
+		break;
+	default:
+		return;
+	}
+
+	doneWaiting();
+}
+
+bool ReadDirController::keyPressed(int key) {
+	switch (key) {
+	case Common::KEYCODE_ESCAPE:
+	case Common::KEYCODE_SPACE:
+	case Common::KEYCODE_RETURN:
+		_value = DIR_NONE;
+		doneWaiting();
+		return true;
+
+	default:
+		break;
+	}
+
+	return false;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/read_dir_controller.h b/engines/ultima/ultima4/controllers/read_dir_controller.h
new file mode 100644
index 0000000000..6f706ef59a
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_dir_controller.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_READ_DIR_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_READ_DIR_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/map/direction.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to read a direction enter with the arrow keys.
+ */
+class ReadDirController : public WaitableController<Direction> {
+public:
+	ReadDirController();
+
+	/**
+	 * Key was pressed
+	 */
+	bool keyPressed(int key) override;
+
+	/**
+	 * Handles keybinder actions
+	 */
+	void keybinder(KeybindingAction action) override;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/read_int_controller.cpp b/engines/ultima/ultima4/controllers/read_int_controller.cpp
new file mode 100644
index 0000000000..56083667b1
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_int_controller.cpp
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/read_int_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+ReadIntController::ReadIntController(int maxlen, int screenX, int screenY) :
+		ReadStringController(maxlen, screenX, screenY, "0123456789 \n\r\010") {}
+
+int ReadIntController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
+	if (!eh)
+		eh = eventHandler;
+
+	ReadIntController ctrl(maxlen, screenX, screenY);
+	eh->pushController(&ctrl);
+	ctrl.waitFor();
+	return ctrl.getInt();
+}
+
+int ReadIntController::getInt() const {
+	return static_cast<int>(strtol(_value.c_str(), NULL, 10));
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/read_int_controller.h b/engines/ultima/ultima4/controllers/read_int_controller.h
new file mode 100644
index 0000000000..05d025e62d
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_int_controller.h
@@ -0,0 +1,46 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_READ_INT_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_READ_INT_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/read_string_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to read a integer, terminated by the enter key.
+ * Non-numeric keys are ignored.
+ */
+class ReadIntController : public ReadStringController {
+public:
+	ReadIntController(int maxlen, int screenX, int screenY);
+
+	static int get(int maxlen, int screenX, int screenY, EventHandler *eh = NULL);
+	int getInt() const;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/read_player_controller.cpp b/engines/ultima/ultima4/controllers/read_player_controller.cpp
new file mode 100644
index 0000000000..f64530ab61
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_player_controller.cpp
@@ -0,0 +1,64 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/read_player_controller.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/ultima4.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+ReadPlayerController::ReadPlayerController() : ReadChoiceController("12345678 \033\n") {
+#ifdef IOS
+	U4IOS::beginCharacterChoiceDialog();
+#endif
+}
+
+ReadPlayerController::~ReadPlayerController() {
+#ifdef IOS
+	U4IOS::endCharacterChoiceDialog();
+#endif
+}
+
+bool ReadPlayerController::keyPressed(int key) {
+	bool valid = ReadChoiceController::keyPressed(key);
+	if (valid) {
+		if (_value < '1' ||
+		        _value > ('0' + g_ultima->_saveGame->_members))
+			_value = '0';
+	} else {
+		_value = '0';
+	}
+	return valid;
+}
+
+int ReadPlayerController::getPlayer() {
+	return _value - '1';
+}
+
+int ReadPlayerController::waitFor() {
+	ReadChoiceController::waitFor();
+	return getPlayer();
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/read_player_controller.h b/engines/ultima/ultima4/controllers/read_player_controller.h
new file mode 100644
index 0000000000..f67de6eb18
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_player_controller.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_READ_PLAYER_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_READ_PLAYER_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to read a player number.
+ */
+class ReadPlayerController : public ReadChoiceController {
+public:
+	ReadPlayerController();
+	~ReadPlayerController();
+	bool keyPressed(int key) override;
+
+	int getPlayer();
+	int waitFor() override;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/read_string_controller.cpp b/engines/ultima/ultima4/controllers/read_string_controller.cpp
new file mode 100644
index 0000000000..8380ea1c3b
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_string_controller.cpp
@@ -0,0 +1,109 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/read_string_controller.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/gfx/screen.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+ReadStringController::ReadStringController(int maxlen, int screenX, int screenY, const Common::String &accepted_chars) {
+	_maxLen = maxlen;
+	_screenX = screenX;
+	_screenY = screenY;
+	_view = NULL;
+	_accepted = accepted_chars;
+}
+
+ReadStringController::ReadStringController(int maxlen, TextView *view, const Common::String &accepted_chars) {
+	_maxLen = maxlen;
+	_screenX = view->getCursorX();
+	_screenY = view->getCursorY();
+	_view = view;
+	_accepted = accepted_chars;
+}
+
+bool ReadStringController::keyPressed(int key) {
+	int valid = true, len = _value.size();
+	size_t pos = Common::String::npos;
+
+	if (key < U4_ALT)
+		pos = _accepted.findFirstOf(key);
+
+	if (pos != Common::String::npos) {
+		if (key == Common::KEYCODE_BACKSPACE) {
+			if (len > 0) {
+				/* remove the last character */
+				_value.erase(len - 1, 1);
+
+				if (_view) {
+					_view->textAt(_screenX + len - 1, _screenY, " ");
+					_view->setCursorPos(_screenX + len - 1, _screenY, true);
+				} else {
+					screenHideCursor();
+					screenTextAt(_screenX + len - 1, _screenY, " ");
+					screenSetCursorPos(_screenX + len - 1, _screenY);
+					screenShowCursor();
+				}
+			}
+		} else if (key == '\n' || key == '\r') {
+			doneWaiting();
+		} else if (len < _maxLen) {
+			/* add a character to the end */
+			_value += key;
+
+			if (_view) {
+				_view->textAt(_screenX + len, _screenY, "%c", key);
+			} else {
+				screenHideCursor();
+				screenTextAt(_screenX + len, _screenY, "%c", key);
+				screenSetCursorPos(_screenX + len + 1, _screenY);
+				g_context->col = len + 1;
+				screenShowCursor();
+			}
+		}
+	} else valid = false;
+
+	return valid || KeyHandler::defaultHandler(key, NULL);
+}
+
+Common::String ReadStringController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
+	if (!eh)
+		eh = eventHandler;
+
+	ReadStringController ctrl(maxlen, screenX, screenY);
+	eh->pushController(&ctrl);
+	return ctrl.waitFor();
+}
+
+Common::String ReadStringController::get(int maxlen, TextView *view, EventHandler *eh) {
+	if (!eh)
+		eh = eventHandler;
+
+	ReadStringController ctrl(maxlen, view);
+	eh->pushController(&ctrl);
+	return ctrl.waitFor();
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/read_string_controller.h b/engines/ultima/ultima4/controllers/read_string_controller.h
new file mode 100644
index 0000000000..8f910c0487
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/read_string_controller.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_READ_STRING_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_READ_STRING_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/game/textview.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to read a Common::String, terminated by the enter key.
+ */
+class ReadStringController : public WaitableController<Common::String> {
+public:
+	/**
+	 * @param maxlen the maximum length of the Common::String
+	 * @param screenX the screen column where to begin input
+	 * @param screenY the screen row where to begin input
+	 * @param accepted_chars a Common::String characters to be accepted for input
+	 */
+	ReadStringController(int maxlen, int screenX, int screenY, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
+	ReadStringController(int maxlen, TextView *view, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
+	bool keyPressed(int key) override;
+
+	static Common::String get(int maxlen, int screenX, int screenY, EventHandler *eh = NULL);
+	static Common::String get(int maxlen, TextView *view, EventHandler *eh = NULL);
+#ifdef IOS
+	void setValue(const Common::String &utf8StringValue) {
+		value = utf8StringValue;
+	}
+#endif
+
+protected:
+	int _maxLen, _screenX, _screenY;
+	TextView *_view;
+	Common::String _accepted;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/wait_controller.cpp b/engines/ultima/ultima4/controllers/wait_controller.cpp
new file mode 100644
index 0000000000..121d99fde8
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/wait_controller.cpp
@@ -0,0 +1,52 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/wait_controller.h"
+#include "ultima/ultima4/events/event.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+WaitController::WaitController(unsigned int c) : Controller(), _cycles(c), _current(0) {
+}
+
+void WaitController::timerFired() {
+	if (++_current >= _cycles) {
+		_current = 0;
+		eventHandler->setControllerDone(true);
+	}
+}
+
+bool WaitController::keyPressed(int key) {
+	return true;
+}
+
+void WaitController::wait() {
+	Controller_startWait();
+}
+
+void WaitController::setCycles(int c) {
+	_cycles = c;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/wait_controller.h b/engines/ultima/ultima4/controllers/wait_controller.h
new file mode 100644
index 0000000000..b3717359af
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/wait_controller.h
@@ -0,0 +1,52 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_WAIT_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_WAIT_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * A controller to pause for a given length of time, ignoring all
+ * keyboard input.
+ */
+class WaitController : public Controller {
+public:
+	WaitController(unsigned int cycles);
+	bool keyPressed(int key) override;
+	void timerFired() override;
+
+	void wait();
+	void setCycles(int c);
+
+private:
+	unsigned int _cycles;
+	unsigned int _current;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/ztats_controller.cpp b/engines/ultima/ultima4/controllers/ztats_controller.cpp
new file mode 100644
index 0000000000..15b3d60dd4
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/ztats_controller.cpp
@@ -0,0 +1,68 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/ztats_controller.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/ultima4.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+bool ZtatsController::keyPressed(int key) {
+	switch (key) {
+	case U4_UP:
+	case U4_LEFT:
+		g_context->_stats->prevItem();
+		return true;
+	case U4_DOWN:
+	case U4_RIGHT:
+		g_context->_stats->nextItem();
+		return true;
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+		if (g_ultima->_saveGame->_members >= key - '0')
+			g_context->_stats->setView(StatsView(STATS_CHAR1 + key - '1'));
+		return true;
+	case '0':
+		g_context->_stats->setView(StatsView(STATS_WEAPONS));
+		return true;
+	case U4_ESC:
+	case U4_SPACE:
+	case U4_ENTER:
+		g_context->_stats->setView(StatsView(STATS_PARTY_OVERVIEW));
+		doneWaiting();
+		return true;
+	default:
+		return KeyHandler::defaultHandler(key, NULL);
+	}
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/ztats_controller.h b/engines/ultima/ultima4/controllers/ztats_controller.h
new file mode 100644
index 0000000000..c76dd6bd02
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/ztats_controller.h
@@ -0,0 +1,42 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_ZTATS_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_ZTATS_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+/**
+ * Controls interaction while Ztats are being displayed.
+ */
+class ZtatsController : public WaitableController<void *> {
+public:
+	bool keyPressed(int key) override;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index 1cfb1585d7..5e26fc5dc7 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -22,6 +22,10 @@
 
 #include "ultima/ultima4/core/debugger.h"
 #include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/controllers/alpha_action_controller.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_dir_controller.h"
+#include "ultima/ultima4/controllers/ztats_controller.h"
 #include "ultima/ultima4/game/armor.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/game.h"
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 67366392c2..7144b136ba 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -23,6 +23,8 @@
 #include "ultima/ultima4/core/debugger_actions.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_int_controller.h"
 #include "ultima/ultima4/conversation/conversation.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/player.h"
diff --git a/engines/ultima/ultima4/events/event.cpp b/engines/ultima/ultima4/events/event.cpp
index 7e365902a7..1e80cc1979 100644
--- a/engines/ultima/ultima4/events/event.cpp
+++ b/engines/ultima/ultima4/events/event.cpp
@@ -21,12 +21,13 @@
  */
 
 #include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/map/location.h"
-#include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/controllers/wait_controller.h"
 #include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/textview.h"
+#include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/map/location.h"
 #include "common/events.h"
 
 namespace Ultima {
@@ -128,205 +129,5 @@ const MouseArea *EventHandler::getMouseAreaSet() const {
 		return NULL;
 }
 
-/*-------------------------------------------------------------------*/
-
-ReadStringController::ReadStringController(int maxlen, int screenX, int screenY, const Common::String &accepted_chars) {
-	_maxLen = maxlen;
-	_screenX = screenX;
-	_screenY = screenY;
-	_view = NULL;
-	_accepted = accepted_chars;
-}
-
-ReadStringController::ReadStringController(int maxlen, TextView *view, const Common::String &accepted_chars) {
-	_maxLen = maxlen;
-	_screenX = view->getCursorX();
-	_screenY = view->getCursorY();
-	_view = view;
-	_accepted = accepted_chars;
-}
-
-bool ReadStringController::keyPressed(int key) {
-	int valid = true, len = _value.size();
-	size_t pos = Common::String::npos;
-
-	if (key < U4_ALT)
-		pos = _accepted.findFirstOf(key);
-
-	if (pos != Common::String::npos) {
-		if (key == Common::KEYCODE_BACKSPACE) {
-			if (len > 0) {
-				/* remove the last character */
-				_value.erase(len - 1, 1);
-
-				if (_view) {
-					_view->textAt(_screenX + len - 1, _screenY, " ");
-					_view->setCursorPos(_screenX + len - 1, _screenY, true);
-				} else {
-					screenHideCursor();
-					screenTextAt(_screenX + len - 1, _screenY, " ");
-					screenSetCursorPos(_screenX + len - 1, _screenY);
-					screenShowCursor();
-				}
-			}
-		} else if (key == '\n' || key == '\r') {
-			doneWaiting();
-		} else if (len < _maxLen) {
-			/* add a character to the end */
-			_value += key;
-
-			if (_view) {
-				_view->textAt(_screenX + len, _screenY, "%c", key);
-			} else {
-				screenHideCursor();
-				screenTextAt(_screenX + len, _screenY, "%c", key);
-				screenSetCursorPos(_screenX + len + 1, _screenY);
-				g_context->col = len + 1;
-				screenShowCursor();
-			}
-		}
-	} else valid = false;
-
-	return valid || KeyHandler::defaultHandler(key, NULL);
-}
-
-Common::String ReadStringController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
-	if (!eh)
-		eh = eventHandler;
-
-	ReadStringController ctrl(maxlen, screenX, screenY);
-	eh->pushController(&ctrl);
-	return ctrl.waitFor();
-}
-
-Common::String ReadStringController::get(int maxlen, TextView *view, EventHandler *eh) {
-	if (!eh)
-		eh = eventHandler;
-
-	ReadStringController ctrl(maxlen, view);
-	eh->pushController(&ctrl);
-	return ctrl.waitFor();
-}
-
-ReadIntController::ReadIntController(int maxlen, int screenX, int screenY) : ReadStringController(maxlen, screenX, screenY, "0123456789 \n\r\010") {}
-
-int ReadIntController::get(int maxlen, int screenX, int screenY, EventHandler *eh) {
-	if (!eh)
-		eh = eventHandler;
-
-	ReadIntController ctrl(maxlen, screenX, screenY);
-	eh->pushController(&ctrl);
-	ctrl.waitFor();
-	return ctrl.getInt();
-}
-
-int ReadIntController::getInt() const {
-	return static_cast<int>(strtol(_value.c_str(), NULL, 10));
-}
-
-/*-------------------------------------------------------------------*/
-
-ReadChoiceController::ReadChoiceController(const Common::String &choices) {
-	_choices = choices;
-}
-
-bool ReadChoiceController::keyPressed(int key) {
-	// Common::isUpper() accepts 1-byte characters, yet the modifier keys
-	// (ALT, SHIFT, ETC) produce values beyond 255
-	if ((key <= 0x7F) && (Common::isUpper(key)))
-		key = tolower(key);
-
-	_value = key;
-
-	if (_choices.empty() || _choices.findFirstOf(_value) < _choices.size()) {
-		// If the value is printable, display it
-		if (!Common::isSpace(key))
-			screenMessage("%c", toupper(key));
-		doneWaiting();
-		return true;
-	}
-
-	return false;
-}
-
-char ReadChoiceController::get(const Common::String &choices, EventHandler *eh) {
-	if (!eh)
-		eh = eventHandler;
-
-	ReadChoiceController ctrl(choices);
-	eh->pushController(&ctrl);
-	return ctrl.waitFor();
-}
-
-/*-------------------------------------------------------------------*/
-
-ReadDirController::ReadDirController() {
-	_value = DIR_NONE;
-}
-
-void ReadDirController::keybinder(KeybindingAction action) {
-	switch (action) {
-	case KEYBIND_UP:
-		_value = DIR_NORTH;
-		break;
-	case KEYBIND_DOWN:
-		_value = DIR_SOUTH;
-		break;
-	case KEYBIND_LEFT:
-		_value = DIR_WEST;
-		break;
-	case KEYBIND_RIGHT:
-		_value = DIR_EAST;
-		break;
-	case KEYBIND_PASS:
-		_value = DIR_NONE;
-		doneWaiting();
-		break;
-	default:
-		return;
-	}
-
-	doneWaiting();
-}
-
-bool ReadDirController::keyPressed(int key) {
-	switch (key) {
-	case Common::KEYCODE_ESCAPE:
-	case Common::KEYCODE_SPACE:
-	case Common::KEYCODE_RETURN:
-		_value = DIR_NONE;
-		doneWaiting();
-		return true;
-
-	default:
-		break;
-	}
-
-	return false;
-}
-
-/*-------------------------------------------------------------------*/
-
-WaitController::WaitController(unsigned int c) : Controller(), _cycles(c), _current(0) {}
-
-void WaitController::timerFired() {
-	if (++_current >= _cycles) {
-		_current = 0;
-		eventHandler->setControllerDone(true);
-	}
-}
-
-bool WaitController::keyPressed(int key) {
-	return true;
-}
-
-void WaitController::wait() {
-	Controller_startWait();
-}
-
-void WaitController::setCycles(int c) {
-	_cycles = c;
-}
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/events/event.h b/engines/ultima/ultima4/events/event.h
index 50df579b29..ee85319f1b 100644
--- a/engines/ultima/ultima4/events/event.h
+++ b/engines/ultima/ultima4/events/event.h
@@ -23,13 +23,15 @@
 #ifndef ULTIMA4_EVENT_H
 #define ULTIMA4_EVENT_H
 
-#include "common/events.h"
-#include "common/list.h"
-#include "common/str.h"
-#include "ultima/ultima4/events/controller.h"
+#include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/events/timed_event_mgr.h"
+#include "ultima/ultima4/controllers/key_handler_controller.h"
 #include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/gfx/screen.h"
 #include "ultima/shared/std/containers.h"
+#include "common/events.h"
+#include "common/list.h"
+#include "common/str.h"
 
 namespace Ultima {
 namespace Ultima4 {
@@ -58,190 +60,6 @@ namespace Ultima4 {
 #define U4_RIGHT_META   Common::KEYCODE_RMETA
 #define U4_LEFT_META    Common::KEYCODE_LMETA
 
-struct MouseArea;
-class EventHandler;
-class TextView;
-
-/**
- * A class for handling keystrokes.
- */
-class KeyHandler {
-public:
-	virtual ~KeyHandler() {}
-
-	/* Typedefs */
-	typedef bool (*Callback)(int, void *);
-
-	/** Additional information to be passed as data param for read buffer key handler */
-	typedef struct ReadBuffer {
-		int (*_handleBuffer)(Common::String *);
-		Common::String *_buffer;
-		int _bufferLen;
-		int _screenX, _screenY;
-	} ReadBuffer;
-
-	/** Additional information to be passed as data param for get choice key handler */
-	typedef struct GetChoice {
-		Common::String _choices;
-		int (*_handleChoice)(int);
-	} GetChoice;
-
-	/* Constructors */
-	KeyHandler(Callback func, void *data = NULL, bool asyncronous = true);
-
-	/* Static functions */
-	static int setKeyRepeat(int delay, int interval);
-
-	/**
-	 * Handles any and all keystrokes.
-	 * Generally used to exit the application, switch applications,
-	 * minimize, maximize, etc.
-	 */
-	static bool globalHandler(int key);
-
-	/* Static default key handler functions */
-	/**
-	 * A default key handler that should be valid everywhere
-	 */
-	static bool defaultHandler(int key, void *data);
-
-	/**
-	 * A key handler that ignores keypresses
-	 */
-	static bool ignoreKeys(int key, void *data);
-
-	/* Operators */
-	bool operator==(Callback cb) const;
-
-	/* Member functions */
-	/**
-	 * Handles a keypress.
-	 * First it makes sure the key combination is not ignored
-	 * by the current key handler. Then, it passes the keypress
-	 * through the global key handler. If the global handler
-	 * does not process the keystroke, then the key handler
-	 * handles it itself by calling its handler callback function.
-	 */
-	bool handle(int key);
-
-	/**
-	 * Returns true if the key or key combination is always ignored by xu4
-	 */
-	virtual bool isKeyIgnored(int key);
-
-protected:
-	Callback _handler;
-	bool _async;
-	void *_data;
-};
-
-/**
- * A controller that wraps a keyhander function.  Keyhandlers are
- * deprecated -- please use a controller instead.
- */
-class KeyHandlerController : public Controller {
-public:
-	KeyHandlerController(KeyHandler *handler);
-	~KeyHandlerController();
-
-	bool keyPressed(int key) override;
-	KeyHandler *getKeyHandler();
-
-private:
-	KeyHandler *_handler;
-};
-
-/**
- * A controller to read a Common::String, terminated by the enter key.
- */
-class ReadStringController : public WaitableController<Common::String> {
-public:
-	/**
-	 * @param maxlen the maximum length of the Common::String
-	 * @param screenX the screen column where to begin input
-	 * @param screenY the screen row where to begin input
-	 * @param accepted_chars a Common::String characters to be accepted for input
-	 */
-	ReadStringController(int maxlen, int screenX, int screenY, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
-	ReadStringController(int maxlen, TextView *view, const Common::String &accepted_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 \n\r\010");
-	bool keyPressed(int key) override;
-
-	static Common::String get(int maxlen, int screenX, int screenY, EventHandler *eh = NULL);
-	static Common::String get(int maxlen, TextView *view, EventHandler *eh = NULL);
-#ifdef IOS
-	void setValue(const Common::String &utf8StringValue) {
-		value = utf8StringValue;
-	}
-#endif
-
-protected:
-	int _maxLen, _screenX, _screenY;
-	TextView *_view;
-	Common::String _accepted;
-};
-
-/**
- * A controller to read a integer, terminated by the enter key.
- * Non-numeric keys are ignored.
- */
-class ReadIntController : public ReadStringController {
-public:
-	ReadIntController(int maxlen, int screenX, int screenY);
-
-	static int get(int maxlen, int screenX, int screenY, EventHandler *eh = NULL);
-	int getInt() const;
-};
-
-/**
- * A controller to read a single key from a provided list.
- */
-class ReadChoiceController : public WaitableController<int> {
-public:
-	ReadChoiceController(const Common::String &choices);
-	bool keyPressed(int key) override;
-
-	static char get(const Common::String &choices, EventHandler *eh = NULL);
-
-protected:
-	Common::String _choices;
-};
-
-/**
- * A controller to read a direction enter with the arrow keys.
- */
-class ReadDirController : public WaitableController<Direction> {
-public:
-	ReadDirController();
-
-	/**
-	 * Key was pressed
-	 */
-	bool keyPressed(int key) override;
-
-	/**
-	 * Handles keybinder actions
-	 */
-	void keybinder(KeybindingAction action) override;
-};
-
-/**
- * A controller to pause for a given length of time, ignoring all
- * keyboard input.
- */
-class WaitController : public Controller {
-public:
-	WaitController(unsigned int cycles);
-	bool keyPressed(int key) override;
-	void timerFired() override;
-
-	void wait();
-	void setCycles(int c);
-
-private:
-	unsigned int _cycles;
-	unsigned int _current;
-};
-
 #if defined(IOS)
 #ifndef __OBJC__
 typedef void *TimedManagerHelper;
diff --git a/engines/ultima/ultima4/events/event_scummvm.cpp b/engines/ultima/ultima4/events/event_scummvm.cpp
index 8f37bfc7cc..0c0e309e83 100644
--- a/engines/ultima/ultima4/events/event_scummvm.cpp
+++ b/engines/ultima/ultima4/events/event_scummvm.cpp
@@ -34,112 +34,6 @@
 namespace Ultima {
 namespace Ultima4 {
 
-KeyHandler::KeyHandler(Callback func, void *d, bool asyncronous) :
-	_handler(func),
-	_async(asyncronous),
-	_data(d) {
-}
-
-/**
- * Sets the key-repeat characteristics of the keyboard.
- */
-int KeyHandler::setKeyRepeat(int delay, int interval) {
-#ifdef TODO
-	return SDL_EnableKeyRepeat(delay, interval);
-#else
-	return 0;
-#endif
-}
-
-bool KeyHandler::globalHandler(int key) {
-	switch (key) {
-#if defined(MACOSX)
-	case U4_META + 'q': /* Cmd+q */
-	case U4_META + 'x': /* Cmd+x */
-#endif
-	case U4_ALT + 'x': /* Alt+x */
-#if defined(WIN32)
-	case U4_ALT + U4_FKEY + 3:
-#endif
-		g_ultima->quitGame();
-		EventHandler::end();
-		return true;
-	default:
-		return false;
-	}
-}
-
-bool KeyHandler::defaultHandler(int key, void *data) {
-	bool valid = true;
-
-	switch (key) {
-	case '`':
-		if (g_context && g_context->_location)
-			debug(1, "x = %d, y = %d, level = %d, tile = %d (%s)\n", g_context->_location->_coords.x, g_context->_location->_coords.y, g_context->_location->_coords.z, g_context->_location->_map->translateToRawTileIndex(*g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)), g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_OBJECTS)->getName().c_str());
-		break;
-	default:
-		valid = false;
-		break;
-	}
-
-	return valid;
-}
-
-bool KeyHandler::ignoreKeys(int key, void *data) {
-	return true;
-}
-
-bool KeyHandler::handle(int key) {
-	bool processed = false;
-	if (!isKeyIgnored(key)) {
-		processed = globalHandler(key);
-		if (!processed)
-			processed = _handler(key, _data);
-	}
-
-	return processed;
-}
-
-bool KeyHandler::isKeyIgnored(int key) {
-	switch (key) {
-	case U4_RIGHT_SHIFT:
-	case U4_LEFT_SHIFT:
-	case U4_RIGHT_CTRL:
-	case U4_LEFT_CTRL:
-	case U4_RIGHT_ALT:
-	case U4_LEFT_ALT:
-	case U4_RIGHT_META:
-	case U4_LEFT_META:
-	case U4_TAB:
-		return true;
-	default:
-		return false;
-	}
-}
-
-bool KeyHandler::operator==(Callback cb) const {
-	return (_handler == cb) ? true : false;
-}
-
-KeyHandlerController::KeyHandlerController(KeyHandler *handler) {
-	this->_handler = handler;
-}
-
-KeyHandlerController::~KeyHandlerController() {
-	delete _handler;
-}
-
-bool KeyHandlerController::keyPressed(int key) {
-	ASSERT(_handler != NULL, "key handler must be initialized");
-	return _handler->handle(key);
-}
-
-KeyHandler *KeyHandlerController::getKeyHandler() {
-	return _handler;
-}
-
-/*-------------------------------------------------------------------*/
-
 EventHandler::EventHandler() : _timer(settings._eventTimerGranularity), _updateScreen(NULL)  {
 }
 
diff --git a/engines/ultima/ultima4/game/death.cpp b/engines/ultima/ultima4/game/death.cpp
index f6b7651640..f40a432c69 100644
--- a/engines/ultima/ultima4/game/death.cpp
+++ b/engines/ultima/ultima4/game/death.cpp
@@ -20,22 +20,23 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
 #include "ultima/ultima4/game/death.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/game/portal.h"
+#include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/controllers/wait_controller.h"
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/map/map.h"
 #include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/map/city.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/game/game.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/mapmgr.h"
 #include "ultima/ultima4/sound/music.h"
-#include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/game/portal.h"
-#include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
-#include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/ultima4.h"
 #include "common/system.h"
 
 namespace Ultima {
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index ff82a024e6..c779640cd8 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -21,6 +21,11 @@
  */
 
 #include "ultima/ultima4/ultima4.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_dir_controller.h"
+#include "ultima/ultima4/controllers/read_int_controller.h"
+#include "ultima/ultima4/controllers/read_player_controller.h"
+#include "ultima/ultima4/controllers/read_string_controller.h"
 #include "ultima/ultima4/conversation/conversation.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/debugger.h"
@@ -72,8 +77,6 @@ namespace Ultima4 {
 
 using namespace std;
 
-GameController *g_game = NULL;
-
 /*-----------------*/
 /* Functions BEGIN */
 
@@ -82,132 +85,15 @@ void gameAdvanceLevel(PartyMember *player);
 void gameInnHandler(void);
 void gameLostEighth(Virtue virtue);
 void gamePartyStarving(void);
-uint32 gameTimeSinceLastCommand(void);
 
 void mixReagentsSuper();
 
 /* action functions */
 void wearArmor(int player = -1);
 
-/* creature functions */
-void gameCreatureAttack(Creature *obj);
-
 /* Functions END */
 /*---------------*/
 
-static const MouseArea MOUSE_AREAS[] = {
-	{ 3, { { 8, 8 }, { 8, 184 }, { 96, 96 } }, MC_WEST, { U4_ENTER, 0, U4_LEFT } },
-	{ 3, { { 8, 8 }, { 184, 8 }, { 96, 96 } }, MC_NORTH, { U4_ENTER, 0, U4_UP }  },
-	{ 3, { { 184, 8 }, { 184, 184 }, { 96, 96 } }, MC_EAST, { U4_ENTER, 0, U4_RIGHT } },
-	{ 3, { { 8, 184 }, { 184, 184 }, { 96, 96 } }, MC_SOUTH, { U4_ENTER, 0, U4_DOWN } },
-	{ 0, { { 0, 0 }, { 0, 0 }, { 0, 0 } }, MC_NORTH, { 0, 0, 0 } }
-};
-
-ReadPlayerController::ReadPlayerController() : ReadChoiceController("12345678 \033\n") {
-#ifdef IOS
-	U4IOS::beginCharacterChoiceDialog();
-#endif
-}
-
-ReadPlayerController::~ReadPlayerController() {
-#ifdef IOS
-	U4IOS::endCharacterChoiceDialog();
-#endif
-}
-
-bool ReadPlayerController::keyPressed(int key) {
-	bool valid = ReadChoiceController::keyPressed(key);
-	if (valid) {
-		if (_value < '1' ||
-		        _value > ('0' + g_ultima->_saveGame->_members))
-			_value = '0';
-	} else {
-		_value = '0';
-	}
-	return valid;
-}
-
-int ReadPlayerController::getPlayer() {
-	return _value - '1';
-}
-
-int ReadPlayerController::waitFor() {
-	ReadChoiceController::waitFor();
-	return getPlayer();
-}
-
-bool AlphaActionController::keyPressed(int key) {
-	if (Common::isLower(key))
-		key = toupper(key);
-
-	if (key >= 'A' && key <= toupper(_lastValidLetter)) {
-		_value = key - 'A';
-		doneWaiting();
-	} else if (key == U4_SPACE || key == U4_ESC || key == U4_ENTER) {
-		screenMessage("\n");
-		_value = -1;
-		doneWaiting();
-	} else {
-		screenMessage("\n%s", _prompt.c_str());
-		g_screen->update();
-		return KeyHandler::defaultHandler(key, NULL);
-	}
-	return true;
-}
-
-int AlphaActionController::get(char lastValidLetter, const Common::String &prompt, EventHandler *eh) {
-	if (!eh)
-		eh = eventHandler;
-
-	AlphaActionController ctrl(lastValidLetter, prompt);
-	eh->pushController(&ctrl);
-	return ctrl.waitFor();
-}
-
-GameController::GameController() : _mapArea(BORDER_WIDTH, BORDER_HEIGHT, VIEWPORT_W, VIEWPORT_H), _paused(false), _pausedTimer(0) {
-	g_game = this;
-}
-
-void GameController::initScreen() {
-	Image *screen = imageMgr->get("screen")->_image;
-
-	screen->fillRect(0, 0, screen->width(), screen->height(), 0, 0, 0);
-	g_screen->update();
-}
-
-void GameController::initScreenWithoutReloadingState() {
-	g_music->play();
-	imageMgr->get(BKGD_BORDERS)->_image->draw(0, 0);
-	g_context->_stats->update(); /* draw the party stats */
-
-	screenMessage("Press Alt-h for help\n");
-	screenPrompt();
-
-	eventHandler->pushMouseAreaSet(MOUSE_AREAS);
-
-	eventHandler->setScreenUpdate(&gameUpdateScreen);
-}
-
-
-void GameController::init() {
-	initScreen();
-
-	// initialize the global game context, conversation and game state variables
-	g_context = new Context();
-	g_context->_line = TEXT_AREA_H - 1;
-	g_context->col = 0;
-	g_context->_stats = new StatsArea();
-	g_context->_moonPhase = 0;
-	g_context->_windDirection = DIR_NORTH;
-	g_context->_windCounter = 0;
-	g_context->_windLock = false;
-	g_context->_aura = new Aura();
-	g_context->_horseSpeed = 0;
-	g_context->_opacity = 1;
-	g_context->_lastCommandTime = g_system->getMillis();
-	g_context->_lastShip = NULL;
-}
-
 /**
  * Sets the view mode.
  */
@@ -241,238 +127,6 @@ void gameUpdateScreen() {
 	}
 }
 
-void GameController::setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter) {
-	int viewMode;
-	LocationContext context;
-	int activePlayer = g_context->_party->getActivePlayer();
-	MapCoords coords;
-
-	if (!turnCompleter)
-		turnCompleter = this;
-
-	if (portal)
-		coords = portal->_start;
-	else
-		coords = MapCoords(map->_width / 2, map->_height / 2);
-
-	/* If we don't want to save the location, then just return to the previous location,
-	   as there may still be ones in the stack we want to keep */
-	if (!saveLocation)
-		exitToParentMap();
-
-	switch (map->_type) {
-	case Map::WORLD:
-		context = CTX_WORLDMAP;
-		viewMode = VIEW_NORMAL;
-		break;
-	case Map::DUNGEON:
-		context = CTX_DUNGEON;
-		viewMode = VIEW_DUNGEON;
-		if (portal)
-			g_ultima->_saveGame->_orientation = DIR_EAST;
-		break;
-	case Map::COMBAT:
-		coords = MapCoords(-1, -1); /* set these to -1 just to be safe; we don't need them */
-		context = CTX_COMBAT;
-		viewMode = VIEW_NORMAL;
-		activePlayer = -1; /* different active player for combat, defaults to 'None' */
-		break;
-	case Map::SHRINE:
-		context = CTX_SHRINE;
-		viewMode = VIEW_NORMAL;
-		break;
-	case Map::CITY:
-	default:
-		context = CTX_CITY;
-		viewMode = VIEW_NORMAL;
-		break;
-	}
-	g_context->_location = new Location(coords, map, viewMode, context, turnCompleter, g_context->_location);
-	g_context->_location->addObserver(this);
-	g_context->_party->setActivePlayer(activePlayer);
-#ifdef IOS
-	U4IOS::updateGameControllerContext(c->location->context);
-#endif
-
-	/* now, actually set our new tileset */
-	_mapArea.setTileset(map->_tileset);
-
-	if (isCity(map)) {
-		City *city = dynamic_cast<City *>(map);
-		city->addPeople();
-	}
-}
-
-int GameController::exitToParentMap() {
-	if (!g_context->_location)
-		return 0;
-
-	if (g_context->_location->_prev != NULL) {
-		// Create the balloon for Hythloth
-		if (g_context->_location->_map->_id == MAP_HYTHLOTH)
-			createBalloon(g_context->_location->_prev->_map);
-
-		// free map info only if previous location was on a different map
-		if (g_context->_location->_prev->_map != g_context->_location->_map) {
-			g_context->_location->_map->_annotations->clear();
-			g_context->_location->_map->clearObjects();
-
-			/* quench the torch of we're on the world map */
-			if (g_context->_location->_prev->_map->isWorldMap())
-				g_context->_party->quenchTorch();
-		}
-		locationFree(&g_context->_location);
-
-		// restore the tileset to the one the current map uses
-		_mapArea.setTileset(g_context->_location->_map->_tileset);
-#ifdef IOS
-		U4IOS::updateGameControllerContext(c->location->context);
-#endif
-
-		return 1;
-	}
-	return 0;
-}
-
-void GameController::finishTurn() {
-	g_context->_lastCommandTime = g_system->getMillis();
-	Creature *attacker = NULL;
-
-	while (1) {
-
-		/* adjust food and moves */
-		g_context->_party->endTurn();
-
-		/* count down the aura, if there is one */
-		g_context->_aura->passTurn();
-
-		gameCheckHullIntegrity();
-
-		/* update party stats */
-		//c->stats->setView(STATS_PARTY_OVERVIEW);
-
-		screenUpdate(&this->_mapArea, true, false);
-		screenWait(1);
-
-		/* Creatures cannot spawn, move or attack while the avatar is on the balloon */
-		if (!g_context->_party->isFlying()) {
-
-			// apply effects from tile avatar is standing on
-			g_context->_party->applyEffect(g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->getEffect());
-
-			// Move creatures and see if something is attacking the avatar
-			attacker = g_context->_location->_map->moveObjects(g_context->_location->_coords);
-
-			// Something's attacking!  Start combat!
-			if (attacker) {
-				gameCreatureAttack(attacker);
-				return;
-			}
-
-			// cleanup old creatures and spawn new ones
-			creatureCleanup();
-			checkRandomCreatures();
-			checkBridgeTrolls();
-		}
-
-		/* update map annotations */
-		g_context->_location->_map->_annotations->passTurn();
-
-		if (!g_context->_party->isImmobilized())
-			break;
-
-		if (g_context->_party->isDead()) {
-			deathStart(0);
-			return;
-		} else {
-			screenMessage("Zzzzzz\n");
-			screenWait(4);
-		}
-	}
-
-	if (g_context->_location->_context == CTX_DUNGEON) {
-		Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
-		if (g_context->_party->getTorchDuration() <= 0)
-			screenMessage("It's Dark!\n");
-		else g_context->_party->burnTorch();
-
-		/* handle dungeon traps */
-		if (dungeon->currentToken() == DUNGEON_TRAP) {
-			dungeonHandleTrap((TrapType)dungeon->currentSubToken());
-			// a little kludgey to have a second test for this
-			// right here.  But without it you can survive an
-			// extra turn after party death and do some things
-			// that could cause a crash, like Hole up and Camp.
-			if (g_context->_party->isDead()) {
-				deathStart(0);
-				return;
-			}
-		}
-	}
-
-
-	/* draw a prompt */
-	screenPrompt();
-	//screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
-}
-
-void GameController::flashTile(const Coords &coords, MapTile tile, int frames) {
-	g_context->_location->_map->_annotations->add(coords, tile, true);
-
-	screenTileUpdate(&g_game->_mapArea, coords);
-
-	screenWait(frames);
-	g_context->_location->_map->_annotations->remove(coords, tile);
-
-	screenTileUpdate(&g_game->_mapArea, coords, false);
-}
-
-void GameController::flashTile(const Coords &coords, const Common::String &tilename, int timeFactor) {
-	Tile *tile = g_context->_location->_map->_tileset->getByName(tilename);
-	ASSERT(tile, "no tile named '%s' found in tileset", tilename.c_str());
-	flashTile(coords, tile->getId(), timeFactor);
-}
-
-void GameController::update(Party *party, PartyEvent &event) {
-	int i;
-
-	switch (event._type) {
-	case PartyEvent::LOST_EIGHTH:
-		// inform a player he has lost zero or more eighths of avatarhood.
-		screenMessage("\n %cThou hast lost\n  an eighth!%c\n", FG_YELLOW, FG_WHITE);
-		break;
-	case PartyEvent::ADVANCED_LEVEL:
-		screenMessage("\n%c%s\nThou art now Level %d%c\n", FG_YELLOW, event._player->getName().c_str(), event._player->getRealLevel(), FG_WHITE);
-		gameSpellEffect('r', -1, SOUND_MAGIC); // Same as resurrect spell
-		break;
-	case PartyEvent::STARVING:
-		screenMessage("\n%cStarving!!!%c\n", FG_YELLOW, FG_WHITE);
-		/* FIXME: add sound effect here */
-
-		// 2 damage to each party member for starving!
-		for (i = 0; i < g_ultima->_saveGame->_members; i++)
-			g_context->_party->member(i)->applyDamage(2);
-		break;
-	default:
-		break;
-	}
-}
-
-void GameController::update(Location *location, MoveEvent &event) {
-	switch (location->_map->_type) {
-	case Map::DUNGEON:
-		avatarMovedInDungeon(event);
-		break;
-	case Map::COMBAT:
-		// FIXME: let the combat controller handle it
-		dynamic_cast<CombatController *>(eventHandler->getController())->movePartyMember(event);
-		break;
-	default:
-		avatarMoved(event);
-		break;
-	}
-}
-
 void gameSpellEffect(int spell, int player, Sound sound) {
 
 	int time;
@@ -519,99 +173,6 @@ void gameSpellEffect(int spell, int player, Sound sound) {
 	}
 }
 
-void GameController::keybinder(KeybindingAction action) {
-	MetaEngine::executeAction(action);
-}
-
-bool GameController::keyPressed(int key) {
-	bool valid = true;
-	int endTurn = 1;
-	Object *obj;
-	MapTile *tile;
-
-	/* Translate context-sensitive action key into a useful command */
-	if (key == U4_ENTER && settings._enhancements && settings._enhancementsOptions._smartEnterKey) {
-		/* Attempt to guess based on the character's surroundings etc, what
-		   action they want */
-
-		/* Do they want to board something? */
-		if (g_context->_transportContext == TRANSPORT_FOOT) {
-			obj = g_context->_location->_map->objectAt(g_context->_location->_coords);
-			if (obj && (obj->getTile().getTileType()->isShip() ||
-			            obj->getTile().getTileType()->isHorse() ||
-			            obj->getTile().getTileType()->isBalloon()))
-				key = 'b';
-		}
-		/* Klimb/Descend Balloon */
-		else if (g_context->_transportContext == TRANSPORT_BALLOON) {
-			if (g_context->_party->isFlying())
-				key = 'd';
-			else {
-#ifdef IOS
-				U4IOS::IOSSuperButtonHelper superHelper;
-				key = ReadChoiceController::get("xk \033\n");
-#else
-				key = 'k';
-#endif
-			}
-		}
-		/* X-it transport */
-		else key = 'x';
-
-		/* Klimb? */
-		if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_KLIMB) != NULL))
-			key = 'k';
-		/* Descend? */
-		else if ((g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_DESCEND) != NULL))
-			key = 'd';
-
-		if (g_context->_location->_context == CTX_DUNGEON) {
-			Dungeon *dungeon = static_cast<Dungeon *>(g_context->_location->_map);
-			bool up = dungeon->ladderUpAt(g_context->_location->_coords);
-			bool down = dungeon->ladderDownAt(g_context->_location->_coords);
-			if (up && down) {
-#ifdef IOS
-				U4IOS::IOSClimbHelper climbHelper;
-				key = ReadChoiceController::get("kd \033\n");
-#else
-				key = 'k'; // This is consistent with the previous code. Ideally, I would have a UI here as well.
-#endif
-			} else if (up) {
-				key = 'k';
-			} else {
-				key = 'd';
-			}
-		}
-
-		/* Enter? */
-		if (g_context->_location->_map->portalAt(g_context->_location->_coords, ACTION_ENTER) != NULL)
-			key = 'e';
-
-		/* Get Chest? */
-		if (!g_context->_party->isFlying()) {
-			tile = g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
-
-			if (tile->getTileType()->isChest()) key = 'g';
-		}
-
-		/* None of these? Default to search */
-		if (key == U4_ENTER) key = 's';
-	}
-
-	if ((g_context->_location->_context & CTX_DUNGEON) && strchr("abefjlotxy", key))
-		screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
-
-	if (valid && endTurn) {
-		if (eventHandler->getController() == g_game)
-			g_context->_location->_turnCompleter->finishTurn();
-	} else if (!endTurn) {
-		/* if our turn did not end, then manually redraw the text prompt */
-		screenPrompt();
-	}
-
-	return valid || KeyHandler::defaultHandler(key, NULL);
-}
-
 Common::String gameGetInput(int maxlen) {
 	screenEnableCursor();
 	screenShowCursor();
@@ -678,42 +239,6 @@ Direction gameGetDirection() {
 	}
 }
 
-bool ZtatsController::keyPressed(int key) {
-	switch (key) {
-	case U4_UP:
-	case U4_LEFT:
-		g_context->_stats->prevItem();
-		return true;
-	case U4_DOWN:
-	case U4_RIGHT:
-		g_context->_stats->nextItem();
-		return true;
-	case '1':
-	case '2':
-	case '3':
-	case '4':
-	case '5':
-	case '6':
-	case '7':
-	case '8':
-		if (g_ultima->_saveGame->_members >= key - '0')
-			g_context->_stats->setView(StatsView(STATS_CHAR1 + key - '1'));
-		return true;
-	case '0':
-		g_context->_stats->setView(StatsView(STATS_WEAPONS));
-		return true;
-	case U4_ESC:
-	case U4_SPACE:
-	case U4_ENTER:
-		g_context->_stats->setView(StatsView(STATS_PARTY_OVERVIEW));
-		doneWaiting();
-		return true;
-	default:
-		return KeyHandler::defaultHandler(key, NULL);
-	}
-}
-
-
 bool fireAt(const Coords &coords, bool originAvatar) {
 	bool validObject = false;
 	bool hitsAvatar = false;
@@ -772,233 +297,6 @@ bool fireAt(const Coords &coords, bool originAvatar) {
 	return objectHit;
 }
 
-void GameController::initMoons() {
-	int trammelphase = g_ultima->_saveGame->_trammelPhase,
-	    feluccaphase = g_ultima->_saveGame->_feluccaPhase;
-
-	ASSERT(g_context != NULL, "Game context doesn't exist!");
-	ASSERT(g_ultima->_saveGame != NULL, "Savegame doesn't exist!");
-	//ASSERT(mapIsWorldMap(c->location->map) && c->location->viewMode == VIEW_NORMAL, "Can only call gameInitMoons() from the world map!");
-
-	g_ultima->_saveGame->_trammelPhase = g_ultima->_saveGame->_feluccaPhase = 0;
-	g_context->_moonPhase = 0;
-
-	while ((g_ultima->_saveGame->_trammelPhase != trammelphase) ||
-	        (g_ultima->_saveGame->_feluccaPhase != feluccaphase))
-		updateMoons(false);
-}
-
-void GameController::updateMoons(bool showmoongates) {
-	int realMoonPhase,
-	    oldTrammel,
-	    trammelSubphase;
-	const Coords *gate;
-
-	if (g_context->_location->_map->isWorldMap()) {
-		oldTrammel = g_ultima->_saveGame->_trammelPhase;
-
-		if (++g_context->_moonPhase >= MOON_PHASES * MOON_SECONDS_PER_PHASE * 4)
-			g_context->_moonPhase = 0;
-
-		trammelSubphase = g_context->_moonPhase % (MOON_SECONDS_PER_PHASE * 4 * 3);
-		realMoonPhase = (g_context->_moonPhase / (4 * MOON_SECONDS_PER_PHASE));
-
-		g_ultima->_saveGame->_trammelPhase = realMoonPhase / 3;
-		g_ultima->_saveGame->_feluccaPhase = realMoonPhase % 8;
-
-		if (g_ultima->_saveGame->_trammelPhase > 7)
-			g_ultima->_saveGame->_trammelPhase = 7;
-
-		if (showmoongates) {
-			/* update the moongates if trammel changed */
-			if (trammelSubphase == 0) {
-				gate = moongateGetGateCoordsForPhase(oldTrammel);
-				if (gate)
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate)
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
-			} else if (trammelSubphase == 1) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
-				}
-			} else if (trammelSubphase == 2) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
-				}
-			} else if (trammelSubphase == 3) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
-				}
-			} else if ((trammelSubphase > 3) && (trammelSubphase < (MOON_SECONDS_PER_PHASE * 4 * 3) - 3)) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
-				}
-			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 3) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x43));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
-				}
-			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 2) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x42));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
-				}
-			} else if (trammelSubphase == (MOON_SECONDS_PER_PHASE * 4 * 3) - 1) {
-				gate = moongateGetGateCoordsForPhase(g_ultima->_saveGame->_trammelPhase);
-				if (gate) {
-					g_context->_location->_map->_annotations->remove(*gate, g_context->_location->_map->translateFromRawTileIndex(0x41));
-					g_context->_location->_map->_annotations->add(*gate, g_context->_location->_map->translateFromRawTileIndex(0x40));
-				}
-			}
-		}
-	}
-}
-
-void GameController::avatarMoved(MoveEvent &event) {
-	if (event._userEvent) {
-
-		// is filterMoveMessages even used?  it doesn't look like the option is hooked up in the configuration menu
-		if (!settings._filterMoveMessages) {
-			switch (g_context->_transportContext) {
-			case TRANSPORT_FOOT:
-			case TRANSPORT_HORSE:
-				screenMessage("%s\n", getDirectionName(event._dir));
-				break;
-			case TRANSPORT_SHIP:
-				if (event._result & MOVE_TURNED)
-					screenMessage("Turn %s!\n", getDirectionName(event._dir));
-				else if (event._result & MOVE_SLOWED)
-					screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
-				else
-					screenMessage("Sail %s!\n", getDirectionName(event._dir));
-				break;
-			case TRANSPORT_BALLOON:
-				screenMessage("%cDrift Only!%c\n", FG_GREY, FG_WHITE);
-				break;
-			default:
-				error("bad transportContext %d in avatarMoved()", g_context->_transportContext);
-			}
-		}
-
-		/* movement was blocked */
-		if (event._result & MOVE_BLOCKED) {
-
-			/* if shortcuts are enabled, try them! */
-			if (settings._shortcutCommands) {
-				MapCoords new_coords = g_context->_location->_coords;
-				MapTile *tile;
-
-				new_coords.move(event._dir, g_context->_location->_map);
-				tile = g_context->_location->_map->tileAt(new_coords, WITH_OBJECTS);
-
-				if (tile->getTileType()->isDoor()) {
-					g_debugger->openAt(new_coords);
-					event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
-				} else if (tile->getTileType()->isLockedDoor()) {
-					g_debugger->jimmyAt(new_coords);
-					event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
-				} /*else if (mapPersonAt(c->location->map, new_coords) != NULL) {
-                    talkAtCoord(newx, newy, 1, NULL);
-                    event.result = MOVE_SUCCEEDED | MOVE_END_TURN;
-                    }*/
-			}
-
-			/* if we're still blocked */
-			if ((event._result & MOVE_BLOCKED) && !settings._filterMoveMessages) {
-				soundPlay(SOUND_BLOCKED, false);
-				screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
-			}
-		} else if (g_context->_transportContext == TRANSPORT_FOOT || g_context->_transportContext == TRANSPORT_HORSE) {
-			/* movement was slowed */
-			if (event._result & MOVE_SLOWED) {
-				soundPlay(SOUND_WALK_SLOWED);
-				screenMessage("%cSlow progress!%c\n", FG_GREY, FG_WHITE);
-			} else {
-				soundPlay(SOUND_WALK_NORMAL);
-			}
-		}
-	}
-
-	/* exited map */
-	if (event._result & MOVE_EXIT_TO_PARENT) {
-		screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
-		exitToParentMap();
-		g_music->play();
-	}
-
-	/* things that happen while not on board the balloon */
-	if (g_context->_transportContext & ~TRANSPORT_BALLOON)
-		checkSpecialCreatures(event._dir);
-	/* things that happen while on foot or horseback */
-	if ((g_context->_transportContext & TRANSPORT_FOOT_OR_HORSE) &&
-	        !(event._result & (MOVE_SLOWED | MOVE_BLOCKED))) {
-		if (checkMoongates())
-			event._result = (MoveResult)(MOVE_MAP_CHANGE | MOVE_END_TURN);
-	}
-}
-
-void GameController::avatarMovedInDungeon(MoveEvent &event) {
-	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
-	Direction realDir = dirNormalize((Direction)g_ultima->_saveGame->_orientation, event._dir);
-
-	if (!settings._filterMoveMessages) {
-		if (event._userEvent) {
-			if (event._result & MOVE_TURNED) {
-				if (dirRotateCCW((Direction)g_ultima->_saveGame->_orientation) == realDir)
-					screenMessage("Turn Left\n");
-				else screenMessage("Turn Right\n");
-			}
-			/* show 'Advance' or 'Retreat' in dungeons */
-			else screenMessage("%s\n", realDir == g_ultima->_saveGame->_orientation ? "Advance" : "Retreat");
-		}
-
-		if (event._result & MOVE_BLOCKED)
-			screenMessage("%cBlocked!%c\n", FG_GREY, FG_WHITE);
-	}
-
-	/* if we're exiting the map, do this */
-	if (event._result & MOVE_EXIT_TO_PARENT) {
-		screenMessage("%cLeaving...%c\n", FG_GREY, FG_WHITE);
-		exitToParentMap();
-		g_music->play();
-	}
-
-	/* check to see if we're entering a dungeon room */
-	if (event._result & MOVE_SUCCEEDED) {
-		if (dungeon->currentToken() == DUNGEON_ROOM) {
-			int room = (int)dungeon->currentSubToken(); /* get room number */
-
-			/**
-			 * recalculate room for the abyss -- there are 16 rooms for every 2 levels,
-			 * each room marked with 0xD* where (* == room number 0-15).
-			 * for levels 1 and 2, there are 16 rooms, levels 3 and 4 there are 16 rooms, etc.
-			 */
-			if (g_context->_location->_map->_id == MAP_ABYSS)
-				room = (0x10 * (g_context->_location->_coords.z / 2)) + room;
-
-			Dungeon *dng = dynamic_cast<Dungeon *>(g_context->_location->_map);
-			dng->_currentRoom = room;
-
-			/* set the map and start combat! */
-			CombatController *cc = new CombatController(dng->_roomMaps[room]);
-			cc->initDungeonRoom(room, dirReverse(realDir));
-			cc->begin();
-		}
-	}
-}
-
-
 /**
  * Peers at a city from A-P (Lycaeum telescope) and functions like a gem
  */
@@ -1062,48 +360,6 @@ void peer(bool useGem) {
 	g_game->_paused = false;
 }
 
-void GameController::timerFired() {
-	if (_pausedTimer > 0) {
-		_pausedTimer--;
-		if (_pausedTimer <= 0) {
-			_pausedTimer = 0;
-			_paused = false; /* unpause the game */
-		}
-	}
-
-	if (!_paused && !_pausedTimer) {
-		if (++g_context->_windCounter >= MOON_SECONDS_PER_PHASE * 4) {
-			if (xu4_random(4) == 1 && !g_context->_windLock)
-				g_context->_windDirection = dirRandomDir(MASK_DIR_ALL);
-			g_context->_windCounter = 0;
-		}
-
-		/* balloon moves about 4 times per second */
-		if ((g_context->_transportContext == TRANSPORT_BALLOON) &&
-		        g_context->_party->isFlying()) {
-			g_context->_location->move(dirReverse((Direction) g_context->_windDirection), false);
-		}
-
-		updateMoons(true);
-
-		screenCycle();
-
-		/*
-		 * force pass if no commands within last 20 seconds
-		 */
-		Controller *controller = eventHandler->getController();
-		if (controller != NULL && (eventHandler->getController() == g_game ||
-			dynamic_cast<CombatController *>(eventHandler->getController()) != NULL) &&
-			gameTimeSinceLastCommand() > 20) {
-
-			/* pass the turn, and redraw the text area so the prompt is shown */
-			MetaEngine::executeAction(KEYBIND_PASS);
-			screenRedrawTextArea(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H);
-		}
-	}
-
-}
-
 /**
  * Checks the hull integrity of the ship and handles
  * the ship sinking, if necessary
@@ -1138,83 +394,6 @@ void gameCheckHullIntegrity() {
 	}
 }
 
-void GameController::checkSpecialCreatures(Direction dir) {
-	int i;
-	Object *obj;
-	static const struct {
-		int x, y;
-		Direction dir;
-	} pirateInfo[] = {
-		{ 224, 220, DIR_EAST }, /* N'M" O'A" */
-		{ 224, 228, DIR_EAST }, /* O'E" O'A" */
-		{ 226, 220, DIR_EAST }, /* O'E" O'C" */
-		{ 227, 228, DIR_EAST }, /* O'E" O'D" */
-		{ 228, 227, DIR_SOUTH }, /* O'D" O'E" */
-		{ 229, 225, DIR_SOUTH }, /* O'B" O'F" */
-		{ 229, 223, DIR_NORTH }, /* N'P" O'F" */
-		{ 228, 222, DIR_NORTH } /* N'O" O'E" */
-	};
-
-	/*
-	 * if heading east into pirates cove (O'A" N'N"), generate pirate
-	 * ships
-	 */
-	if (dir == DIR_EAST &&
-	        g_context->_location->_coords.x == 0xdd &&
-	        g_context->_location->_coords.y == 0xe0) {
-		for (i = 0; i < 8; i++) {
-			obj = g_context->_location->_map->addCreature(creatureMgr->getById(PIRATE_ID), MapCoords(pirateInfo[i].x, pirateInfo[i].y));
-			obj->setDirection(pirateInfo[i].dir);
-		}
-	}
-
-	/*
-	 * if heading south towards the shrine of humility, generate
-	 * daemons unless horn has been blown
-	 */
-	if (dir == DIR_SOUTH &&
-	        g_context->_location->_coords.x >= 229 &&
-	        g_context->_location->_coords.x < 234 &&
-	        g_context->_location->_coords.y >= 212 &&
-	        g_context->_location->_coords.y < 217 &&
-	        *g_context->_aura != Aura::HORN) {
-		for (i = 0; i < 8; i++)
-			g_context->_location->_map->addCreature(creatureMgr->getById(DAEMON_ID), MapCoords(231, g_context->_location->_coords.y + 1, g_context->_location->_coords.z));
-	}
-}
-
-bool GameController::checkMoongates() {
-	Coords dest;
-
-	if (moongateFindActiveGateAt(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase, g_context->_location->_coords, dest)) {
-
-		gameSpellEffect(-1, -1, SOUND_MOONGATE); // Default spell effect (screen inversion without 'spell' sound effects)
-
-		if (g_context->_location->_coords != dest) {
-			g_context->_location->_coords = dest;
-			gameSpellEffect(-1, -1, SOUND_MOONGATE); // Again, after arriving
-		}
-
-		if (moongateIsEntryToShrineOfSpirituality(g_ultima->_saveGame->_trammelPhase, g_ultima->_saveGame->_feluccaPhase)) {
-			Shrine *shrine_spirituality;
-
-			shrine_spirituality = dynamic_cast<Shrine *>(mapMgr->get(MAP_SHRINE_SPIRITUALITY));
-
-			if (!g_context->_party->canEnterShrine(VIRT_SPIRITUALITY))
-				return true;
-
-			setMap(shrine_spirituality, 1, NULL);
-			g_music->play();
-
-			shrine_spirituality->enter();
-		}
-
-		return true;
-	}
-
-	return false;
-}
-
 /**
  * Fixes objects initially loaded by saveGameMonstersRead,
  * and alters movement behavior accordingly to match the creature
@@ -1446,60 +625,6 @@ void gameSetActivePlayer(int player) {
 	}
 }
 
-void GameController::creatureCleanup() {
-	ObjectDeque::iterator i;
-	Map *map = g_context->_location->_map;
-
-	for (i = map->_objects.begin(); i != map->_objects.end();) {
-		Object *obj = *i;
-		MapCoords o_coords = obj->getCoords();
-
-		if ((obj->getType() == Object::CREATURE) && (o_coords.z == g_context->_location->_coords.z) &&
-		        o_coords.distance(g_context->_location->_coords, g_context->_location->_map) > MAX_CREATURE_DISTANCE) {
-
-			/* delete the object and remove it from the map */
-			i = map->removeObject(i);
-		} else i++;
-	}
-}
-
-void GameController::checkRandomCreatures() {
-	int canSpawnHere = g_context->_location->_map->isWorldMap() || g_context->_location->_context & CTX_DUNGEON;
-#ifdef IOS
-	int spawnDivisor = c->location->context & CTX_DUNGEON ? (53 - (c->location->coords.z << 2)) : 53;
-#else
-	int spawnDivisor = g_context->_location->_context & CTX_DUNGEON ? (32 - (g_context->_location->_coords.z << 2)) : 32;
-#endif
-
-	/* If there are too many creatures already,
-	   or we're not on the world map, don't worry about it! */
-	if (!canSpawnHere ||
-	        g_context->_location->_map->getNumberOfCreatures() >= MAX_CREATURES_ON_MAP ||
-	        xu4_random(spawnDivisor) != 0)
-		return;
-
-	gameSpawnCreature(NULL);
-}
-
-void GameController::checkBridgeTrolls() {
-	const Tile *bridge = g_context->_location->_map->_tileset->getByName("bridge");
-	if (!bridge)
-		return;
-
-	// TODO: CHEST: Make a user option to not make chests block bridge trolls
-	if (!g_context->_location->_map->isWorldMap() ||
-	        g_context->_location->_map->tileAt(g_context->_location->_coords, WITH_OBJECTS)->_id != bridge->getId() ||
-	        xu4_random(8) != 0)
-		return;
-
-	screenMessage("\nBridge Trolls!\n");
-
-	Creature *m = g_context->_location->_map->addCreature(creatureMgr->getById(TROLL_ID), g_context->_location->_coords);
-	CombatController *cc = new CombatController(MAP_BRIDGE_CON);
-	cc->init(m);
-	cc->begin();
-}
-
 /**
  * Spawns a creature (m) just offscreen of the avatar.
  * If (m==NULL) then it finds its own creature to spawn and spawns it.
@@ -1626,22 +751,6 @@ void gameDestroyAllCreatures(void) {
 	g_context->_location->_map->alertGuards();
 }
 
-bool GameController::createBalloon(Map *map) {
-	ObjectDeque::iterator i;
-
-	/* see if the balloon has already been created (and not destroyed) */
-	for (i = map->_objects.begin(); i != map->_objects.end(); i++) {
-		Object *obj = *i;
-		if (obj->getTile().getTileType()->isBalloon())
-			return false;
-	}
-
-	const Tile *balloon = map->_tileset->getByName("balloon");
-	ASSERT(balloon, "no balloon tile found in tileset");
-	map->addObject(balloon->getId(), balloon->getId(), map->getLabel("balloon"));
-	return true;
-}
-
 // Colors assigned to reagents based on my best reading of them
 // from the book of wisdom.  Maybe we could use BOLD to distinguish
 // the two grey and the two red reagents.
diff --git a/engines/ultima/ultima4/game/game.h b/engines/ultima/ultima4/game/game.h
index 7bef392a38..980a8ca42e 100644
--- a/engines/ultima/ultima4/game/game.h
+++ b/engines/ultima/ultima4/game/game.h
@@ -23,13 +23,13 @@
 #ifndef ULTIMA4_GAME_H
 #define ULTIMA4_GAME_H
 
-#include "ultima/ultima4/events/controller.h"
 #include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/map/map.h"
+#include "ultima/ultima4/controllers/game_controller.h"
 #include "ultima/ultima4/core/observer.h"
-#include "ultima/ultima4/sound/sound.h"
-#include "ultima/ultima4/map/tileview.h"
 #include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/map/map.h"
+#include "ultima/ultima4/map/tileview.h"
+#include "ultima/ultima4/sound/sound.h"
 
 namespace Ultima {
 namespace Ultima4 {
@@ -53,186 +53,9 @@ typedef enum {
 	VIEW_MIXTURES
 } ViewMode;
 
-/**
- * A controller to read a player number.
- */
-class ReadPlayerController : public ReadChoiceController {
-public:
-	ReadPlayerController();
-	~ReadPlayerController();
-	bool keyPressed(int key) override;
-
-	int getPlayer();
-	int waitFor() override;
-};
-
-/**
- * A controller to handle input for commands requiring a letter
- * argument in the range 'a' - lastValidLetter.
- */
-class AlphaActionController : public WaitableController<int> {
-public:
-	AlphaActionController(char letter, const Common::String &p) : _lastValidLetter(letter), _prompt(p) {}
-	bool keyPressed(int key) override;
-
-	static int get(char lastValidLetter, const Common::String &prompt, EventHandler *eh = NULL);
-
-private:
-	char _lastValidLetter;
-	Common::String _prompt;
-};
-
-/**
- * Controls interaction while Ztats are being displayed.
- */
-class ZtatsController : public WaitableController<void *> {
-public:
-	bool keyPressed(int key) override;
-};
-
-class TurnCompleter {
-public:
-	virtual ~TurnCompleter() {}
-	virtual void finishTurn() = 0;
-};
-
-/**
- * The main game controller that handles basic game flow and keypresses.
- *
- * @todo
- *  <ul>
- *      <li>separate the dungeon specific stuff into another class (subclass?)</li>
- *  </ul>
- */
-class GameController : public Controller, public Observer<Party *, PartyEvent &>, public Observer<Location *, MoveEvent &>,
-	public TurnCompleter {
-public:
-	GameController();
-
-	/* controller functions */
-
-	/**
-	 * Keybinder actions
-	 */
-	void keybinder(KeybindingAction action) override;
-
-	/**
-	 * The main key handler for the game.  Interpretes each key as a
-	 * command - 'a' for attack, 't' for talk, etc.
-	 */
-	bool keyPressed(int key) override;
-
-	/**
-	 * This function is called every quarter second.
-	 */
-	void timerFired() override;
-
-	/* main game functions */
-	void init();
-	void initScreen();
-	void initScreenWithoutReloadingState();
-	void setMap(Map *map, bool saveLocation, const Portal *portal, TurnCompleter *turnCompleter = NULL);
-
-	/**
-	 * Exits the current map and location and returns to its parent location
-	 * This restores all relevant information from the previous location,
-	 * such as the map, map position, etc. (such as exiting a city)
-	 **/
-	int exitToParentMap();
-
-	/**
-	 * Terminates a game turn.  This performs the post-turn housekeeping
-	 * tasks like adjusting the party's food, incrementing the number of
-	 * moves, etc.
-	 */
-	void finishTurn() override;
-
-	/**
-	 * Provide feedback to user after a party event happens.
-	 */
-	void update(Party *party, PartyEvent &event) override;
-
-	/**
-	 * Provide feedback to user after a movement event happens.
-	 */
-	void update(Location *location, MoveEvent &event) override;
-
-	/**
-	 * Initializes the moon state according to the savegame file. This method of
-	 * initializing the moons (rather than just setting them directly) is necessary
-	 * to make sure trammel and felucca stay in sync
-	 */
-	void initMoons();
-
-	/**
-	 * Updates the phases of the moons and shows
-	 * the visual moongates on the map, if desired
-	 */
-	void updateMoons(bool showmoongates);
-
-	/**
-	 * Show an attack flash at x, y on the current map.
-	 * This is used for 'being hit' or 'being missed'
-	 * by weapons, cannon fire, spells, etc.
-	 */
-	static void flashTile(const Coords &coords, MapTile tile, int timeFactor);
-
-	static void flashTile(const Coords &coords, const Common::String &tilename, int timeFactor);
-	static void doScreenAnimationsWhilePausing(int timeFactor);
-
-	TileView _mapArea;
-	bool _paused;
-	int _pausedTimer;
-
-private:
-	/**
-	 * Handles feedback after avatar moved during normal 3rd-person view.
-	 */
-	void avatarMoved(MoveEvent &event);
-
-	/**
-	 * Handles feedback after moving the avatar in the 3-d dungeon view.
-	 */
-	void avatarMovedInDungeon(MoveEvent &event);
-
-	/**
-	 * Removes creatures from the current map if they are too far away from the avatar
-	 */
-	void creatureCleanup();
-
-	/**
-	 * Handles trolls under bridges
-	 */
-	void checkBridgeTrolls();
-
-	/**
-	 * Checks creature conditions and spawns new creatures if necessary
-	 */
-	void checkRandomCreatures();
-
-	/**
-	 * Checks for valid conditions and handles
-	 * special creatures guarding the entrance to the
-	 * abyss and to the shrine of spirituality
-	 */
-	void checkSpecialCreatures(Direction dir);
-
-	/**
-	 * Checks for and handles when the avatar steps on a moongate
-	 */
-	bool checkMoongates();
-
-	/**
-	 * Creates the balloon near Hythloth, but only if the balloon doesn't already exists somewhere
-	 */
-	bool createBalloon(Map *map);
-};
-
-extern GameController *g_game;
-
 /* map and screen functions */
 void gameSetViewMode(ViewMode newMode);
-void gameUpdateScreen(void);
+void gameUpdateScreen();
 
 /* spell functions */
 void gameSpellEffect(int spell, int player, Sound sound);
@@ -242,16 +65,18 @@ bool gamePeerCity(int city, void *data);
 void peer(bool useGem = true);
 bool fireAt(const Coords &coords, bool originAvatar);
 Direction gameGetDirection();
+uint32 gameTimeSinceLastCommand();
 
 /* checking functions */
-void gameCheckHullIntegrity(void);
+void gameCheckHullIntegrity();
 
 /* creature functions */
 bool creatureRangeAttack(const Coords &coords, Creature *m);
-void gameCreatureCleanup(void);
+void gameCreatureCleanup();
 bool gameSpawnCreature(const class Creature *m);
 void gameFixupObjects(Map *map);
-void gameDestroyAllCreatures(void);
+void gameDestroyAllCreatures();
+void gameCreatureAttack(Creature *obj);
 
 /* etc */
 Common::String gameGetInput(int maxlen = 32);
diff --git a/engines/ultima/ultima4/game/intro.cpp b/engines/ultima/ultima4/game/intro.cpp
index 3694bc6bb7..54e430eb98 100644
--- a/engines/ultima/ultima4/game/intro.cpp
+++ b/engines/ultima/ultima4/game/intro.cpp
@@ -20,24 +20,26 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/game/intro.h"
+#include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/game/menu.h"
+#include "ultima/ultima4/controllers/read_string_controller.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/core/config.h"
+#include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/core/error.h"
+#include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/gfx/imagemgr.h"
-#include "ultima/ultima4/game/menu.h"
-#include "ultima/ultima4/sound/music.h"
-#include "ultima/ultima4/sound/sound.h"
-#include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/filesys/u4file.h"
+#include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/map/shrine.h"
 #include "ultima/ultima4/map/tileset.h"
 #include "ultima/ultima4/map/tilemap.h"
-#include "ultima/ultima4/filesys/u4file.h"
-#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/sound/music.h"
+#include "ultima/ultima4/sound/sound.h"
+#include "ultima/ultima4/ultima4.h"
 #include "common/savefile.h"
 #include "common/system.h"
 
diff --git a/engines/ultima/ultima4/game/intro.h b/engines/ultima/ultima4/game/intro.h
index 5fe7978488..c9eb33f344 100644
--- a/engines/ultima/ultima4/game/intro.h
+++ b/engines/ultima/ultima4/game/intro.h
@@ -23,12 +23,12 @@
 #ifndef ULTIMA4_INTRO_H
 #define ULTIMA4_INTRO_H
 
-#include "ultima/ultima4/events/controller.h"
-#include "ultima/ultima4/game/menu.h"
+#include "ultima/ultima4/controllers/controller.h"
 #include "ultima/ultima4/core/observer.h"
 #include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/gfx/imageview.h"
+#include "ultima/ultima4/game/menu.h"
 #include "ultima/ultima4/game/textview.h"
+#include "ultima/ultima4/gfx/imageview.h"
 #include "ultima/ultima4/map/tileview.h"
 
 namespace Ultima {
diff --git a/engines/ultima/ultima4/game/item.cpp b/engines/ultima/ultima4/game/item.cpp
index 2b1915b97b..15a5d3d7d9 100644
--- a/engines/ultima/ultima4/game/item.cpp
+++ b/engines/ultima/ultima4/game/item.cpp
@@ -21,24 +21,25 @@
  */
 
 #include "ultima/ultima4/game/item.h"
-#include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/game/codex.h"
-#include "ultima/ultima4/map/combat.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/map/dungeon.h"
 #include "ultima/ultima4/game/game.h"
-#include "ultima/ultima4/map/location.h"
-#include "ultima/ultima4/map/map.h"
-#include "ultima/ultima4/map/mapmgr.h"
 #include "ultima/ultima4/game/names.h"
 #include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/game/portal.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/weapon.h"
+#include "ultima/ultima4/controllers/alpha_action_controller.h"
+#include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/map/annotation.h"
+#include "ultima/ultima4/map/combat.h"
+#include "ultima/ultima4/map/dungeon.h"
+#include "ultima/ultima4/map/location.h"
+#include "ultima/ultima4/map/map.h"
+#include "ultima/ultima4/map/mapmgr.h"
 #include "ultima/ultima4/map/tileset.h"
 #include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/core/utils.h"
-#include "ultima/ultima4/game/weapon.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/game/person.cpp b/engines/ultima/ultima4/game/person.cpp
index 070b9ed556..3236a2c9e3 100644
--- a/engines/ultima/ultima4/game/person.cpp
+++ b/engines/ultima/ultima4/game/person.cpp
@@ -20,25 +20,27 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/game/person.h"
-#include "ultima/ultima4/map/city.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/conversation/conversation.h"
-#include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/game/game.h"   // Included for ReadPlayerController
-#include "ultima/ultima4/map/location.h"
-#include "ultima/ultima4/sound/music.h"
 #include "ultima/ultima4/game/names.h"
 #include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/script.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_int_controller.h"
+#include "ultima/ultima4/controllers/read_player_controller.h"
+#include "ultima/ultima4/conversation/conversation.h"
+#include "ultima/ultima4/core/config.h"
+#include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/core/types.h"
-#include "ultima/ultima4/filesys/u4file.h"
 #include "ultima/ultima4/core/utils.h"
-#include "ultima/ultima4/game/script.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/filesys/u4file.h"
+#include "ultima/ultima4/map/city.h"
+#include "ultima/ultima4/map/location.h"
+#include "ultima/ultima4/sound/music.h"
+#include "ultima/ultima4/ultima4.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/map/combat.cpp b/engines/ultima/ultima4/map/combat.cpp
index 8707f7355c..01302dec95 100644
--- a/engines/ultima/ultima4/map/combat.cpp
+++ b/engines/ultima/ultima4/map/combat.cpp
@@ -20,7 +20,16 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
+#include "ultima/ultima4/map/combat.h"
+#include "ultima/ultima4/map/annotation.h"
+#include "ultima/ultima4/map/dungeon.h"
+#include "ultima/ultima4/map/location.h"
+#include "ultima/ultima4/map/mapmgr.h"
+#include "ultima/ultima4/map/movement.h"
+#include "ultima/ultima4/map/tileset.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_dir_controller.h"
+#include "ultima/ultima4/controllers/ztats_controller.h"
 #include "ultima/ultima4/core/debugger.h"
 #include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/core/utils.h"
@@ -38,14 +47,8 @@
 #include "ultima/ultima4/game/stats.h"
 #include "ultima/ultima4/game/weapon.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/map/combat.h"
-#include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/map/dungeon.h"
-#include "ultima/ultima4/map/location.h"
-#include "ultima/ultima4/map/mapmgr.h"
-#include "ultima/ultima4/map/movement.h"
-#include "ultima/ultima4/map/tileset.h"
 #include "ultima/shared/std/containers.h"
+#include "ultima/ultima4/ultima4.h"
 #include "common/system.h"
 
 namespace Ultima {
diff --git a/engines/ultima/ultima4/map/combat.h b/engines/ultima/ultima4/map/combat.h
index 700a54cca3..31d9780794 100644
--- a/engines/ultima/ultima4/map/combat.h
+++ b/engines/ultima/ultima4/map/combat.h
@@ -25,14 +25,14 @@
 
 #include "ultima/ultima4/map/direction.h"
 #include "ultima/ultima4/map/map.h"
-#include "ultima/ultima4/events/controller.h"
+#include "ultima/ultima4/controllers/controller.h"
+#include "ultima/ultima4/core/observer.h"
+#include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/game/creature.h"
 #include "ultima/ultima4/game/game.h"
 #include "ultima/ultima4/game/object.h"
-#include "ultima/ultima4/core/observer.h"
 #include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/core/types.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/map/shrine.cpp b/engines/ultima/ultima4/map/shrine.cpp
index 34278c383f..3c9f623913 100644
--- a/engines/ultima/ultima4/map/shrine.cpp
+++ b/engines/ultima/ultima4/map/shrine.cpp
@@ -20,25 +20,28 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/core/config.h"
-#include "ultima/ultima4/map/shrine.h"
 #include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/game/game.h"
-#include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/mapmgr.h"
+#include "ultima/ultima4/map/shrine.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_string_controller.h"
+#include "ultima/ultima4/controllers/wait_controller.h"
+#include "ultima/ultima4/core/config.h"
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/game.h"
 #include "ultima/ultima4/game/creature.h"
-#include "ultima/ultima4/sound/music.h"
 #include "ultima/ultima4/game/names.h"
 #include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/game/portal.h"
+#include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/sound/music.h"
+#include "ultima/ultima4/ultima4.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 39d58b1ce2..fec9fa983f 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -108,7 +108,7 @@ static const KeybindingRecord CHEAT_KEYS[] = {
 	{ KEYBIND_CHEAT_UP, "CHEAT-UP", "Up Level", "up", "A+UP", nullptr },
 	{ KEYBIND_CHEAT_DOWN, "CHEAT-DOWN", "Down Level", "down", "A+DOWN", nullptr },
 	{ KEYBIND_CHEAT_VIRTUE, "CHEAT-VIRTUE", "Grant Virtue", "virtue", "A+v", nullptr },
-	{ KEYBIND_CHEAT_WIND, "CHEAT-WIND", "Change Wind", "wind", "A+w", nullptr },
+ 	{ KEYBIND_CHEAT_WIND, "CHEAT-WIND", "Change Wind", "wind", "A+w", nullptr },
 
 	{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
 };


Commit: 7aae5d45c9c40d5aa8c9f6e5cc0bc4c58eed09c5
    https://github.com/scummvm/scummvm/commit/7aae5d45c9c40d5aa8c9f6e5cc0bc4c58eed09c5
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-16T19:30:47-07:00

Commit Message:
ULTIMA4: Moving more controller classes to controllers/

Changed paths:
  A engines/ultima/ultima4/controllers/camp_controller.cpp
  A engines/ultima/ultima4/controllers/camp_controller.h
  A engines/ultima/ultima4/controllers/combat_controller.cpp
  A engines/ultima/ultima4/controllers/combat_controller.h
  A engines/ultima/ultima4/controllers/inn_controller.cpp
  A engines/ultima/ultima4/controllers/inn_controller.h
  A engines/ultima/ultima4/controllers/intro_controller.cpp
  A engines/ultima/ultima4/controllers/intro_controller.h
  A engines/ultima/ultima4/controllers/menu_controller.cpp
  A engines/ultima/ultima4/controllers/menu_controller.h
  A engines/ultima/ultima4/controllers/reagents_menu_controller.cpp
  A engines/ultima/ultima4/controllers/reagents_menu_controller.h
  R engines/ultima/ultima4/game/intro.cpp
  R engines/ultima/ultima4/game/intro.h
  R engines/ultima/ultima4/map/camp.cpp
  R engines/ultima/ultima4/map/camp.h
  R engines/ultima/ultima4/map/combat.cpp
  R engines/ultima/ultima4/map/combat.h
    engines/ultima/module.mk
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/game/creature.cpp
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/item.cpp
    engines/ultima/ultima4/game/menu.cpp
    engines/ultima/ultima4/game/menu.h
    engines/ultima/ultima4/game/player.cpp
    engines/ultima/ultima4/game/script.cpp
    engines/ultima/ultima4/game/spell.cpp
    engines/ultima/ultima4/game/stats.cpp
    engines/ultima/ultima4/game/stats.h
    engines/ultima/ultima4/gfx/screen.cpp
    engines/ultima/ultima4/gfx/screen_scummvm.cpp
    engines/ultima/ultima4/map/dungeon.h
    engines/ultima/ultima4/ultima4.cpp


diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index d7c91d2381..113ebdd5ab 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -135,14 +135,20 @@ MODULE_OBJS := \
 	ultima1/widgets/wench.o \
 	ultima1/game.o \
 	ultima4/controllers/alpha_action_controller.o \
+	ultima4/controllers/camp_controller.o \
+	ultima4/controllers/combat_controller.o \
 	ultima4/controllers/controller.o \
 	ultima4/controllers/game_controller.o \
+	ultima4/controllers/inn_controller.o \
+	ultima4/controllers/intro_controller.o \
 	ultima4/controllers/key_handler_controller.o \
+	ultima4/controllers/menu_controller.o \
 	ultima4/controllers/read_choice_controller.o \
 	ultima4/controllers/read_dir_controller.o \
 	ultima4/controllers/read_int_controller.o \
 	ultima4/controllers/read_player_controller.o \
 	ultima4/controllers/read_string_controller.o \
+	ultima4/controllers/reagents_menu_controller.o \
 	ultima4/controllers/wait_controller.o \
 	ultima4/controllers/ztats_controller.o \
 	ultima4/conversation/conversation.o \
@@ -173,7 +179,6 @@ MODULE_OBJS := \
 	ultima4/game/creature.o \
 	ultima4/game/death.o \
 	ultima4/game/game.o \
-	ultima4/game/intro.o \
 	ultima4/game/item.o \
 	ultima4/game/menu.o \
 	ultima4/game/menuitem.o \
@@ -200,9 +205,7 @@ MODULE_OBJS := \
 	ultima4/gfx/screen.o \
 	ultima4/gfx/screen_scummvm.o \
 	ultima4/map/annotation.o \
-	ultima4/map/camp.o \
 	ultima4/map/city.o \
-	ultima4/map/combat.o \
 	ultima4/map/direction.o \
 	ultima4/map/dungeon.o \
 	ultima4/map/dungeonview.o \
diff --git a/engines/ultima/ultima4/controllers/camp_controller.cpp b/engines/ultima/ultima4/controllers/camp_controller.cpp
new file mode 100644
index 0000000000..4630c4f166
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/camp_controller.cpp
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/camp_controller.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/map/mapmgr.h"
+#include "ultima/ultima4/ultima4.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+CampController::CampController() {
+	MapId id;
+
+	/* setup camp (possible, but not for-sure combat situation */
+	if (g_context->_location->_context & CTX_DUNGEON)
+		id = MAP_CAMP_DNG;
+	else
+		id = MAP_CAMP_CON;
+
+	_map = getCombatMap(mapMgr->get(id));
+	g_game->setMap(_map, true, NULL, this);
+}
+
+void CampController::init(Creature *m) {
+	CombatController::init(m);
+	_camping = true;
+}
+
+void CampController::begin() {
+	// make sure everyone's asleep
+	for (int i = 0; i < g_context->_party->size(); i++)
+		g_context->_party->member(i)->putToSleep();
+
+	CombatController::begin();
+
+	g_music->camp();
+
+	screenMessage("Resting...\n");
+	screenDisableCursor();
+
+	EventHandler::wait_msecs(settings._campTime * 1000);
+
+	screenEnableCursor();
+
+	/* Is the party ambushed during their rest? */
+	if (settings._campingAlwaysCombat || (xu4_random(8) == 0)) {
+		const Creature *m = creatureMgr->randomAmbushing();
+
+		g_music->play();
+		screenMessage("Ambushed!\n");
+
+		/* create an ambushing creature (so it leaves a chest) */
+		setCreature(g_context->_location->_prev->_map->addCreature(m, g_context->_location->_prev->_coords));
+
+		/* fill the creature table with creatures and place them */
+		fillCreatureTable(m);
+		placeCreatures();
+
+		/* creatures go first! */
+		finishTurn();
+	} else {
+		/* Wake everyone up! */
+		for (int i = 0; i < g_context->_party->size(); i++)
+			g_context->_party->member(i)->wakeUp();
+
+		/* Make sure we've waited long enough for camping to be effective */
+		bool healed = false;
+		if (((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) >= 0x10000) ||
+		        (((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff) != g_ultima->_saveGame->_lastCamp))
+			healed = heal();
+
+		screenMessage(healed ? "Party Healed!\n" : "No effect.\n");
+		g_ultima->_saveGame->_lastCamp = (g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff;
+
+		eventHandler->popController();
+		g_game->exitToParentMap();
+		g_music->fadeIn(CAMP_FADE_IN_TIME, true);
+		delete this;
+	}
+}
+
+void CampController::end(bool adjustKarma) {
+	// wake everyone up!
+	for (int i = 0; i < g_context->_party->size(); i++)
+		g_context->_party->member(i)->wakeUp();
+	CombatController::end(adjustKarma);
+}
+
+bool CampController::heal() {
+	// restore each party member to max mp, and restore some hp
+	bool healed = false;
+	for (int i = 0; i < g_context->_party->size(); i++) {
+		PartyMember *m = g_context->_party->member(i);
+		m->setMp(m->getMaxMp());
+		if ((m->getHp() < m->getMaxHp()) && m->heal(HT_CAMPHEAL))
+			healed = true;
+	}
+
+	return healed;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/map/camp.h b/engines/ultima/ultima4/controllers/camp_controller.h
similarity index 82%
rename from engines/ultima/ultima4/map/camp.h
rename to engines/ultima/ultima4/controllers/camp_controller.h
index a7e2f59a79..c4a973cde2 100644
--- a/engines/ultima/ultima4/map/camp.h
+++ b/engines/ultima/ultima4/controllers/camp_controller.h
@@ -20,10 +20,10 @@
  *
  */
 
-#ifndef ULTIMA4_CAMP_H
-#define ULTIMA4_CAMP_H
+#ifndef ULTIMA4_CONTROLLERS_CAMP_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_CAMP_CONTROLLER_H
 
-#include "ultima/ultima4/map/combat.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
 
 namespace Ultima {
 namespace Ultima4 {
@@ -41,19 +41,6 @@ private:
 	bool heal();
 };
 
-class InnController : public CombatController {
-public:
-	InnController();
-
-	void begin() override;
-	void awardLoot() override;
-
-private:
-	bool heal();
-	void maybeMeetIsaac();
-	void maybeAmbush();
-};
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima4/map/combat.cpp b/engines/ultima/ultima4/controllers/combat_controller.cpp
similarity index 99%
rename from engines/ultima/ultima4/map/combat.cpp
rename to engines/ultima/ultima4/controllers/combat_controller.cpp
index 01302dec95..c39e6b1168 100644
--- a/engines/ultima/ultima4/map/combat.cpp
+++ b/engines/ultima/ultima4/controllers/combat_controller.cpp
@@ -20,16 +20,16 @@
  *
  */
 
-#include "ultima/ultima4/map/combat.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
+#include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/controllers/read_dir_controller.h"
+#include "ultima/ultima4/controllers/ztats_controller.h"
 #include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/map/dungeon.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/mapmgr.h"
 #include "ultima/ultima4/map/movement.h"
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/controllers/read_choice_controller.h"
-#include "ultima/ultima4/controllers/read_dir_controller.h"
-#include "ultima/ultima4/controllers/ztats_controller.h"
 #include "ultima/ultima4/core/debugger.h"
 #include "ultima/ultima4/core/settings.h"
 #include "ultima/ultima4/core/utils.h"
diff --git a/engines/ultima/ultima4/map/combat.h b/engines/ultima/ultima4/controllers/combat_controller.h
similarity index 100%
rename from engines/ultima/ultima4/map/combat.h
rename to engines/ultima/ultima4/controllers/combat_controller.h
diff --git a/engines/ultima/ultima4/map/camp.cpp b/engines/ultima/ultima4/controllers/inn_controller.cpp
similarity index 60%
rename from engines/ultima/ultima4/map/camp.cpp
rename to engines/ultima/ultima4/controllers/inn_controller.cpp
index 3f937ed8e3..75b511c836 100644
--- a/engines/ultima/ultima4/map/camp.cpp
+++ b/engines/ultima/ultima4/controllers/inn_controller.cpp
@@ -20,129 +20,17 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/map/camp.h"
-#include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/map/city.h"
-#include "ultima/ultima4/map/combat.h"
-#include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/controllers/inn_controller.h"
 #include "ultima/ultima4/conversation/conversation.h"
-#include "ultima/ultima4/events/event.h"
-#include "ultima/ultima4/game/game.h"
-#include "ultima/ultima4/map/location.h"
-#include "ultima/ultima4/map/map.h"
-#include "ultima/ultima4/map/mapmgr.h"
-#include "ultima/ultima4/game/creature.h"
-#include "ultima/ultima4/sound/music.h"
-#include "ultima/ultima4/game/names.h"
-#include "ultima/ultima4/game/object.h"
-#include "ultima/ultima4/game/person.h"
-#include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
-#include "ultima/ultima4/game/stats.h"
-#include "ultima/ultima4/map/tileset.h"
 #include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/map/city.h"
+#include "ultima/ultima4/map/mapmgr.h"
 
 namespace Ultima {
 namespace Ultima4 {
 
-void campTimer(void *data);
-void campEnd(void);
-int campHeal(HealType heal_type);
 void innTimer(void *data);
 
-CampController::CampController() {
-	MapId id;
-
-	/* setup camp (possible, but not for-sure combat situation */
-	if (g_context->_location->_context & CTX_DUNGEON)
-		id = MAP_CAMP_DNG;
-	else
-		id = MAP_CAMP_CON;
-
-	_map = getCombatMap(mapMgr->get(id));
-	g_game->setMap(_map, true, NULL, this);
-}
-
-void CampController::init(Creature *m) {
-	CombatController::init(m);
-	_camping = true;
-}
-
-void CampController::begin() {
-	// make sure everyone's asleep
-	for (int i = 0; i < g_context->_party->size(); i++)
-		g_context->_party->member(i)->putToSleep();
-
-	CombatController::begin();
-
-	g_music->camp();
-
-	screenMessage("Resting...\n");
-	screenDisableCursor();
-
-	EventHandler::wait_msecs(settings._campTime * 1000);
-
-	screenEnableCursor();
-
-	/* Is the party ambushed during their rest? */
-	if (settings._campingAlwaysCombat || (xu4_random(8) == 0)) {
-		const Creature *m = creatureMgr->randomAmbushing();
-
-		g_music->play();
-		screenMessage("Ambushed!\n");
-
-		/* create an ambushing creature (so it leaves a chest) */
-		setCreature(g_context->_location->_prev->_map->addCreature(m, g_context->_location->_prev->_coords));
-
-		/* fill the creature table with creatures and place them */
-		fillCreatureTable(m);
-		placeCreatures();
-
-		/* creatures go first! */
-		finishTurn();
-	} else {
-		/* Wake everyone up! */
-		for (int i = 0; i < g_context->_party->size(); i++)
-			g_context->_party->member(i)->wakeUp();
-
-		/* Make sure we've waited long enough for camping to be effective */
-		bool healed = false;
-		if (((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) >= 0x10000) ||
-		        (((g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff) != g_ultima->_saveGame->_lastCamp))
-			healed = heal();
-
-		screenMessage(healed ? "Party Healed!\n" : "No effect.\n");
-		g_ultima->_saveGame->_lastCamp = (g_ultima->_saveGame->_moves / CAMP_HEAL_INTERVAL) & 0xffff;
-
-		eventHandler->popController();
-		g_game->exitToParentMap();
-		g_music->fadeIn(CAMP_FADE_IN_TIME, true);
-		delete this;
-	}
-}
-
-void CampController::end(bool adjustKarma) {
-	// wake everyone up!
-	for (int i = 0; i < g_context->_party->size(); i++)
-		g_context->_party->member(i)->wakeUp();
-	CombatController::end(adjustKarma);
-}
-
-bool CampController::heal() {
-	// restore each party member to max mp, and restore some hp
-	bool healed = false;
-	for (int i = 0; i < g_context->_party->size(); i++) {
-		PartyMember *m = g_context->_party->member(i);
-		m->setMp(m->getMaxMp());
-		if ((m->getHp() < m->getMaxHp()) && m->heal(HT_CAMPHEAL))
-			healed = true;
-	}
-
-	return healed;
-}
-
 InnController::InnController() {
 	_map = NULL;
 	/*
diff --git a/engines/ultima/ultima4/controllers/inn_controller.h b/engines/ultima/ultima4/controllers/inn_controller.h
new file mode 100644
index 0000000000..14ce371b0f
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/inn_controller.h
@@ -0,0 +1,47 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_INN_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_INN_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/combat_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+class InnController : public CombatController {
+public:
+	InnController();
+
+	void begin() override;
+	void awardLoot() override;
+
+private:
+	bool heal();
+	void maybeMeetIsaac();
+	void maybeAmbush();
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/game/intro.cpp b/engines/ultima/ultima4/controllers/intro_controller.cpp
similarity index 99%
rename from engines/ultima/ultima4/game/intro.cpp
rename to engines/ultima/ultima4/controllers/intro_controller.cpp
index 54e430eb98..f01aa7dfec 100644
--- a/engines/ultima/ultima4/game/intro.cpp
+++ b/engines/ultima/ultima4/controllers/intro_controller.cpp
@@ -20,11 +20,12 @@
  *
  */
 
-#include "ultima/ultima4/game/intro.h"
-#include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/game/menu.h"
+#include "ultima/ultima4/controllers/intro_controller.h"
+#include "ultima/ultima4/controllers/menu_controller.h"
 #include "ultima/ultima4/controllers/read_string_controller.h"
 #include "ultima/ultima4/controllers/read_choice_controller.h"
+#include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/game/menu.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/core/error.h"
diff --git a/engines/ultima/ultima4/game/intro.h b/engines/ultima/ultima4/controllers/intro_controller.h
similarity index 100%
rename from engines/ultima/ultima4/game/intro.h
rename to engines/ultima/ultima4/controllers/intro_controller.h
diff --git a/engines/ultima/ultima4/controllers/menu_controller.cpp b/engines/ultima/ultima4/controllers/menu_controller.cpp
new file mode 100644
index 0000000000..54b720cec3
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/menu_controller.cpp
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/menu_controller.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/game/menu.h"
+#include "ultima/ultima4/game/textview.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+MenuController::MenuController(Menu *menu, TextView *view) {
+	this->_menu = menu;
+	this->_view = view;
+}
+
+bool MenuController::keyPressed(int key) {
+	bool handled = true;
+	bool cursorOn = _view->getCursorEnabled();
+
+	if (cursorOn)
+		_view->disableCursor();
+
+	switch (key) {
+	case U4_UP:
+		_menu->prev();
+		break;
+	case U4_DOWN:
+		_menu->next();
+		break;
+	case U4_LEFT:
+	case U4_RIGHT:
+	case U4_ENTER: {
+		MenuEvent::Type action = MenuEvent::ACTIVATE;
+
+		if (key == U4_LEFT)
+			action = MenuEvent::DECREMENT;
+		else if (key == U4_RIGHT)
+			action = MenuEvent::INCREMENT;
+		_menu->activateItem(-1, action);
+	}
+	break;
+	default:
+		handled = _menu->activateItemByShortcut(key, MenuEvent::ACTIVATE);
+	}
+
+	_menu->show(_view);
+
+	if (cursorOn)
+		_view->enableCursor();
+	_view->update();
+
+	if (_menu->getClosed())
+		doneWaiting();
+
+	return handled;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/menu_controller.h b/engines/ultima/ultima4/controllers/menu_controller.h
new file mode 100644
index 0000000000..acfb74dd15
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/menu_controller.h
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_MENU_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_MENU_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+class Menu;
+class TextView;
+
+/**
+ * This class controls a menu.  The value field of WaitableController
+ * isn't used.
+ */
+class MenuController : public WaitableController<void *> {
+public:
+	MenuController(Menu *menu, TextView *view);
+	bool keyPressed(int key);
+
+protected:
+	Menu *_menu;
+	TextView *_view;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/controllers/reagents_menu_controller.cpp b/engines/ultima/ultima4/controllers/reagents_menu_controller.cpp
new file mode 100644
index 0000000000..613f07c4c2
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/reagents_menu_controller.cpp
@@ -0,0 +1,80 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima4/controllers/reagents_menu_controller.h"
+#include "ultima/ultima4/game/menu.h"
+#include "ultima/ultima4/game/spell.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+bool ReagentsMenuController::keyPressed(int key) {
+	switch (key) {
+	case 'a':
+	case 'b':
+	case 'c':
+	case 'd':
+	case 'e':
+	case 'f':
+	case 'g':
+	case 'h': {
+		/* select the corresponding reagent (if visible) */
+		Menu::MenuItemList::iterator mi = _menu->getById(key - 'a');
+		if ((*mi)->isVisible()) {
+			_menu->setCurrent(_menu->getById(key - 'a'));
+			keyPressed(U4_SPACE);
+		}
+	}
+	break;
+	case U4_LEFT:
+	case U4_RIGHT:
+	case U4_SPACE:
+		if (_menu->isVisible()) {
+			MenuItem *item = *_menu->getCurrent();
+
+			/* change whether or not it's selected */
+			item->setSelected(!item->isSelected());
+
+			if (item->isSelected())
+				_ingredients->addReagent((Reagent)item->getId());
+			else
+				_ingredients->removeReagent((Reagent)item->getId());
+		}
+		break;
+	case U4_ENTER:
+		eventHandler->setControllerDone();
+		break;
+
+	case U4_ESC:
+		_ingredients->revert();
+		eventHandler->setControllerDone();
+		break;
+
+	default:
+		return MenuController::keyPressed(key);
+	}
+
+	return true;
+}
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
diff --git a/engines/ultima/ultima4/controllers/reagents_menu_controller.h b/engines/ultima/ultima4/controllers/reagents_menu_controller.h
new file mode 100644
index 0000000000..af5d40127b
--- /dev/null
+++ b/engines/ultima/ultima4/controllers/reagents_menu_controller.h
@@ -0,0 +1,53 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ULTIMA4_CONTROLLERS_REAGENTS_MENU_CONTROLLER_H
+#define ULTIMA4_CONTROLLERS_REAGENTS_MENU_CONTROLLER_H
+
+#include "ultima/ultima4/controllers/menu_controller.h"
+
+namespace Ultima {
+namespace Ultima4 {
+
+class Ingredients;
+
+/**
+ * Controller for the reagents menu used when mixing spells.  Fills
+ * the passed in Ingredients with the selected reagents.
+ */
+class ReagentsMenuController : public MenuController {
+public:
+	ReagentsMenuController(Menu *menu, Ingredients *i, TextView *view) : MenuController(menu, view), _ingredients(i) { }
+
+	/**
+	 * Handles spell mixing for the Ultima V-style menu-system
+	 */
+	bool keyPressed(int key) override;
+
+private:
+	Ingredients *_ingredients;
+};
+
+} // End of namespace Ultima4
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index 5e26fc5dc7..8982fbae21 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -23,6 +23,7 @@
 #include "ultima/ultima4/core/debugger.h"
 #include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/controllers/alpha_action_controller.h"
+#include "ultima/ultima4/controllers/camp_controller.h"
 #include "ultima/ultima4/controllers/read_choice_controller.h"
 #include "ultima/ultima4/controllers/read_dir_controller.h"
 #include "ultima/ultima4/controllers/ztats_controller.h"
@@ -37,7 +38,6 @@
 #include "ultima/ultima4/game/weapon.h"
 #include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/map/camp.h"
 #include "ultima/ultima4/map/city.h"
 #include "ultima/ultima4/map/dungeonview.h"
 #include "ultima/ultima4/map/mapmgr.h"
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 7144b136ba..c8ec780f08 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -23,8 +23,10 @@
 #include "ultima/ultima4/core/debugger_actions.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
 #include "ultima/ultima4/controllers/read_choice_controller.h"
 #include "ultima/ultima4/controllers/read_int_controller.h"
+#include "ultima/ultima4/controllers/reagents_menu_controller.h"
 #include "ultima/ultima4/conversation/conversation.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/player.h"
@@ -33,7 +35,6 @@
 #include "ultima/ultima4/gfx/textcolor.h"
 #include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/map/city.h"
-#include "ultima/ultima4/map/combat.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/game/creature.cpp b/engines/ultima/ultima4/game/creature.cpp
index 631189b9ae..489142653f 100644
--- a/engines/ultima/ultima4/game/creature.cpp
+++ b/engines/ultima/ultima4/game/creature.cpp
@@ -21,20 +21,20 @@
  */
 
 #include "ultima/ultima4/game/creature.h"
-#include "ultima/ultima4/map/combat.h"
-#include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
+#include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/error.h"
-#include "ultima/ultima4/game/game.h"       /* required by specialAction and specialEffect functions */
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/gfx/textcolor.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/map.h"
-#include "ultima/ultima4/game/player.h"     /* required by specialAction and specialEffect functions */
-#include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/gfx/screen.h"     /* FIXME: remove dependence on this */
-#include "ultima/ultima4/core/settings.h"
-#include "ultima/ultima4/gfx/textcolor.h"  /* required to change the color of screen message text */
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/core/utils.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index c779640cd8..00839ee2f8 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -21,6 +21,9 @@
  */
 
 #include "ultima/ultima4/ultima4.h"
+#include "ultima/ultima4/controllers/camp_controller.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
+#include "ultima/ultima4/controllers/intro_controller.h"
 #include "ultima/ultima4/controllers/read_choice_controller.h"
 #include "ultima/ultima4/controllers/read_dir_controller.h"
 #include "ultima/ultima4/controllers/read_int_controller.h"
@@ -38,7 +41,6 @@
 #include "ultima/ultima4/game/armor.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/death.h"
-#include "ultima/ultima4/game/intro.h"
 #include "ultima/ultima4/game/item.h"
 #include "ultima/ultima4/game/menu.h"
 #include "ultima/ultima4/game/creature.h"
@@ -53,11 +55,9 @@
 #include "ultima/ultima4/game/weapon.h"
 #include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/map/camp.h"
 #include "ultima/ultima4/map/city.h"
 #include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/map/dungeon.h"
-#include "ultima/ultima4/map/combat.h"
 #include "ultima/ultima4/map/direction.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/mapmgr.h"
diff --git a/engines/ultima/ultima4/game/item.cpp b/engines/ultima/ultima4/game/item.cpp
index 15a5d3d7d9..91abf712a5 100644
--- a/engines/ultima/ultima4/game/item.cpp
+++ b/engines/ultima/ultima4/game/item.cpp
@@ -29,11 +29,11 @@
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/weapon.h"
 #include "ultima/ultima4/controllers/alpha_action_controller.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
 #include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/map/combat.h"
 #include "ultima/ultima4/map/dungeon.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/map.h"
diff --git a/engines/ultima/ultima4/game/menu.cpp b/engines/ultima/ultima4/game/menu.cpp
index 52524b4936..597647ec02 100644
--- a/engines/ultima/ultima4/game/menu.cpp
+++ b/engines/ultima/ultima4/game/menu.cpp
@@ -275,52 +275,5 @@ void Menu::setTitle(const Common::String &text, int x, int y) {
 	_titleY = y;
 }
 
-MenuController::MenuController(Menu *menu, TextView *view) {
-	this->_menu = menu;
-	this->_view = view;
-}
-
-bool MenuController::keyPressed(int key) {
-	bool handled = true;
-	bool cursorOn = _view->getCursorEnabled();
-
-	if (cursorOn)
-		_view->disableCursor();
-
-	switch (key) {
-	case U4_UP:
-		_menu->prev();
-		break;
-	case U4_DOWN:
-		_menu->next();
-		break;
-	case U4_LEFT:
-	case U4_RIGHT:
-	case U4_ENTER: {
-		MenuEvent::Type action = MenuEvent::ACTIVATE;
-
-		if (key == U4_LEFT)
-			action = MenuEvent::DECREMENT;
-		else if (key == U4_RIGHT)
-			action = MenuEvent::INCREMENT;
-		_menu->activateItem(-1, action);
-	}
-	break;
-	default:
-		handled = _menu->activateItemByShortcut(key, MenuEvent::ACTIVATE);
-	}
-
-	_menu->show(_view);
-
-	if (cursorOn)
-		_view->enableCursor();
-	_view->update();
-
-	if (_menu->getClosed())
-		doneWaiting();
-
-	return handled;
-}
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/game/menu.h b/engines/ultima/ultima4/game/menu.h
index 57ab9f1ed5..be7df79d0a 100644
--- a/engines/ultima/ultima4/game/menu.h
+++ b/engines/ultima/ultima4/game/menu.h
@@ -20,8 +20,8 @@
  *
  */
 
-#ifndef ULTIMA4_MENU_H
-#define ULTIMA4_MENU_H
+#ifndef ULTIMA4_GAME_MENU_H
+#define ULTIMA4_GAME_MENU_H
 
 #include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/game/menuitem.h"
@@ -192,20 +192,6 @@ private:
 	int _titleX, _titleY;
 };
 
-/**
- * This class controls a menu.  The value field of WaitableController
- * isn't used.
- */
-class MenuController : public WaitableController<void *> {
-public:
-	MenuController(Menu *menu, TextView *view);
-	bool keyPressed(int key);
-
-protected:
-	Menu *_menu;
-	TextView *_view;
-};
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima4/game/player.cpp b/engines/ultima/ultima4/game/player.cpp
index 34d82ca352..d1a17c69ba 100644
--- a/engines/ultima/ultima4/game/player.cpp
+++ b/engines/ultima/ultima4/game/player.cpp
@@ -21,19 +21,19 @@
  */
 
 #include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/game/armor.h"
-#include "ultima/ultima4/map/combat.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/names.h"
+#include "ultima/ultima4/game/weapon.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
+#include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/map/annotation.h"
 #include "ultima/ultima4/map/location.h"
 #include "ultima/ultima4/map/mapmgr.h"
-#include "ultima/ultima4/game/names.h"
 #include "ultima/ultima4/map/tilemap.h"
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/core/types.h"
-#include "ultima/ultima4/core/utils.h"
-#include "ultima/ultima4/game/weapon.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/game/script.cpp b/engines/ultima/ultima4/game/script.cpp
index 765bcdb055..f427655881 100644
--- a/engines/ultima/ultima4/game/script.cpp
+++ b/engines/ultima/ultima4/game/script.cpp
@@ -22,24 +22,24 @@
 
 #include "ultima/ultima4/game/script.h"
 #include "ultima/ultima4/game/armor.h"
-#include "ultima/ultima4/map/camp.h"
 #include "ultima/ultima4/game/context.h"
+#include "ultima/ultima4/controllers/inn_controller.h"
 #include "ultima/ultima4/conversation/conversation.h"
 #include "ultima/ultima4/core/error.h"
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/filesys/filesystem.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/filesys/u4file.h"
 #include "ultima/ultima4/game/game.h"
-#include "ultima/ultima4/sound/music.h"
 #include "ultima/ultima4/game/player.h"
-#include "ultima/ultima4/filesys/savegame.h"
-#include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/game/weapon.h"
 #include "ultima/ultima4/game/spell.h"
 #include "ultima/ultima4/game/stats.h"
+#include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/filesys/u4file.h"
-#include "ultima/ultima4/core/utils.h"
-#include "ultima/ultima4/game/weapon.h"
+#include "ultima/ultima4/sound/music.h"
 #include "ultima/shared/conf/xml_tree.h"
 
 namespace Ultima {
diff --git a/engines/ultima/ultima4/game/spell.cpp b/engines/ultima/ultima4/game/spell.cpp
index 968cd4c10d..226a71f1b7 100644
--- a/engines/ultima/ultima4/game/spell.cpp
+++ b/engines/ultima/ultima4/game/spell.cpp
@@ -20,20 +20,19 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/core/settings.h"
-#include "ultima/ultima4/core/debugger.h"
-#include "ultima/ultima4/core/utils.h"
-#include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/game/game.h"
 #include "ultima/ultima4/game/spell.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/creature.h"
 #include "ultima/ultima4/game/moongate.h"
 #include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/debugger.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/map/annotation.h"
-#include "ultima/ultima4/map/combat.h"
 #include "ultima/ultima4/map/direction.h"
 #include "ultima/ultima4/map/dungeon.h"
 #include "ultima/ultima4/map/location.h"
@@ -41,6 +40,7 @@
 #include "ultima/ultima4/map/mapmgr.h"
 #include "ultima/ultima4/map/tile.h"
 #include "ultima/ultima4/map/tileset.h"
+#include "ultima/ultima4/ultima4.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/game/stats.cpp b/engines/ultima/ultima4/game/stats.cpp
index 259d6f10d0..a107d1e6d3 100644
--- a/engines/ultima/ultima4/game/stats.cpp
+++ b/engines/ultima/ultima4/game/stats.cpp
@@ -409,54 +409,5 @@ void StatsArea::resetReagentsMenu() {
 	_reagentsMixMenu.reset(false);
 }
 
-bool ReagentsMenuController::keyPressed(int key) {
-	switch (key) {
-	case 'a':
-	case 'b':
-	case 'c':
-	case 'd':
-	case 'e':
-	case 'f':
-	case 'g':
-	case 'h': {
-		/* select the corresponding reagent (if visible) */
-		Menu::MenuItemList::iterator mi = _menu->getById(key - 'a');
-		if ((*mi)->isVisible()) {
-			_menu->setCurrent(_menu->getById(key - 'a'));
-			keyPressed(U4_SPACE);
-		}
-	}
-	break;
-	case U4_LEFT:
-	case U4_RIGHT:
-	case U4_SPACE:
-		if (_menu->isVisible()) {
-			MenuItem *item = *_menu->getCurrent();
-
-			/* change whether or not it's selected */
-			item->setSelected(!item->isSelected());
-
-			if (item->isSelected())
-				_ingredients->addReagent((Reagent)item->getId());
-			else
-				_ingredients->removeReagent((Reagent)item->getId());
-		}
-		break;
-	case U4_ENTER:
-		eventHandler->setControllerDone();
-		break;
-
-	case U4_ESC:
-		_ingredients->revert();
-		eventHandler->setControllerDone();
-		break;
-
-	default:
-		return MenuController::keyPressed(key);
-	}
-
-	return true;
-}
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/game/stats.h b/engines/ultima/ultima4/game/stats.h
index aa04c1951c..8dbabe3a4d 100644
--- a/engines/ultima/ultima4/game/stats.h
+++ b/engines/ultima/ultima4/game/stats.h
@@ -164,23 +164,6 @@ private:
 	Menu _reagentsMixMenu;
 };
 
-/**
- * Controller for the reagents menu used when mixing spells.  Fills
- * the passed in Ingredients with the selected reagents.
- */
-class ReagentsMenuController : public MenuController {
-public:
-	ReagentsMenuController(Menu *menu, Ingredients *i, TextView *view) : MenuController(menu, view), _ingredients(i) { }
-
-	/**
-	 * Handles spell mixing for the Ultima V-style menu-system
-	 */
-	bool keyPressed(int key) override;
-
-private:
-	Ingredients *_ingredients;
-};
-
 } // End of namespace Ultima4
 } // End of namespace Ultima
 
diff --git a/engines/ultima/ultima4/gfx/screen.cpp b/engines/ultima/ultima4/gfx/screen.cpp
index 0d3282b9ed..47ce06dc65 100644
--- a/engines/ultima/ultima4/gfx/screen.cpp
+++ b/engines/ultima/ultima4/gfx/screen.cpp
@@ -20,8 +20,7 @@
  *
  */
 
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/gfx/screen.h"
+#include "ultima/ultima4/controllers/intro_controller.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/error.h"
 #include "ultima/ultima4/core/utils.h"
@@ -29,11 +28,11 @@
 #include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/game/intro.h"
 #include "ultima/ultima4/game/names.h"
 #include "ultima/ultima4/game/object.h"
 #include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/gfx/imagemgr.h"
+#include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/gfx/textcolor.h"
 #include "ultima/ultima4/map/dungeonview.h"
 #include "ultima/ultima4/map/location.h"
@@ -41,6 +40,7 @@
 #include "ultima/ultima4/map/tileset.h"
 #include "ultima/ultima4/map/tileview.h"
 #include "ultima/ultima4/map/annotation.h"
+#include "ultima/ultima4/ultima4.h"
 #include "common/system.h"
 #include "engines/util.h"
 #include "graphics/cursorman.h"
diff --git a/engines/ultima/ultima4/gfx/screen_scummvm.cpp b/engines/ultima/ultima4/gfx/screen_scummvm.cpp
index d88637f00b..b96bb804b6 100644
--- a/engines/ultima/ultima4/gfx/screen_scummvm.cpp
+++ b/engines/ultima/ultima4/gfx/screen_scummvm.cpp
@@ -20,24 +20,24 @@
  *
  */
 
+#include "ultima/ultima4/controllers/intro_controller.h"
 #include "ultima/ultima4/core/config.h"
-#include "ultima/ultima4/game/context.h"
-#include "ultima/ultima4/map/dungeonview.h"
 #include "ultima/ultima4/core/error.h"
-#include "ultima/ultima4/game/intro.h"
-#include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/events/event.h"
+#include "ultima/ultima4/filesys/savegame.h"
+#include "ultima/ultima4/filesys/u4file.h"
+#include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/gfx/scale.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/gfx/image.h"
 #include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/map/tileanim.h"
 #include "ultima/ultima4/map/tileset.h"
-#include "ultima/ultima4/ultima4.h"
-#include "ultima/ultima4/filesys/u4file.h"
-#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/map/dungeonview.h"
 #include "ultima/shared/core/file.h"
+#include "ultima/ultima4/ultima4.h"
 #include "common/system.h"
 #include "engines/util.h"
 #include "graphics/cursorman.h"
diff --git a/engines/ultima/ultima4/map/dungeon.h b/engines/ultima/ultima4/map/dungeon.h
index 9c3625c633..4597008b1f 100644
--- a/engines/ultima/ultima4/map/dungeon.h
+++ b/engines/ultima/ultima4/map/dungeon.h
@@ -23,9 +23,9 @@
 #ifndef ULTIMA4_DUNGEON_H
 #define ULTIMA4_DUNGEON_H
 
-#include "ultima/ultima4/map/combat.h"
-#include "ultima/ultima4/map/map.h"
+#include "ultima/ultima4/controllers/combat_controller.h"
 #include "ultima/ultima4/core/types.h"
+#include "ultima/ultima4/map/map.h"
 
 namespace Ultima {
 namespace Ultima4 {
diff --git a/engines/ultima/ultima4/ultima4.cpp b/engines/ultima/ultima4/ultima4.cpp
index 1168dcb5f2..cbc3385cc4 100644
--- a/engines/ultima/ultima4/ultima4.cpp
+++ b/engines/ultima/ultima4/ultima4.cpp
@@ -21,24 +21,24 @@
  */
 
 #include "ultima/ultima4/ultima4.h"
+#include "ultima/ultima4/controllers/intro_controller.h"
 #include "ultima/ultima4/conversation/dialogueloader.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/debugger.h"
 #include "ultima/ultima4/core/error.h"
+#include "ultima/ultima4/core/settings.h"
+#include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/events/event.h"
 #include "ultima/ultima4/filesys/savegame.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/game.h"
-#include "ultima/ultima4/game/intro.h"
-#include "ultima/ultima4/sound/music.h"
 #include "ultima/ultima4/game/person.h"
 #include "ultima/ultima4/gfx/screen.h"
-#include "ultima/ultima4/core/settings.h"
-#include "ultima/ultima4/sound/sound.h"
-#include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/gfx/imageloader.h"
 #include "ultima/ultima4/gfx/imagemgr.h"
 #include "ultima/ultima4/map/tileset.h"
+#include "ultima/ultima4/sound/music.h"
+#include "ultima/ultima4/sound/sound.h"
 #include "common/debug.h"
 #include "common/system.h"
 




More information about the Scummvm-git-logs mailing list