1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "ultima/ultima4/game/item.h"
24 #include "ultima/ultima4/game/codex.h"
25 #include "ultima/ultima4/game/game.h"
26 #include "ultima/ultima4/game/names.h"
27 #include "ultima/ultima4/game/player.h"
28 #include "ultima/ultima4/game/portal.h"
29 #include "ultima/ultima4/game/context.h"
30 #include "ultima/ultima4/game/weapon.h"
31 #include "ultima/ultima4/controllers/alpha_action_controller.h"
32 #include "ultima/ultima4/controllers/combat_controller.h"
33 #include "ultima/ultima4/core/utils.h"
34 #include "ultima/ultima4/filesys/savegame.h"
35 #include "ultima/ultima4/gfx/screen.h"
36 #include "ultima/ultima4/map/annotation.h"
37 #include "ultima/ultima4/map/dungeon.h"
38 #include "ultima/ultima4/map/location.h"
39 #include "ultima/ultima4/map/map.h"
40 #include "ultima/ultima4/map/mapmgr.h"
41 #include "ultima/ultima4/map/tileset.h"
42 #include "ultima/ultima4/ultima4.h"
43 
44 namespace Ultima {
45 namespace Ultima4 {
46 
47 Items *g_items;
48 
49 const ItemLocation Items::ITEMS[N_ITEMS] = {
50 	{
51 		"Mandrake Root", nullptr, "mandrake1",
52 		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
53 	},
54 	{
55 		"Mandrake Root", nullptr, "mandrake2",
56 		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_MANDRAKE, SC_NEWMOONS | SC_REAGENTDELAY
57 	},
58 	{
59 		"Nightshade", nullptr, "nightshade1",
60 		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
61 	},
62 	{
63 		"Nightshade", nullptr, "nightshade2",
64 		&Items::isReagentInInventory, &Items::putReagentInInventory, nullptr, REAG_NIGHTSHADE, SC_NEWMOONS | SC_REAGENTDELAY
65 	},
66 	{
67 		"the Bell of Courage", "bell", "bell",
68 		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BELL, 0
69 	},
70 	{
71 		"the Book of Truth", "book", "book",
72 		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_BOOK, 0
73 	},
74 	{
75 		"the Candle of Love", "candle", "candle",
76 		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useBBC, ITEM_CANDLE, 0
77 	},
78 	{
79 		"A Silver Horn", "horn", "horn",
80 		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useHorn, ITEM_HORN, 0
81 	},
82 	{
83 		"the Wheel from the H.M.S. Cape", "wheel", "wheel",
84 		&Items::isItemInInventory, &Items::putItemInInventory, &Items::useWheel, ITEM_WHEEL, 0
85 	},
86 	{
87 		"the Skull of Modain the Wizard", "skull", "skull",
88 		&Items::isSkullInInventory, &Items::putItemInInventory, &Items::useSkull, ITEM_SKULL, SC_NEWMOONS
89 	},
90 	{
91 		"the Red Stone", "red", "redstone",
92 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_RED, 0
93 	},
94 	{
95 		"the Orange Stone", "orange", "orangestone",
96 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_ORANGE, 0
97 	},
98 	{
99 		"the Yellow Stone", "yellow", "yellowstone",
100 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_YELLOW, 0
101 	},
102 	{
103 		"the Green Stone", "green", "greenstone",
104 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_GREEN, 0
105 	},
106 	{
107 		"the Blue Stone", "blue", "bluestone",
108 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLUE, 0
109 	},
110 	{
111 		"the Purple Stone", "purple", "purplestone",
112 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_PURPLE, 0
113 	},
114 	{
115 		"the Black Stone", "black", "blackstone",
116 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_BLACK, SC_NEWMOONS
117 	},
118 	{
119 		"the White Stone", "white", "whitestone",
120 		&Items::isStoneInInventory, &Items::putStoneInInventory, &Items::useStone, STONE_WHITE, 0
121 	},
122 
123 	/* handlers for using generic objects */
124 	{ nullptr, "stone",  nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
125 	{ nullptr, "stones", nullptr, &Items::isStoneInInventory, nullptr, &Items::useStone, -1, 0 },
126 	{ nullptr, "key",    nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
127 	{ nullptr, "keys",   nullptr, &Items::isItemInInventory, nullptr, &Items::useKey, (ITEM_KEY_C | ITEM_KEY_L | ITEM_KEY_T), 0 },
128 
129 	/* Lycaeum telescope */
130 	{ nullptr, nullptr, "telescope", nullptr, &Items::useTelescope, nullptr, 0, 0 },
131 
132 	{
133 		"Mystic Armor", nullptr, "mysticarmor",
134 		&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, ARMR_MYSTICROBES, SC_FULLAVATAR
135 	},
136 	{
137 		"Mystic Swords", nullptr, "mysticswords",
138 		&Items::isMysticInInventory, &Items::putMysticInInventory, nullptr, WEAP_MYSTICSWORD, SC_FULLAVATAR
139 	},
140 	{
141 		"the sulfury remains of an ancient Sosarian Laser Gun. It turns to ash in your fingers", nullptr, "lasergun", // lol, where'd that come from?
142 		//Looks like someone was experimenting with "maps.xml". It effectively increments sulfur ash by one due to '16' being an invalid weapon index.
143 		&Items::isWeaponInInventory, &Items::putWeaponInInventory, nullptr, 16, 0
144 	},
145 	{
146 		"the rune of Honesty", nullptr, "honestyrune",
147 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONESTY, 0
148 	},
149 	{
150 		"the rune of Compassion", nullptr, "compassionrune",
151 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_COMPASSION, 0
152 	},
153 	{
154 		"the rune of Valor", nullptr, "valorrune",
155 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_VALOR, 0
156 	},
157 	{
158 		"the rune of Justice", nullptr, "justicerune",
159 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_JUSTICE, 0
160 	},
161 	{
162 		"the rune of Sacrifice", nullptr, "sacrificerune",
163 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SACRIFICE, 0
164 	},
165 	{
166 		"the rune of Honor", nullptr, "honorrune",
167 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HONOR, 0
168 	},
169 	{
170 		"the rune of Spirituality", nullptr, "spiritualityrune",
171 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_SPIRITUALITY, 0
172 	},
173 	{
174 		"the rune of Humility", nullptr, "humilityrune",
175 		&Items::isRuneInInventory, &Items::putRuneInInventory, nullptr, RUNE_HUMILITY, 0
176 	}
177 };
178 
Items()179 Items::Items() : destroyAllCreaturesCallback(nullptr),
180 		needStoneNames(0), stoneMask(0) {
181 	g_items = this;
182 }
183 
~Items()184 Items::~Items() {
185 	g_items = nullptr;
186 }
187 
setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback)188 void Items::setDestroyAllCreaturesCallback(DestroyAllCreaturesCallback callback) {
189 	destroyAllCreaturesCallback = callback;
190 }
191 
isRuneInInventory(int virt)192 bool Items::isRuneInInventory(int virt) {
193 	return g_ultima->_saveGame->_runes & virt;
194 }
195 
putRuneInInventory(int virt)196 void Items::putRuneInInventory(int virt) {
197 	g_context->_party->member(0)->awardXp(100);
198 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
199 	g_ultima->_saveGame->_runes |= virt;
200 #ifdef IOS_ULTIMA4
201 	Common::String virtueName;
202 	switch (virt) {
203 	default:
204 	case RUNE_HONESTY:
205 		virtueName = "Honesty";
206 		break;
207 	case RUNE_HONOR:
208 		virtueName = "Honor";
209 		break;
210 	case RUNE_HUMILITY:
211 		virtueName = "Humility";
212 		break;
213 	case RUNE_JUSTICE:
214 		virtueName = "Justice";
215 		break;
216 	case RUNE_SACRIFICE:
217 		virtueName = "Sacrifice";
218 		break;
219 	case RUNE_SPIRITUALITY:
220 		virtueName = "Spirituality";
221 		break;
222 	case RUNE_VALOR:
223 		virtueName = "Valor";
224 		break;
225 	case RUNE_COMPASSION:
226 		virtueName = "Compassion";
227 		break;
228 	}
229 	U4IOS::testFlightPassCheckPoint("Player got stone: " + virtueName);
230 #endif
231 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
232 }
233 
isStoneInInventory(int virt)234 bool Items::isStoneInInventory(int virt) {
235 	/* generic test: does the party have any stones yet? */
236 	if (virt == -1)
237 		return (g_ultima->_saveGame->_stones > 0);
238 	/* specific test: does the party have a specific stone? */
239 	else
240 		return g_ultima->_saveGame->_stones & virt;
241 }
242 
putStoneInInventory(int virt)243 void Items::putStoneInInventory(int virt) {
244 	g_context->_party->member(0)->awardXp(200);
245 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
246 	g_ultima->_saveGame->_stones |= virt;
247 #ifdef IOS_ULTIMA4
248 	Common::String stoneName;
249 	switch (virt) {
250 	default:
251 	case STONE_BLACK:
252 		stoneName = "Black";
253 		break;
254 	case STONE_BLUE:
255 		stoneName = "Blue";
256 		break;
257 	case STONE_GREEN:
258 		stoneName = "Green";
259 		break;
260 	case STONE_ORANGE:
261 		stoneName = "Orange";
262 		break;
263 	case STONE_PURPLE:
264 		stoneName = "Purple";
265 		break;
266 	case STONE_RED:
267 		stoneName = "Red";
268 		break;
269 	case STONE_WHITE:
270 		stoneName = "White";
271 		break;
272 	case STONE_YELLOW:
273 		stoneName = "Yellow";
274 		break;
275 	}
276 	U4IOS::testFlightPassCheckPoint("Player got rune: " + stoneName);
277 #endif
278 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
279 }
280 
isItemInInventory(int item)281 bool Items::isItemInInventory(int item) {
282 	return g_ultima->_saveGame->_items & item;
283 }
284 
isSkullInInventory(int unused)285 bool Items::isSkullInInventory(int unused) {
286 	return (g_ultima->_saveGame->_items & (ITEM_SKULL | ITEM_SKULL_DESTROYED));
287 }
288 
putItemInInventory(int item)289 void Items::putItemInInventory(int item) {
290 	g_context->_party->member(0)->awardXp(400);
291 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
292 	g_ultima->_saveGame->_items |= item;
293 #ifdef IOS_ULTIMA4
294 	Common::String itemName;
295 	switch (item) {
296 	default:
297 	case ITEM_BELL:
298 		itemName = "Bell";
299 		break;
300 	case ITEM_BOOK:
301 		itemName = "Book";
302 		break;
303 	case ITEM_CANDLE:
304 		itemName = "Candle";
305 		break;
306 	case ITEM_HORN:
307 		itemName = "Horn";
308 		break;
309 	case ITEM_KEY_C:
310 		itemName = "Key Courage";
311 		break;
312 	case ITEM_KEY_L:
313 		itemName = "Key Love";
314 		break;
315 	case ITEM_KEY_T:
316 		itemName = "Key Truth";
317 		break;
318 	case ITEM_SKULL:
319 		itemName = "Skull";
320 		break;
321 	case ITEM_WHEEL:
322 		itemName = "Wheel";
323 		break;
324 
325 	}
326 	U4IOS::testFlightPassCheckPoint("Player got rune: " + itemName);
327 #endif
328 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
329 }
330 
useBBC(int item)331 void Items::useBBC(int item) {
332 	Coords abyssEntrance(0xe9, 0xe9);
333 	/* on top of the Abyss entrance */
334 	if (g_context->_location->_coords == abyssEntrance) {
335 		/* must use bell first */
336 		if (item == ITEM_BELL) {
337 #ifdef IOS_ULTIMA4
338 			U4IOS::testFlightPassCheckPoint("The Bell rings on and on!");
339 #endif
340 			g_screen->screenMessage("\nThe Bell rings on and on!\n");
341 			g_ultima->_saveGame->_items |= ITEM_BELL_USED;
342 		}
343 		/* then the book */
344 		else if ((item == ITEM_BOOK) && (g_ultima->_saveGame->_items & ITEM_BELL_USED)) {
345 #ifdef IOS_ULTIMA4
346 			U4IOS::testFlightPassCheckPoint("The words resonate with the ringing!");
347 #endif
348 			g_screen->screenMessage("\nThe words resonate with the ringing!\n");
349 			g_ultima->_saveGame->_items |= ITEM_BOOK_USED;
350 		}
351 		/* then the candle */
352 		else if ((item == ITEM_CANDLE) && (g_ultima->_saveGame->_items & ITEM_BOOK_USED)) {
353 			g_screen->screenMessage("\nAs you light the Candle the Earth Trembles!\n");
354 #ifdef IOS_ULTIMA4
355 			U4IOS::testFlightPassCheckPoint("As you light the Candle the Earth Trembles!");
356 #endif
357 			g_ultima->_saveGame->_items |= ITEM_CANDLE_USED;
358 		} else {
359 			g_screen->screenMessage("\nHmm...No effect!\n");
360 		}
361 	}
362 	/* somewhere else */
363 	else {
364 		g_screen->screenMessage("\nHmm...No effect!\n");
365 	}
366 }
367 
useHorn(int item)368 void Items::useHorn(int item) {
369 	g_screen->screenMessage("\nThe Horn sounds an eerie tone!\n");
370 	g_context->_aura->set(Aura::HORN, 10);
371 }
372 
useWheel(int item)373 void Items::useWheel(int item) {
374 	if ((g_context->_transportContext == TRANSPORT_SHIP) && (g_ultima->_saveGame->_shipHull == 50)) {
375 		g_screen->screenMessage("\nOnce mounted, the Wheel glows with a blue light!\n");
376 		g_context->_party->setShipHull(99);
377 	} else {
378 		g_screen->screenMessage("\nHmm...No effect!\n");
379 	}
380 }
381 
useSkull(int item)382 void Items::useSkull(int item) {
383 	/* FIXME: check to see if the abyss must be opened first
384 	   for the skull to be *able* to be destroyed */
385 
386 	/* We do the check here instead of in the table, because we need to distinguish between a
387 	   never-found skull and a destroyed skull. */
388 	if (g_ultima->_saveGame->_items & ITEM_SKULL_DESTROYED) {
389 		g_screen->screenMessage("\nNone owned!\n");
390 		return;
391 	}
392 
393 	/* destroy the skull! pat yourself on the back */
394 	if (g_context->_location->_coords.x == 0xe9 && g_context->_location->_coords.y == 0xe9) {
395 		g_screen->screenMessage("\n\nYou cast the Skull of Mondain into the Abyss!\n");
396 #ifdef IOS_ULTIMA4
397 		U4IOS::testFlightPassCheckPoint("You cast the Skull of Mondain into the Abyss!");
398 #endif
399 
400 		g_ultima->_saveGame->_items = (g_ultima->_saveGame->_items & ~ITEM_SKULL) | ITEM_SKULL_DESTROYED;
401 		g_context->_party->adjustKarma(KA_DESTROYED_SKULL);
402 	}
403 
404 	/* use the skull... bad, very bad */
405 	else {
406 		g_screen->screenMessage("\n\nYou hold the evil Skull of Mondain the Wizard aloft...\n");
407 #ifdef IOS_ULTIMA4
408 		U4IOS::testFlightPassCheckPoint("You hold the evil Skull of Mondain the Wizard aloft...");
409 #endif
410 
411 		/* destroy all creatures */
412 		(*destroyAllCreaturesCallback)();
413 
414 		/* we don't lose the skull until we toss it into the abyss */
415 		//c->saveGame->_items = (c->saveGame->_items & ~ITEM_SKULL);
416 		g_context->_party->adjustKarma(KA_USED_SKULL);
417 	}
418 }
419 
useStone(int item)420 void Items::useStone(int item) {
421 	MapCoords coords;
422 	byte stone = static_cast<byte>(item);
423 
424 	static byte truth   = STONE_WHITE | STONE_PURPLE | STONE_GREEN  | STONE_BLUE;
425 	static byte love    = STONE_WHITE | STONE_YELLOW | STONE_GREEN  | STONE_ORANGE;
426 	static byte courage = STONE_WHITE | STONE_RED    | STONE_PURPLE | STONE_ORANGE;
427 	static byte *attr   = nullptr;
428 
429 	g_context->_location->getCurrentPosition(&coords);
430 
431 	/**
432 	 * Named a specific stone (after using "stone" or "stones")
433 	 */
434 	if (item != -1) {
435 		CombatMap *cm = getCombatMap();
436 
437 		if (needStoneNames) {
438 			/* named a stone while in a dungeon altar room */
439 			if (g_context->_location->_context & CTX_ALTAR_ROOM) {
440 				needStoneNames--;
441 
442 				switch (cm->getAltarRoom()) {
443 				case VIRT_TRUTH:
444 					attr = &truth;
445 					break;
446 				case VIRT_LOVE:
447 					attr = &love;
448 					break;
449 				case VIRT_COURAGE:
450 					attr = &courage;
451 					break;
452 				default:
453 					break;
454 				}
455 
456 				/* make sure we're in an altar room */
457 				if (attr) {
458 					/* we need to use the stone, and we haven't used it yet */
459 					if ((*attr & stone) && (stone & ~stoneMask))
460 						stoneMask |= stone;
461 					/* we already used that stone! */
462 					else if (stone & stoneMask) {
463 						g_screen->screenMessage("\nAlready used!\n");
464 						needStoneNames = 0;
465 						stoneMask = 0; /* reset the mask so you can try again */
466 						return;
467 					}
468 				} else {
469 					error("Not in an altar room!");
470 				}
471 				/* see if we have all the stones, if not, get more names! */
472 				if (attr && needStoneNames) {
473 					g_screen->screenMessage("\n%c:", 'E' - needStoneNames);
474 #ifdef IOS_ULTIMA4
475 					U4IOS::IOSConversationHelper::setIntroString("Which Color?");
476 #endif
477 					itemHandleStones(gameGetInput());
478 				}
479 				/* all the stones have been entered, verify them! */
480 				else {
481 					unsigned short key = 0xFFFF;
482 					switch (cm->getAltarRoom()) {
483 					case VIRT_TRUTH:
484 						key = ITEM_KEY_T;
485 						break;
486 					case VIRT_LOVE:
487 						key = ITEM_KEY_L;
488 						break;
489 					case VIRT_COURAGE:
490 						key = ITEM_KEY_C;
491 						break;
492 					default:
493 						break;
494 					}
495 
496 					/* in an altar room, named all of the stones, and don't have the key yet... */
497 					if (attr && (stoneMask == *attr) && !(g_ultima->_saveGame->_items & key)) {
498 #ifdef IOS_ULTIMA4
499 						Common::String keyName;
500 						switch (key) {
501 						case ITEM_KEY_C:
502 							keyName = "Key Courage";
503 							break;
504 						case ITEM_KEY_L:
505 							keyName = "Key Love";
506 							break;
507 						case ITEM_KEY_T:
508 							keyName = "Key Truth";
509 							break;
510 						}
511 						U4IOS::testFlightPassCheckPoint("Receive a key: " + keyName);
512 #endif
513 						g_screen->screenMessage("\nThou doth find one third of the Three Part Key!\n");
514 						g_ultima->_saveGame->_items |= key;
515 					} else {
516 						g_screen->screenMessage("\nHmm...No effect!\n");
517 					}
518 
519 					stoneMask = 0; /* reset the mask so you can try again */
520 				}
521 			} else {
522 				/* Otherwise, we're asking for a stone while in the abyss on top of an altar */
523 				/* see if they entered the correct stone */
524 				if (stone == (1 << g_context->_location->_coords.z)) {
525 					if (g_context->_location->_coords.z < 7) {
526 						/* replace the altar with a down-ladder */
527 						MapCoords pos;
528 						g_screen->screenMessage("\n\nThe altar changes before thyne eyes!\n");
529 						g_context->_location->getCurrentPosition(&pos);
530 						g_context->_location->_map->_annotations->add(pos, g_context->_location->_map->_tileSet->getByName("down_ladder")->getId());
531 					} else {
532 						// Start chamber of the codex sequence...
533 						g_codex->start();
534 					}
535 				} else {
536 					g_screen->screenMessage("\nHmm...No effect!\n");
537 				}
538 			}
539 		} else {
540 			g_screen->screenMessage("\nNot a Usable Item!\n");
541 			stoneMask = 0; /* reset the mask so you can try again */
542 		}
543 	}
544 
545 	/**
546 	 * in the abyss, on an altar to place the stones
547 	 */
548 	else if ((g_context->_location->_map->_id == MAP_ABYSS) &&
549 			(g_context->_location->_context & CTX_DUNGEON) &&
550 			(static_cast<Dungeon *>(g_context->_location->_map)->currentToken() == DUNGEON_ALTAR)) {
551 
552 		int virtueMask = getBaseVirtues((Virtue)g_context->_location->_coords.z);
553 		if (virtueMask > 0)
554 			g_screen->screenMessage("\n\nAs thou doth approach, a voice rings out: What virtue dost stem from %s?\n\n", getBaseVirtueName(virtueMask));
555 		else
556 			g_screen->screenMessage("\n\nA voice rings out:  What virtue exists independently of Truth, Love, and Courage?\n\n");
557 #ifdef IOS_ULTIMA4
558 		U4IOS::IOSConversationHelper::setIntroString("Which virtue?");
559 #endif
560 		Common::String virtue = gameGetInput();
561 
562 		if (scumm_strnicmp(virtue.c_str(), getVirtueName((Virtue)g_context->_location->_coords.z), 6) == 0) {
563 			/* now ask for stone */
564 			g_screen->screenMessage("\n\nThe Voice says: Use thy Stone.\n\nColor:\n");
565 			needStoneNames = 1;
566 #ifdef IOS_ULTIMA4
567 			U4IOS::IOSConversationHelper::setIntroString("Which color?");
568 #endif
569 			itemHandleStones(gameGetInput());
570 		} else {
571 			g_screen->screenMessage("\nHmm...No effect!\n");
572 		}
573 	}
574 
575 	/**
576 	 * in a dungeon altar room, on the altar
577 	 */
578 	else if ((g_context->_location->_context & CTX_ALTAR_ROOM) &&
579 	         coords.x == 5 && coords.y == 5) {
580 		needStoneNames = 4;
581 		g_screen->screenMessage("\n\nThere are holes for 4 stones.\nWhat colors:\nA:");
582 #ifdef IOS_ULTIMA4
583 		U4IOS::IOSConversationHelper::setIntroString("Which color?");
584 #endif
585 		itemHandleStones(gameGetInput());
586 	} else {
587 		g_screen->screenMessage("\nNo place to Use them!\n");
588 		// This used to say "\nNo place to Use them!\nHmm...No effect!\n"
589 		// That doesn't match U4DOS; does it match another?
590 	}
591 }
592 
useKey(int item)593 void Items::useKey(int item) {
594 	g_screen->screenMessage("\nNo place to Use them!\n");
595 }
596 
isMysticInInventory(int mystic)597 bool Items::isMysticInInventory(int mystic) {
598 	/* FIXME: you could feasibly get more mystic weapons and armor if you
599 	   have 8 party members and equip them all with everything,
600 	   then search for Mystic Weapons/Armor again
601 
602 	   or, you could just sell them all and search again.  What an easy
603 	   way to make some cash!
604 
605 	   This would be a good candidate for an xu4 "extended" savegame
606 	   format.
607 	*/
608 	if (mystic == WEAP_MYSTICSWORD)
609 		return g_ultima->_saveGame->_weapons[WEAP_MYSTICSWORD] > 0;
610 	else if (mystic == ARMR_MYSTICROBES)
611 		return g_ultima->_saveGame->_armor[ARMR_MYSTICROBES] > 0;
612 	else
613 		error("Invalid mystic item was tested in isMysticInInventory()");
614 	return false;
615 }
616 
putMysticInInventory(int mystic)617 void Items::putMysticInInventory(int mystic) {
618 	g_context->_party->member(0)->awardXp(400);
619 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
620 	if (mystic == WEAP_MYSTICSWORD)
621 		g_ultima->_saveGame->_weapons[WEAP_MYSTICSWORD] += 8;
622 	else if (mystic == ARMR_MYSTICROBES)
623 		g_ultima->_saveGame->_armor[ARMR_MYSTICROBES] += 8;
624 	else
625 		error("Invalid mystic item was added in putMysticInInventory()");
626 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
627 }
628 
isWeaponInInventory(int weapon)629 bool Items::isWeaponInInventory(int weapon) {
630 	if (g_ultima->_saveGame->_weapons[weapon])
631 		return true;
632 	else {
633 		for (int i = 0; i < g_context->_party->size(); i++) {
634 			if (g_context->_party->member(i)->getWeapon()->getType() == weapon)
635 				return true;
636 		}
637 	}
638 	return false;
639 }
640 
putWeaponInInventory(int weapon)641 void Items::putWeaponInInventory(int weapon) {
642 	g_ultima->_saveGame->_weapons[weapon]++;
643 }
644 
useTelescope(int notused)645 void Items::useTelescope(int notused) {
646 	g_screen->screenMessage("You see a knob\non the telescope\nmarked A-P\nYou Select:");
647 #ifdef IOS_ULTIMA4
648 	U4IOS::IOSConversationChoiceHelper telescopeHelper;
649 	telescopeHelper.updateChoices("abcdefghijklmnop ");
650 #endif
651 	int choice = AlphaActionController::get('p', "You Select:");
652 
653 	if (choice == -1)
654 		return;
655 
656 	gamePeerCity(choice, nullptr);
657 }
658 
isReagentInInventory(int reag)659 bool Items::isReagentInInventory(int reag) {
660 	return false;
661 }
662 
putReagentInInventory(int reag)663 void Items::putReagentInInventory(int reag) {
664 	g_context->_party->adjustKarma(KA_FOUND_ITEM);
665 	g_ultima->_saveGame->_reagents[reag] += xu4_random(8) + 2;
666 	g_ultima->_saveGame->_lastReagent = g_ultima->_saveGame->_moves & 0xF0;
667 
668 	if (g_ultima->_saveGame->_reagents[reag] > 99) {
669 		g_ultima->_saveGame->_reagents[reag] = 99;
670 		g_screen->screenMessage("Dropped some!\n");
671 	}
672 }
673 
itemConditionsMet(byte conditions)674 bool Items::itemConditionsMet(byte conditions) {
675 	int i;
676 
677 	if ((conditions & SC_NEWMOONS) &&
678 	        !(g_ultima->_saveGame->_trammelPhase == 0 && g_ultima->_saveGame->_feluccaPhase == 0))
679 		return false;
680 
681 	if (conditions & SC_FULLAVATAR) {
682 		for (i = 0; i < VIRT_MAX; i++) {
683 			if (g_ultima->_saveGame->_karma[i] != 0)
684 				return false;
685 		}
686 	}
687 
688 	if ((conditions & SC_REAGENTDELAY) &&
689 	        (g_ultima->_saveGame->_moves & 0xF0) == g_ultima->_saveGame->_lastReagent)
690 		return false;
691 
692 	return true;
693 }
694 
itemAtLocation(const Map * map,const Coords & coords)695 const ItemLocation *Items::itemAtLocation(const Map *map, const Coords &coords) {
696 	uint i;
697 	for (i = 0; i < N_ITEMS; i++) {
698 		if (!ITEMS[i]._locationLabel)
699 			continue;
700 		if (map->getLabel(ITEMS[i]._locationLabel) == coords &&
701 		        itemConditionsMet(ITEMS[i]._conditions))
702 			return &(ITEMS[i]);
703 	}
704 	return nullptr;
705 }
706 
itemUse(const Common::String & shortName)707 void Items::itemUse(const Common::String &shortName) {
708 	uint i;
709 	const ItemLocation *item = nullptr;
710 
711 	for (i = 0; i < N_ITEMS; i++) {
712 		if (ITEMS[i]._shortName &&
713 		        scumm_stricmp(ITEMS[i]._shortName, shortName.c_str()) == 0) {
714 
715 			item = &ITEMS[i];
716 
717 			/* item name found, see if we have that item in our inventory */
718 			if (!ITEMS[i]._isItemInInventory || (this->*(ITEMS[i]._isItemInInventory))(ITEMS[i]._data)) {
719 
720 				/* use the item, if we can! */
721 				if (!item || !item->_useItem)
722 					g_screen->screenMessage("\nNot a Usable item!\n");
723 				else
724 					(this->*(item->_useItem))(ITEMS[i]._data);
725 			} else
726 				g_screen->screenMessage("\nNone owned!\n");
727 
728 			/* we found the item, no need to keep searching */
729 			break;
730 		}
731 	}
732 
733 	/* item was not found */
734 	if (!item)
735 		g_screen->screenMessage("\nNot a Usable item!\n");
736 }
737 
itemHandleStones(const Common::String & color)738 void Items::itemHandleStones(const Common::String &color) {
739 	bool found = false;
740 
741 	for (int i = 0; i < 8; i++) {
742 		if (scumm_stricmp(color.c_str(), getStoneName((Virtue)i)) == 0 &&
743 		        isStoneInInventory(1 << i)) {
744 			found = true;
745 			itemUse(color.c_str());
746 		}
747 	}
748 
749 	if (!found) {
750 		g_screen->screenMessage("\nNone owned!\n");
751 		stoneMask = 0; /* make sure stone mask is reset */
752 	}
753 }
754 
isAbyssOpened(const Portal * p)755 bool Items::isAbyssOpened(const Portal *p) {
756 	/* make sure the bell, book and candle have all been used */
757 	int items = g_ultima->_saveGame->_items;
758 	int isopened = (items & ITEM_BELL_USED) && (items & ITEM_BOOK_USED) && (items & ITEM_CANDLE_USED);
759 
760 	if (!isopened)
761 		g_screen->screenMessage("Enter Can't!\n");
762 	return isopened;
763 }
764 
765 } // End of namespace Ultima4
766 } // End of namespace Ultima
767