[Scummvm-git-logs] scummvm master -> 0a0e650b61110f06434b59cd7735a203a66d3af1

dreammaster paulfgilbert at gmail.com
Tue Apr 14 03:57:43 UTC 2020


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

Summary:
6bc1ef85b7 ULTIMA4: Locate Position to keybinder
405db293c5 ULTIMA4: Mix Reagents to keybinder
c1f584751c ULTIMA4: New Order to keybinder
aa0e1be887 ULTIMA4: Adding some asserts before dereferences
7bb3369c2c ULTIMA4: Open door to keybinder
f2bd9f0f92 ULTIMA4: Cast spell to keybinder
22bc8e5438 ULTIMA4: Ready weapon to keybinder
77c038f181 ULTIMA4: Search to keybinder
5144447327 ULTIMA4: Talk to keybinder
aa04a30eb3 ULTIMA4: Use to keybinder
8916674257 ULTIMA4: Xit to keybinder
6f8a940d7f ULTIMA4: Yell to keybinder
0a0e650b61 ULTIMA4: Stats to keybinder


Commit: 6bc1ef85b75efc70fc4838a11ec0b995edbc9474
    https://github.com/scummvm/scummvm/commit/6bc1ef85b75efc70fc4838a11ec0b995edbc9474
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Locate Position to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 b81bbeaf92..37c6060d37 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -397,6 +397,22 @@ bool Debugger::cmdJimmy(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdLocate(int argc, const char **argv) {
+	// can't use sextant in dungeon or in combat
+	if (g_context->_location->_context & ~(CTX_DUNGEON | CTX_COMBAT)) {
+		if (g_ultima->_saveGame->_sextants >= 1)
+			print("Locate position\nwith sextant\n Latitude: %c'%c\"\nLongitude: %c'%c\"",
+				g_context->_location->_coords.y / 16 + 'A', g_context->_location->_coords.y % 16 + 'A',
+				g_context->_location->_coords.x / 16 + 'A', g_context->_location->_coords.x % 16 + 'A');
+		else
+			print("%cLocate position with what?%c", FG_GREY, FG_WHITE);
+	} else {
+		screenMessage("%cNot here!%c", FG_GREY, FG_WHITE);
+	}
+
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdPass(int argc, const char **argv) {
 	print("Pass");
 	return isDebuggerActive();
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index dc75632ac0..f5f3139a3c 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -128,6 +128,11 @@ private:
 	 */
 	bool cmdJimmy(int argc, const char **argv);
 
+	/**
+	 * Locate position
+	 */
+	bool cmdLocate(int argc, const char **argv);
+
 	/**
 	 * Pass turn
 	 */
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 4aebcd0506..2f191e4007 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -725,18 +725,6 @@ bool GameController::keyPressed(int key) {
 			}
 			break;
 
-		case 'l':
-			/* can't use sextant in dungeon or in combat */
-			if (g_context->_location->_context & ~(CTX_DUNGEON | CTX_COMBAT)) {
-				if (g_ultima->_saveGame->_sextants >= 1)
-					screenMessage("Locate position\nwith sextant\n Latitude: %c'%c\"\nLongitude: %c'%c\"\n",
-					              g_context->_location->_coords.y / 16 + 'A', g_context->_location->_coords.y % 16 + 'A',
-					              g_context->_location->_coords.x / 16 + 'A', g_context->_location->_coords.x % 16 + 'A');
-				else
-					screenMessage("%cLocate position with what?%c\n", FG_GREY, FG_WHITE);
-			} else screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
-			break;
-
 		case 'm':
 			mixReagents();
 #ifdef IOS
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 24cf884e9c..5d4454f889 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -52,6 +52,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_HOLE_UP, "HOLE-UP", "Hole Up", "hole", "h", nullptr },
 	{ KEYBIND_JIMMY, "JIMMY", "Jimmy", "jimmy", "j", nullptr },
 	{ KEYBIND_IGNITE, "IGNITE", "Ignite", "ignite", "i", nullptr },
+	{ KEYBIND_LOCATE, "LOCATE", "Locate Position", "locate", "l", nullptr },
 
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
 
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index 65a015d418..377d130c05 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -32,7 +32,7 @@ enum KeybindingAction {
 	KEYBIND_UP, KEYBIND_DOWN, KEYBIND_LEFT, KEYBIND_RIGHT,
 	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_ENTER,
 	KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP, KEYBIND_IGNITE,
-	KEYBIND_JIMMY,
+	KEYBIND_JIMMY, KEYBIND_LOCATE,
 	KEYBIND_PASS,
 
 	KEYBIND_NONE


Commit: 405db293c5b79b7391d4bbaa50fcb9bd2299cea1
    https://github.com/scummvm/scummvm/commit/405db293c5b79b7391d4bbaa50fcb9bd2299cea1
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Mix Reagents to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/core/debugger_actions.h
    engines/ultima/ultima4/game/game.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 37c6060d37..55da3de5ad 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -56,7 +56,8 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("hole", WRAP_METHOD(Debugger, cmdHoleUp));
 	registerCmd("ignite", WRAP_METHOD(Debugger, cmdIgnite));
 	registerCmd("jimmy", WRAP_METHOD(Debugger, cmdJimmy));
-
+	registerCmd("locate", WRAP_METHOD(Debugger, cmdLocate));
+	registerCmd("mix", WRAP_METHOD(Debugger, cmdMixReagents));
 	registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
@@ -407,9 +408,68 @@ bool Debugger::cmdLocate(int argc, const char **argv) {
 		else
 			print("%cLocate position with what?%c", FG_GREY, FG_WHITE);
 	} else {
-		screenMessage("%cNot here!%c", FG_GREY, FG_WHITE);
+		print("%cNot here!%c", FG_GREY, FG_WHITE);
+	}
+
+	return isDebuggerActive();
+}
+
+bool Debugger::cmdMixReagents(int argc, const char **argv) {
+	/*  uncomment this line to activate new spell mixing code */
+	//   return mixReagentsSuper();
+	bool done = false;
+
+	while (!done) {
+		print("Mix reagents");
+#ifdef IOS
+		U4IOS::beginMixSpellController();
+		return; // Just return, the dialog takes control from here.
+#endif
+
+		// Verify that there are reagents remaining in the inventory
+		bool found = false;
+		for (int i = 0; i < 8; i++) {
+			if (g_ultima->_saveGame->_reagents[i] > 0) {
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			printN("%cNone Left!%c", FG_GREY, FG_WHITE);
+			done = true;
+		} else {
+			printN("For Spell: ");
+			g_context->_stats->setView(STATS_MIXTURES);
+
+			int choice = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
+			if (choice == ' ' || choice == '\033' || choice == '\n' || choice == '\r')
+				break;
+
+			int spell = choice - 'a';
+			print("%s", spellGetName(spell));
+
+			// ensure the mixtures for the spell isn't already maxed out
+			if (g_ultima->_saveGame->_mixtures[spell] == 99) {
+				print("\n%cYou cannot mix any more of that spell!%c", FG_GREY, FG_WHITE);
+				break;
+			}
+
+			// Reset the reagent spell mix menu by removing
+			// the menu highlight from the current item, and
+			// hiding reagents that you don't have
+			g_context->_stats->resetReagentsMenu();
+
+			g_context->_stats->setView(MIX_REAGENTS);
+			if (settings._enhancements && settings._enhancementsOptions._u5spellMixing)
+				done = mixReagentsForSpellU5(spell);
+			else
+				done = mixReagentsForSpellU4(spell);
+		}
 	}
 
+	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
+	print("");
+
 	return isDebuggerActive();
 }
 
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index f5f3139a3c..f8f086c466 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -133,6 +133,11 @@ private:
 	 */
 	bool cmdLocate(int argc, const char **argv);
 
+	/**
+	 * Mix reagents
+	 */
+	bool cmdMixReagents(int argc, const char **argv);
+
 	/**
 	 * Pass turn
 	 */
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 2f6cd284a9..b9ad2a773b 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -21,9 +21,11 @@
  */
 
 #include "ultima/ultima4/core/debugger_actions.h"
+#include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/utils.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/player.h"
+#include "ultima/ultima4/game/stats.h"
 #include "ultima/ultima4/gfx/screen.h"
 #include "ultima/ultima4/gfx/textcolor.h"
 #include "ultima/ultima4/map/annotation.h"
@@ -212,5 +214,101 @@ bool DebuggerActions::jimmyAt(const Coords &coords) {
 	return true;
 }
 
+bool DebuggerActions::mixReagentsForSpellU4(int spell) {
+	Ingredients ingredients;
+
+	screenMessage("Reagent: ");
+
+	while (1) {
+		int choice = ReadChoiceController::get("abcdefgh\n\r \033");
+
+		// done selecting reagents? mix it up and prompt to mix
+		// another spell
+		if (choice == '\n' || choice == '\r' || choice == ' ') {
+			screenMessage("\n\nYou mix the Reagents, and...\n");
+
+			if (spellMix(spell, &ingredients))
+				screenMessage("Success!\n\n");
+			else
+				screenMessage("It Fizzles!\n\n");
+
+			return false;
+		}
+
+		// escape: put ingredients back and quit mixing
+		if (choice == '\033') {
+			ingredients.revert();
+			return true;
+		}
+
+		screenMessage("%c\n", toupper(choice));
+		if (!ingredients.addReagent((Reagent)(choice - 'a')))
+			screenMessage("%cNone Left!%c\n", FG_GREY, FG_WHITE);
+		screenMessage("Reagent: ");
+	}
+
+	return true;
+}
+
+bool DebuggerActions::mixReagentsForSpellU5(int spell) {
+	Ingredients ingredients;
+
+	screenDisableCursor();
+
+	g_context->_stats->getReagentsMenu()->reset(); // reset the menu, highlighting the first item
+	ReagentsMenuController getReagentsController(g_context->_stats->getReagentsMenu(), &ingredients, g_context->_stats->getMainArea());
+	eventHandler->pushController(&getReagentsController);
+	getReagentsController.waitFor();
+
+	g_context->_stats->getMainArea()->disableCursor();
+	screenEnableCursor();
+
+	printN("How many? ");
+
+	int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->col, TEXT_AREA_Y + g_context->_line);
+	gameSpellMixHowMany(spell, howmany, &ingredients);
+
+	return true;
+}
+
+bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingredients) {
+	int i;
+
+	/* entered 0 mixtures, don't mix anything! */
+	if (num == 0) {
+		print("\nNone mixed!");
+		ingredients->revert();
+		return false;
+	}
+
+	/* if they ask for more than will give them 99, only use what they need */
+	if (num > 99 - g_ultima->_saveGame->_mixtures[spell]) {
+		num = 99 - g_ultima->_saveGame->_mixtures[spell];
+		print("\n%cOnly need %d!%c", FG_GREY, num, FG_WHITE);
+	}
+
+	print("\nMixing %d...", num);
+
+	/* see if there's enough reagents to make number of mixtures requested */
+	if (!ingredients->checkMultiple(num)) {
+		print("\n%cYou don't have enough reagents to mix %d spells!%c", FG_GREY, num, FG_WHITE);
+		ingredients->revert();
+		return false;
+	}
+
+	print("\nYou mix the Reagents, and...");
+	if (spellMix(spell, ingredients)) {
+		print("Success!\n");
+		/* mix the extra spells */
+		ingredients->multiply(num);
+		for (i = 0; i < num - 1; i++)
+			spellMix(spell, ingredients);
+	} else {
+		print("It Fizzles!\n");
+	}
+
+	return true;
+}
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/core/debugger_actions.h b/engines/ultima/ultima4/core/debugger_actions.h
index 7df8ca83da..aa20d4b17e 100644
--- a/engines/ultima/ultima4/core/debugger_actions.h
+++ b/engines/ultima/ultima4/core/debugger_actions.h
@@ -24,6 +24,7 @@
 #define ULTIMA4_CORE_DEBUGGER_ACTIONS_H
 
 #include "ultima/ultima4/core/coords.h"
+#include "ultima/ultima4/game/spell.h"
 #include "ultima/ultima4/core/types.h"
 #include "ultima/shared/engine/debugger.h"
 
@@ -89,6 +90,18 @@ public:
 	 */
 	bool jimmyAt(const Coords &coords);
 
+	/**
+	 * Prompts for spell reagents to mix in the traditional Ultima IV
+	 * style.
+	 */
+	bool mixReagentsForSpellU4(int spell);
+
+	/**
+	 * Prompts for spell reagents to mix with an Ultima V-like menu.
+	 */
+	bool mixReagentsForSpellU5(int spell);
+
+	bool gameSpellMixHowMany(int spell, int num, Ingredients *ingredients);
 };
 
 } // End of namespace Ultima4
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 2f191e4007..94d2e72eb3 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -86,7 +86,6 @@ uint32 gameTimeSinceLastCommand(void);
 
 /* spell functions */
 void gameCastSpell(unsigned int spell, int caster, int param);
-bool gameSpellMixHowMany(int spell, int num, Ingredients *ingredients);
 
 void mixReagents();
 bool mixReagentsForSpellU4(int spell);
@@ -725,15 +724,6 @@ bool GameController::keyPressed(int key) {
 			}
 			break;
 
-		case 'm':
-			mixReagents();
-#ifdef IOS
-			// The iOS MixSpell dialog needs control of the event loop, so it is its
-			// job to complete the turn.
-			endTurn = false;
-#endif
-			break;
-
 		case 'n':
 			newOrder();
 			break;
@@ -1089,44 +1079,6 @@ Direction gameGetDirection() {
 	}
 }
 
-bool gameSpellMixHowMany(int spell, int num, Ingredients *ingredients) {
-	int i;
-
-	/* entered 0 mixtures, don't mix anything! */
-	if (num == 0) {
-		screenMessage("\nNone mixed!\n");
-		ingredients->revert();
-		return false;
-	}
-
-	/* if they ask for more than will give them 99, only use what they need */
-	if (num > 99 - g_ultima->_saveGame->_mixtures[spell]) {
-		num = 99 - g_ultima->_saveGame->_mixtures[spell];
-		screenMessage("\n%cOnly need %d!%c\n", FG_GREY, num, FG_WHITE);
-	}
-
-	screenMessage("\nMixing %d...\n", num);
-
-	/* see if there's enough reagents to make number of mixtures requested */
-	if (!ingredients->checkMultiple(num)) {
-		screenMessage("\n%cYou don't have enough reagents to mix %d spells!%c\n", FG_GREY, num, FG_WHITE);
-		ingredients->revert();
-		return false;
-	}
-
-	screenMessage("\nYou mix the Reagents, and...\n");
-	if (spellMix(spell, ingredients)) {
-		screenMessage("Success!\n\n");
-		/* mix the extra spells */
-		ingredients->multiply(num);
-		for (i = 0; i < num - 1; i++)
-			spellMix(spell, ingredients);
-	} else
-		screenMessage("It Fizzles!\n\n");
-
-	return true;
-}
-
 bool ZtatsController::keyPressed(int key) {
 	switch (key) {
 	case U4_UP:
@@ -1715,132 +1667,6 @@ void talk() {
 	screenMessage("Funny, no response!\n");
 }
 
-/**
- * Mixes reagents.  Prompts for a spell, then which reagents to
- * include in the mix.
- */
-void mixReagents() {
-
-	/*  uncomment this line to activate new spell mixing code */
-	//   return mixReagentsSuper();
-	bool done = false;
-
-	while (!done) {
-		screenMessage("Mix reagents\n");
-#ifdef IOS
-		U4IOS::beginMixSpellController();
-		return; // Just return, the dialog takes control from here.
-#endif
-
-		// Verify that there are reagents remaining in the inventory
-		bool found = false;
-		for (int i = 0; i < 8; i++) {
-			if (g_ultima->_saveGame->_reagents[i] > 0) {
-				found = true;
-				break;
-			}
-		}
-		if (!found) {
-			screenMessage("%cNone Left!%c", FG_GREY, FG_WHITE);
-			done = true;
-		} else {
-			screenMessage("For Spell: ");
-			g_context->_stats->setView(STATS_MIXTURES);
-
-			int choice = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
-			if (choice == ' ' || choice == '\033' || choice == '\n' || choice == '\r')
-				break;
-
-			int spell = choice - 'a';
-			screenMessage("%s\n", spellGetName(spell));
-
-			// ensure the mixtures for the spell isn't already maxed out
-			if (g_ultima->_saveGame->_mixtures[spell] == 99) {
-				screenMessage("\n%cYou cannot mix any more of that spell!%c\n", FG_GREY, FG_WHITE);
-				break;
-			}
-
-			// Reset the reagent spell mix menu by removing
-			// the menu highlight from the current item, and
-			// hiding reagents that you don't have
-			g_context->_stats->resetReagentsMenu();
-
-			g_context->_stats->setView(MIX_REAGENTS);
-			if (settings._enhancements && settings._enhancementsOptions._u5spellMixing)
-				done = mixReagentsForSpellU5(spell);
-			else
-				done = mixReagentsForSpellU4(spell);
-		}
-	}
-
-	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
-	screenMessage("\n\n");
-}
-
-/**
- * Prompts for spell reagents to mix in the traditional Ultima IV
- * style.
- */
-bool mixReagentsForSpellU4(int spell) {
-	Ingredients ingredients;
-
-	screenMessage("Reagent: ");
-
-	while (1) {
-		int choice = ReadChoiceController::get("abcdefgh\n\r \033");
-
-		// done selecting reagents? mix it up and prompt to mix
-		// another spell
-		if (choice == '\n' || choice == '\r' || choice == ' ') {
-			screenMessage("\n\nYou mix the Reagents, and...\n");
-
-			if (spellMix(spell, &ingredients))
-				screenMessage("Success!\n\n");
-			else
-				screenMessage("It Fizzles!\n\n");
-
-			return false;
-		}
-
-		// escape: put ingredients back and quit mixing
-		if (choice == '\033') {
-			ingredients.revert();
-			return true;
-		}
-
-		screenMessage("%c\n", toupper(choice));
-		if (!ingredients.addReagent((Reagent)(choice - 'a')))
-			screenMessage("%cNone Left!%c\n", FG_GREY, FG_WHITE);
-		screenMessage("Reagent: ");
-	}
-
-	return true;
-}
-
-/**
- * Prompts for spell reagents to mix with an Ultima V-like menu.
- */
-bool mixReagentsForSpellU5(int spell) {
-	Ingredients ingredients;
-
-	screenDisableCursor();
-
-	g_context->_stats->getReagentsMenu()->reset(); // reset the menu, highlighting the first item
-	ReagentsMenuController getReagentsController(g_context->_stats->getReagentsMenu(), &ingredients, g_context->_stats->getMainArea());
-	eventHandler->pushController(&getReagentsController);
-	getReagentsController.waitFor();
-
-	g_context->_stats->getMainArea()->disableCursor();
-	screenEnableCursor();
-
-	screenMessage("How many? ");
-
-	int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->col, TEXT_AREA_Y + g_context->_line);
-	gameSpellMixHowMany(spell, howmany, &ingredients);
-
-	return true;
-}
-
 /**
  * Exchanges the position of two players in the party.  Prompts the
  * user for the player numbers.
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 5d4454f889..5211f61a1e 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -53,6 +53,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_JIMMY, "JIMMY", "Jimmy", "jimmy", "j", nullptr },
 	{ KEYBIND_IGNITE, "IGNITE", "Ignite", "ignite", "i", nullptr },
 	{ KEYBIND_LOCATE, "LOCATE", "Locate Position", "locate", "l", nullptr },
+	{ KEYBIND_MIX, "MIX", "Mix Reagents", "mix", "m", nullptr },
 
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
 
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index 377d130c05..7dc1e989cc 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -32,7 +32,7 @@ enum KeybindingAction {
 	KEYBIND_UP, KEYBIND_DOWN, KEYBIND_LEFT, KEYBIND_RIGHT,
 	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_ENTER,
 	KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP, KEYBIND_IGNITE,
-	KEYBIND_JIMMY, KEYBIND_LOCATE,
+	KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
 	KEYBIND_PASS,
 
 	KEYBIND_NONE


Commit: c1f584751ce2e94df557f544207c9a370abae0a8
    https://github.com/scummvm/scummvm/commit/c1f584751ce2e94df557f544207c9a370abae0a8
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: New Order to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 55da3de5ad..78d084d81a 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -58,6 +58,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("jimmy", WRAP_METHOD(Debugger, cmdJimmy));
 	registerCmd("locate", WRAP_METHOD(Debugger, cmdLocate));
 	registerCmd("mix", WRAP_METHOD(Debugger, cmdMixReagents));
+	registerCmd("order", WRAP_METHOD(Debugger, cmdNewOrder));
 	registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
@@ -473,12 +474,45 @@ bool Debugger::cmdMixReagents(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdNewOrder(int argc, const char **argv) {
+	printN("New Order!\nExchange # ");
+
+	int player1 = gameGetPlayer(true, false);
+
+	if (player1 == -1)
+		return isDebuggerActive();
+
+	if (player1 == 0) {
+		print("%s, You must lead!", g_context->_party->member(0)->getName().c_str());
+		return isDebuggerActive();
+	}
+
+	printN("    with # ");
+
+	int player2 = gameGetPlayer(true, false);
+
+	if (player2 == -1)
+		return isDebuggerActive();
+
+	if (player2 == 0) {
+		print("%s, You must lead!", g_context->_party->member(0)->getName().c_str());
+		return isDebuggerActive();
+	}
+
+	if (player1 == player2) {
+		print("%cWhat?%c", FG_GREY, FG_WHITE);
+		return isDebuggerActive();
+	}
+
+	g_context->_party->swapPlayers(player1, player2);
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdPass(int argc, const char **argv) {
 	print("Pass");
 	return isDebuggerActive();
 }
 
-
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
 		print("3-D view %s\n", DungeonViewer.toggle3DDungeonView() ? "on" : "off");
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index f8f086c466..bb82e30265 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -138,6 +138,12 @@ private:
 	 */
 	bool cmdMixReagents(int argc, const char **argv);
 
+	/**
+	 * Exchanges the position of two players in the party.  Prompts the
+	 * user for the player numbers.
+	 */
+	bool cmdNewOrder(int argc, const char **argv);
+
 	/**
 	 * Pass turn
 	 */
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 94d2e72eb3..b4eb6be10c 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -87,11 +87,7 @@ uint32 gameTimeSinceLastCommand(void);
 /* spell functions */
 void gameCastSpell(unsigned int spell, int caster, int param);
 
-void mixReagents();
-bool mixReagentsForSpellU4(int spell);
-bool mixReagentsForSpellU5(int spell);
 void mixReagentsSuper();
-void newOrder();
 
 /* conversation functions */
 bool talkAt(const Coords &coords);
@@ -724,10 +720,6 @@ bool GameController::keyPressed(int key) {
 			}
 			break;
 
-		case 'n':
-			newOrder();
-			break;
-
 		case 'o':
 			opendoor();
 			break;
@@ -1667,43 +1659,6 @@ void talk() {
 	screenMessage("Funny, no response!\n");
 }
 
-/**
- * Exchanges the position of two players in the party.  Prompts the
- * user for the player numbers.
- */
-void newOrder() {
-	screenMessage("New Order!\nExchange # ");
-
-	int player1 = gameGetPlayer(true, false);
-
-	if (player1 == -1)
-		return;
-
-	if (player1 == 0) {
-		screenMessage("%s, You must lead!\n", g_context->_party->member(0)->getName().c_str());
-		return;
-	}
-
-	screenMessage("    with # ");
-
-	int player2 = gameGetPlayer(true, false);
-
-	if (player2 == -1)
-		return;
-
-	if (player2 == 0) {
-		screenMessage("%s, You must lead!\n", g_context->_party->member(0)->getName().c_str());
-		return;
-	}
-
-	if (player1 == player2) {
-		screenMessage("%cWhat?%c\n", FG_GREY, FG_WHITE);
-		return;
-	}
-
-	g_context->_party->swapPlayers(player1, player2);
-}
-
 /**
  * Peers at a city from A-P (Lycaeum telescope) and functions like a gem
  */
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 5211f61a1e..82a6455d1f 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -54,6 +54,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_IGNITE, "IGNITE", "Ignite", "ignite", "i", nullptr },
 	{ KEYBIND_LOCATE, "LOCATE", "Locate Position", "locate", "l", nullptr },
 	{ KEYBIND_MIX, "MIX", "Mix Reagents", "mix", "m", nullptr },
+	{ KEYBIND_NEW_ORDER, "NEW-ORDER", "New Order", "order", "n", nullptr },
 
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
 
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index 7dc1e989cc..0390a47780 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -32,7 +32,7 @@ enum KeybindingAction {
 	KEYBIND_UP, KEYBIND_DOWN, KEYBIND_LEFT, KEYBIND_RIGHT,
 	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_ENTER,
 	KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP, KEYBIND_IGNITE,
-	KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
+	KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX, KEYBIND_NEW_ORDER,
 	KEYBIND_PASS,
 
 	KEYBIND_NONE


Commit: aa0e1be88705d7d587d47338fadd3221b64c9d85
    https://github.com/scummvm/scummvm/commit/aa0e1be88705d7d587d47338fadd3221b64c9d85
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Adding some asserts before dereferences

Changed paths:
    engines/ultima/ultima4/map/camp.cpp
    engines/ultima/ultima4/map/combat.cpp
    engines/ultima/ultima4/map/mapmgr.cpp
    engines/ultima/ultima4/map/tileanim.cpp


diff --git a/engines/ultima/ultima4/map/camp.cpp b/engines/ultima/ultima4/map/camp.cpp
index 3982c4e293..3f937ed8e3 100644
--- a/engines/ultima/ultima4/map/camp.cpp
+++ b/engines/ultima/ultima4/map/camp.cpp
@@ -218,6 +218,7 @@ void InnController::maybeMeetIsaac() {
 	//  }
 	if ((g_context->_location->_map->_id == 11) && (xu4_random(4) == 0)) {
 		City *city = dynamic_cast<City *>(g_context->_location->_map);
+		assert(city);
 
 		if (city->_extraDialogues.size() == 1 &&
 		        city->_extraDialogues[0]->getName() == "Isaac") {
diff --git a/engines/ultima/ultima4/map/combat.cpp b/engines/ultima/ultima4/map/combat.cpp
index f51a824f1f..3cff030b65 100644
--- a/engines/ultima/ultima4/map/combat.cpp
+++ b/engines/ultima/ultima4/map/combat.cpp
@@ -225,6 +225,7 @@ void CombatController::initDungeonRoom(int room, Direction from) {
 			error("Invalid 'from' direction passed to initDungeonRoom()");
 		}
 
+		// TODO: Check for possible memory overrun below
 		for (i = 0; i < AREA_PLAYERS; i++) {
 			_map->player_start[i].x = *(party_x + (offset * AREA_PLAYERS * 2) + i);
 			_map->player_start[i].y = *(party_y + (offset * AREA_PLAYERS * 2) + i);
diff --git a/engines/ultima/ultima4/map/mapmgr.cpp b/engines/ultima/ultima4/map/mapmgr.cpp
index 3fb2d3c606..de3c8436a9 100644
--- a/engines/ultima/ultima4/map/mapmgr.cpp
+++ b/engines/ultima/ultima4/map/mapmgr.cpp
@@ -192,12 +192,15 @@ Map *MapMgr::initMapFromConf(const ConfigElement &mapConf) {
 	for (Std::vector<ConfigElement>::iterator i = children.begin(); i != children.end(); i++) {
 		if (i->getName() == "city") {
 			City *city = dynamic_cast<City *>(map);
+			assert(city);
 			initCityFromConf(*i, city);
 		} else if (i->getName() == "shrine") {
 			Shrine *shrine = dynamic_cast<Shrine *>(map);
+			assert(shrine);
 			initShrineFromConf(*i, shrine);
 		} else if (i->getName() == "dungeon") {
 			Dungeon *dungeon = dynamic_cast<Dungeon *>(map);
+			assert(dungeon);
 			initDungeonFromConf(*i, dungeon);
 		} else if (i->getName() == "portal")
 			map->_portals.push_back(initPortalFromConf(*i));
diff --git a/engines/ultima/ultima4/map/tileanim.cpp b/engines/ultima/ultima4/map/tileanim.cpp
index a0596554ed..99876bc5d6 100644
--- a/engines/ultima/ultima4/map/tileanim.cpp
+++ b/engines/ultima/ultima4/map/tileanim.cpp
@@ -189,6 +189,7 @@ TileAnimPixelColorTransform::TileAnimPixelColorTransform(int xp, int yp, int wid
 	this->y = yp;
 	this->w = width;
 	this->h = height;
+	_start = _end = nullptr;
 }
 
 bool TileAnimPixelColorTransform::drawsTile() const {


Commit: 7bb3369c2cf01ba63770c1207cca5127962b9dc3
    https://github.com/scummvm/scummvm/commit/7bb3369c2cf01ba63770c1207cca5127962b9dc3
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Open door to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/core/debugger_actions.h
    engines/ultima/ultima4/game/game.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 78d084d81a..e199cdf25b 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -58,8 +58,10 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("jimmy", WRAP_METHOD(Debugger, cmdJimmy));
 	registerCmd("locate", WRAP_METHOD(Debugger, cmdLocate));
 	registerCmd("mix", WRAP_METHOD(Debugger, cmdMixReagents));
+	registerCmd("open", WRAP_METHOD(Debugger, cmdOpenDoor));
 	registerCmd("order", WRAP_METHOD(Debugger, cmdNewOrder));
 	registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
+	registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -77,7 +79,6 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("mixtures", WRAP_METHOD(Debugger, cmdMixtures));
 	registerCmd("moon", WRAP_METHOD(Debugger, cmdMoon));
 	registerCmd("opacity", WRAP_METHOD(Debugger, cmdOpacity));
-	registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
 	registerCmd("reagents", WRAP_METHOD(Debugger, cmdReagents));
 	registerCmd("stats", WRAP_METHOD(Debugger, cmdStats));
 	registerCmd("summon", WRAP_METHOD(Debugger, cmdSummon));
@@ -424,7 +425,7 @@ bool Debugger::cmdMixReagents(int argc, const char **argv) {
 		print("Mix reagents");
 #ifdef IOS
 		U4IOS::beginMixSpellController();
-		return; // Just return, the dialog takes control from here.
+		return isDebuggerActive(); // Just return, the dialog takes control from here.
 #endif
 
 		// Verify that there are reagents remaining in the inventory
@@ -508,11 +509,50 @@ bool Debugger::cmdNewOrder(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdOpenDoor(int argc, const char **argv) {
+	///  XXX: Pressing "o" should close any open door.
+
+	printN("Open: ");
+
+	if (g_context->_party->isFlying()) {
+		print("%cNot Here!%c", FG_GREY, FG_WHITE);
+		return isDebuggerActive();
+	}
+
+	Direction dir = gameGetDirection();
+
+	if (dir == DIR_NONE)
+		return isDebuggerActive();
+
+	Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
+		1, 1, NULL, true);
+	for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
+		if (openAt(*i))
+			return isDebuggerActive();
+	}
+
+	print("%cNot Here!%c", FG_GREY, FG_WHITE);
+
+}
+
 bool Debugger::cmdPass(int argc, const char **argv) {
 	print("Pass");
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdPeer(int argc, const char **argv) {
+	if ((g_context->_location->_viewMode == VIEW_NORMAL) || (g_context->_location->_viewMode == VIEW_DUNGEON))
+		g_context->_location->_viewMode = VIEW_GEM;
+	else if (g_context->_location->_context == CTX_DUNGEON)
+		g_context->_location->_viewMode = VIEW_DUNGEON;
+	else
+		g_context->_location->_viewMode = VIEW_NORMAL;
+
+	print("Toggle view");
+	return isDebuggerActive();
+}
+
+
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
 		print("3-D view %s\n", DungeonViewer.toggle3DDungeonView() ? "on" : "off");
@@ -819,18 +859,6 @@ bool Debugger::cmdOpacity(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
-bool Debugger::cmdPeer(int argc, const char **argv) {
-	if ((g_context->_location->_viewMode == VIEW_NORMAL) || (g_context->_location->_viewMode == VIEW_DUNGEON))
-		g_context->_location->_viewMode = VIEW_GEM;
-	else if (g_context->_location->_context == CTX_DUNGEON)
-		g_context->_location->_viewMode = VIEW_DUNGEON;
-	else
-		g_context->_location->_viewMode = VIEW_NORMAL;
-
-	print("Toggle view");
-	return isDebuggerActive();
-}
-
 bool Debugger::cmdReagents(int argc, const char **argv) {
 	for (int i = 0; i < REAG_MAX; i++)
 		g_ultima->_saveGame->_reagents[i] = 99;
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index bb82e30265..7b00db7708 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -144,11 +144,21 @@ private:
 	 */
 	bool cmdNewOrder(int argc, const char **argv);
 
+	/**
+	 * Open door
+	 */
+	bool cmdOpenDoor(int argc, const char **argv);
+
 	/**
 	 * Pass turn
 	 */
 	bool cmdPass(int argc, const char **argv);
 
+	/**
+	 * Peer
+	 */
+	bool cmdPeer(int argc, const char **argv);
+
 private:
 	/**
 	 * Collision detection on/off
@@ -230,11 +240,6 @@ private:
 	 */
 	bool cmdOpacity(int argc, const char **argv);
 
-	/**
-	 * Peer
-	 */
-	bool cmdPeer(int argc, const char **argv);
-
 	/**
 	 * Give all the reagents
 	 */
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index b9ad2a773b..3f62cdf233 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -310,5 +310,26 @@ bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingre
 	return true;
 }
 
+bool DebuggerActions::openAt(const Coords &coords) {
+	const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS);
+
+	if (!tile->isDoor() &&
+		!tile->isLockedDoor())
+		return false;
+
+	if (tile->isLockedDoor()) {
+		screenMessage("%cCan't!%c\n", FG_GREY, FG_WHITE);
+		return true;
+	}
+
+	Tile *floor = g_context->_location->_map->_tileset->getByName("brick_floor");
+	ASSERT(floor, "no floor tile found in tileset");
+	g_context->_location->_map->_annotations->add(coords, floor->getId(), false, true)->setTTL(4);
+
+	screenMessage("\nOpened!\n");
+
+	return true;
+}
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/core/debugger_actions.h b/engines/ultima/ultima4/core/debugger_actions.h
index aa20d4b17e..449bab44e3 100644
--- a/engines/ultima/ultima4/core/debugger_actions.h
+++ b/engines/ultima/ultima4/core/debugger_actions.h
@@ -102,6 +102,12 @@ public:
 	bool mixReagentsForSpellU5(int spell);
 
 	bool gameSpellMixHowMany(int spell, int num, Ingredients *ingredients);
+
+	/**
+	 * Attempts to open a door at map coordinates x,y.  The door is
+	 * replaced by a temporary annotation of a floor tile for 4 turns.
+	 */
+	bool openAt(const Coords &coords);
 };
 
 } // End of namespace Ultima4
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index b4eb6be10c..844e1697ed 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -94,7 +94,6 @@ bool talkAt(const Coords &coords);
 void talkRunConversation(Conversation &conv, Person *talker, bool showPrompt);
 
 /* action functions */
-bool openAt(const Coords &coords);
 void wearArmor(int player = -1);
 void ztatsFor(int player = -1);
 
@@ -720,10 +719,6 @@ bool GameController::keyPressed(int key) {
 			}
 			break;
 
-		case 'o':
-			opendoor();
-			break;
-
 		case 'p':
 			peer();
 			break;
@@ -1430,7 +1425,7 @@ void GameController::avatarMoved(MoveEvent &event) {
 				tile = g_context->_location->_map->tileAt(new_coords, WITH_OBJECTS);
 
 				if (tile->getTileType()->isDoor()) {
-					openAt(new_coords);
+					g_debugger->openAt(new_coords);
 					event._result = (MoveResult)(MOVE_SUCCEEDED | MOVE_END_TURN);
 				} else if (tile->getTileType()->isLockedDoor()) {
 					g_debugger->jimmyAt(new_coords);
@@ -1525,56 +1520,6 @@ void GameController::avatarMovedInDungeon(MoveEvent &event) {
 	}
 }
 
-void opendoor() {
-	///  XXX: Pressing "o" should close any open door.
-
-	screenMessage("Open: ");
-
-	if (g_context->_party->isFlying()) {
-		screenMessage("%cNot Here!%c\n", FG_GREY, FG_WHITE);
-		return;
-	}
-
-	Direction dir = gameGetDirection();
-
-	if (dir == DIR_NONE)
-		return;
-
-	Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
-	                           1, 1, NULL, true);
-	for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
-		if (openAt(*i))
-			return;
-	}
-
-	screenMessage("%cNot Here!%c\n", FG_GREY, FG_WHITE);
-}
-
-/**
- * Attempts to open a door at map coordinates x,y.  The door is
- * replaced by a temporary annotation of a floor tile for 4 turns.
- */
-bool openAt(const Coords &coords) {
-	const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS);
-
-	if (!tile->isDoor() &&
-	        !tile->isLockedDoor())
-		return false;
-
-	if (tile->isLockedDoor()) {
-		screenMessage("%cCan't!%c\n", FG_GREY, FG_WHITE);
-		return true;
-	}
-
-	Tile *floor = g_context->_location->_map->_tileset->getByName("brick_floor");
-	ASSERT(floor, "no floor tile found in tileset");
-	g_context->_location->_map->_annotations->add(coords, floor->getId(), false, true)->setTTL(4);
-
-	screenMessage("\nOpened!\n");
-
-	return true;
-}
-
 /**
  * Readies a weapon for a player.  Prompts for the player and/or the
  * weapon if not provided.
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 82a6455d1f..96c93cd2be 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -55,8 +55,9 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_LOCATE, "LOCATE", "Locate Position", "locate", "l", nullptr },
 	{ KEYBIND_MIX, "MIX", "Mix Reagents", "mix", "m", nullptr },
 	{ KEYBIND_NEW_ORDER, "NEW-ORDER", "New Order", "order", "n", nullptr },
-
+	{ KEYBIND_OPEN_DOOR, "OPEN-DOOR", "Open Door", "open", "o", nullptr },
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
+	{ KEYBIND_PEER, "PEER", "Peer", "peer", "p", 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 0390a47780..e56cb6eed5 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -33,7 +33,7 @@ enum KeybindingAction {
 	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_ENTER,
 	KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP, KEYBIND_IGNITE,
 	KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX, KEYBIND_NEW_ORDER,
-	KEYBIND_PASS,
+	KEYBIND_OPEN_DOOR, KEYBIND_PASS, KEYBIND_PEER,
 
 	KEYBIND_NONE
 };


Commit: f2bd9f0f92170e8c7717fad5a7568b3c81b3c02b
    https://github.com/scummvm/scummvm/commit/f2bd9f0f92170e8c7717fad5a7568b3c81b3c02b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Cast spell to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/core/debugger_actions.h
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/game.h
    engines/ultima/ultima4/map/combat.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 e199cdf25b..ac780f161d 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -50,6 +50,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("attack", WRAP_METHOD(Debugger, cmdAttack));
 	registerCmd("board", WRAP_METHOD(Debugger, cmdBoard));
 	registerCmd("cast", WRAP_METHOD(Debugger, cmdCastSpell));
+	registerCmd("climb", WRAP_METHOD(Debugger, cmdClimb));
 	registerCmd("enter", WRAP_METHOD(Debugger, cmdEnter));
 	registerCmd("fire", WRAP_METHOD(Debugger, cmdFire));
 	registerCmd("get", WRAP_METHOD(Debugger, cmdGet));
@@ -62,6 +63,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("order", WRAP_METHOD(Debugger, cmdNewOrder));
 	registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
 	registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
+	registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -145,6 +147,14 @@ void Debugger::getChest(int player) {
 	cmdGet(2, argv);
 }
 
+void Debugger::castSpell(int player) {
+	Common::String param = Common::String::format("%d", player);
+	const char *argv[2] = { "cast", param.c_str() };
+
+	cmdCastSpell(2, argv);
+}
+
+
 
 
 bool Debugger::cmdMove(int argc, const char **argv) {
@@ -247,7 +257,161 @@ bool Debugger::cmdBoard(int argc, const char **argv) {
 }
 
 bool Debugger::cmdCastSpell(int argc, const char **argv) {
-	// TODO
+	int player = -1;
+	if (argc == 2)
+		player = strToInt(argv[1]);
+
+	if (player == -1) {
+		printN("Cast Spell!\nPlayer: ");
+		player = gameGetPlayer(false, true);
+	}
+	if (player == -1)
+		return isDebuggerActive();
+
+	// get the spell to cast
+	g_context->_stats->setView(STATS_MIXTURES);
+	printN("Spell: ");
+	// ### Put the iPad thing too.
+#ifdef IOS
+	U4IOS::IOSCastSpellHelper castSpellController;
+#endif
+	int spell = AlphaActionController::get('z', "Spell: ");
+	if (spell == -1)
+		return isDebuggerActive();
+
+	screenMessage("%s!\n", spellGetName(spell)); //Prints spell name at prompt
+
+	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
+
+	// if we can't really cast this spell, skip the extra parameters
+	if (spellCheckPrerequisites(spell, player) != CASTERR_NOERROR) {
+		gameCastSpell(spell, player, 0);
+		return isDebuggerActive();
+	}
+
+	// Get the final parameters for the spell
+	switch (spellGetParamType(spell)) {
+	case Spell::PARAM_NONE:
+		gameCastSpell(spell, player, 0);
+		break;
+
+	case Spell::PARAM_PHASE: {
+		screenMessage("To Phase: ");
+#ifdef IOS
+		U4IOS::IOSConversationChoiceHelper choiceController;
+		choiceController.fullSizeChoicePanel();
+		choiceController.updateGateSpellChoices();
+#endif
+		int choice = ReadChoiceController::get("12345678 \033\n");
+		if (choice < '1' || choice > '8')
+			print("None");
+		else {
+			print("");
+			gameCastSpell(spell, player, choice - '1');
+		}
+		break;
+	}
+
+	case Spell::PARAM_PLAYER: {
+		printN("Who: ");
+		int subject = gameGetPlayer(true, false);
+		if (subject != -1)
+			gameCastSpell(spell, player, subject);
+		break;
+	}
+
+	case Spell::PARAM_DIR:
+		if (g_context->_location->_context == CTX_DUNGEON)
+			gameCastSpell(spell, player, g_ultima->_saveGame->_orientation);
+		else {
+			printN("Dir: ");
+			Direction dir = gameGetDirection();
+			if (dir != DIR_NONE)
+				gameCastSpell(spell, player, (int)dir);
+		}
+		break;
+
+	case Spell::PARAM_TYPEDIR: {
+		printN("Energy type? ");
+#ifdef IOS
+		U4IOS::IOSConversationChoiceHelper choiceController;
+		choiceController.fullSizeChoicePanel();
+		choiceController.updateEnergyFieldSpellChoices();
+#endif
+		EnergyFieldType fieldType = ENERGYFIELD_NONE;
+		char key = ReadChoiceController::get("flps \033\n\r");
+		switch (key) {
+		case 'f':
+			fieldType = ENERGYFIELD_FIRE;
+			break;
+		case 'l':
+			fieldType = ENERGYFIELD_LIGHTNING;
+			break;
+		case 'p':
+			fieldType = ENERGYFIELD_POISON;
+			break;
+		case 's':
+			fieldType = ENERGYFIELD_SLEEP;
+			break;
+		default:
+			break;
+		}
+
+		if (fieldType != ENERGYFIELD_NONE) {
+			print("");
+
+			Direction dir;
+			if (g_context->_location->_context == CTX_DUNGEON)
+				dir = (Direction)g_ultima->_saveGame->_orientation;
+			else {
+				printN("Dir: ");
+				dir = gameGetDirection();
+			}
+
+			if (dir != DIR_NONE) {
+
+				/* Need to pack both dir and fieldType into param */
+				int param = fieldType << 4;
+				param |= (int)dir;
+
+				gameCastSpell(spell, player, param);
+			}
+		} else {
+			/* Invalid input here = spell failure */
+			print("Failed!");
+
+			/*
+			 * Confirmed both mixture loss and mp loss in this situation in the
+			 * original Ultima IV (at least, in the Amiga version.)
+			 */
+			 //c->saveGame->_mixtures[castSpell]--;
+			g_context->_party->member(player)->adjustMp(-spellGetRequiredMP(spell));
+		}
+		break;
+	}
+
+	case Spell::PARAM_FROMDIR: {
+		printN("From Dir: ");
+		Direction dir = gameGetDirection();
+		if (dir != DIR_NONE)
+			gameCastSpell(spell, player, (int)dir);
+		break;
+	}
+	}
+
+	return isDebuggerActive();
+}
+
+bool Debugger::cmdClimb(int argc, const char **argv) {
+	if (!usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_KLIMB)) {
+		if (g_context->_transportContext == TRANSPORT_BALLOON) {
+				g_ultima->_saveGame->_balloonState = 1;
+				g_context->_opacity = 0;
+			print("Klimb altitude");
+		} else
+			print("%cKlimb what?%c", FG_GREY, FG_WHITE);
+	}
+
 	return isDebuggerActive();
 }
 
@@ -532,7 +696,7 @@ bool Debugger::cmdOpenDoor(int argc, const char **argv) {
 	}
 
 	print("%cNot Here!%c", FG_GREY, FG_WHITE);
-
+	return isDebuggerActive();
 }
 
 bool Debugger::cmdPass(int argc, const char **argv) {
@@ -552,6 +716,18 @@ bool Debugger::cmdPeer(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdQuitAndSave(int argc, const char **argv) {
+	print("Quit & Save...\n%d moves", g_ultima->_saveGame->_moves);
+	if (g_context->_location->_context & CTX_CAN_SAVE_GAME) {
+		if (g_ultima->saveGameDialog())
+			print("Press Alt-x to quit");
+	} else {
+		print("%cNot here!%c", FG_GREY, FG_WHITE);
+	}
+
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index 7b00db7708..a3742ba2ae 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -98,6 +98,11 @@ private:
 	 */
 	bool cmdCastSpell(int argc, const char **argv);
 
+	/**
+	 * Climb
+	 */
+	bool cmdClimb(int argc, const char **argv);
+
 	/**
 	 * Enter location
 	 */
@@ -159,6 +164,11 @@ private:
 	 */
 	bool cmdPeer(int argc, const char **argv);
 
+	/**
+	 * Save and quit
+	 */
+	bool cmdQuitAndSave(int argc, const char **argv);
+
 private:
 	/**
 	 * Collision detection on/off
@@ -297,6 +307,11 @@ public:
 	 * user. Otherwise, a non-negative player number is expected
 	 */
 	void getChest(int player = -2);
+
+	/**
+	 * Cast a spell
+	 */
+	void castSpell(int player);
 };
 
 extern Debugger *g_debugger;
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 3f62cdf233..3009830076 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -331,5 +331,16 @@ bool DebuggerActions::openAt(const Coords &coords) {
 	return true;
 }
 
+void DebuggerActions::gameCastSpell(unsigned int spell, int caster, int param) {
+	SpellCastError spellError;
+	Common::String msg;
+
+	if (!spellCast(spell, caster, param, &spellError, true)) {
+		msg = spellGetErrorMessage(spell, spellError);
+		if (!msg.empty())
+			screenMessage("%s", msg.c_str());
+	}
+}
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/core/debugger_actions.h b/engines/ultima/ultima4/core/debugger_actions.h
index 449bab44e3..cf321e4ed4 100644
--- a/engines/ultima/ultima4/core/debugger_actions.h
+++ b/engines/ultima/ultima4/core/debugger_actions.h
@@ -108,6 +108,8 @@ public:
 	 * replaced by a temporary annotation of a floor tile for 4 turns.
 	 */
 	bool openAt(const Coords &coords);
+
+	void gameCastSpell(unsigned int spell, int caster, int param);
 };
 
 } // End of namespace Ultima4
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 844e1697ed..b7092b9d6a 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -84,9 +84,6 @@ void gameLostEighth(Virtue virtue);
 void gamePartyStarving(void);
 uint32 gameTimeSinceLastCommand(void);
 
-/* spell functions */
-void gameCastSpell(unsigned int spell, int caster, int param);
-
 void mixReagentsSuper();
 
 /* conversation functions */
@@ -530,17 +527,6 @@ void gameSpellEffect(int spell, int player, Sound sound) {
 	}
 }
 
-void gameCastSpell(unsigned int spell, int caster, int param) {
-	SpellCastError spellError;
-	Common::String msg;
-
-	if (!spellCast(spell, caster, param, &spellError, true)) {
-		msg = spellGetErrorMessage(spell, spellError);
-		if (!msg.empty())
-			screenMessage("%s", msg.c_str());
-	}
-}
-
 void GameController::keybinder(KeybindingAction action) {
 	MetaEngine::executeAction(action);
 }
@@ -681,10 +667,6 @@ bool GameController::keyPressed(int key) {
 			endTurn = false;
 			break;
 
-		case 'c':
-			castSpell();
-			break;
-
 		case 'd': {
 			// unload the map for the second level of Lord British's Castle. The reason
 			// why is that Lord British's farewell is dependent on the number of party members.
@@ -708,32 +690,6 @@ bool GameController::keyPressed(int key) {
 			break;
 		}
 
-		case 'k':
-			if (!usePortalAt(g_context->_location, g_context->_location->_coords, ACTION_KLIMB)) {
-				if (g_context->_transportContext == TRANSPORT_BALLOON) {
-					g_ultima->_saveGame->_balloonState = 1;
-					g_context->_opacity = 0;
-					screenMessage("Klimb altitude\n");
-				} else
-					screenMessage("%cKlimb what?%c\n", FG_GREY, FG_WHITE);
-			}
-			break;
-
-		case 'p':
-			peer();
-			break;
-
-		case 'q':
-			screenMessage("Quit & Save...\n%d moves\n", g_ultima->_saveGame->_moves);
-			if (g_context->_location->_context & CTX_CAN_SAVE_GAME) {
-				if (g_ultima->saveGameDialog())
-					screenMessage("Press Alt-x to quit\n");
-			} else {
-				screenMessage("%cNot here!%c\n", FG_GREY, FG_WHITE);
-			}
-
-			break;
-
 		case 'r':
 			readyWeapon();
 			break;
@@ -1101,140 +1057,6 @@ bool ZtatsController::keyPressed(int key) {
 	}
 }
 
-void castSpell(int player) {
-	if (player == -1) {
-		screenMessage("Cast Spell!\nPlayer: ");
-		player = gameGetPlayer(false, true);
-	}
-	if (player == -1)
-		return;
-
-	// get the spell to cast
-	g_context->_stats->setView(STATS_MIXTURES);
-	screenMessage("Spell: ");
-	// ### Put the iPad thing too.
-#ifdef IOS
-	U4IOS::IOSCastSpellHelper castSpellController;
-#endif
-	int spell = AlphaActionController::get('z', "Spell: ");
-	if (spell == -1)
-		return;
-
-	screenMessage("%s!\n", spellGetName(spell)); //Prints spell name at prompt
-
-	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
-
-	// if we can't really cast this spell, skip the extra parameters
-	if (spellCheckPrerequisites(spell, player) != CASTERR_NOERROR) {
-		gameCastSpell(spell, player, 0);
-		return;
-	}
-
-	// Get the final parameters for the spell
-	switch (spellGetParamType(spell)) {
-	case Spell::PARAM_NONE:
-		gameCastSpell(spell, player, 0);
-		break;
-	case Spell::PARAM_PHASE: {
-		screenMessage("To Phase: ");
-#ifdef IOS
-		U4IOS::IOSConversationChoiceHelper choiceController;
-		choiceController.fullSizeChoicePanel();
-		choiceController.updateGateSpellChoices();
-#endif
-		int choice = ReadChoiceController::get("12345678 \033\n");
-		if (choice < '1' || choice > '8')
-			screenMessage("None\n");
-		else {
-			screenMessage("\n");
-			gameCastSpell(spell, player, choice - '1');
-		}
-		break;
-	}
-	case Spell::PARAM_PLAYER: {
-		screenMessage("Who: ");
-		int subject = gameGetPlayer(true, false);
-		if (subject != -1)
-			gameCastSpell(spell, player, subject);
-		break;
-	}
-	case Spell::PARAM_DIR:
-		if (g_context->_location->_context == CTX_DUNGEON)
-			gameCastSpell(spell, player, g_ultima->_saveGame->_orientation);
-		else {
-			screenMessage("Dir: ");
-			Direction dir = gameGetDirection();
-			if (dir != DIR_NONE)
-				gameCastSpell(spell, player, (int) dir);
-		}
-		break;
-	case Spell::PARAM_TYPEDIR: {
-		screenMessage("Energy type? ");
-#ifdef IOS
-		U4IOS::IOSConversationChoiceHelper choiceController;
-		choiceController.fullSizeChoicePanel();
-		choiceController.updateEnergyFieldSpellChoices();
-#endif
-		EnergyFieldType fieldType = ENERGYFIELD_NONE;
-		char key = ReadChoiceController::get("flps \033\n\r");
-		switch (key) {
-		case 'f':
-			fieldType = ENERGYFIELD_FIRE;
-			break;
-		case 'l':
-			fieldType = ENERGYFIELD_LIGHTNING;
-			break;
-		case 'p':
-			fieldType = ENERGYFIELD_POISON;
-			break;
-		case 's':
-			fieldType = ENERGYFIELD_SLEEP;
-			break;
-		default:
-			break;
-		}
-
-		if (fieldType != ENERGYFIELD_NONE) {
-			screenMessage("\n");
-
-			Direction dir;
-			if (g_context->_location->_context == CTX_DUNGEON)
-				dir = (Direction)g_ultima->_saveGame->_orientation;
-			else {
-				screenMessage("Dir: ");
-				dir = gameGetDirection();
-			}
-
-			if (dir != DIR_NONE) {
-
-				/* Need to pack both dir and fieldType into param */
-				int param = fieldType << 4;
-				param |= (int) dir;
-
-				gameCastSpell(spell, player, param);
-			}
-		} else {
-			/* Invalid input here = spell failure */
-			screenMessage("Failed!\n");
-
-			/*
-			 * Confirmed both mixture loss and mp loss in this situation in the
-			 * original Ultima IV (at least, in the Amiga version.)
-			 */
-			//c->saveGame->_mixtures[castSpell]--;
-			g_context->_party->member(player)->adjustMp(-spellGetRequiredMP(spell));
-		}
-		break;
-	}
-	case Spell::PARAM_FROMDIR: {
-		screenMessage("From Dir: ");
-		Direction dir = gameGetDirection();
-		if (dir != DIR_NONE)
-			gameCastSpell(spell, player, (int) dir);
-		break;
-	}
-	}
-}
 
 bool fireAt(const Coords &coords, bool originAvatar) {
 	bool validObject = false;
diff --git a/engines/ultima/ultima4/game/game.h b/engines/ultima/ultima4/game/game.h
index cdb9dcebe6..572dca84cb 100644
--- a/engines/ultima/ultima4/game/game.h
+++ b/engines/ultima/ultima4/game/game.h
@@ -235,7 +235,6 @@ void gameSetViewMode(ViewMode newMode);
 void gameUpdateScreen(void);
 
 /* spell functions */
-void castSpell(int player = -1);
 void gameSpellEffect(int spell, int player, Sound sound);
 
 /* action functions */
diff --git a/engines/ultima/ultima4/map/combat.cpp b/engines/ultima/ultima4/map/combat.cpp
index 3cff030b65..56d09110a5 100644
--- a/engines/ultima/ultima4/map/combat.cpp
+++ b/engines/ultima/ultima4/map/combat.cpp
@@ -917,7 +917,7 @@ bool CombatController::keyPressed(int key) {
 
 	case 'c':
 		screenMessage("Cast Spell!\n");
-		castSpell(_focus);
+		g_debugger->castSpell(_focus);
 		break;
 
 #ifdef IOS
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 96c93cd2be..023d223e98 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -46,6 +46,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_ATTACK, "ATTACK", "Attack", "attack", "a", nullptr },
 	{ KEYBIND_BOARD, "BOARD", "Board", "board", "b", nullptr },
 	{ KEYBIND_CAST, "CAST", "Cast", "cast", "c", nullptr },
+	{ KEYBIND_CLIMB, "CLIMB", "Climb", "climb", "k", nullptr },
 	{ KEYBIND_ENTER, "ENTER", "Enter", "enter", "e", nullptr },
 	{ KEYBIND_FIRE, "FIRE", "Fire", "fire", "f", nullptr },
 	{ KEYBIND_GET, "GET", "Get Chest", "get", "g", nullptr },
@@ -58,8 +59,9 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_OPEN_DOOR, "OPEN-DOOR", "Open Door", "open", "o", nullptr },
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
 	{ KEYBIND_PEER, "PEER", "Peer", "peer", "p", nullptr },
+	{ KEYBIND_QUIT_SAVE, "QUIT-SAVE", "Quit and Save", "quitAndSave", "q", nullptr },
 
-	{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
+{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
 };
 
 static const KeybindingRecord CHEAT_KEYS[] = {
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index e56cb6eed5..d81700b633 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -30,10 +30,11 @@ namespace Ultima4 {
 
 enum KeybindingAction {
 	KEYBIND_UP, KEYBIND_DOWN, KEYBIND_LEFT, KEYBIND_RIGHT,
-	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_ENTER,
-	KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP, KEYBIND_IGNITE,
-	KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX, KEYBIND_NEW_ORDER,
-	KEYBIND_OPEN_DOOR, KEYBIND_PASS, KEYBIND_PEER,
+	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_CLIMB,
+	KEYBIND_ENTER, KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP,
+	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
+	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
+	KEYBIND_PEER, KEYBIND_QUIT_SAVE,
 
 	KEYBIND_NONE
 };


Commit: 22bc8e543803a970a91c712e3b5249e1536ab8ab
    https://github.com/scummvm/scummvm/commit/22bc8e543803a970a91c712e3b5249e1536ab8ab
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:33-07:00

Commit Message:
ULTIMA4: Ready weapon to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/game.h
    engines/ultima/ultima4/map/combat.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 ac780f161d..bae4716912 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -64,6 +64,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("pass", WRAP_METHOD(Debugger, cmdPass));
 	registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
 	registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
+	registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -154,7 +155,12 @@ void Debugger::castSpell(int player) {
 	cmdCastSpell(2, argv);
 }
 
+void Debugger::readyWeapon(int player) {
+	Common::String param = Common::String::format("%d", player);
+	const char *argv[2] = { "ready", param.c_str() };
 
+	cmdReadyWeapon(2, argv);
+}
 
 
 bool Debugger::cmdMove(int argc, const char **argv) {
@@ -728,6 +734,69 @@ bool Debugger::cmdQuitAndSave(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdReadyWeapon(int argc, const char **argv) {
+	int player = -1;
+	if (argc == 2)
+		player = strToInt(argv[1]);
+
+	// get the player if not provided
+	if (player == -1) {
+		printN("Ready a weapon for: ");
+		player = gameGetPlayer(true, false);
+		if (player == -1)
+			return isDebuggerActive();
+	}
+
+	// get the weapon to use
+	g_context->_stats->setView(STATS_WEAPONS);
+	printN("Weapon: ");
+	WeaponType weapon = (WeaponType)AlphaActionController::get(WEAP_MAX + 'a' - 1, "Weapon: ");
+	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
+	if (weapon == -1)
+		return isDebuggerActive();
+
+	PartyMember *p = g_context->_party->member(player);
+	const Weapon *w = Weapon::get(weapon);
+
+
+	if (!w) {
+		print("");
+		return isDebuggerActive();
+	}
+	switch (p->setWeapon(w)) {
+	case EQUIP_SUCCEEDED:
+		print("%s", w->getName().c_str());
+		break;
+	case EQUIP_NONE_LEFT:
+		print("%cNone left!%c", FG_GREY, FG_WHITE);
+		break;
+	case EQUIP_CLASS_RESTRICTED:
+	{
+		Common::String indef_article;
+
+		switch (tolower(w->getName()[0])) {
+		case 'a':
+		case 'e':
+		case 'i':
+		case 'o':
+		case 'u':
+		case 'y':
+			indef_article = "an";
+			break;
+		default:
+			indef_article = "a";
+			break;
+		}
+
+		print("\n%cA %s may NOT use %s %s%c", FG_GREY, getClassName(p->getClass()),
+			indef_article.c_str(), w->getName().c_str(), FG_WHITE);
+		break;
+	}
+	}
+
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index a3742ba2ae..9d1f724e6e 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -169,6 +169,12 @@ private:
 	 */
 	bool cmdQuitAndSave(int argc, const char **argv);
 
+	/**
+	 * Readies a weapon for a player.  Prompts for the player and/or the
+	 * weapon if not provided.
+	 */
+	bool cmdReadyWeapon(int argc, const char **argv);
+
 private:
 	/**
 	 * Collision detection on/off
@@ -312,6 +318,11 @@ public:
 	 * Cast a spell
 	 */
 	void castSpell(int player);
+
+	/**
+	 * Ready a weapon
+	 */
+	void readyWeapon(int player);
 };
 
 extern Debugger *g_debugger;
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index b7092b9d6a..807d467add 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -690,10 +690,6 @@ bool GameController::keyPressed(int key) {
 			break;
 		}
 
-		case 'r':
-			readyWeapon();
-			break;
-
 		case 's':
 			if (g_context->_location->_context == CTX_DUNGEON)
 				dungeonSearch();
@@ -1342,66 +1338,6 @@ void GameController::avatarMovedInDungeon(MoveEvent &event) {
 	}
 }
 
-/**
- * Readies a weapon for a player.  Prompts for the player and/or the
- * weapon if not provided.
- */
-void readyWeapon(int player) {
-
-	// get the player if not provided
-	if (player == -1) {
-		screenMessage("Ready a weapon for: ");
-		player = gameGetPlayer(true, false);
-		if (player == -1)
-			return;
-	}
-
-	// get the weapon to use
-	g_context->_stats->setView(STATS_WEAPONS);
-	screenMessage("Weapon: ");
-	WeaponType weapon = (WeaponType) AlphaActionController::get(WEAP_MAX + 'a' - 1, "Weapon: ");
-	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
-	if (weapon == -1)
-		return;
-
-	PartyMember *p = g_context->_party->member(player);
-	const Weapon *w = Weapon::get(weapon);
-
-
-	if (!w) {
-		screenMessage("\n");
-		return;
-	}
-	switch (p->setWeapon(w)) {
-	case EQUIP_SUCCEEDED:
-		screenMessage("%s\n", w->getName().c_str());
-		break;
-	case EQUIP_NONE_LEFT:
-		screenMessage("%cNone left!%c\n", FG_GREY, FG_WHITE);
-		break;
-	case EQUIP_CLASS_RESTRICTED: {
-		Common::String indef_article;
-
-		switch (tolower(w->getName()[0])) {
-		case 'a':
-		case 'e':
-		case 'i':
-		case 'o':
-		case 'u':
-		case 'y':
-			indef_article = "an";
-			break;
-		default:
-			indef_article = "a";
-			break;
-		}
-
-		screenMessage("\n%cA %s may NOT use %s %s%c\n", FG_GREY, getClassName(p->getClass()),
-		              indef_article.c_str(), w->getName().c_str(), FG_WHITE);
-		break;
-	}
-	}
-}
 
 void talk() {
 	screenMessage("Talk: ");
diff --git a/engines/ultima/ultima4/game/game.h b/engines/ultima/ultima4/game/game.h
index 572dca84cb..dbabb49229 100644
--- a/engines/ultima/ultima4/game/game.h
+++ b/engines/ultima/ultima4/game/game.h
@@ -238,13 +238,11 @@ void gameUpdateScreen(void);
 void gameSpellEffect(int spell, int player, Sound sound);
 
 /* action functions */
-void opendoor();
 bool gamePeerCity(int city, void *data);
 void peer(bool useGem = true);
 void talk();
 bool fireAt(const Coords &coords, bool originAvatar);
 Direction gameGetDirection();
-void readyWeapon(int player = -1);
 
 /* checking functions */
 void gameCheckHullIntegrity(void);
diff --git a/engines/ultima/ultima4/map/combat.cpp b/engines/ultima/ultima4/map/combat.cpp
index 56d09110a5..8707f7355c 100644
--- a/engines/ultima/ultima4/map/combat.cpp
+++ b/engines/ultima/ultima4/map/combat.cpp
@@ -939,7 +939,7 @@ bool CombatController::keyPressed(int key) {
 		break;
 
 	case 'r':
-		readyWeapon(getFocus());
+		g_debugger->readyWeapon(getFocus());
 		break;
 
 	case 't':
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 023d223e98..7ab6cd5ac1 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -60,8 +60,9 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_PASS, "PASS", "Pass", "pass", "SPACE", nullptr },
 	{ KEYBIND_PEER, "PEER", "Peer", "peer", "p", nullptr },
 	{ KEYBIND_QUIT_SAVE, "QUIT-SAVE", "Quit and Save", "quitAndSave", "q", nullptr },
+	{ KEYBIND_READY_WEAPON, "READY-WEAPON", "Ready Weapon", "ready", nullptr },
 
-{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
+	{ KEYBIND_NONE, nullptr, nullptr, nullptr, nullptr, nullptr }
 };
 
 static const KeybindingRecord CHEAT_KEYS[] = {
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index d81700b633..ffb9e62796 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -34,7 +34,7 @@ enum KeybindingAction {
 	KEYBIND_ENTER, KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP,
 	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
 	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
-	KEYBIND_PEER, KEYBIND_QUIT_SAVE,
+	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
 
 	KEYBIND_NONE
 };


Commit: 77c038f181330ee434e765dc7c0f36c81aeb7fae
    https://github.com/scummvm/scummvm/commit/77c038f181330ee434e765dc7c0f36c81aeb7fae
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Search to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 bae4716912..194b9e672f 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/game/context.h"
 #include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/item.h"
 #include "ultima/ultima4/game/moongate.h"
 #include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/game/portal.h"
@@ -65,6 +66,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("peer", WRAP_METHOD(Debugger, cmdPeer));
 	registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
 	registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
+	registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -797,6 +799,31 @@ bool Debugger::cmdReadyWeapon(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdSearch(int argc, const char **argv) {
+	if (g_context->_location->_context == CTX_DUNGEON) {
+		dungeonSearch();
+	} else if (g_context->_party->isFlying()) {
+		print("Searching...\n%cDrift only!%c", FG_GREY, FG_WHITE);
+	} else {
+		print("Searching...");
+
+		const ItemLocation *item = itemAtLocation(g_context->_location->_map, g_context->_location->_coords);
+		if (item) {
+			if (*item->_isItemInInventory != NULL && (*item->_isItemInInventory)(item->_data))
+				print("%cNothing Here!%c", FG_GREY, FG_WHITE);
+			else {
+				if (item->_name)
+					print("You find...\n%s!", item->_name);
+				(*item->_putItemInInventory)(item->_data);
+			}
+		} else {
+			print("%cNothing Here!%c", FG_GREY, FG_WHITE);
+		}
+	}
+
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index 9d1f724e6e..f3b11c61cb 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -175,6 +175,10 @@ private:
 	 */
 	bool cmdReadyWeapon(int argc, const char **argv);
 
+	/**
+	 * Search
+	 */
+	bool cmdSearch(int argc, const char **argv);
 private:
 	/**
 	 * Collision detection on/off
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 807d467add..2192534217 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -690,29 +690,6 @@ bool GameController::keyPressed(int key) {
 			break;
 		}
 
-		case 's':
-			if (g_context->_location->_context == CTX_DUNGEON)
-				dungeonSearch();
-			else if (g_context->_party->isFlying())
-				screenMessage("Searching...\n%cDrift only!%c\n", FG_GREY, FG_WHITE);
-			else {
-				screenMessage("Searching...\n");
-
-				const ItemLocation *item = itemAtLocation(g_context->_location->_map, g_context->_location->_coords);
-				if (item) {
-					if (*item->_isItemInInventory != NULL && (*item->_isItemInInventory)(item->_data))
-						screenMessage("%cNothing Here!%c\n", FG_GREY, FG_WHITE);
-					else {
-						if (item->_name)
-							screenMessage("You find...\n%s!\n", item->_name);
-						(*item->_putItemInInventory)(item->_data);
-					}
-				} else
-					screenMessage("%cNothing Here!%c\n", FG_GREY, FG_WHITE);
-			}
-
-			break;
-
 		case 't':
 			talk();
 			break;
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 7ab6cd5ac1..97211f4f8a 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -61,6 +61,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_PEER, "PEER", "Peer", "peer", "p", nullptr },
 	{ KEYBIND_QUIT_SAVE, "QUIT-SAVE", "Quit and Save", "quitAndSave", "q", nullptr },
 	{ KEYBIND_READY_WEAPON, "READY-WEAPON", "Ready Weapon", "ready", nullptr },
+	{ KEYBIND_SEARCH, "SEARCH", "Search", "search", "s", 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 ffb9e62796..621667fb21 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -35,6 +35,7 @@ enum KeybindingAction {
 	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
 	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
+	KEYBIND_SEARCH,
 
 	KEYBIND_NONE
 };


Commit: 51444473274e31eed9874957937795e5bc0fec69
    https://github.com/scummvm/scummvm/commit/51444473274e31eed9874957937795e5bc0fec69
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Talk to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/core/debugger_actions.h
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/game.h
    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 194b9e672f..88de547010 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -67,6 +67,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
 	registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
 	registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
+	registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -824,6 +825,30 @@ bool Debugger::cmdSearch(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdTalk(int argc, const char **argv) {
+	printN("Talk: ");
+
+	if (g_context->_party->isFlying()) {
+		print("%cDrift only!%c", FG_GREY, FG_WHITE);
+		return isDebuggerActive();
+	}
+
+	Direction dir = gameGetDirection();
+
+	if (dir == DIR_NONE)
+		return isDebuggerActive();
+
+	Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
+		1, 2, &Tile::canTalkOverTile, true);
+	for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
+		if (talkAt(*i))
+			return isDebuggerActive();
+	}
+
+	print("Funny, no response!");
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index f3b11c61cb..7c7c2d7533 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -179,6 +179,11 @@ private:
 	 * Search
 	 */
 	bool cmdSearch(int argc, const char **argv);
+
+	/**
+	 * Talk
+	 */
+	bool cmdTalk(int argc, const char **argv);
 private:
 	/**
 	 * Collision detection on/off
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 3009830076..67366392c2 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -23,12 +23,14 @@
 #include "ultima/ultima4/core/debugger_actions.h"
 #include "ultima/ultima4/core/config.h"
 #include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/conversation/conversation.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/player.h"
 #include "ultima/ultima4/game/stats.h"
 #include "ultima/ultima4/gfx/screen.h"
 #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 {
@@ -342,5 +344,176 @@ void DebuggerActions::gameCastSpell(unsigned int spell, int caster, int param) {
 	}
 }
 
+bool DebuggerActions::talkAt(const Coords &coords) {
+	extern int personIsVendor(const Person * person);
+	City *city;
+
+	/* can't have any conversations outside of town */
+	if (!isCity(g_context->_location->_map)) {
+		screenMessage("Funny, no response!\n");
+		return true;
+	}
+
+	city = dynamic_cast<City *>(g_context->_location->_map);
+	Person *talker = city->personAt(coords);
+
+	/* make sure we have someone we can talk with */
+	if (!talker || !talker->canConverse())
+		return false;
+
+	/* No response from alerted guards... does any monster both
+	   attack and talk besides Nate the Snake? */
+	if (talker->getMovementBehavior() == MOVEMENT_ATTACK_AVATAR &&
+		talker->getId() != PYTHON_ID)
+		return false;
+
+	/* if we're talking to Lord British and the avatar is dead, LB resurrects them! */
+	if (talker->getNpcType() == NPC_LORD_BRITISH &&
+		g_context->_party->member(0)->getStatus() == STAT_DEAD) {
+		screenMessage("%s, Thou shalt live again!\n", g_context->_party->member(0)->getName().c_str());
+
+		g_context->_party->member(0)->setStatus(STAT_GOOD);
+		g_context->_party->member(0)->heal(HT_FULLHEAL);
+		gameSpellEffect('r', -1, SOUND_LBHEAL);
+	}
+
+	Conversation conv;
+	conv._script->addProvider("party", g_context->_party);
+	conv._script->addProvider("context", g_context);
+
+	conv._state = Conversation::INTRO;
+	conv._reply = talker->getConversationText(&conv, "");
+	conv._playerInput.clear();
+	talkRunConversation(conv, talker, false);
+
+	return true;
+}
+
+void DebuggerActions::talkRunConversation(Conversation &conv, Person *talker, bool showPrompt) {
+	while (conv._state != Conversation::DONE) {
+		// TODO: instead of calculating linesused again, cache the
+		// result in person.cpp somewhere.
+		int linesused = linecount(conv._reply.front(), TEXT_AREA_W);
+		screenMessage("%s", conv._reply.front().c_str());
+		conv._reply.pop_front();
+
+		/* if all chunks haven't been shown, wait for a key and process next chunk*/
+		int size = conv._reply.size();
+		if (size > 0) {
+#ifdef IOS
+			U4IOS::IOSConversationChoiceHelper continueDialog;
+			continueDialog.updateChoices(" ");
+#endif
+			ReadChoiceController::get("");
+			continue;
+		}
+
+		/* otherwise, clear current reply and proceed based on conversation state */
+		conv._reply.clear();
+
+		/* they're attacking you! */
+		if (conv._state == Conversation::ATTACK) {
+			conv._state = Conversation::DONE;
+			talker->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
+		}
+
+		if (conv._state == Conversation::DONE)
+			break;
+
+		/* When Lord British heals the party */
+		else if (conv._state == Conversation::FULLHEAL) {
+			int i;
+
+			for (i = 0; i < g_context->_party->size(); i++) {
+				g_context->_party->member(i)->heal(HT_CURE);        // cure the party
+				g_context->_party->member(i)->heal(HT_FULLHEAL);    // heal the party
+			}
+			gameSpellEffect('r', -1, SOUND_MAGIC); // same spell effect as 'r'esurrect
+
+			conv._state = Conversation::TALK;
+		}
+		/* When Lord British checks and advances each party member's level */
+		else if (conv._state == Conversation::ADVANCELEVELS) {
+			gameLordBritishCheckLevels();
+			conv._state = Conversation::TALK;
+		}
+
+		if (showPrompt) {
+			Common::String prompt = talker->getPrompt(&conv);
+			if (!prompt.empty()) {
+				if (linesused + linecount(prompt, TEXT_AREA_W) > TEXT_AREA_H) {
+#ifdef IOS
+					U4IOS::IOSConversationChoiceHelper continueDialog;
+					continueDialog.updateChoices(" ");
+#endif
+					ReadChoiceController::get("");
+				}
+
+				screenMessage("%s", prompt.c_str());
+			}
+		}
+
+		int maxlen;
+		switch (conv.getInputRequired(&maxlen)) {
+		case Conversation::INPUT_STRING:
+		{
+			conv._playerInput = gameGetInput(maxlen);
+#ifdef IOS
+			screenMessage("%s", conv.playerInput.c_str()); // Since we put this in a different window, we need to show it again.
+#endif
+			conv._reply = talker->getConversationText(&conv, conv._playerInput.c_str());
+			conv._playerInput.clear();
+			showPrompt = true;
+			break;
+		}
+		case Conversation::INPUT_CHARACTER:
+		{
+			char message[2];
+#ifdef IOS
+			U4IOS::IOSConversationChoiceHelper yesNoHelper;
+			yesNoHelper.updateChoices("yn ");
+#endif
+			int choice = ReadChoiceController::get("");
+
+
+			message[0] = choice;
+			message[1] = '\0';
+
+			conv._reply = talker->getConversationText(&conv, message);
+			conv._playerInput.clear();
+
+			showPrompt = true;
+			break;
+		}
+
+		case Conversation::INPUT_NONE:
+			conv._state = Conversation::DONE;
+			break;
+		}
+	}
+	if (conv._reply.size() > 0)
+		screenMessage("%s", conv._reply.front().c_str());
+}
+
+void DebuggerActions::gameLordBritishCheckLevels() {
+	bool advanced = false;
+
+	for (int i = 0; i < g_context->_party->size(); i++) {
+		PartyMember *player = g_context->_party->member(i);
+		if (player->getRealLevel() <
+			player->getMaxLevel())
+
+			// add an extra space to separate messages
+			if (!advanced) {
+				screenMessage("\n");
+				advanced = true;
+			}
+
+		player->advanceLevel();
+	}
+
+	screenMessage("\nWhat would thou\nask of me?\n");
+}
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/core/debugger_actions.h b/engines/ultima/ultima4/core/debugger_actions.h
index cf321e4ed4..519775d8f4 100644
--- a/engines/ultima/ultima4/core/debugger_actions.h
+++ b/engines/ultima/ultima4/core/debugger_actions.h
@@ -37,6 +37,16 @@ namespace Ultima4 {
  * different actions players can take in the game
  */
 class DebuggerActions {
+private:
+	/**
+	 * Executes the current conversation until it is done.
+	 */
+	void talkRunConversation(Conversation &conv, Person *talker, bool showPrompt);
+
+	/**
+	 * Check the levels of each party member while talking to Lord British
+	 */
+	void gameLordBritishCheckLevels();
 protected:
 	/**
 	 * Returns true if the debugger is active
@@ -110,6 +120,13 @@ public:
 	bool openAt(const Coords &coords);
 
 	void gameCastSpell(unsigned int spell, int caster, int param);
+
+
+	/**
+	 * Begins a conversation with the NPC at map coordinates x,y.  If no
+	 * NPC is present at that point, zero is returned.
+	 */
+	bool talkAt(const Coords &coords);
 };
 
 } // End of namespace Ultima4
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 2192534217..6374ed0eb7 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -86,17 +86,10 @@ uint32 gameTimeSinceLastCommand(void);
 
 void mixReagentsSuper();
 
-/* conversation functions */
-bool talkAt(const Coords &coords);
-void talkRunConversation(Conversation &conv, Person *talker, bool showPrompt);
-
 /* action functions */
 void wearArmor(int player = -1);
 void ztatsFor(int player = -1);
 
-/* checking functions */
-void gameLordBritishCheckLevels(void);
-
 /* creature functions */
 void gameCreatureAttack(Creature *obj);
 
@@ -690,10 +683,6 @@ bool GameController::keyPressed(int key) {
 			break;
 		}
 
-		case 't':
-			talk();
-			break;
-
 		case 'u': {
 			screenMessage("Use which item:\n");
 			if (settings._enhancements) {
@@ -1316,29 +1305,6 @@ void GameController::avatarMovedInDungeon(MoveEvent &event) {
 }
 
 
-void talk() {
-	screenMessage("Talk: ");
-
-	if (g_context->_party->isFlying()) {
-		screenMessage("%cDrift only!%c\n", FG_GREY, FG_WHITE);
-		return;
-	}
-
-	Direction dir = gameGetDirection();
-
-	if (dir == DIR_NONE)
-		return;
-
-	Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, g_context->_location->_coords,
-	                           1, 2, &Tile::canTalkOverTile, true);
-	for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
-		if (talkAt(*i))
-			return;
-	}
-
-	screenMessage("Funny, no response!\n");
-}
-
 /**
  * Peers at a city from A-P (Lycaeum telescope) and functions like a gem
  */
@@ -1402,161 +1368,6 @@ void peer(bool useGem) {
 	g_game->_paused = false;
 }
 
-/**
- * Begins a conversation with the NPC at map coordinates x,y.  If no
- * NPC is present at that point, zero is returned.
- */
-bool talkAt(const Coords &coords) {
-	extern int personIsVendor(const Person * person);
-	City *city;
-
-	/* can't have any conversations outside of town */
-	if (!isCity(g_context->_location->_map)) {
-		screenMessage("Funny, no response!\n");
-		return true;
-	}
-
-	city = dynamic_cast<City *>(g_context->_location->_map);
-	Person *talker = city->personAt(coords);
-
-	/* make sure we have someone we can talk with */
-	if (!talker || !talker->canConverse())
-		return false;
-
-	/* No response from alerted guards... does any monster both
-	   attack and talk besides Nate the Snake? */
-	if (talker->getMovementBehavior() == MOVEMENT_ATTACK_AVATAR &&
-	        talker->getId() != PYTHON_ID)
-		return false;
-
-	/* if we're talking to Lord British and the avatar is dead, LB resurrects them! */
-	if (talker->getNpcType() == NPC_LORD_BRITISH &&
-	        g_context->_party->member(0)->getStatus() == STAT_DEAD) {
-		screenMessage("%s, Thou shalt live again!\n", g_context->_party->member(0)->getName().c_str());
-
-		g_context->_party->member(0)->setStatus(STAT_GOOD);
-		g_context->_party->member(0)->heal(HT_FULLHEAL);
-		gameSpellEffect('r', -1, SOUND_LBHEAL);
-	}
-
-	Conversation conv;
-	conv._script->addProvider("party", g_context->_party);
-	conv._script->addProvider("context", g_context);
-
-	conv._state = Conversation::INTRO;
-	conv._reply = talker->getConversationText(&conv, "");
-	conv._playerInput.clear();
-	talkRunConversation(conv, talker, false);
-
-	return true;
-}
-
-/**
- * Executes the current conversation until it is done.
- */
-void talkRunConversation(Conversation &conv, Person *talker, bool showPrompt) {
-	while (conv._state != Conversation::DONE) {
-		// TODO: instead of calculating linesused again, cache the
-		// result in person.cpp somewhere.
-		int linesused = linecount(conv._reply.front(), TEXT_AREA_W);
-		screenMessage("%s", conv._reply.front().c_str());
-		conv._reply.pop_front();
-
-		/* if all chunks haven't been shown, wait for a key and process next chunk*/
-		int size = conv._reply.size();
-		if (size > 0) {
-#ifdef IOS
-			U4IOS::IOSConversationChoiceHelper continueDialog;
-			continueDialog.updateChoices(" ");
-#endif
-			ReadChoiceController::get("");
-			continue;
-		}
-
-		/* otherwise, clear current reply and proceed based on conversation state */
-		conv._reply.clear();
-
-		/* they're attacking you! */
-		if (conv._state == Conversation::ATTACK) {
-			conv._state = Conversation::DONE;
-			talker->setMovementBehavior(MOVEMENT_ATTACK_AVATAR);
-		}
-
-		if (conv._state == Conversation::DONE)
-			break;
-
-		/* When Lord British heals the party */
-		else if (conv._state == Conversation::FULLHEAL) {
-			int i;
-
-			for (i = 0; i < g_context->_party->size(); i++) {
-				g_context->_party->member(i)->heal(HT_CURE);        // cure the party
-				g_context->_party->member(i)->heal(HT_FULLHEAL);    // heal the party
-			}
-			gameSpellEffect('r', -1, SOUND_MAGIC); // same spell effect as 'r'esurrect
-
-			conv._state = Conversation::TALK;
-		}
-		/* When Lord British checks and advances each party member's level */
-		else if (conv._state == Conversation::ADVANCELEVELS) {
-			gameLordBritishCheckLevels();
-			conv._state = Conversation::TALK;
-		}
-
-		if (showPrompt) {
-			Common::String prompt = talker->getPrompt(&conv);
-			if (!prompt.empty()) {
-				if (linesused + linecount(prompt, TEXT_AREA_W) > TEXT_AREA_H) {
-#ifdef IOS
-					U4IOS::IOSConversationChoiceHelper continueDialog;
-					continueDialog.updateChoices(" ");
-#endif
-					ReadChoiceController::get("");
-				}
-
-				screenMessage("%s", prompt.c_str());
-			}
-		}
-
-		int maxlen;
-		switch (conv.getInputRequired(&maxlen)) {
-		case Conversation::INPUT_STRING: {
-			conv._playerInput = gameGetInput(maxlen);
-#ifdef IOS
-			screenMessage("%s", conv.playerInput.c_str()); // Since we put this in a different window, we need to show it again.
-#endif
-			conv._reply = talker->getConversationText(&conv, conv._playerInput.c_str());
-			conv._playerInput.clear();
-			showPrompt = true;
-			break;
-		}
-		case Conversation::INPUT_CHARACTER: {
-			char message[2];
-#ifdef IOS
-			U4IOS::IOSConversationChoiceHelper yesNoHelper;
-			yesNoHelper.updateChoices("yn ");
-#endif
-			int choice = ReadChoiceController::get("");
-
-
-			message[0] = choice;
-			message[1] = '\0';
-
-			conv._reply = talker->getConversationText(&conv, message);
-			conv._playerInput.clear();
-
-			showPrompt = true;
-			break;
-		}
-
-		case Conversation::INPUT_NONE:
-			conv._state = Conversation::DONE;
-			break;
-		}
-	}
-	if (conv._reply.size() > 0)
-		screenMessage("%s", conv._reply.front().c_str());
-}
 
 /**
  * Changes a player's armor.  Prompts for the player and/or the armor
@@ -2063,29 +1874,6 @@ void GameController::checkBridgeTrolls() {
 	cc->begin();
 }
 
-/**
- * Check the levels of each party member while talking to Lord British
- */
-void gameLordBritishCheckLevels() {
-	bool advanced = false;
-
-	for (int i = 0; i < g_context->_party->size(); i++) {
-		PartyMember *player = g_context->_party->member(i);
-		if (player->getRealLevel() <
-		        player->getMaxLevel())
-
-			// add an extra space to separate messages
-			if (!advanced) {
-				screenMessage("\n");
-				advanced = true;
-			}
-
-		player->advanceLevel();
-	}
-
-	screenMessage("\nWhat would thou\nask of me?\n");
-}
-
 /**
  * Spawns a creature (m) just offscreen of the avatar.
  * If (m==NULL) then it finds its own creature to spawn and spawns it.
diff --git a/engines/ultima/ultima4/game/game.h b/engines/ultima/ultima4/game/game.h
index dbabb49229..7bef392a38 100644
--- a/engines/ultima/ultima4/game/game.h
+++ b/engines/ultima/ultima4/game/game.h
@@ -240,7 +240,6 @@ void gameSpellEffect(int spell, int player, Sound sound);
 /* action functions */
 bool gamePeerCity(int city, void *data);
 void peer(bool useGem = true);
-void talk();
 bool fireAt(const Coords &coords, bool originAvatar);
 Direction gameGetDirection();
 
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 97211f4f8a..205d806ddd 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -62,6 +62,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_QUIT_SAVE, "QUIT-SAVE", "Quit and Save", "quitAndSave", "q", nullptr },
 	{ KEYBIND_READY_WEAPON, "READY-WEAPON", "Ready Weapon", "ready", nullptr },
 	{ KEYBIND_SEARCH, "SEARCH", "Search", "search", "s", nullptr },
+	{ KEYBIND_TALK, "TALK", "Talk", "talk", "t", 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 621667fb21..8e0b4a7579 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -35,7 +35,7 @@ enum KeybindingAction {
 	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
 	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
-	KEYBIND_SEARCH,
+	KEYBIND_SEARCH, KEYBIND_TALK,
 
 	KEYBIND_NONE
 };


Commit: aa04a30eb38852fa24b9b8941f7968907c9c1d03
    https://github.com/scummvm/scummvm/commit/aa04a30eb38852fa24b9b8941f7968907c9c1d03
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Use to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 88de547010..81bc2fdf95 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -68,6 +68,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
 	registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
 	registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
+	registerCmd("use", WRAP_METHOD(Debugger, cmdUse));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -849,6 +850,20 @@ bool Debugger::cmdTalk(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdUse(int argc, const char **argv) {
+	print("Use which item:");
+
+	if (settings._enhancements) {
+		// a little xu4 enhancement: show items in inventory when prompted for an item to use
+		g_context->_stats->setView(STATS_ITEMS);
+	}
+#ifdef IOS
+	U4IOS::IOSConversationHelper::setIntroString("Use which item?");
+#endif
+	itemUse(gameGetInput().c_str());
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index 7c7c2d7533..e083120b72 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -184,6 +184,12 @@ private:
 	 * Talk
 	 */
 	bool cmdTalk(int argc, const char **argv);
+
+	/**
+	 * Use
+	 */
+	bool cmdUse(int argc, const char **argv);
+
 private:
 	/**
 	 * Collision detection on/off
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 6374ed0eb7..562ca98659 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -683,19 +683,6 @@ bool GameController::keyPressed(int key) {
 			break;
 		}
 
-		case 'u': {
-			screenMessage("Use which item:\n");
-			if (settings._enhancements) {
-				/* a little xu4 enhancement: show items in inventory when prompted for an item to use */
-				g_context->_stats->setView(STATS_ITEMS);
-			}
-#ifdef IOS
-			U4IOS::IOSConversationHelper::setIntroString("Use which item?");
-#endif
-			itemUse(gameGetInput().c_str());
-			break;
-		}
-
 		case 'v':
 			if (g_music->toggle())
 				screenMessage("Volume On!\n");
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 205d806ddd..3ce7b28f02 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -63,6 +63,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_READY_WEAPON, "READY-WEAPON", "Ready Weapon", "ready", nullptr },
 	{ KEYBIND_SEARCH, "SEARCH", "Search", "search", "s", nullptr },
 	{ KEYBIND_TALK, "TALK", "Talk", "talk", "t", nullptr },
+	{ KEYBIND_USE, "USE", "Use", "use", "u", 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 8e0b4a7579..b30d831d3b 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -35,7 +35,7 @@ enum KeybindingAction {
 	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
 	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
-	KEYBIND_SEARCH, KEYBIND_TALK,
+	KEYBIND_SEARCH, KEYBIND_TALK, KEYBIND_USE,
 
 	KEYBIND_NONE
 };


Commit: 8916674257c07446c12a82ef940a7b5cc641035b
    https://github.com/scummvm/scummvm/commit/8916674257c07446c12a82ef940a7b5cc641035b
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Xit to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 81bc2fdf95..868a07d51c 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -21,6 +21,8 @@
  */
 
 #include "ultima/ultima4/core/debugger.h"
+#include "ultima/ultima4/core/utils.h"
+#include "ultima/ultima4/game/armor.h"
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/game.h"
 #include "ultima/ultima4/game/item.h"
@@ -53,6 +55,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("cast", WRAP_METHOD(Debugger, cmdCastSpell));
 	registerCmd("climb", WRAP_METHOD(Debugger, cmdClimb));
 	registerCmd("enter", WRAP_METHOD(Debugger, cmdEnter));
+	registerCmd("exit", WRAP_METHOD(Debugger, cmdExit));
 	registerCmd("fire", WRAP_METHOD(Debugger, cmdFire));
 	registerCmd("get", WRAP_METHOD(Debugger, cmdGet));
 	registerCmd("hole", WRAP_METHOD(Debugger, cmdHoleUp));
@@ -69,6 +72,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
 	registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
 	registerCmd("use", WRAP_METHOD(Debugger, cmdUse));
+	registerCmd("wear", WRAP_METHOD(Debugger, cmdWearArmor));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -436,6 +440,25 @@ bool Debugger::cmdEnter(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdExit(int argc, const char **argv) {
+	if ((g_context->_transportContext != TRANSPORT_FOOT) && !g_context->_party->isFlying()) {
+		Object *obj = g_context->_location->_map->addObject(g_context->_party->getTransport(), g_context->_party->getTransport(), g_context->_location->_coords);
+		if (g_context->_transportContext == TRANSPORT_SHIP)
+			g_context->_lastShip = obj;
+
+		Tile *avatar = g_context->_location->_map->_tileset->getByName("avatar");
+		ASSERT(avatar, "no avatar tile found in tileset");
+
+		g_context->_party->setTransport(avatar->getId());
+		g_context->_horseSpeed = 0;
+		print("X-it");
+	} else {
+		print("%cX-it What?%c", FG_GREY, FG_WHITE);
+	}
+
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdFire(int argc, const char **argv) {
 	if (g_context->_transportContext != TRANSPORT_SHIP) {
 		print("%cFire What?%c", FG_GREY, FG_WHITE);
@@ -864,6 +887,48 @@ bool Debugger::cmdUse(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdWearArmor(int argc, const char **argv) {
+	int player = -1;
+	if (argc == 2)
+		player = strToInt(argv[1]);
+
+	// get the player if not provided
+	if (player == -1) {
+		printN("Wear Armour\nfor: ");
+		player = gameGetPlayer(true, false);
+		if (player == -1)
+			return isDebuggerActive();
+	}
+
+	g_context->_stats->setView(STATS_ARMOR);
+	printN("Armour: ");
+	int armor = AlphaActionController::get(ARMR_MAX + 'a' - 1, "Armour: ");
+	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
+	if (armor == -1)
+		return isDebuggerActive();
+
+	const Armor *a = Armor::get((ArmorType)armor);
+	PartyMember *p = g_context->_party->member(player);
+
+	if (!a) {
+		print("");
+		return isDebuggerActive();
+	}
+	switch (p->setArmor(a)) {
+	case EQUIP_SUCCEEDED:
+		print("%s", a->getName().c_str());
+		break;
+	case EQUIP_NONE_LEFT:
+		print("%cNone left!%c", FG_GREY, FG_WHITE);
+		break;
+	case EQUIP_CLASS_RESTRICTED:
+		print("\n%cA %s may NOT use %s%c", FG_GREY, getClassName(p->getClass()), a->getName().c_str(), FG_WHITE);
+		break;
+	}
+
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
@@ -979,17 +1044,6 @@ bool Debugger::cmdEquipment(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
-bool Debugger::cmdExit(int argc, const char **argv) {
-	if (!g_game->exitToParentMap()) {
-		print("Not Here");
-	} else {
-		g_music->play();
-		print("Exited");
-	}
-
-	return isDebuggerActive();
-}
-
 bool Debugger::cmdGate(int argc, const char **argv) {
 	int gateNum = (argc == 2) ? strToInt(argv[1]) : -1;
 
@@ -1115,6 +1169,17 @@ bool Debugger::cmdKarma(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdLeave(int argc, const char **argv) {
+	if (!g_game->exitToParentMap()) {
+		print("Not Here");
+	} else {
+		g_music->play();
+		print("Exited");
+	}
+
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdLocation(int argc, const char **argv) {
 	const MapCoords &pos = g_context->_location->_coords;
 
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index e083120b72..c3548d94f4 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -108,6 +108,11 @@ private:
 	 */
 	bool cmdEnter(int argc, const char **argv);
 
+	/**
+	 * Exit
+	 */
+	bool cmdExit(int argc, const char **argv);
+
 	/**
 	 * Fire
 	 */
@@ -190,6 +195,10 @@ private:
 	 */
 	bool cmdUse(int argc, const char **argv);
 
+	/**
+	 * Changes a player's armor
+	 */
+	bool cmdWearArmor(int argc, const char **argv);
 private:
 	/**
 	 * Collision detection on/off
@@ -221,11 +230,6 @@ private:
 	 */
 	bool cmdEquipment(int argc, const char **argv);
 
-	/**
-	 * Exit the current location
-	 */
-	bool cmdExit(int argc, const char **argv);
-
 	/**
 	 * Moongate teleportation
 	 */
@@ -251,6 +255,11 @@ private:
 	 */
 	bool cmdItems(int argc, const char **argv);
 
+	/**
+	 * Leave the current location
+	 */
+	bool cmdLeave(int argc, const char **argv);
+
 	/**
 	 * Displays the current location
 	 */
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 562ca98659..b09392de9b 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -691,25 +691,6 @@ bool GameController::keyPressed(int key) {
 			endTurn = false;
 			break;
 
-		case 'w':
-			wearArmor();
-			break;
-
-		case 'x':
-			if ((g_context->_transportContext != TRANSPORT_FOOT) && !g_context->_party->isFlying()) {
-				obj = g_context->_location->_map->addObject(g_context->_party->getTransport(), g_context->_party->getTransport(), g_context->_location->_coords);
-				if (g_context->_transportContext == TRANSPORT_SHIP)
-					g_context->_lastShip = obj;
-
-				Tile *avatar = g_context->_location->_map->_tileset->getByName("avatar");
-				ASSERT(avatar, "no avatar tile found in tileset");
-				g_context->_party->setTransport(avatar->getId());
-				g_context->_horseSpeed = 0;
-				screenMessage("X-it\n");
-			} else
-				screenMessage("%cX-it What?%c\n", FG_GREY, FG_WHITE);
-			break;
-
 		case 'y':
 			screenMessage("Yell ");
 			if (g_context->_transportContext == TRANSPORT_HORSE) {
@@ -1356,47 +1337,6 @@ void peer(bool useGem) {
 }
 
 
-/**
- * Changes a player's armor.  Prompts for the player and/or the armor
- * type if not provided.
- */
-void wearArmor(int player) {
-
-	// get the player if not provided
-	if (player == -1) {
-		screenMessage("Wear Armour\nfor: ");
-		player = gameGetPlayer(true, false);
-		if (player == -1)
-			return;
-	}
-
-	g_context->_stats->setView(STATS_ARMOR);
-	screenMessage("Armour: ");
-	int armor = AlphaActionController::get(ARMR_MAX + 'a' - 1, "Armour: ");
-	g_context->_stats->setView(STATS_PARTY_OVERVIEW);
-	if (armor == -1)
-		return;
-
-	const Armor *a = Armor::get((ArmorType)armor);
-	PartyMember *p = g_context->_party->member(player);
-
-	if (!a) {
-		screenMessage("\n");
-		return;
-	}
-	switch (p->setArmor(a)) {
-	case EQUIP_SUCCEEDED:
-		screenMessage("%s\n", a->getName().c_str());
-		break;
-	case EQUIP_NONE_LEFT:
-		screenMessage("%cNone left!%c\n", FG_GREY, FG_WHITE);
-		break;
-	case EQUIP_CLASS_RESTRICTED:
-		screenMessage("\n%cA %s may NOT use %s%c\n", FG_GREY, getClassName(p->getClass()), a->getName().c_str(), FG_WHITE);
-		break;
-	}
-}
-
 /**
  * Called when the player selects a party member for ztats
  */
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 3ce7b28f02..feda8def62 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -48,6 +48,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_CAST, "CAST", "Cast", "cast", "c", nullptr },
 	{ KEYBIND_CLIMB, "CLIMB", "Climb", "climb", "k", nullptr },
 	{ KEYBIND_ENTER, "ENTER", "Enter", "enter", "e", nullptr },
+	{ KEYBIND_EXIT, "EXIT", "Exit", "exit", "x", nullptr },
 	{ KEYBIND_FIRE, "FIRE", "Fire", "fire", "f", nullptr },
 	{ KEYBIND_GET, "GET", "Get Chest", "get", "g", nullptr },
 	{ KEYBIND_HOLE_UP, "HOLE-UP", "Hole Up", "hole", "h", nullptr },
@@ -64,6 +65,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_SEARCH, "SEARCH", "Search", "search", "s", nullptr },
 	{ KEYBIND_TALK, "TALK", "Talk", "talk", "t", nullptr },
 	{ KEYBIND_USE, "USE", "Use", "use", "u", nullptr },
+	{ KEYBIND_WEAR, "WEAR", "Wear Armor", "wear", "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 b30d831d3b..ecb2db6845 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -31,11 +31,11 @@ namespace Ultima4 {
 enum KeybindingAction {
 	KEYBIND_UP, KEYBIND_DOWN, KEYBIND_LEFT, KEYBIND_RIGHT,
 	KEYBIND_ATTACK, KEYBIND_BOARD, KEYBIND_CAST, KEYBIND_CLIMB,
-	KEYBIND_ENTER, KEYBIND_FIRE, KEYBIND_GET, KEYBIND_HOLE_UP,
-	KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE, KEYBIND_MIX,
-	KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
+	KEYBIND_ENTER, KEYBIND_EXIT, KEYBIND_FIRE, KEYBIND_GET,
+	KEYBIND_HOLE_UP, KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE,
+	KEYBIND_MIX, KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
-	KEYBIND_SEARCH, KEYBIND_TALK, KEYBIND_USE,
+	KEYBIND_SEARCH, KEYBIND_TALK, KEYBIND_USE, KEYBIND_WEAR,
 
 	KEYBIND_NONE
 };


Commit: 6f8a940d7fb0ed20a469f15565000c429a15edd5
    https://github.com/scummvm/scummvm/commit/6f8a940d7fb0ed20a469f15565000c429a15edd5
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Yell to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 868a07d51c..d52cbf615e 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -73,6 +73,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
 	registerCmd("use", WRAP_METHOD(Debugger, cmdUse));
 	registerCmd("wear", WRAP_METHOD(Debugger, cmdWearArmor));
+	registerCmd("yell", WRAP_METHOD(Debugger, cmdYell));
 
 	registerCmd("3d", WRAP_METHOD(Debugger, cmd3d));
 	registerCmd("collisions", WRAP_METHOD(Debugger, cmdCollisions));
@@ -929,6 +930,23 @@ bool Debugger::cmdWearArmor(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdYell(int argc, const char **argv) {
+	printN("Yell ");
+	if (g_context->_transportContext == TRANSPORT_HORSE) {
+		if (g_context->_horseSpeed == 0) {
+			print("Giddyup!");
+			g_context->_horseSpeed = 1;
+		} else {
+			print("Whoa!");
+			g_context->_horseSpeed = 0;
+		}
+	} else {
+		print("%cWhat?%c", FG_GREY, FG_WHITE);
+	}
+
+	return isDebuggerActive();
+}
+
 
 bool Debugger::cmd3d(int argc, const char **argv) {
 	if (g_context->_location->_context == CTX_DUNGEON) {
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index c3548d94f4..92ab07f4f7 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -199,6 +199,11 @@ private:
 	 * Changes a player's armor
 	 */
 	bool cmdWearArmor(int argc, const char **argv);
+
+	/**
+	 * Yell
+	 */
+	bool cmdYell(int argc, const char **argv);
 private:
 	/**
 	 * Collision detection on/off
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index b09392de9b..a2f755e8a1 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -691,20 +691,6 @@ bool GameController::keyPressed(int key) {
 			endTurn = false;
 			break;
 
-		case 'y':
-			screenMessage("Yell ");
-			if (g_context->_transportContext == TRANSPORT_HORSE) {
-				if (g_context->_horseSpeed == 0) {
-					screenMessage("Giddyup!\n");
-					g_context->_horseSpeed = 1;
-				} else {
-					screenMessage("Whoa!\n");
-					g_context->_horseSpeed = 0;
-				}
-			} else
-				screenMessage("%cWhat?%c\n", FG_GREY, FG_WHITE);
-			break;
-
 		case 'z':
 			ztatsFor();
 			break;
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index feda8def62..70beac3c72 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -66,6 +66,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_TALK, "TALK", "Talk", "talk", "t", nullptr },
 	{ KEYBIND_USE, "USE", "Use", "use", "u", nullptr },
 	{ KEYBIND_WEAR, "WEAR", "Wear Armor", "wear", "w", nullptr },
+	{ KEYBIND_YELL, "YELL", "Yell", "yell", "y", 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 ecb2db6845..7942454fcc 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -36,6 +36,7 @@ enum KeybindingAction {
 	KEYBIND_MIX, KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
 	KEYBIND_SEARCH, KEYBIND_TALK, KEYBIND_USE, KEYBIND_WEAR,
+	KEYBIND_YELL,
 
 	KEYBIND_NONE
 };


Commit: 0a0e650b61110f06434b59cd7735a203a66d3af1
    https://github.com/scummvm/scummvm/commit/0a0e650b61110f06434b59cd7735a203a66d3af1
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-13T20:56:34-07:00

Commit Message:
ULTIMA4: Stats to keybinder

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger.h
    engines/ultima/ultima4/game/game.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 d52cbf615e..12263bdf00 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -70,6 +70,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("quitAndSave", WRAP_METHOD(Debugger, cmdQuitAndSave));
 	registerCmd("ready", WRAP_METHOD(Debugger, cmdReadyWeapon));
 	registerCmd("search", WRAP_METHOD(Debugger, cmdSearch));
+	registerCmd("stats", WRAP_METHOD(Debugger, cmdStats));
 	registerCmd("talk", WRAP_METHOD(Debugger, cmdTalk));
 	registerCmd("use", WRAP_METHOD(Debugger, cmdUse));
 	registerCmd("wear", WRAP_METHOD(Debugger, cmdWearArmor));
@@ -82,6 +83,7 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("dungeon", WRAP_METHOD(Debugger, cmdDungeon));
 	registerCmd("equipment", WRAP_METHOD(Debugger, cmdEquipment));
 	registerCmd("exit", WRAP_METHOD(Debugger, cmdExit));
+	registerCmd("fullstats", WRAP_METHOD(Debugger, cmdFullStats));
 	registerCmd("gate", WRAP_METHOD(Debugger, cmdGate));
 	registerCmd("goto", WRAP_METHOD(Debugger, cmdGoto));
 	registerCmd("help", WRAP_METHOD(Debugger, cmdHelp));
@@ -92,7 +94,6 @@ Debugger::Debugger() : Shared::Debugger() {
 	registerCmd("moon", WRAP_METHOD(Debugger, cmdMoon));
 	registerCmd("opacity", WRAP_METHOD(Debugger, cmdOpacity));
 	registerCmd("reagents", WRAP_METHOD(Debugger, cmdReagents));
-	registerCmd("stats", WRAP_METHOD(Debugger, cmdStats));
 	registerCmd("summon", WRAP_METHOD(Debugger, cmdSummon));
 	registerCmd("torch", WRAP_METHOD(Debugger, cmdTorch));
 	registerCmd("transport", WRAP_METHOD(Debugger, cmdTransport));
@@ -850,6 +851,35 @@ bool Debugger::cmdSearch(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
+bool Debugger::cmdStats(int argc, const char **argv) {
+	int player = -1;
+	if (argc == 2)
+		player = strToInt(argv[1]);
+
+	// get the player if not provided
+	if (player == -1) {
+		printN("Ztats for: ");
+		player = gameGetPlayer(true, false);
+		if (player == -1)
+			return isDebuggerActive();
+	}
+
+	// Reset the reagent spell mix menu by removing
+	// the menu highlight from the current item, and
+	// hiding reagents that you don't have
+	g_context->_stats->resetReagentsMenu();
+
+	g_context->_stats->setView(StatsView(STATS_CHAR1 + player));
+#ifdef IOS
+	U4IOS::IOSHideActionKeysHelper hideExtraControls;
+#endif
+	ZtatsController ctrl;
+	eventHandler->pushController(&ctrl);
+	ctrl.waitFor();
+
+	return isDebuggerActive();
+}
+
 bool Debugger::cmdTalk(int argc, const char **argv) {
 	printN("Talk: ");
 
@@ -1262,7 +1292,7 @@ bool Debugger::cmdReagents(int argc, const char **argv) {
 	return isDebuggerActive();
 }
 
-bool Debugger::cmdStats(int argc, const char **argv) {
+bool Debugger::cmdFullStats(int argc, const char **argv) {
 	for (int i = 0; i < g_ultima->_saveGame->_members; i++) {
 		g_ultima->_saveGame->_players[i]._str = 50;
 		g_ultima->_saveGame->_players[i]._dex = 50;
diff --git a/engines/ultima/ultima4/core/debugger.h b/engines/ultima/ultima4/core/debugger.h
index 92ab07f4f7..b35e513a20 100644
--- a/engines/ultima/ultima4/core/debugger.h
+++ b/engines/ultima/ultima4/core/debugger.h
@@ -185,6 +185,11 @@ private:
 	 */
 	bool cmdSearch(int argc, const char **argv);
 
+	/**
+	 * Show character stats
+	 */
+	bool cmdStats(int argc, const char **argv);
+
 	/**
 	 * Talk
 	 */
@@ -235,6 +240,11 @@ private:
 	 */
 	bool cmdEquipment(int argc, const char **argv);
 
+	/**
+	 * Full stats
+	 */
+	bool cmdFullStats(int argc, const char **argv);
+
 	/**
 	 * Moongate teleportation
 	 */
@@ -290,11 +300,6 @@ private:
 	 */
 	bool cmdReagents(int argc, const char **argv);
 
-	/**
-	 * Full stats
-	 */
-	bool cmdStats(int argc, const char **argv);
-
 	/**
 	 * Summons a creature to fight
 	 */
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index a2f755e8a1..69c45c44ef 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -88,7 +88,6 @@ void mixReagentsSuper();
 
 /* action functions */
 void wearArmor(int player = -1);
-void ztatsFor(int player = -1);
 
 /* creature functions */
 void gameCreatureAttack(Creature *obj);
@@ -691,10 +690,6 @@ bool GameController::keyPressed(int key) {
 			endTurn = false;
 			break;
 
-		case 'z':
-			ztatsFor();
-			break;
-
 		case 'c' + U4_ALT:
 			if (settings._debug && g_context->_location->_map->isWorldMap()) {
 				/* first teleport to the abyss */
@@ -1322,33 +1317,6 @@ void peer(bool useGem) {
 	g_game->_paused = false;
 }
 
-
-/**
- * Called when the player selects a party member for ztats
- */
-void ztatsFor(int player) {
-	// get the player if not provided
-	if (player == -1) {
-		screenMessage("Ztats for: ");
-		player = gameGetPlayer(true, false);
-		if (player == -1)
-			return;
-	}
-
-	// Reset the reagent spell mix menu by removing
-	// the menu highlight from the current item, and
-	// hiding reagents that you don't have
-	g_context->_stats->resetReagentsMenu();
-
-	g_context->_stats->setView(StatsView(STATS_CHAR1 + player));
-#ifdef IOS
-	U4IOS::IOSHideActionKeysHelper hideExtraControls;
-#endif
-	ZtatsController ctrl;
-	eventHandler->pushController(&ctrl);
-	ctrl.waitFor();
-}
-
 void GameController::timerFired() {
 	if (_pausedTimer > 0) {
 		_pausedTimer--;
diff --git a/engines/ultima/ultima4/meta_engine.cpp b/engines/ultima/ultima4/meta_engine.cpp
index 70beac3c72..ee7825c7a7 100644
--- a/engines/ultima/ultima4/meta_engine.cpp
+++ b/engines/ultima/ultima4/meta_engine.cpp
@@ -63,6 +63,7 @@ static const KeybindingRecord KEYS[] = {
 	{ KEYBIND_QUIT_SAVE, "QUIT-SAVE", "Quit and Save", "quitAndSave", "q", nullptr },
 	{ KEYBIND_READY_WEAPON, "READY-WEAPON", "Ready Weapon", "ready", nullptr },
 	{ KEYBIND_SEARCH, "SEARCH", "Search", "search", "s", nullptr },
+	{ KEYBIND_STATS, "STATS", "Stats", "stats", "z", nullptr },
 	{ KEYBIND_TALK, "TALK", "Talk", "talk", "t", nullptr },
 	{ KEYBIND_USE, "USE", "Use", "use", "u", nullptr },
 	{ KEYBIND_WEAR, "WEAR", "Wear Armor", "wear", "w", nullptr },
diff --git a/engines/ultima/ultima4/meta_engine.h b/engines/ultima/ultima4/meta_engine.h
index 7942454fcc..44ddc026be 100644
--- a/engines/ultima/ultima4/meta_engine.h
+++ b/engines/ultima/ultima4/meta_engine.h
@@ -35,8 +35,8 @@ enum KeybindingAction {
 	KEYBIND_HOLE_UP, KEYBIND_IGNITE, KEYBIND_JIMMY, KEYBIND_LOCATE,
 	KEYBIND_MIX, KEYBIND_NEW_ORDER, KEYBIND_OPEN_DOOR, KEYBIND_PASS,
 	KEYBIND_PEER, KEYBIND_QUIT_SAVE, KEYBIND_READY_WEAPON,
-	KEYBIND_SEARCH, KEYBIND_TALK, KEYBIND_USE, KEYBIND_WEAR,
-	KEYBIND_YELL,
+	KEYBIND_SEARCH, KEYBIND_STATS, KEYBIND_TALK, KEYBIND_USE,
+	KEYBIND_WEAR, KEYBIND_YELL,
 
 	KEYBIND_NONE
 };




More information about the Scummvm-git-logs mailing list