[Scummvm-git-logs] scummvm master -> 2ab300cf355239f5e5f07e77edbc8a9644e01a2e

dreammaster paulfgilbert at gmail.com
Wed Apr 29 01:47:32 UTC 2020


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

Summary:
32c418e81d ULTIMA4: Refactored items methods into their own class
ac1bd28d22 ULTIMA4: Removal of outstanding using namespace
2ab300cf35 ULTIMA4: Refactored spell methods into their own class


Commit: 32c418e81d9c13dd96db5eafc745f8f430e5bfe8
    https://github.com/scummvm/scummvm/commit/32c418e81d9c13dd96db5eafc745f8f430e5bfe8
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-28T17:00:11-07:00

Commit Message:
ULTIMA4: Refactored items methods into their own class

Changed paths:
    engines/ultima/ultima4/controllers/combat_controller.cpp
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/filesys/savegame.cpp
    engines/ultima/ultima4/game/item.cpp
    engines/ultima/ultima4/game/item.h
    engines/ultima/ultima4/map/dungeon.cpp
    engines/ultima/ultima4/map/mapmgr.cpp
    engines/ultima/ultima4/ultima4.cpp
    engines/ultima/ultima4/ultima4.h


diff --git a/engines/ultima/ultima4/controllers/combat_controller.cpp b/engines/ultima/ultima4/controllers/combat_controller.cpp
index 2662b6823a..53406464ad 100644
--- a/engines/ultima/ultima4/controllers/combat_controller.cpp
+++ b/engines/ultima/ultima4/controllers/combat_controller.cpp
@@ -998,7 +998,7 @@ bool CombatController::keyPressed(int key) {
 #ifdef IOS_ULTIMA4
 		U4IOS::IOSConversationHelper::setIntroString("Use which item?");
 #endif
-		itemUse(gameGetInput().c_str());
+		g_items->itemUse(gameGetInput().c_str());
 		break;
 
 	case 'v':
diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index 0484a8c24f..02cbc5b7bb 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -985,14 +985,14 @@ bool Debugger::cmdSearch(int argc, const char **argv) {
 	} else {
 		print("Searching...");
 
-		const ItemLocation *item = itemAtLocation(g_context->_location->_map, g_context->_location->_coords);
+		const ItemLocation *item = g_items->itemAtLocation(g_context->_location->_map, g_context->_location->_coords);
 		if (item) {
-			if (item->_isItemInInventory && (*item->_isItemInInventory)(item->_data)) {
+			if (item->_isItemInInventory && (g_items->*(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);
+				(g_items->*(item->_putItemInInventory))(item->_data);
 			}
 		} else {
 			print("%cNothing Here!%c", FG_GREY, FG_WHITE);
@@ -1097,7 +1097,7 @@ bool Debugger::cmdUse(int argc, const char **argv) {
 #ifdef IOS_ULTIMA4
 	U4IOS::IOSConversationHelper::setIntroString("Use which item?");
 #endif
-	itemUse(gameGetInput().c_str());
+	g_items->itemUse(gameGetInput().c_str());
 	return isDebuggerActive();
 }
 
diff --git a/engines/ultima/ultima4/filesys/savegame.cpp b/engines/ultima/ultima4/filesys/savegame.cpp
index 34ffed8fce..8a56cae5cb 100644
--- a/engines/ultima/ultima4/filesys/savegame.cpp
+++ b/engines/ultima/ultima4/filesys/savegame.cpp
@@ -213,7 +213,7 @@ void SaveGame::load(Common::SeekableReadStream *stream) {
 	}
 
 	spellSetEffectCallback(&gameSpellEffect);
-	itemSetDestroyAllCreaturesCallback(&gameDestroyAllCreatures);
+	g_items->setDestroyAllCreaturesCallback(&gameDestroyAllCreatures);
 
 	g_context->_stats->resetReagentsMenu();
 
diff --git a/engines/ultima/ultima4/game/item.cpp b/engines/ultima/ultima4/game/item.cpp
index 1db4c89d9e..4edefa2b7c 100644
--- a/engines/ultima/ultima4/game/item.cpp
+++ b/engines/ultima/ultima4/game/item.cpp
@@ -44,177 +44,156 @@
 namespace Ultima {
 namespace Ultima4 {
 
-using Common::String;
+Items *g_items;
 
-DestroyAllCreaturesCallback destroyAllCreaturesCallback;
-
-void itemSetDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback) {
-	destroyAllCreaturesCallback = callback;
-}
-
-int needStoneNames = 0;
-byte stoneMask = 0;
-
-bool isRuneInInventory(int virt);
-void putRuneInInventory(int virt);
-bool isStoneInInventory(int virt);
-void putStoneInInventory(int virt);
-bool isItemInInventory(int item);
-bool isSkullInInventory(int item);
-void putItemInInventory(int item);
-void useBBC(int item);
-void useHorn(int item);
-void useWheel(int item);
-void useSkull(int item);
-void useStone(int item);
-void useKey(int item);
-bool isMysticInInventory(int mystic);
-void putMysticInInventory(int mystic);
-bool isWeaponInInventory(int weapon);
-void putWeaponInInventory(int weapon);
-void useTelescope(int notused);
-bool isReagentInInventory(int reag);
-void putReagentInInventory(int reag);
-bool isAbyssOpened(const Portal *p);
-void itemHandleStones(const Common::String &color);
-
-static const ItemLocation ITEMS[] = {
+const ItemLocation Items::ITEMS[N_ITEMS] = {
 	{
 		"Mandrake Root", nullptr, "mandrake1",
-		&isReagentInInventory, &putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
+		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
 	},
 	{
 		"Mandrake Root", nullptr, "mandrake2",
-		&isReagentInInventory, &putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
+		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
 	},
 	{
 		"Nightshade", nullptr, "nightshade1",
-		&isReagentInInventory, &putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
+		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
 	},
 	{
 		"Nightshade", nullptr, "nightshade2",
-		&isReagentInInventory, &putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
+		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
 	},
 	{
 		"the Bell of Courage", "bell", "bell",
-		&isItemInInventory, &putItemInInventory, &useBBC, ITEM_BELL, 0
+		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BELL, 0
 	},
 	{
 		"the Book of Truth", "book", "book",
-		&isItemInInventory, &putItemInInventory, &useBBC, ITEM_BOOK, 0
+		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BOOK, 0
 	},
 	{
 		"the Candle of Love", "candle", "candle",
-		&isItemInInventory, &putItemInInventory, &useBBC, ITEM_CANDLE, 0
+		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_CANDLE, 0
 	},
 	{
 		"A Silver Horn", "horn", "horn",
-		&isItemInInventory, &putItemInInventory, &useHorn, ITEM_HORN, 0
+		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useHorn, ITEM_HORN, 0
 	},
 	{
 		"the Wheel from the H.M.S. Cape", "wheel", "wheel",
-		&isItemInInventory, &putItemInInventory, &useWheel, ITEM_WHEEL, 0
+		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useWheel, ITEM_WHEEL, 0
 	},
 	{
 		"the Skull of Modain the Wizard", "skull", "skull",
-		&isSkullInInventory, &putItemInInventory, &useSkull, ITEM_SKULL, SC_NEWMOONS
+		&Items::isSkullInInventory, &Items::putItemInInventory, &Items::useSkull, ITEM_SKULL, SC_NEWMOONS
 	},
 	{
 		"the Red Stone", "red", "redstone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_RED, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_RED, 0
 	},
 	{
 		"the Orange Stone", "orange", "orangestone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_ORANGE, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_ORANGE, 0
 	},
 	{
 		"the Yellow Stone", "yellow", "yellowstone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_YELLOW, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_YELLOW, 0
 	},
 	{
 		"the Green Stone", "green", "greenstone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_GREEN, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_GREEN, 0
 	},
 	{
 		"the Blue Stone", "blue", "bluestone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_BLUE, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLUE, 0
 	},
 	{
 		"the Purple Stone", "purple", "purplestone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_PURPLE, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_PURPLE, 0
 	},
 	{
 		"the Black Stone", "black", "blackstone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_BLACK, SC_NEWMOONS
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLACK, SC_NEWMOONS
 	},
 	{
 		"the White Stone", "white", "whitestone",
-		&isStoneInInventory, &putStoneInInventory, &useStone, STONE_WHITE, 0
+		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_WHITE, 0
 	},
 
 	/* handlers for using generic objects */
-	{ nullptr, "stone",  nullptr, &isStoneInInventory, nullptr, &useStone, -1, 0 },
-	{ nullptr, "stones", nullptr, &isStoneInInventory, nullptr, &useStone, -1, 0 },
-	{ nullptr, "key",    nullptr, &isItemInInventory, nullptr, &useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
-	{ nullptr, "keys",   nullptr, &isItemInInventory, nullptr, &useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
+	{ nullptr, "stone",  nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
+	{ nullptr, "stones", nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
+	{ nullptr, "key",    nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
+	{ nullptr, "keys",   nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
 
 	/* Lycaeum telescope */
-	{ nullptr, nullptr, "telescope", nullptr, &useTelescope, nullptr, 0, 0 },
+	{ nullptr, nullptr, "telescope", nullptr, &Items::useTelescope, nullptr, 0, 0 },
 
 	{
 		"Mystic Armor", nullptr, "mysticarmor",
-		&isMysticInInventory, &putMysticInInventory, nullptr, ARMR_MYSTICROBES, SC_FULLAVATAR
+		&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, ARMR_MYSTICROBES, SC_FULLAVATAR
 	},
 	{
 		"Mystic Swords", nullptr, "mysticswords",
-		&isMysticInInventory, &putMysticInInventory, nullptr, WEAP_MYSTICSWORD, SC_FULLAVATAR
+		&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, WEAP_MYSTICSWORD, SC_FULLAVATAR
 	},
 	{
 		"the sulfury remains of an ancient Sosarian Laser Gun. It turns to ash in your fingers", nullptr, "lasergun", // lol, where'd that come from?
 		//Looks like someone was experimenting with "maps.xml". It effectively increments sulfur ash by one due to '16' being an invalid weapon index.
-		&isWeaponInInventory, &putWeaponInInventory, nullptr, 16, 0
+		&Items::isWeaponInInventory, &Items::putWeaponInInventory, nullptr, 16, 0
 	},
 	{
 		"the rune of Honesty", nullptr, "honestyrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_HONESTY, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONESTY, 0
 	},
 	{
 		"the rune of Compassion", nullptr, "compassionrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_COMPASSION, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_COMPASSION, 0
 	},
 	{
 		"the rune of Valor", nullptr, "valorrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_VALOR, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_VALOR, 0
 	},
 	{
 		"the rune of Justice", nullptr, "justicerune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_JUSTICE, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_JUSTICE, 0
 	},
 	{
 		"the rune of Sacrifice", nullptr, "sacrificerune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_SACRIFICE, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SACRIFICE, 0
 	},
 	{
 		"the rune of Honor", nullptr, "honorrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_HONOR, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONOR, 0
 	},
 	{
 		"the rune of Spirituality", nullptr, "spiritualityrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_SPIRITUALITY, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SPIRITUALITY, 0
 	},
 	{
 		"the rune of Humility", nullptr, "humilityrune",
-		&isRuneInInventory, &putRuneInInventory, nullptr, RUNE_HUMILITY, 0
+		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HUMILITY, 0
 	}
 };
 
-#define N_ITEMS (sizeof(ITEMS) / sizeof(ITEMS[0]))
+Items::Items() : destroyAllCreaturesCallback(nullptr),
+		needStoneNames(0), stoneMask(0) {
+	g_items = this;
+}
+
+Items::~Items() {
+	g_items = nullptr;
+}
+
+void Items::setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback) {
+	destroyAllCreaturesCallback = callback;
+}
 
-bool isRuneInInventory(int virt) {
+bool Items::isRuneInInventory(int virt) {
 	return g_ultima->_saveGame->_runes & virt;
 }
 
-void putRuneInInventory(int virt) {
+void Items::putRuneInInventory(int virt) {
 	g_context->_party->member(0)->awardXp(100);
 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
 	g_ultima->_saveGame->_runes |= virt;
@@ -252,7 +231,7 @@ void putRuneInInventory(int virt) {
 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
 }
 
-bool isStoneInInventory(int virt) {
+bool Items::isStoneInInventory(int virt) {
 	/* generic test: does the party have any stones yet? */
 	if (virt == -1)
 		return (g_ultima->_saveGame->_stones > 0);
@@ -260,7 +239,7 @@ bool isStoneInInventory(int virt) {
 	else return g_ultima->_saveGame->_stones & virt;
 }
 
-void putStoneInInventory(int virt) {
+void Items::putStoneInInventory(int virt) {
 	g_context->_party->member(0)->awardXp(200);
 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
 	g_ultima->_saveGame->_stones |= virt;
@@ -298,15 +277,15 @@ void putStoneInInventory(int virt) {
 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
 }
 
-bool isItemInInventory(int item) {
+bool Items::isItemInInventory(int item) {
 	return g_ultima->_saveGame->_items & item;
 }
 
-bool isSkullInInventory(int unused) {
+bool Items::isSkullInInventory(int unused) {
 	return (g_ultima->_saveGame->_items & (ITEM_SKULL | ITEM_SKULL_DESTROYED));
 }
 
-void putItemInInventory(int item) {
+void Items::putItemInInventory(int item) {
 	g_context->_party->member(0)->awardXp(400);
 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
 	g_ultima->_saveGame->_items |= item;
@@ -348,10 +327,7 @@ void putItemInInventory(int item) {
 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
 }
 
-/**
- * Use bell, book, or candle on the entrance to the Abyss
- */
-void useBBC(int item) {
+void Items::useBBC(int item) {
 	Coords abyssEntrance(0xe9, 0xe9);
 	/* on top of the Abyss entrance */
 	if (g_context->_location->_coords == abyssEntrance) {
@@ -384,28 +360,19 @@ void useBBC(int item) {
 	else g_screen->screenMessage("\nHmm...No effect!\n");
 }
 
-/**
- * Uses the silver horn
- */
-void useHorn(int item) {
+void Items::useHorn(int item) {
 	g_screen->screenMessage("\nThe Horn sounds an eerie tone!\n");
 	g_context->_aura->set(Aura::HORN, 10);
 }
 
-/**
- * Uses the wheel (if on board a ship)
- */
-void useWheel(int item) {
+void Items::useWheel(int item) {
 	if ((g_context->_transportContext == TRANSPORT_SHIP) && (g_ultima->_saveGame->_shipHull == 50)) {
 		g_screen->screenMessage("\nOnce mounted, the Wheel glows with a blue light!\n");
 		g_context->_party->setShipHull(99);
 	} else g_screen->screenMessage("\nHmm...No effect!\n");
 }
 
-/**
- * Uses or destroys the skull of Mondain
- */
-void useSkull(int item) {
+void Items::useSkull(int item) {
 	/* FIXME: check to see if the abyss must be opened first
 	   for the skull to be *able* to be destroyed */
 
@@ -443,10 +410,7 @@ void useSkull(int item) {
 	}
 }
 
-/**
- * Handles using the virtue stones in dungeon altar rooms and on dungeon altars
- */
-void useStone(int item) {
+void Items::useStone(int item) {
 	MapCoords coords;
 	byte stone = static_cast<byte>(item);
 
@@ -613,11 +577,11 @@ void useStone(int item) {
 	// That doesn't match U4DOS; does it match another?
 }
 
-void useKey(int item) {
+void Items::useKey(int item) {
 	g_screen->screenMessage("\nNo place to Use them!\n");
 }
 
-bool isMysticInInventory(int mystic) {
+bool Items::isMysticInInventory(int mystic) {
 	/* FIXME: you could feasibly get more mystic weapons and armor if you
 	   have 8 party members and equip them all with everything,
 	   then search for Mystic Weapons/Armor again
@@ -637,7 +601,7 @@ bool isMysticInInventory(int mystic) {
 	return false;
 }
 
-void putMysticInInventory(int mystic) {
+void Items::putMysticInInventory(int mystic) {
 	g_context->_party->member(0)->awardXp(400);
 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
 	if (mystic == WEAP_MYSTICSWORD)
@@ -649,7 +613,7 @@ void putMysticInInventory(int mystic) {
 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
 }
 
-bool isWeaponInInventory(int weapon) {
+bool Items::isWeaponInInventory(int weapon) {
 	if (g_ultima->_saveGame->_weapons[weapon])
 		return true;
 	else {
@@ -661,11 +625,11 @@ bool isWeaponInInventory(int weapon) {
 	return false;
 }
 
-void putWeaponInInventory(int weapon) {
+void Items::putWeaponInInventory(int weapon) {
 	g_ultima->_saveGame->_weapons[weapon]++;
 }
 
-void useTelescope(int notused) {
+void Items::useTelescope(int notused) {
 	g_screen->screenMessage("You see a knob\non the telescope\nmarked A-P\nYou Select:");
 #ifdef IOS_ULTIMA4
 	U4IOS::IOSConversationChoiceHelper telescopeHelper;
@@ -679,11 +643,11 @@ void useTelescope(int notused) {
 	gamePeerCity(choice, nullptr);
 }
 
-bool isReagentInInventory(int reag) {
+bool Items::isReagentInInventory(int reag) {
 	return false;
 }
 
-void putReagentInInventory(int reag) {
+void Items::putReagentInInventory(int reag) {
 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
 	g_ultima->_saveGame->_reagents[reag] += xu4_random(8) + 2;
 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
@@ -694,10 +658,7 @@ void putReagentInInventory(int reag) {
 	}
 }
 
-/**
- * Returns true if the specified conditions are met to be able to get the item
- */
-bool itemConditionsMet(byte conditions) {
+bool Items::itemConditionsMet(byte conditions) {
 	int i;
 
 	if ((conditions & SC_NEWMOONS) &&
@@ -718,7 +679,7 @@ bool itemConditionsMet(byte conditions) {
 	return true;
 }
 
-const ItemLocation *itemAtLocation(const Map *map, const Coords &coords) {
+const ItemLocation *Items::itemAtLocation(const Map *map, const Coords &coords) {
 	uint i;
 	for (i = 0; i < N_ITEMS; i++) {
 		if (!ITEMS[i]._locationLabel)
@@ -730,24 +691,24 @@ const ItemLocation *itemAtLocation(const Map *map, const Coords &coords) {
 	return nullptr;
 }
 
-void itemUse(const Common::String &shortname) {
+void Items::itemUse(const Common::String &shortName) {
 	uint i;
 	const ItemLocation *item = nullptr;
 
 	for (i = 0; i < N_ITEMS; i++) {
 		if (ITEMS[i]._shortName &&
-		        scumm_stricmp(ITEMS[i]._shortName, shortname.c_str()) == 0) {
+		        scumm_stricmp(ITEMS[i]._shortName, shortName.c_str()) == 0) {
 
 			item = &ITEMS[i];
 
 			/* item name found, see if we have that item in our inventory */
-			if (!ITEMS[i]._isItemInInventory || (*ITEMS[i]._isItemInInventory)(ITEMS[i]._data)) {
+			if (!ITEMS[i]._isItemInInventory || (this->*(ITEMS[i]._isItemInInventory))(ITEMS[i]._data)) {
 
 				/* use the item, if we can! */
 				if (!item || !item->_useItem)
 					g_screen->screenMessage("\nNot a Usable item!\n");
 				else
-					(*item->_useItem)(ITEMS[i]._data);
+					(this->*(item->_useItem))(ITEMS[i]._data);
 			} else
 				g_screen->screenMessage("\nNone owned!\n");
 
@@ -761,23 +722,7 @@ void itemUse(const Common::String &shortname) {
 		g_screen->screenMessage("\nNot a Usable item!\n");
 }
 
-/**
- * Checks to see if the abyss was opened
- */
-bool isAbyssOpened(const Portal *p) {
-	/* make sure the bell, book and candle have all been used */
-	int items = g_ultima->_saveGame->_items;
-	int isopened = (items & ITEM_BELL_USED) && (items & ITEM_BOOK_USED) && (items & ITEM_CANDLE_USED);
-
-	if (!isopened)
-		g_screen->screenMessage("Enter Can't!\n");
-	return isopened;
-}
-
-/**
- * Handles naming of stones when used
- */
-void itemHandleStones(const Common::String &color) {
+void Items::itemHandleStones(const Common::String &color) {
 	bool found = false;
 
 	for (int i = 0; i < 8; i++) {
@@ -794,5 +739,15 @@ void itemHandleStones(const Common::String &color) {
 	}
 }
 
+bool Items::isAbyssOpened(const Portal *p) {
+	/* make sure the bell, book and candle have all been used */
+	int items = g_ultima->_saveGame->_items;
+	int isopened = (items & ITEM_BELL_USED) && (items & ITEM_BOOK_USED) && (items & ITEM_CANDLE_USED);
+
+	if (!isopened)
+		g_screen->screenMessage("Enter Can't!\n");
+	return isopened;
+}
+
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/game/item.h b/engines/ultima/ultima4/game/item.h
index 6a23c3be64..ae0cb3665f 100644
--- a/engines/ultima/ultima4/game/item.h
+++ b/engines/ultima/ultima4/game/item.h
@@ -29,8 +29,9 @@
 namespace Ultima {
 namespace Ultima4 {
 
-class Map;
 class Coords;
+class Map;
+struct Portal;
 
 enum SearchCondition {
 	SC_NONE         = 0x00,
@@ -39,31 +40,108 @@ enum SearchCondition {
 	SC_REAGENTDELAY = 0x04
 };
 
+class Items;
+typedef bool (Items::*IsInInventoryProc)(int item);
+typedef void (Items::*InventoryActionProc)(int item);
+
+#include "common/pack-start.h"	// START STRUCT PACKING
 struct ItemLocation {
 	const char *_name;
 	const char *_shortName;
 	const char *_locationLabel;
-	bool (*_isItemInInventory)(int item);
-	void (*_putItemInInventory)(int item);
-	void (*_useItem)(int item);
+	IsInInventoryProc _isItemInInventory;
+	InventoryActionProc _putItemInInventory;
+	InventoryActionProc _useItem;
 	int _data;
 	byte _conditions;
-};
+} PACKED_STRUCT;
+#include "common/pack-end.h"	// END STRUCT PACKING
 
 typedef void (*DestroyAllCreaturesCallback)();
+#define N_ITEMS 34
 
-void itemSetDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback);
+class Items {
+private:
 
-/**
- * Returns an item location record if a searchable object exists at
- * the given location. nullptr is returned if nothing is there.
- */
-const ItemLocation *itemAtLocation(const Map *map, const Coords &coords);
+	static const ItemLocation ITEMS[N_ITEMS];
+	DestroyAllCreaturesCallback destroyAllCreaturesCallback;
+	int needStoneNames;
+	byte stoneMask;
+private:
+	bool isRuneInInventory(int virt);
+	void putRuneInInventory(int virt);
+	bool isStoneInInventory(int virt);
+	void putStoneInInventory(int virt);
+	bool isItemInInventory(int item);
+	bool isSkullInInventory(int item);
+	void putItemInInventory(int item);
 
-/**
- * Uses the item indicated by 'shortname'
- */
-void itemUse(const Common::String &shortname);
+	/**
+	 * Use bell, book, or candle on the entrance to the Abyss
+	 */
+	void useBBC(int item);
+
+	/**
+	 * Uses the silver horn
+	 */
+	void useHorn(int item);
+
+	/**
+	 * Uses the wheel (if on board a ship)
+	 */
+	void useWheel(int item);
+
+	/**
+	 * Uses or destroys the skull of Mondain
+	 */
+	void useSkull(int item);
+
+	/**
+	 * Handles using the virtue stones in dungeon altar rooms and on dungeon altars
+	 */
+	void useStone(int item);
+	void useKey(int item);
+	bool isMysticInInventory(int mystic);
+	void putMysticInInventory(int mystic);
+	bool isWeaponInInventory(int weapon);
+	void putWeaponInInventory(int weapon);
+	void useTelescope(int notused);
+	bool isReagentInInventory(int reag);
+	void putReagentInInventory(int reag);
+
+	/**
+	 * Handles naming of stones when used
+	 */
+	void itemHandleStones(const Common::String &color);
+
+	/**
+	 * Returns true if the specified conditions are met to be able to get the item
+	 */
+	bool itemConditionsMet(byte conditions);
+public:
+	Items();
+	~Items();
+
+	void setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback);
+
+	/**
+	 * Returns an item location record if a searchable object exists at
+	 * the given location. nullptr is returned if nothing is there.
+	 */
+	const ItemLocation *itemAtLocation(const Map *map, const Coords &coords);
+
+	/**
+	 * Uses the item indicated by 'shortname'
+	 */
+	void itemUse(const Common::String &shortName);
+
+	/**
+	 * Checks to see if the abyss was opened
+	 */
+	static bool isAbyssOpened(const Portal *p);
+};
+
+extern Items *g_items;
 
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/map/dungeon.cpp b/engines/ultima/ultima4/map/dungeon.cpp
index 771317e64f..c3bc45b839 100644
--- a/engines/ultima/ultima4/map/dungeon.cpp
+++ b/engines/ultima/ultima4/map/dungeon.cpp
@@ -122,14 +122,14 @@ void dungeonSearch(void) {
 
 	default: {
 		/* see if there is an item at the current location (stones on altars, etc.) */
-		item = itemAtLocation(dungeon, g_context->_location->_coords);
+		item = g_items->itemAtLocation(dungeon, g_context->_location->_coords);
 		if (item) {
-			if (item->_isItemInInventory && (*item->_isItemInInventory)(item->_data)) {
+			if (item->_isItemInInventory && (g_items->*(item->_isItemInInventory))(item->_data)) {
 				g_screen->screenMessage("Nothing Here!\n");
 			} else {
 				if (item->_name)
 					g_screen->screenMessage("You find...\n%s!\n", item->_name);
-				(*item->_putItemInInventory)(item->_data);
+				(g_items->*(item->_putItemInInventory))(item->_data);
 			}
 		} else
 			g_screen->screenMessage("\nYou find Nothing!\n");
diff --git a/engines/ultima/ultima4/map/mapmgr.cpp b/engines/ultima/ultima4/map/mapmgr.cpp
index 50f7b5448d..ae48b93f37 100644
--- a/engines/ultima/ultima4/map/mapmgr.cpp
+++ b/engines/ultima/ultima4/map/mapmgr.cpp
@@ -28,6 +28,7 @@
 #include "ultima/ultima4/map/map.h"
 #include "ultima/ultima4/map/maploader.h"
 #include "ultima/ultima4/map/mapmgr.h"
+#include "ultima/ultima4/game/item.h"
 #include "ultima/ultima4/game/moongate.h"
 #include "ultima/ultima4/game/person.h"
 #include "ultima/ultima4/game/portal.h"
@@ -46,7 +47,6 @@ using Std::pair;
 
 MapMgr *MapMgr::_instance = nullptr;
 
-extern bool isAbyssOpened(const Portal *p);
 extern bool shrineCanEnter(const Portal *p);
 
 MapMgr *MapMgr::getInstance() {
@@ -286,7 +286,7 @@ Portal *MapMgr::initPortalFromConf(const ConfigElement &portalConf) {
 		if (prop == "shrine")
 			portal->_portalConditionsMet = &shrineCanEnter;
 		else if (prop == "abyss")
-			portal->_portalConditionsMet = &isAbyssOpened;
+			portal->_portalConditionsMet = &Items::isAbyssOpened;
 		else
 			error("unknown portalConditionsMet: %s", prop.c_str());
 	}
diff --git a/engines/ultima/ultima4/ultima4.cpp b/engines/ultima/ultima4/ultima4.cpp
index 76643f82aa..7eb04edb25 100644
--- a/engines/ultima/ultima4/ultima4.cpp
+++ b/engines/ultima/ultima4/ultima4.cpp
@@ -35,6 +35,7 @@
 #include "ultima/ultima4/game/context.h"
 #include "ultima/ultima4/game/death.h"
 #include "ultima/ultima4/game/game.h"
+#include "ultima/ultima4/game/item.h"
 #include "ultima/ultima4/game/moongate.h"
 #include "ultima/ultima4/game/person.h"
 #include "ultima/ultima4/game/weapon.h"
@@ -58,16 +59,17 @@ Ultima4Engine *g_ultima;
 Ultima4Engine::Ultima4Engine(OSystem *syst, const Ultima::UltimaGameDescription *gameDesc) :
 		Shared::UltimaEngine(syst, gameDesc), _saveSlotToLoad(-1), _armors(nullptr),
 		_codex(nullptr), _config(nullptr), _context(nullptr), _death(nullptr),
-		_dialogueLoaders(nullptr), _game(nullptr), _music(nullptr), _imageLoaders(nullptr),
-		_mapLoaders(nullptr), _moongates(nullptr), _responseParts(nullptr), _saveGame(nullptr),
-		_screen(nullptr), _shrines(nullptr), _tileMaps(nullptr), _tileRules(nullptr),
-		_tileSets(nullptr), _weapons(nullptr) {
+		_dialogueLoaders(nullptr), _game(nullptr), _items(nullptr), _music(nullptr),
+		_imageLoaders(nullptr), _mapLoaders(nullptr), _moongates(nullptr),
+		_responseParts(nullptr), _saveGame(nullptr), _screen(nullptr), _shrines(nullptr),
+		_tileMaps(nullptr), _tileRules(nullptr), _tileSets(nullptr), _weapons(nullptr) {
 	g_ultima = this;
 	g_armors = nullptr;
 	g_codex = nullptr;
 	g_context = nullptr;
 	g_death = nullptr;
 	g_game = nullptr;
+	g_items = nullptr;
 	g_mapLoaders = nullptr;
 	g_moongates = nullptr;
 	g_responseParts = nullptr;
@@ -88,6 +90,7 @@ Ultima4Engine::~Ultima4Engine() {
 	delete _dialogueLoaders;
 	delete _game;
 	delete _imageLoaders;
+	delete _items;
 	delete _mapLoaders;
 	delete _moongates;
 	delete _music;
@@ -117,6 +120,7 @@ bool Ultima4Engine::initialize() {
 	_context = new Context();
 	_death = new Death();
 	_dialogueLoaders = new DialogueLoaders();
+	_items = new Items();
 	_mapLoaders = new MapLoaders();
 	_moongates = new Moongates();
 	_responseParts = new ResponseParts();
diff --git a/engines/ultima/ultima4/ultima4.h b/engines/ultima/ultima4/ultima4.h
index db9b63acc4..e5cbdbf51f 100644
--- a/engines/ultima/ultima4/ultima4.h
+++ b/engines/ultima/ultima4/ultima4.h
@@ -36,6 +36,7 @@ class Context;
 class Death;
 class DialogueLoaders;
 class ImageLoaders;
+class Items;
 class GameController;
 class MapLoaders;
 class Moongates;
@@ -73,6 +74,7 @@ public:
 	DialogueLoaders *_dialogueLoaders;
 	ImageLoaders *_imageLoaders;
 	GameController *_game;
+	Items *_items;
 	MapLoaders *_mapLoaders;
 	Moongates *_moongates;
 	Music *_music;


Commit: ac1bd28d22f68bddf20df47e19526a68c1bb9b88
    https://github.com/scummvm/scummvm/commit/ac1bd28d22f68bddf20df47e19526a68c1bb9b88
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-28T17:02:38-07:00

Commit Message:
ULTIMA4: Removal of outstanding using namespace

Changed paths:
    engines/ultima/ultima4/game/object.cpp
    engines/ultima/ultima4/game/person.cpp


diff --git a/engines/ultima/ultima4/game/object.cpp b/engines/ultima/ultima4/game/object.cpp
index 6e112263b4..18223b6674 100644
--- a/engines/ultima/ultima4/game/object.cpp
+++ b/engines/ultima/ultima4/game/object.cpp
@@ -29,8 +29,6 @@
 namespace Ultima {
 namespace Ultima4 {
 
-using namespace Std;
-
 bool Object::setDirection(Direction d) {
 	return _tile.setDirection(d);
 }
diff --git a/engines/ultima/ultima4/game/person.cpp b/engines/ultima/ultima4/game/person.cpp
index 8d975d3796..8c2ac033fc 100644
--- a/engines/ultima/ultima4/game/person.cpp
+++ b/engines/ultima/ultima4/game/person.cpp
@@ -45,8 +45,6 @@
 namespace Ultima {
 namespace Ultima4 {
 
-using namespace Std;
-
 int chars_needed(const char *s, int columnmax, int linesdesired, int *real_lines);
 
 /**
@@ -355,8 +353,8 @@ Common::String Person::getIntro(Conversation *cnv) {
 
 Common::String Person::processResponse(Conversation *cnv, Response *response) {
 	Common::String text;
-	const vector<ResponsePart> &parts = response->getParts();
-	for (vector<ResponsePart>::const_iterator i = parts.begin(); i != parts.end(); i++) {
+	const Std::vector<ResponsePart> &parts = response->getParts();
+	for (Std::vector<ResponsePart>::const_iterator i = parts.begin(); i != parts.end(); i++) {
 
 		// check for command triggers
 		if (i->isCommand())
@@ -441,7 +439,7 @@ Common::String Person::getResponse(Conversation *cnv, const char *inquiry) {
 	}
 
 	else if (settings._debug && scumm_strnicmp(inquiry, "dump", 4) == 0) {
-		vector<Common::String> words = split(inquiry, " \t");
+		Std::vector<Common::String> words = split(inquiry, " \t");
 		if (words.size() <= 1)
 			reply = _dialogue->dump("");
 		else


Commit: 2ab300cf355239f5e5f07e77edbc8a9644e01a2e
    https://github.com/scummvm/scummvm/commit/2ab300cf355239f5e5f07e77edbc8a9644e01a2e
Author: Paul Gilbert (dreammaster at scummvm.org)
Date: 2020-04-28T18:47:17-07:00

Commit Message:
ULTIMA4: Refactored spell methods into their own class

Changed paths:
    engines/ultima/ultima4/core/debugger.cpp
    engines/ultima/ultima4/core/debugger_actions.cpp
    engines/ultima/ultima4/filesys/savegame.cpp
    engines/ultima/ultima4/game/game.cpp
    engines/ultima/ultima4/game/script.cpp
    engines/ultima/ultima4/game/spell.cpp
    engines/ultima/ultima4/game/spell.h
    engines/ultima/ultima4/ultima4.cpp
    engines/ultima/ultima4/ultima4.h


diff --git a/engines/ultima/ultima4/core/debugger.cpp b/engines/ultima/ultima4/core/debugger.cpp
index 02cbc5b7bb..12f28de3ff 100644
--- a/engines/ultima/ultima4/core/debugger.cpp
+++ b/engines/ultima/ultima4/core/debugger.cpp
@@ -326,18 +326,18 @@ bool Debugger::cmdCastSpell(int argc, const char **argv) {
 	if (spell == -1)
 		return isDebuggerActive();
 
-	print("%s!", spellGetName(spell)); //Prints spell name at prompt
+	print("%s!", g_spells->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) {
+	if (g_spells->spellCheckPrerequisites(spell, player) != CASTERR_NOERROR) {
 		gameCastSpell(spell, player, 0);
 		return isDebuggerActive();
 	}
 
 	// Get the final parameters for the spell
-	switch (spellGetParamType(spell)) {
+	switch (g_spells->spellGetParamType(spell)) {
 	case Spell::PARAM_NONE:
 		gameCastSpell(spell, player, 0);
 		break;
@@ -432,7 +432,8 @@ bool Debugger::cmdCastSpell(int argc, const char **argv) {
 			 * original Ultima IV (at least, in the Amiga version.)
 			 */
 			 //c->saveGame->_mixtures[castSpell]--;
-			g_context->_party->member(player)->adjustMp(-spellGetRequiredMP(spell));
+			g_context->_party->member(player)->adjustMp(
+				-g_spells->spellGetRequiredMP(spell));
 		}
 		break;
 	}
@@ -777,7 +778,7 @@ bool Debugger::cmdMixReagents(int argc, const char **argv) {
 				break;
 
 			int spell = choice - 'a';
-			print("%s", spellGetName(spell));
+			print("%s", g_spells->spellGetName(spell));
 
 			// ensure the mixtures for the spell isn't already maxed out
 			if (g_ultima->_saveGame->_mixtures[spell] == 99) {
diff --git a/engines/ultima/ultima4/core/debugger_actions.cpp b/engines/ultima/ultima4/core/debugger_actions.cpp
index 59dd09289c..5e8a914270 100644
--- a/engines/ultima/ultima4/core/debugger_actions.cpp
+++ b/engines/ultima/ultima4/core/debugger_actions.cpp
@@ -233,7 +233,7 @@ bool DebuggerActions::mixReagentsForSpellU4(int spell) {
 		if (choice == '\n' || choice == '\r' || choice == ' ') {
 			g_screen->screenMessage("\n\nYou mix the Reagents, and...\n");
 
-			if (spellMix(spell, &ingredients))
+			if (g_spells->spellMix(spell, &ingredients))
 				g_screen->screenMessage("Success!\n\n");
 			else
 				g_screen->screenMessage("It Fizzles!\n\n");
@@ -280,14 +280,14 @@ bool DebuggerActions::mixReagentsForSpellU5(int spell) {
 bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingredients) {
 	int i;
 
-	/* entered 0 mixtures, don't mix anything! */
+	// 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 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);
@@ -295,7 +295,7 @@ bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingre
 
 	print("\nMixing %d...", num);
 
-	/* see if there's enough reagents to make number of mixtures requested */
+	// 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();
@@ -303,12 +303,12 @@ bool DebuggerActions::gameSpellMixHowMany(int spell, int num, Ingredients *ingre
 	}
 
 	print("\nYou mix the Reagents, and...");
-	if (spellMix(spell, ingredients)) {
+	if (g_spells->spellMix(spell, ingredients)) {
 		print("Success!\n");
-		/* mix the extra spells */
+		// Mix the extra spells
 		ingredients->multiply(num);
 		for (i = 0; i < num - 1; i++)
-			spellMix(spell, ingredients);
+			g_spells->spellMix(spell, ingredients);
 	} else {
 		print("It Fizzles!\n");
 	}
@@ -341,8 +341,8 @@ void DebuggerActions::gameCastSpell(uint spell, int caster, int param) {
 	SpellCastError spellError;
 	Common::String msg;
 
-	if (!spellCast(spell, caster, param, &spellError, true)) {
-		msg = spellGetErrorMessage(spell, spellError);
+	if (!g_spells->spellCast(spell, caster, param, &spellError, true)) {
+		msg = g_spells->spellGetErrorMessage(spell, spellError);
 		if (!msg.empty())
 			g_screen->screenMessage("%s", msg.c_str());
 	}
diff --git a/engines/ultima/ultima4/filesys/savegame.cpp b/engines/ultima/ultima4/filesys/savegame.cpp
index 8a56cae5cb..7a0ed3db08 100644
--- a/engines/ultima/ultima4/filesys/savegame.cpp
+++ b/engines/ultima/ultima4/filesys/savegame.cpp
@@ -212,7 +212,7 @@ void SaveGame::load(Common::SeekableReadStream *stream) {
 		gameFixupObjects(g_context->_location->_prev->_map);
 	}
 
-	spellSetEffectCallback(&gameSpellEffect);
+	g_spells->spellSetEffectCallback(&gameSpellEffect);
 	g_items->setDestroyAllCreaturesCallback(&gameDestroyAllCreatures);
 
 	g_context->_stats->resetReagentsMenu();
diff --git a/engines/ultima/ultima4/game/game.cpp b/engines/ultima/ultima4/game/game.cpp
index 721f8544b3..5711bedf11 100644
--- a/engines/ultima/ultima4/game/game.cpp
+++ b/engines/ultima/ultima4/game/game.cpp
@@ -761,7 +761,7 @@ void showMixturesSuper(int page = 0) {
 	for (int i = 0; i < 13; i++) {
 		char buf[4];
 
-		const Spell *s = getSpell(i + 13 * page);
+		const Spell *s = g_spells->getSpell(i + 13 * page);
 		int line = i + 8;
 		g_screen->screenTextAt(2, line, "%s", s->_name);
 
@@ -835,7 +835,7 @@ void mixReagentsSuper() {
 			done = true;
 		} else {
 			spell -= 'a';
-			const Spell *s = getSpell(spell);
+			const Spell *s = g_spells->getSpell(spell);
 			g_screen->screenMessage("%s\n", s->_name);
 			page = (spell >= 13);
 			showMixturesSuper(page);
diff --git a/engines/ultima/ultima4/game/script.cpp b/engines/ultima/ultima4/game/script.cpp
index da841fcfe6..04fcfe742c 100644
--- a/engines/ultima/ultima4/game/script.cpp
+++ b/engines/ultima/ultima4/game/script.cpp
@@ -44,8 +44,6 @@
 namespace Ultima {
 namespace Ultima4 {
 
-extern SpellEffectCallback spellEffectCallback;
-
 /*
  * Script::Variable class
  */
@@ -1211,7 +1209,7 @@ Script::ReturnCode Script::heal(Shared::XMLNode *script, Shared::XMLNode *curren
 }
 
 Script::ReturnCode Script::castSpell(Shared::XMLNode *script, Shared::XMLNode *current) {
-	(*spellEffectCallback)('r', -1, SOUND_MAGIC);
+	g_spells->spellEffect('r', -1, SOUND_MAGIC);
 	if (_debug)
 		debug("<Spell effect>");
 
diff --git a/engines/ultima/ultima4/game/spell.cpp b/engines/ultima/ultima4/game/spell.cpp
index bd298611dd..bb33770012 100644
--- a/engines/ultima/ultima4/game/spell.cpp
+++ b/engines/ultima/ultima4/game/spell.cpp
@@ -45,39 +45,6 @@
 namespace Ultima {
 namespace Ultima4 {
 
-SpellEffectCallback spellEffectCallback = nullptr;
-
-CombatController *spellCombatController();
-void spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage);
-bool spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage);
-
-static int spellAwaken(int player);
-static int spellBlink(int dir);
-static int spellCure(int player);
-static int spellDispel(int dir);
-static int spellEField(int param);
-static int spellFireball(int dir);
-static int spellGate(int phase);
-static int spellHeal(int player);
-static int spellIceball(int dir);
-static int spellJinx(int unused);
-static int spellKill(int dir);
-static int spellLight(int unused);
-static int spellMMissle(int dir);
-static int spellNegate(int unused);
-static int spellOpen(int unused);
-static int spellProtect(int unused);
-static int spellRez(int player);
-static int spellQuick(int unused);
-static int spellSleep(int unused);
-static int spellTremor(int unused);
-static int spellUndead(int unused);
-static int spellView(int unsued);
-static int spellWinds(int fromdir);
-static int spellXit(int unused);
-static int spellYup(int unused);
-static int spellZdown(int unused);
-
 /* masks for reagents */
 #define ASH (1 << REAG_ASH)
 #define GINSENG (1 << REAG_GINSENG)
@@ -92,7 +59,7 @@ static int spellZdown(int unused);
 static const struct {
 	SpellCastError err;
 	const char *msg;
-} spellErrorMsgs[] = {
+} SPELL_ERROR_MSGS[] = {
 	{ CASTERR_NOMIX, "None Mixed!\n" },
 	{ CASTERR_MPTOOLOW, "Not Enough MP!\n" },
 	{ CASTERR_FAILED, "Failed!\n" },
@@ -102,55 +69,51 @@ static const struct {
 	{ CASTERR_WORLDMAPONLY, "Outdoors only!\nFailed!\n" }
 };
 
-static const Spell spells[] = {
-	{ "Awaken",       GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &spellAwaken,  Spell::PARAM_PLAYER,  5 },
+const Spell Spells::SPELL_LIST[N_SPELLS] = {
+	{ "Awaken",       GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &Spells::spellAwaken,  Spell::PARAM_PLAYER,  5 },
 	{
 		"Blink",        SILK | MOSS,              CTX_WORLDMAP,   TRANSPORT_FOOT_OR_HORSE,
-		&spellBlink,   Spell::PARAM_DIR,     15
+		&Spells::spellBlink,   Spell::PARAM_DIR,     15
 	},
-	{ "Cure",         GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &spellCure,    Spell::PARAM_PLAYER,  5 },
-	{ "Dispell",      ASH | GARLIC | PEARL,     CTX_ANY,        TRANSPORT_ANY,  &spellDispel,  Spell::PARAM_DIR,     20 },
+	{ "Cure",         GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &Spells::spellCure,    Spell::PARAM_PLAYER,  5 },
+	{ "Dispell",      ASH | GARLIC | PEARL,     CTX_ANY,        TRANSPORT_ANY,  &Spells::spellDispel,  Spell::PARAM_DIR,     20 },
 	{
 		"Energy Field", ASH | SILK | PEARL, (LocationContext)(CTX_COMBAT | CTX_DUNGEON),
-		TRANSPORT_ANY,  &spellEField,  Spell::PARAM_TYPEDIR, 10
+		TRANSPORT_ANY,  &Spells::spellEField,  Spell::PARAM_TYPEDIR, 10
 	},
-	{ "Fireball",     ASH | PEARL,              CTX_COMBAT,     TRANSPORT_ANY,  &spellFireball, Spell::PARAM_DIR,     15 },
+	{ "Fireball",     ASH | PEARL,              CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellFireball, Spell::PARAM_DIR,     15 },
 	{
 		"Gate",         ASH | PEARL | MANDRAKE,   CTX_WORLDMAP,   TRANSPORT_FOOT_OR_HORSE,
-		&spellGate,    Spell::PARAM_PHASE,   40
+		&Spells::spellGate,    Spell::PARAM_PHASE,   40
 	},
-	{ "Heal",         GINSENG | SILK,           CTX_ANY,        TRANSPORT_ANY,  &spellHeal,    Spell::PARAM_PLAYER,  10 },
-	{ "Iceball",      PEARL | MANDRAKE,         CTX_COMBAT,     TRANSPORT_ANY,  &spellIceball, Spell::PARAM_DIR,     20 },
+	{ "Heal",         GINSENG | SILK,           CTX_ANY,        TRANSPORT_ANY,  &Spells::spellHeal,    Spell::PARAM_PLAYER,  10 },
+	{ "Iceball",      PEARL | MANDRAKE,         CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellIceball, Spell::PARAM_DIR,     20 },
 	{
 		"Jinx",         PEARL | NIGHTSHADE | MANDRAKE,
-		CTX_ANY,        TRANSPORT_ANY,  &spellJinx,    Spell::PARAM_NONE,    30
+		CTX_ANY,        TRANSPORT_ANY,  &Spells::spellJinx,    Spell::PARAM_NONE,    30
 	},
-	{ "Kill",         PEARL | NIGHTSHADE,       CTX_COMBAT,     TRANSPORT_ANY,  &spellKill,    Spell::PARAM_DIR,     25 },
-	{ "Light",        ASH,                      CTX_DUNGEON,    TRANSPORT_ANY,  &spellLight,   Spell::PARAM_NONE,    5 },
-	{ "Magic missile", ASH | PEARL,             CTX_COMBAT,     TRANSPORT_ANY,  &spellMMissle, Spell::PARAM_DIR,     5 },
-	{ "Negate",       ASH | GARLIC | MANDRAKE,  CTX_ANY,        TRANSPORT_ANY,  &spellNegate,  Spell::PARAM_NONE,    20 },
-	{ "Open",         ASH | MOSS,               CTX_ANY,        TRANSPORT_ANY,  &spellOpen,    Spell::PARAM_NONE,    5 },
-	{ "Protection",   ASH | GINSENG | GARLIC,   CTX_ANY,        TRANSPORT_ANY,  &spellProtect, Spell::PARAM_NONE,    15 },
-	{ "Quickness",    ASH | GINSENG | MOSS,     CTX_ANY,        TRANSPORT_ANY,  &spellQuick,   Spell::PARAM_NONE,    20 },
+	{ "Kill",         PEARL | NIGHTSHADE,       CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellKill,    Spell::PARAM_DIR,     25 },
+	{ "Light",        ASH,                      CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellLight,   Spell::PARAM_NONE,    5 },
+	{ "Magic missile", ASH | PEARL,             CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellMMissle, Spell::PARAM_DIR,     5 },
+	{ "Negate",       ASH | GARLIC | MANDRAKE,  CTX_ANY,        TRANSPORT_ANY,  &Spells::spellNegate,  Spell::PARAM_NONE,    20 },
+	{ "Open",         ASH | MOSS,               CTX_ANY,        TRANSPORT_ANY,  &Spells::spellOpen,    Spell::PARAM_NONE,    5 },
+	{ "Protection",   ASH | GINSENG | GARLIC,   CTX_ANY,        TRANSPORT_ANY,  &Spells::spellProtect, Spell::PARAM_NONE,    15 },
+	{ "Quickness",    ASH | GINSENG | MOSS,     CTX_ANY,        TRANSPORT_ANY,  &Spells::spellQuick,   Spell::PARAM_NONE,    20 },
 	{
 		"Resurrect",    ASH | GINSENG | GARLIC | SILK | MOSS | MANDRAKE,
-		CTX_NON_COMBAT, TRANSPORT_ANY,  &spellRez,     Spell::PARAM_PLAYER,  45
+		CTX_NON_COMBAT, TRANSPORT_ANY,  &Spells::spellRez,     Spell::PARAM_PLAYER,  45
 	},
-	{ "Sleep",        SILK | GINSENG,           CTX_COMBAT,     TRANSPORT_ANY,  &spellSleep,   Spell::PARAM_NONE,    15 },
-	{ "Tremor",       ASH | MOSS | MANDRAKE,    CTX_COMBAT,     TRANSPORT_ANY,  &spellTremor,  Spell::PARAM_NONE,    30 },
-	{ "Undead",       ASH | GARLIC,             CTX_COMBAT,     TRANSPORT_ANY,  &spellUndead,  Spell::PARAM_NONE,    15 },
-	{ "View",         NIGHTSHADE | MANDRAKE,    CTX_NON_COMBAT, TRANSPORT_ANY,  &spellView,    Spell::PARAM_NONE,    15 },
-	{ "Winds",        ASH | MOSS,               CTX_WORLDMAP,   TRANSPORT_ANY,  &spellWinds,   Spell::PARAM_FROMDIR, 10 },
-	{ "X-it",         ASH | SILK | MOSS,        CTX_DUNGEON,    TRANSPORT_ANY,  &spellXit,     Spell::PARAM_NONE,    15 },
-	{ "Y-up",         SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &spellYup,     Spell::PARAM_NONE,    10 },
-	{ "Z-down",       SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &spellZdown,   Spell::PARAM_NONE,    5 }
+	{ "Sleep",        SILK | GINSENG,           CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellSleep,   Spell::PARAM_NONE,    15 },
+	{ "Tremor",       ASH | MOSS | MANDRAKE,    CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellTremor,  Spell::PARAM_NONE,    30 },
+	{ "Undead",       ASH | GARLIC,             CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellUndead,  Spell::PARAM_NONE,    15 },
+	{ "View",         NIGHTSHADE | MANDRAKE,    CTX_NON_COMBAT, TRANSPORT_ANY,  &Spells::spellView,    Spell::PARAM_NONE,    15 },
+	{ "Winds",        ASH | MOSS,               CTX_WORLDMAP,   TRANSPORT_ANY,  &Spells::spellWinds,   Spell::PARAM_FROMDIR, 10 },
+	{ "X-it",         ASH | SILK | MOSS,        CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellXit,     Spell::PARAM_NONE,    15 },
+	{ "Y-up",         SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellYup,     Spell::PARAM_NONE,    10 },
+	{ "Z-down",       SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellZdown,   Spell::PARAM_NONE,    5 }
 };
 
-#define N_SPELLS (sizeof(spells) / sizeof(spells[0]))
-
-void spellSetEffectCallback(SpellEffectCallback callback) {
-	spellEffectCallback = callback;
-}
+/*-------------------------------------------------------------------*/
 
 Ingredients::Ingredients() {
 	memset(_reagents, 0, sizeof(_reagents));
@@ -208,37 +171,53 @@ void Ingredients::multiply(int batches) {
 	}
 }
 
-const char *spellGetName(uint spell) {
+/*-------------------------------------------------------------------*/
+
+Spells *g_spells;
+
+Spells::Spells() : spellEffectCallback(nullptr) {
+	g_spells = this;
+}
+
+Spells::~Spells() {
+	g_spells = nullptr;
+}
+
+void Spells::spellSetEffectCallback(SpellEffectCallback callback) {
+	spellEffectCallback = callback;
+}
+
+const char *Spells::spellGetName(uint spell) const {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 
-	return spells[spell]._name;
+	return SPELL_LIST[spell]._name;
 }
 
-int spellGetRequiredMP(uint spell) {
+int Spells::spellGetRequiredMP(uint spell) const {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 
-	return spells[spell]._mp;
+	return SPELL_LIST[spell]._mp;
 }
 
-LocationContext spellGetContext(uint spell) {
+LocationContext Spells::spellGetContext(uint spell) const {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 
-	return spells[spell]._context;
+	return SPELL_LIST[spell]._context;
 }
 
-TransportContext spellGetTransportContext(uint spell) {
+TransportContext Spells::spellGetTransportContext(uint spell) const {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 
-	return spells[spell]._transportContext;
+	return SPELL_LIST[spell]._transportContext;
 }
 
-Common::String spellGetErrorMessage(uint spell, SpellCastError error) {
+Common::String Spells::spellGetErrorMessage(uint spell, SpellCastError error) {
 	uint i;
 	SpellCastError err = error;
 
 	/* try to find a more specific error message */
 	if (err == CASTERR_WRONGCONTEXT) {
-		switch (spells[spell]._context) {
+		switch (SPELL_LIST[spell]._context) {
 		case CTX_COMBAT:
 			err = CASTERR_COMBATONLY;
 			break;
@@ -254,19 +233,15 @@ Common::String spellGetErrorMessage(uint spell, SpellCastError error) {
 	}
 
 	/* find the message that we're looking for and return it! */
-	for (i = 0; i < sizeof(spellErrorMsgs) / sizeof(spellErrorMsgs[0]); i++) {
-		if (err == spellErrorMsgs[i].err)
-			return Common::String(spellErrorMsgs[i].msg);
+	for (i = 0; i < sizeof(SPELL_ERROR_MSGS) / sizeof(SPELL_ERROR_MSGS[0]); i++) {
+		if (err == SPELL_ERROR_MSGS[i].err)
+			return Common::String(SPELL_ERROR_MSGS[i].msg);
 	}
 
 	return Common::String();
 }
 
-/**
- * Mix reagents for a spell.  Fails and returns false if the reagents
- * selected were not correct.
- */
-int spellMix(uint spell, const Ingredients *ingredients) {
+int Spells::spellMix(uint spell, const Ingredients *ingredients) {
 	int regmask, reg;
 
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
@@ -277,7 +252,7 @@ int spellMix(uint spell, const Ingredients *ingredients) {
 			regmask |= (1 << reg);
 	}
 
-	if (regmask != spells[spell]._components)
+	if (regmask != SPELL_LIST[spell]._components)
 		return 0;
 
 	g_ultima->_saveGame->_mixtures[spell]++;
@@ -285,42 +260,33 @@ int spellMix(uint spell, const Ingredients *ingredients) {
 	return 1;
 }
 
-Spell::Param spellGetParamType(uint spell) {
+Spell::Param Spells::spellGetParamType(uint spell) const {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 
-	return spells[spell]._paramType;
+	return SPELL_LIST[spell]._paramType;
 }
 
-/**
- * Checks some basic prerequistes for casting a spell.  Returns an
- * error if no mixture is available, the context is invalid, or the
- * character doesn't have enough magic points.
- */
-SpellCastError spellCheckPrerequisites(uint spell, int character) {
+SpellCastError Spells::spellCheckPrerequisites(uint spell, int character) {
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
 	ASSERT(character >= 0 && character < g_ultima->_saveGame->_members, "character out of range: %d", character);
 
 	if (g_ultima->_saveGame->_mixtures[spell] == 0)
 		return CASTERR_NOMIX;
 
-	if ((g_context->_location->_context & spells[spell]._context) == 0)
+	if ((g_context->_location->_context & SPELL_LIST[spell]._context) == 0)
 		return CASTERR_WRONGCONTEXT;
 
-	if ((g_context->_transportContext & spells[spell]._transportContext) == 0)
+	if ((g_context->_transportContext & SPELL_LIST[spell]._transportContext) == 0)
 		return CASTERR_FAILED;
 
-	if (g_context->_party->member(character)->getMp() < spells[spell]._mp)
+	if (g_context->_party->member(character)->getMp() < SPELL_LIST[spell]._mp)
 		return CASTERR_MPTOOLOW;
 
 	return CASTERR_NOERROR;
 }
 
-/**
- * Casts spell.  Fails and returns false if the spell cannot be cast.
- * The error code is updated with the reason for failure.
- */
-bool spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect) {
-	int subject = (spells[spell]._paramType == Spell::PARAM_PLAYER) ? param : -1;
+bool Spells::spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect) {
+	int subject = (SPELL_LIST[spell]._paramType == Spell::PARAM_PLAYER) ? param : -1;
 	PartyMember *p = g_context->_party->member(character);
 
 	ASSERT(spell < N_SPELLS, "invalid spell: %d", spell);
@@ -341,21 +307,20 @@ bool spellCast(uint spell, int character, int param, SpellCastError *error, bool
 	}
 
 	// subtract the mp needed for the spell
-	p->adjustMp(-spells[spell]._mp);
+	p->adjustMp(-SPELL_LIST[spell]._mp);
 
 	if (spellEffect) {
 		int time;
 		/* recalculate spell speed - based on 5/sec */
 		float MP_OF_LARGEST_SPELL = 45;
-		int spellMp = spells[spell]._mp;
+		int spellMp = SPELL_LIST[spell]._mp;
 		time = int(10000.0 / settings._spellEffectSpeed  *  spellMp / MP_OF_LARGEST_SPELL);
 		soundPlay(SOUND_PREMAGIC_MANA_JUMBLE, false, time);
 		EventHandler::wait_msecs(time);
-
-		(*spellEffectCallback)(spell + 'a', subject, SOUND_MAGIC);
+		g_spells->spellEffect(spell + 'a', subject, SOUND_MAGIC);
 	}
 
-	if (!(*spells[spell]._spellFunc)(param)) {
+	if (!(g_spells->*SPELL_LIST[spell]._spellFunc)(param)) {
 		*error = CASTERR_FAILED;
 		return false;
 	}
@@ -363,15 +328,12 @@ bool spellCast(uint spell, int character, int param, SpellCastError *error, bool
 	return true;
 }
 
-CombatController *spellCombatController() {
+CombatController *Spells::spellCombatController() {
 	CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
 	return cc;
 }
 
-/**
- * Makes a special magic ranged attack in the given direction
- */
-void spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage) {
+void Spells::spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage) {
 	CombatController *controller = spellCombatController();
 	PartyMemberVector *party = controller->getParty();
 
@@ -389,7 +351,7 @@ void spellMagicAttack(const Common::String &tilename, Direction dir, int minDama
 	}
 }
 
-bool spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage) {
+bool Spells::spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage) {
 	bool objectHit = false;
 //    int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
 	CombatMap *cm = getCombatMap();
@@ -415,7 +377,7 @@ bool spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDama
 	return objectHit;
 }
 
-static int spellAwaken(int player) {
+int Spells::spellAwaken(int player) {
 	ASSERT(player < 8, "player out of range: %d", player);
 	PartyMember *p = g_context->_party->member(player);
 
@@ -427,7 +389,7 @@ static int spellAwaken(int player) {
 	return 0;
 }
 
-static int spellBlink(int dir) {
+int Spells::spellBlink(int dir) {
 	int i,
 	    failed = 0,
 	    distance,
@@ -476,14 +438,14 @@ static int spellBlink(int dir) {
 	return (failed ? 0 : 1);
 }
 
-static int spellCure(int player) {
+int Spells::spellCure(int player) {
 	ASSERT(player < 8, "player out of range: %d", player);
 
 	GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
 	return g_context->_party->member(player)->heal(HT_CURE);
 }
 
-static int spellDispel(int dir) {
+int Spells::spellDispel(int dir) {
 	MapTile *tile;
 	MapCoords field;
 
@@ -541,7 +503,7 @@ static int spellDispel(int dir) {
 	return 1;
 }
 
-static int spellEField(int param) {
+int Spells::spellEField(int param) {
 	MapTile fieldTile(0);
 	int fieldType;
 	int dir;
@@ -603,12 +565,12 @@ static int spellEField(int param) {
 	return 1;
 }
 
-static int spellFireball(int dir) {
+int Spells::spellFireball(int dir) {
 	spellMagicAttack("hit_flash", (Direction)dir, 24, 128);
 	return 1;
 }
 
-static int spellGate(int phase) {
+int Spells::spellGate(int phase) {
 	const Coords *moongate;
 
 	GameController::flashTile(g_context->_location->_coords, "moongate", 2);
@@ -620,7 +582,7 @@ static int spellGate(int phase) {
 	return 1;
 }
 
-static int spellHeal(int player) {
+int Spells::spellHeal(int player) {
 	ASSERT(player < 8, "player out of range: %d", player);
 
 	GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
@@ -628,58 +590,58 @@ static int spellHeal(int player) {
 	return 1;
 }
 
-static int spellIceball(int dir) {
+int Spells::spellIceball(int dir) {
 	spellMagicAttack("magic_flash", (Direction)dir, 32, 224);
 	return 1;
 }
 
-static int spellJinx(int unused) {
+int Spells::spellJinx(int unused) {
 	g_context->_aura->set(Aura::JINX, 10);
 	return 1;
 }
 
-static int spellKill(int dir) {
+int Spells::spellKill(int dir) {
 	spellMagicAttack("whirlpool", (Direction)dir, -1, 232);
 	return 1;
 }
 
-static int spellLight(int unused) {
+int Spells::spellLight(int unused) {
 	g_context->_party->lightTorch(100, false);
 	return 1;
 }
 
-static int spellMMissle(int dir) {
+int Spells::spellMMissle(int dir) {
 	spellMagicAttack("miss_flash", (Direction)dir, 64, 16);
 	return 1;
 }
 
-static int spellNegate(int unused) {
+int Spells::spellNegate(int unused) {
 	g_context->_aura->set(Aura::NEGATE, 10);
 	return 1;
 }
 
-static int spellOpen(int unused) {
+int Spells::spellOpen(int unused) {
 	g_debugger->getChest();
 	return 1;
 }
 
-static int spellProtect(int unused) {
+int Spells::spellProtect(int unused) {
 	g_context->_aura->set(Aura::PROTECTION, 10);
 	return 1;
 }
 
-static int spellRez(int player) {
+int Spells::spellRez(int player) {
 	ASSERT(player < 8, "player out of range: %d", player);
 
 	return g_context->_party->member(player)->heal(HT_RESURRECT);
 }
 
-static int spellQuick(int unused) {
+int Spells::spellQuick(int unused) {
 	g_context->_aura->set(Aura::QUICKNESS, 10);
 	return 1;
 }
 
-static int spellSleep(int unused) {
+int Spells::spellSleep(int unused) {
 	CombatMap *cm = getCombatMap();
 	CreatureVector creatures = cm->getCreatures();
 	CreatureVector::iterator i;
@@ -702,7 +664,7 @@ static int spellSleep(int unused) {
 	return 1;
 }
 
-static int spellTremor(int unused) {
+int Spells::spellTremor(int unused) {
 	CombatController *ct = spellCombatController();
 	CreatureVector creatures = ct->getMap()->getCreatures();
 	CreatureVector::iterator i;
@@ -740,7 +702,7 @@ static int spellTremor(int unused) {
 	return 1;
 }
 
-static int spellUndead(int unused) {
+int Spells::spellUndead(int unused) {
 	CombatController *ct = spellCombatController();
 	CreatureVector creatures = ct->getMap()->getCreatures();
 	CreatureVector::iterator i;
@@ -754,17 +716,17 @@ static int spellUndead(int unused) {
 	return 1;
 }
 
-static int spellView(int unsued) {
+int Spells::spellView(int unsued) {
 	peer(false);
 	return 1;
 }
 
-static int spellWinds(int fromdir) {
+int Spells::spellWinds(int fromdir) {
 	g_context->_windDirection = fromdir;
 	return 1;
 }
 
-static int spellXit(int unused) {
+int Spells::spellXit(int unused) {
 	if (!g_context->_location->_map->isWorldMap()) {
 		g_screen->screenMessage("Leaving...\n");
 		g_game->exitToParentMap();
@@ -774,7 +736,7 @@ static int spellXit(int unused) {
 	return 0;
 }
 
-static int spellYup(int unused) {
+int Spells::spellYup(int unused) {
 	MapCoords coords = g_context->_location->_coords;
 	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
 
@@ -803,7 +765,7 @@ static int spellYup(int unused) {
 	return 0;
 }
 
-static int spellZdown(int unused) {
+int Spells::spellZdown(int unused) {
 	MapCoords coords = g_context->_location->_coords;
 	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
 	assert(dungeon);
@@ -828,8 +790,8 @@ static int spellZdown(int unused) {
 	return 0;
 }
 
-const Spell *getSpell(int i) {
-	return &spells[i];
+const Spell *Spells::getSpell(int i) const {
+	return &SPELL_LIST[i];
 }
 
 } // End of namespace Ultima4
diff --git a/engines/ultima/ultima4/game/spell.h b/engines/ultima/ultima4/game/spell.h
index 231e19ab26..6de614c9c3 100644
--- a/engines/ultima/ultima4/game/spell.h
+++ b/engines/ultima/ultima4/game/spell.h
@@ -44,7 +44,9 @@ enum SpellCastError {
 	CASTERR_WORLDMAPONLY       /* e.g. spell must be cast on the world map */
 };
 
-/* Field types for the Energy field spell */
+/**
+ * Field types for the Energy field spell
+ */
 enum EnergyFieldType {
 	ENERGYFIELD_NONE,
 	ENERGYFIELD_FIRE,
@@ -70,44 +72,124 @@ private:
 	unsigned short _reagents[REAG_MAX];
 };
 
+class Spells;
+typedef int (Spells::*SpellProc)(int);
+
 struct Spell {
 	enum Param {
-		PARAM_NONE,             /* none */
-		PARAM_PLAYER,           /* number of a player required */
-		PARAM_DIR,              /* direction required */
-		PARAM_TYPEDIR,          /* type of field and direction required (energy field) */
-		PARAM_PHASE,            /* phase required (gate) */
-		PARAM_FROMDIR           /* direction from required (winds) */
+		PARAM_NONE,             ///< None
+		PARAM_PLAYER,           ///< number of a player required
+		PARAM_DIR,              ///< direction required
+		PARAM_TYPEDIR,          ///< type of field and direction required (energy field)
+		PARAM_PHASE,            ///< phase required (gate)
+		PARAM_FROMDIR           ///< direction from required (winds)
 	};
 
 	enum SpecialEffects {
-		SFX_NONE,               /* none */
-		SFX_INVERT,             /* invert the screen (moongates, most normal spells) */
-		SFX_TREMOR              /* tremor spell */
+		SFX_NONE,               ///< none
+		SFX_INVERT,             ///< invert the screen (moongates, most normal spells)
+		SFX_TREMOR              ///< tremor spell
 	};
 
 	const char *_name;
 	int _components;
 	LocationContext _context;
 	TransportContext _transportContext;
-	int (*_spellFunc)(int);
+	SpellProc _spellFunc;
 	Param _paramType;
 	int _mp;
 };
 
 typedef void (*SpellEffectCallback)(int spell, int player, Sound sound);
+#define N_SPELLS 26
+
+class Spells {
+private:
+	static const Spell SPELL_LIST[N_SPELLS];
+	SpellEffectCallback spellEffectCallback;
+private:
+	int spellAwaken(int player);
+	int spellBlink(int dir);
+	int spellCure(int player);
+	int spellDispel(int dir);
+	int spellEField(int param);
+	int spellFireball(int dir);
+	int spellGate(int phase);
+	int spellHeal(int player);
+	int spellIceball(int dir);
+	int spellJinx(int unused);
+	int spellKill(int dir);
+	int spellLight(int unused);
+	int spellMMissle(int dir);
+	int spellNegate(int unused);
+	int spellOpen(int unused);
+	int spellProtect(int unused);
+	int spellRez(int player);
+	int spellQuick(int unused);
+	int spellSleep(int unused);
+	int spellTremor(int unused);
+	int spellUndead(int unused);
+	int spellView(int unsued);
+	int spellWinds(int fromdir);
+	int spellXit(int unused);
+	int spellYup(int unused);
+	int spellZdown(int unused);
+private:
+	CombatController *spellCombatController();
+
+	/**
+	 * Makes a special magic ranged attack in the given direction
+	 */
+	void spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage);
+	bool spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage);
+
+	LocationContext spellGetContext(uint spell) const;
+	TransportContext spellGetTransportContext(uint spell) const;
+public:
+	/**
+	 * Constructor
+	 */
+	Spells();
+
+	/**
+	 * Destructor
+	 */
+	~Spells();
+
+	void spellSetEffectCallback(SpellEffectCallback callback);
+
+	void spellEffect(int spell, int player, Sound sound) {
+		(spellEffectCallback)(spell, player, sound);
+	}
+
+	/**
+	 * Mix reagents for a spell.  Fails and returns false if the reagents
+	 * selected were not correct.
+	 */
+	int spellMix(uint spell, const Ingredients *ingredients);
+
+	/**
+	 * Casts spell.  Fails and returns false if the spell cannot be cast.
+	 * The error code is updated with the reason for failure.
+	 */
+	bool spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect);
+
+	Common::String spellGetErrorMessage(uint spell, SpellCastError error);
+
+	const char *spellGetName(uint spell) const;
+
+	/**
+	 * Checks some basic prerequistes for casting a spell.  Returns an
+	 * error if no mixture is available, the context is invalid, or the
+	 * character doesn't have enough magic points.
+	 */
+	SpellCastError spellCheckPrerequisites(uint spell, int character);
+	Spell::Param spellGetParamType(uint spell) const;
+	int spellGetRequiredMP(uint spell) const;
+	const Spell *getSpell(int i) const;
+};
 
-void spellSetEffectCallback(SpellEffectCallback callback);
-const char *spellGetName(uint spell);
-int spellGetRequiredMP(uint spell);
-LocationContext spellGetContext(uint spell);
-TransportContext spellGetTransportContext(uint spell);
-Common::String spellGetErrorMessage(uint spell, SpellCastError error);
-int spellMix(uint spell, const Ingredients *ingredients);
-Spell::Param spellGetParamType(uint spell);
-SpellCastError spellCheckPrerequisites(uint spell, int character);
-bool spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect);
-const Spell *getSpell(int i);
+extern Spells *g_spells;
 
 } // End of namespace Ultima4
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima4/ultima4.cpp b/engines/ultima/ultima4/ultima4.cpp
index 7eb04edb25..e8cdcfa250 100644
--- a/engines/ultima/ultima4/ultima4.cpp
+++ b/engines/ultima/ultima4/ultima4.cpp
@@ -62,7 +62,8 @@ Ultima4Engine::Ultima4Engine(OSystem *syst, const Ultima::UltimaGameDescription
 		_dialogueLoaders(nullptr), _game(nullptr), _items(nullptr), _music(nullptr),
 		_imageLoaders(nullptr), _mapLoaders(nullptr), _moongates(nullptr),
 		_responseParts(nullptr), _saveGame(nullptr), _screen(nullptr), _shrines(nullptr),
-		_tileMaps(nullptr), _tileRules(nullptr), _tileSets(nullptr), _weapons(nullptr) {
+		_spells(nullptr), _tileMaps(nullptr), _tileRules(nullptr), _tileSets(nullptr),
+		_weapons(nullptr) {
 	g_ultima = this;
 	g_armors = nullptr;
 	g_codex = nullptr;
@@ -75,6 +76,7 @@ Ultima4Engine::Ultima4Engine(OSystem *syst, const Ultima::UltimaGameDescription
 	g_responseParts = nullptr;
 	g_screen = nullptr;
 	g_shrines = nullptr;
+	g_spells = nullptr;
 	g_tileMaps = nullptr;
 	g_tileRules = nullptr;
 	g_tileSets = nullptr;
@@ -98,6 +100,7 @@ Ultima4Engine::~Ultima4Engine() {
 	delete _saveGame;
 	delete _screen;
 	delete _shrines;
+	delete _spells;
 	delete _tileMaps;
 	delete _tileRules;
 	delete _tileSets;
@@ -127,6 +130,7 @@ bool Ultima4Engine::initialize() {
 	_screen = new Screen();
 	_screen->init();
 	_shrines = new Shrines();
+	_spells = new Spells();
 	_tileRules = new TileRules();
 	_tileSets = new TileSets();
 	_tileMaps = new TileMaps();
diff --git a/engines/ultima/ultima4/ultima4.h b/engines/ultima/ultima4/ultima4.h
index e5cbdbf51f..4ba9a7180f 100644
--- a/engines/ultima/ultima4/ultima4.h
+++ b/engines/ultima/ultima4/ultima4.h
@@ -45,6 +45,7 @@ class ResponseParts;
 struct SaveGame;
 class Screen;
 class Shrines;
+class Spells;
 class TileMaps;
 class TileRules;
 class TileSets;
@@ -82,6 +83,7 @@ public:
 	SaveGame *_saveGame;
 	Screen *_screen;
 	Shrines *_shrines;
+	Spells *_spells;
 	TileMaps *_tileMaps;
 	TileRules *_tileRules;
 	TileSets *_tileSets;




More information about the Scummvm-git-logs mailing list