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/game.h"
24 #include "ultima/ultima4/game/spell.h"
25 #include "ultima/ultima4/game/context.h"
26 #include "ultima/ultima4/game/creature.h"
27 #include "ultima/ultima4/game/moongate.h"
28 #include "ultima/ultima4/game/player.h"
29 #include "ultima/ultima4/controllers/combat_controller.h"
30 #include "ultima/ultima4/core/settings.h"
31 #include "ultima/ultima4/core/debugger.h"
32 #include "ultima/ultima4/core/utils.h"
33 #include "ultima/ultima4/events/event_handler.h"
34 #include "ultima/ultima4/gfx/screen.h"
35 #include "ultima/ultima4/map/annotation.h"
36 #include "ultima/ultima4/map/direction.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/tile.h"
42 #include "ultima/ultima4/map/tileset.h"
43 #include "ultima/ultima4/ultima4.h"
44 
45 namespace Ultima {
46 namespace Ultima4 {
47 
48 /* masks for reagents */
49 #define ASH (1 << REAG_ASH)
50 #define GINSENG (1 << REAG_GINSENG)
51 #define GARLIC (1 << REAG_GARLIC)
52 #define SILK (1 << REAG_SILK)
53 #define MOSS (1 << REAG_MOSS)
54 #define PEARL (1 << REAG_PEARL)
55 #define NIGHTSHADE (1 << REAG_NIGHTSHADE)
56 #define MANDRAKE (1 << REAG_MANDRAKE)
57 
58 /* spell error messages */
59 static const struct {
60 	SpellCastError err;
61 	const char *msg;
62 } SPELL_ERROR_MSGS[] = {
63 	{ CASTERR_NOMIX, "None Mixed!\n" },
64 	{ CASTERR_MPTOOLOW, "Not Enough MP!\n" },
65 	{ CASTERR_FAILED, "Failed!\n" },
66 	{ CASTERR_WRONGCONTEXT, "Not here!\n" },
67 	{ CASTERR_COMBATONLY, "Combat only!\nFailed!\n" },
68 	{ CASTERR_DUNGEONONLY, "Dungeon only!\nFailed!\n" },
69 	{ CASTERR_WORLDMAPONLY, "Outdoors only!\nFailed!\n" }
70 };
71 
72 const Spell Spells::SPELL_LIST[N_SPELLS] = {
73 	{ "Awaken",       GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &Spells::spellAwaken,  Spell::PARAM_PLAYER,  5 },
74 	{
75 		"Blink",        SILK | MOSS,              CTX_WORLDMAP,   TRANSPORT_FOOT_OR_HORSE,
76 		&Spells::spellBlink,   Spell::PARAM_DIR,     15
77 	},
78 	{ "Cure",         GINSENG | GARLIC,         CTX_ANY,        TRANSPORT_ANY,  &Spells::spellCure,    Spell::PARAM_PLAYER,  5 },
79 	{ "Dispell",      ASH | GARLIC | PEARL,     CTX_ANY,        TRANSPORT_ANY,  &Spells::spellDispel,  Spell::PARAM_DIR,     20 },
80 	{
81 		"Energy Field", ASH | SILK | PEARL, (LocationContext)(CTX_COMBAT | CTX_DUNGEON),
82 		TRANSPORT_ANY,  &Spells::spellEField,  Spell::PARAM_TYPEDIR, 10
83 	},
84 	{ "Fireball",     ASH | PEARL,              CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellFireball, Spell::PARAM_DIR,     15 },
85 	{
86 		"Gate",         ASH | PEARL | MANDRAKE,   CTX_WORLDMAP,   TRANSPORT_FOOT_OR_HORSE,
87 		&Spells::spellGate,    Spell::PARAM_PHASE,   40
88 	},
89 	{ "Heal",         GINSENG | SILK,           CTX_ANY,        TRANSPORT_ANY,  &Spells::spellHeal,    Spell::PARAM_PLAYER,  10 },
90 	{ "Iceball",      PEARL | MANDRAKE,         CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellIceball, Spell::PARAM_DIR,     20 },
91 	{
92 		"Jinx",         PEARL | NIGHTSHADE | MANDRAKE,
93 		CTX_ANY,        TRANSPORT_ANY,  &Spells::spellJinx,    Spell::PARAM_NONE,    30
94 	},
95 	{ "Kill",         PEARL | NIGHTSHADE,       CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellKill,    Spell::PARAM_DIR,     25 },
96 	{ "Light",        ASH,                      CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellLight,   Spell::PARAM_NONE,    5 },
97 	{ "Magic missile", ASH | PEARL,             CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellMMissle, Spell::PARAM_DIR,     5 },
98 	{ "Negate",       ASH | GARLIC | MANDRAKE,  CTX_ANY,        TRANSPORT_ANY,  &Spells::spellNegate,  Spell::PARAM_NONE,    20 },
99 	{ "Open",         ASH | MOSS,               CTX_ANY,        TRANSPORT_ANY,  &Spells::spellOpen,    Spell::PARAM_NONE,    5 },
100 	{ "Protection",   ASH | GINSENG | GARLIC,   CTX_ANY,        TRANSPORT_ANY,  &Spells::spellProtect, Spell::PARAM_NONE,    15 },
101 	{ "Quickness",    ASH | GINSENG | MOSS,     CTX_ANY,        TRANSPORT_ANY,  &Spells::spellQuick,   Spell::PARAM_NONE,    20 },
102 	{
103 		"Resurrect",    ASH | GINSENG | GARLIC | SILK | MOSS | MANDRAKE,
104 		CTX_NON_COMBAT, TRANSPORT_ANY,  &Spells::spellRez,     Spell::PARAM_PLAYER,  45
105 	},
106 	{ "Sleep",        SILK | GINSENG,           CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellSleep,   Spell::PARAM_NONE,    15 },
107 	{ "Tremor",       ASH | MOSS | MANDRAKE,    CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellTremor,  Spell::PARAM_NONE,    30 },
108 	{ "Undead",       ASH | GARLIC,             CTX_COMBAT,     TRANSPORT_ANY,  &Spells::spellUndead,  Spell::PARAM_NONE,    15 },
109 	{ "View",         NIGHTSHADE | MANDRAKE,    CTX_NON_COMBAT, TRANSPORT_ANY,  &Spells::spellView,    Spell::PARAM_NONE,    15 },
110 	{ "Winds",        ASH | MOSS,               CTX_WORLDMAP,   TRANSPORT_ANY,  &Spells::spellWinds,   Spell::PARAM_FROMDIR, 10 },
111 	{ "X-it",         ASH | SILK | MOSS,        CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellXit,     Spell::PARAM_NONE,    15 },
112 	{ "Y-up",         SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellYup,     Spell::PARAM_NONE,    10 },
113 	{ "Z-down",       SILK | MOSS,              CTX_DUNGEON,    TRANSPORT_ANY,  &Spells::spellZdown,   Spell::PARAM_NONE,    5 }
114 };
115 
116 /*-------------------------------------------------------------------*/
117 
Ingredients()118 Ingredients::Ingredients() {
119 	memset(_reagents, 0, sizeof(_reagents));
120 }
121 
addReagent(Reagent reagent)122 bool Ingredients::addReagent(Reagent reagent) {
123 	assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
124 	if (g_context->_party->getReagent(reagent) < 1)
125 		return false;
126 	g_context->_party->adjustReagent(reagent, -1);
127 	_reagents[reagent]++;
128 	return true;
129 }
130 
removeReagent(Reagent reagent)131 bool Ingredients::removeReagent(Reagent reagent) {
132 	assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
133 	if (_reagents[reagent] == 0)
134 		return false;
135 	g_context->_party->adjustReagent(reagent, 1);
136 	_reagents[reagent]--;
137 	return true;
138 }
139 
getReagent(Reagent reagent) const140 int Ingredients::getReagent(Reagent reagent) const {
141 	assertMsg(reagent < REAG_MAX, "invalid reagent: %d", reagent);
142 	return _reagents[reagent];
143 }
144 
revert()145 void Ingredients::revert() {
146 	int reg;
147 
148 	for (reg = 0; reg < REAG_MAX; reg++) {
149 		g_ultima->_saveGame->_reagents[reg] += _reagents[reg];
150 		_reagents[reg] = 0;
151 	}
152 }
153 
checkMultiple(int batches) const154 bool Ingredients::checkMultiple(int batches) const {
155 	for (int i = 0; i < REAG_MAX; i++) {
156 		/* see if there's enough reagents to mix (-1 because one is already counted) */
157 		if (_reagents[i] > 0 && g_ultima->_saveGame->_reagents[i] < batches - 1) {
158 			return false;
159 		}
160 	}
161 	return true;
162 }
163 
multiply(int batches)164 void Ingredients::multiply(int batches) {
165 	assertMsg(checkMultiple(batches), "not enough reagents to multiply ingredients by %d\n", batches);
166 	for (int i = 0; i < REAG_MAX; i++) {
167 		if (_reagents[i] > 0) {
168 			g_ultima->_saveGame->_reagents[i] -= batches - 1;
169 			_reagents[i] += batches - 1;
170 		}
171 	}
172 }
173 
174 /*-------------------------------------------------------------------*/
175 
176 Spells *g_spells;
177 
Spells()178 Spells::Spells() : spellEffectCallback(nullptr) {
179 	g_spells = this;
180 }
181 
~Spells()182 Spells::~Spells() {
183 	g_spells = nullptr;
184 }
185 
spellSetEffectCallback(SpellEffectCallback callback)186 void Spells::spellSetEffectCallback(SpellEffectCallback callback) {
187 	spellEffectCallback = callback;
188 }
189 
spellGetName(uint spell) const190 const char *Spells::spellGetName(uint spell) const {
191 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
192 
193 	return SPELL_LIST[spell]._name;
194 }
195 
spellGetRequiredMP(uint spell) const196 int Spells::spellGetRequiredMP(uint spell) const {
197 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
198 
199 	return SPELL_LIST[spell]._mp;
200 }
201 
spellGetContext(uint spell) const202 LocationContext Spells::spellGetContext(uint spell) const {
203 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
204 
205 	return SPELL_LIST[spell]._context;
206 }
207 
spellGetTransportContext(uint spell) const208 TransportContext Spells::spellGetTransportContext(uint spell) const {
209 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
210 
211 	return SPELL_LIST[spell]._transportContext;
212 }
213 
spellGetErrorMessage(uint spell,SpellCastError error)214 Common::String Spells::spellGetErrorMessage(uint spell, SpellCastError error) {
215 	uint i;
216 	SpellCastError err = error;
217 
218 	/* try to find a more specific error message */
219 	if (err == CASTERR_WRONGCONTEXT) {
220 		switch (SPELL_LIST[spell]._context) {
221 		case CTX_COMBAT:
222 			err = CASTERR_COMBATONLY;
223 			break;
224 		case CTX_DUNGEON:
225 			err = CASTERR_DUNGEONONLY;
226 			break;
227 		case CTX_WORLDMAP:
228 			err = CASTERR_WORLDMAPONLY;
229 			break;
230 		default:
231 			break;
232 		}
233 	}
234 
235 	/* find the message that we're looking for and return it! */
236 	for (i = 0; i < sizeof(SPELL_ERROR_MSGS) / sizeof(SPELL_ERROR_MSGS[0]); i++) {
237 		if (err == SPELL_ERROR_MSGS[i].err)
238 			return Common::String(SPELL_ERROR_MSGS[i].msg);
239 	}
240 
241 	return Common::String();
242 }
243 
spellMix(uint spell,const Ingredients * ingredients)244 int Spells::spellMix(uint spell, const Ingredients *ingredients) {
245 	int regmask, reg;
246 
247 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
248 
249 	regmask = 0;
250 	for (reg = 0; reg < REAG_MAX; reg++) {
251 		if (ingredients->getReagent((Reagent) reg) > 0)
252 			regmask |= (1 << reg);
253 	}
254 
255 	if (regmask != SPELL_LIST[spell]._components)
256 		return 0;
257 
258 	g_ultima->_saveGame->_mixtures[spell]++;
259 
260 	return 1;
261 }
262 
spellGetParamType(uint spell) const263 Spell::Param Spells::spellGetParamType(uint spell) const {
264 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
265 
266 	return SPELL_LIST[spell]._paramType;
267 }
268 
isDebuggerActive() const269 bool Spells::isDebuggerActive() const {
270 	return g_ultima->getDebugger()->isActive();
271 }
272 
spellCheckPrerequisites(uint spell,int character)273 SpellCastError Spells::spellCheckPrerequisites(uint spell, int character) {
274 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
275 	assertMsg(character >= 0 && character < g_ultima->_saveGame->_members, "character out of range: %d", character);
276 
277 	// Don't bother checking mix count and map when the spell
278 	// has been manually triggered from the debugger
279 	if (!isDebuggerActive()) {
280 		if (g_ultima->_saveGame->_mixtures[spell] == 0)
281 			return CASTERR_NOMIX;
282 
283 		if (g_context->_party->member(character)->getMp() < SPELL_LIST[spell]._mp)
284 			return CASTERR_MPTOOLOW;
285 	}
286 
287 	if ((g_context->_location->_context & SPELL_LIST[spell]._context) == 0)
288 		return CASTERR_WRONGCONTEXT;
289 
290 	if ((g_context->_transportContext & SPELL_LIST[spell]._transportContext) == 0)
291 		return CASTERR_FAILED;
292 
293 	return CASTERR_NOERROR;
294 }
295 
spellCast(uint spell,int character,int param,SpellCastError * error,bool spellEffect)296 bool Spells::spellCast(uint spell, int character, int param, SpellCastError *error, bool spellEffect) {
297 	int subject = (SPELL_LIST[spell]._paramType == Spell::PARAM_PLAYER) ? param : -1;
298 	PartyMember *p = g_context->_party->member(character);
299 
300 	assertMsg(spell < N_SPELLS, "invalid spell: %d", spell);
301 	assertMsg(character >= 0 && character < g_ultima->_saveGame->_members, "character out of range: %d", character);
302 
303 	*error = spellCheckPrerequisites(spell, character);
304 
305 	if (!isDebuggerActive())
306 		// Subtract the mixture for even trying to cast the spell
307 		AdjustValueMin(g_ultima->_saveGame->_mixtures[spell], -1, 0);
308 
309 	if (*error != CASTERR_NOERROR)
310 		return false;
311 
312 	// If there's a negate magic aura, spells fail!
313 	if (*g_context->_aura == Aura::NEGATE) {
314 		*error = CASTERR_FAILED;
315 		return false;
316 	}
317 
318 	if (!isDebuggerActive())
319 		// Subtract the mp needed for the spell
320 		p->adjustMp(-SPELL_LIST[spell]._mp);
321 
322 	if (spellEffect) {
323 		int time;
324 		/* recalculate spell speed - based on 5/sec */
325 		float MP_OF_LARGEST_SPELL = 45;
326 		int spellMp = SPELL_LIST[spell]._mp;
327 		time = int(10000.0 / settings._spellEffectSpeed  *  spellMp / MP_OF_LARGEST_SPELL);
328 		soundPlay(SOUND_PREMAGIC_MANA_JUMBLE, false, time);
329 		EventHandler::wait_msecs(time);
330 		g_spells->spellEffect(spell + 'a', subject, SOUND_MAGIC);
331 	}
332 
333 	if (!(g_spells->*SPELL_LIST[spell]._spellFunc)(param)) {
334 		*error = CASTERR_FAILED;
335 		return false;
336 	}
337 
338 	return true;
339 }
340 
spellCombatController()341 CombatController *Spells::spellCombatController() {
342 	CombatController *cc = dynamic_cast<CombatController *>(eventHandler->getController());
343 	return cc;
344 }
345 
spellMagicAttack(const Common::String & tilename,Direction dir,int minDamage,int maxDamage)346 void Spells::spellMagicAttack(const Common::String &tilename, Direction dir, int minDamage, int maxDamage) {
347 	CombatController *controller = spellCombatController();
348 	PartyMemberVector *party = controller->getParty();
349 
350 	MapTile tile = g_context->_location->_map->_tileSet->getByName(tilename)->getId();
351 
352 	int attackDamage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
353 	                   xu4_random((maxDamage + 1) - minDamage) + minDamage :
354 	                   maxDamage;
355 
356 	Std::vector<Coords> path = gameGetDirectionalActionPath(MASK_DIR(dir), MASK_DIR_ALL, (*party)[controller->getFocus()]->getCoords(),
357 	                           1, 11, Tile::canAttackOverTile, false);
358 	for (Std::vector<Coords>::iterator i = path.begin(); i != path.end(); i++) {
359 		if (spellMagicAttackAt(*i, tile, attackDamage))
360 			return;
361 	}
362 }
363 
spellMagicAttackAt(const Coords & coords,MapTile attackTile,int attackDamage)364 bool Spells::spellMagicAttackAt(const Coords &coords, MapTile attackTile, int attackDamage) {
365 	bool objectHit = false;
366 //    int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
367 	CombatMap *cm = getCombatMap();
368 
369 	Creature *creature = cm->creatureAt(coords);
370 
371 	if (!creature) {
372 		GameController::flashTile(coords, attackTile, 2);
373 	} else {
374 		objectHit = true;
375 
376 		/* show the 'hit' tile */
377 		soundPlay(SOUND_NPC_STRUCK);
378 		GameController::flashTile(coords, attackTile, 3);
379 
380 
381 		/* apply the damage to the creature */
382 		CombatController *controller = spellCombatController();
383 		controller->getCurrentPlayer()->dealDamage(creature, attackDamage);
384 		GameController::flashTile(coords, attackTile, 1);
385 	}
386 
387 	return objectHit;
388 }
389 
spellAwaken(int player)390 int Spells::spellAwaken(int player) {
391 	assertMsg(player < 8, "player out of range: %d", player);
392 	PartyMember *p = g_context->_party->member(player);
393 
394 	if ((player < g_context->_party->size()) && (p->getStatus() == STAT_SLEEPING)) {
395 		p->wakeUp();
396 		return 1;
397 	}
398 
399 	return 0;
400 }
401 
spellBlink(int dir)402 int Spells::spellBlink(int dir) {
403 	int i,
404 	    failed = 0,
405 	    distance,
406 	    diff,
407 	    *var;
408 	Direction reverseDir = dirReverse((Direction)dir);
409 	MapCoords coords = g_context->_location->_coords;
410 
411 	/* Blink doesn't work near the mouth of the abyss */
412 	/* Note: This means you can teleport to Hythloth from the top of the map,
413 	   and that you can teleport to the abyss from the left edge of the map,
414 	   Unfortunately, this matches the bugs in the game. :(  Consider fixing. */
415 	if (coords.x >= 192 && coords.y >= 192)
416 		return 0;
417 
418 	/* figure out what numbers we're working with */
419 	var = (dir & (DIR_WEST | DIR_EAST)) ? &coords.x : &coords.y;
420 
421 	/* find the distance we are going to move */
422 	distance = (*var) % 0x10;
423 	if (dir == DIR_EAST || dir == DIR_SOUTH)
424 		distance = 0x10 - distance;
425 
426 	/* see if we move another 16 spaces over */
427 	diff = 0x10 - distance;
428 	if ((diff > 0) && (xu4_random(diff * diff) > distance))
429 		distance += 0x10;
430 
431 	/* test our distance, and see if it works */
432 	for (i = 0; i < distance; i++)
433 		coords.move((Direction)dir, g_context->_location->_map);
434 
435 	i = distance;
436 	/* begin walking backward until you find a valid spot */
437 	while ((i-- > 0) && !g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS)->isWalkable())
438 		coords.move(reverseDir, g_context->_location->_map);
439 
440 	if (g_context->_location->_map->tileTypeAt(coords, WITH_OBJECTS)->isWalkable()) {
441 		/* we didn't move! */
442 		if (g_context->_location->_coords == coords)
443 			failed = 1;
444 
445 		g_context->_location->_coords = coords;
446 	} else {
447 		failed = 1;
448 	}
449 
450 	return (failed ? 0 : 1);
451 }
452 
spellCure(int player)453 int Spells::spellCure(int player) {
454 	assertMsg(player < 8, "player out of range: %d", player);
455 
456 	GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
457 	return g_context->_party->member(player)->heal(HT_CURE);
458 }
459 
spellDispel(int dir)460 int Spells::spellDispel(int dir) {
461 	MapTile *tile;
462 	MapCoords field;
463 
464 	/*
465 	 * get the location of the avatar (or current party member, if in battle)
466 	 */
467 	g_context->_location->getCurrentPosition(&field);
468 
469 	/*
470 	 * find where we want to dispel the field
471 	 */
472 	field.move((Direction)dir, g_context->_location->_map);
473 
474 	GameController::flashTile(field, "wisp", 2);
475 	/*
476 	 * if there is a field annotation, remove it and replace it with a valid
477 	 * replacement annotation.  We do this because sometimes dungeon triggers
478 	 * create annotations, that, if just removed, leave a wall tile behind
479 	 * (or other unwalkable surface).  So, we need to provide a valid replacement
480 	 * annotation to fill in the gap :)
481 	 */
482 	Annotation::List a = g_context->_location->_map->_annotations->allAt(field);
483 	if (a.size() > 0) {
484 		Annotation::List::iterator i;
485 		for (i = a.begin(); i != a.end(); i++) {
486 			if (i->getTile().getTileType()->canDispel()) {
487 
488 				/*
489 				 * get a replacement tile for the field
490 				 */
491 				MapTile newTile(g_context->_location->getReplacementTile(field, i->getTile().getTileType()));
492 
493 				g_context->_location->_map->_annotations->remove(*i);
494 				g_context->_location->_map->_annotations->add(field, newTile, false, true);
495 				return 1;
496 			}
497 		}
498 	}
499 
500 	/*
501 	 * if the map tile itself is a field, overlay it with a replacement tile
502 	 */
503 
504 	tile = g_context->_location->_map->tileAt(field, WITHOUT_OBJECTS);
505 	if (!tile->getTileType()->canDispel())
506 		return 0;
507 
508 	/*
509 	 * get a replacement tile for the field
510 	 */
511 	MapTile newTile(g_context->_location->getReplacementTile(field, tile->getTileType()));
512 
513 	g_context->_location->_map->_annotations->add(field, newTile, false, true);
514 
515 	return 1;
516 }
517 
spellEField(int param)518 int Spells::spellEField(int param) {
519 	MapTile fieldTile(0);
520 	int fieldType;
521 	int dir;
522 	MapCoords coords;
523 
524 	/* Unpack fieldType and direction */
525 	fieldType = param >> 4;
526 	dir = param & 0xF;
527 
528 	/* Make sure params valid */
529 	switch (fieldType) {
530 	case ENERGYFIELD_FIRE:
531 		fieldTile = g_context->_location->_map->_tileSet->getByName("fire_field")->getId();
532 		break;
533 	case ENERGYFIELD_LIGHTNING:
534 		fieldTile = g_context->_location->_map->_tileSet->getByName("energy_field")->getId();
535 		break;
536 	case ENERGYFIELD_POISON:
537 		fieldTile = g_context->_location->_map->_tileSet->getByName("poison_field")->getId();
538 		break;
539 	case ENERGYFIELD_SLEEP:
540 		fieldTile = g_context->_location->_map->_tileSet->getByName("sleep_field")->getId();
541 		break;
542 	default:
543 		return 0;
544 		break;
545 	}
546 
547 	g_context->_location->getCurrentPosition(&coords);
548 
549 	coords.move((Direction)dir, g_context->_location->_map);
550 	if (MAP_IS_OOB(g_context->_location->_map, coords))
551 		return 0;
552 	else {
553 		/*
554 		 * Observed behaviour on Amiga version of Ultima IV:
555 		 * Field cast on other field: Works, unless original field is lightning
556 		 * in which case it doesn't.
557 		 * Field cast on creature: Works, creature remains the visible tile
558 		 * Field cast on top of field and then dispel = no fields left
559 		 * The code below seems to produce this behaviour.
560 		 */
561 		const Tile *tile = g_context->_location->_map->tileTypeAt(coords, WITH_GROUND_OBJECTS);
562 		if (!tile->isWalkable()) return 0;
563 
564 		/* Get rid of old field, if any */
565 		Annotation::List a = g_context->_location->_map->_annotations->allAt(coords);
566 		if (a.size() > 0) {
567 			Annotation::List::iterator i;
568 			for (i = a.begin(); i != a.end(); i++) {
569 				if (i->getTile().getTileType()->canDispel())
570 					g_context->_location->_map->_annotations->remove(*i);
571 			}
572 		}
573 
574 		g_context->_location->_map->_annotations->add(coords, fieldTile);
575 	}
576 
577 	return 1;
578 }
579 
spellFireball(int dir)580 int Spells::spellFireball(int dir) {
581 	spellMagicAttack("hit_flash", (Direction)dir, 24, 128);
582 	return 1;
583 }
584 
spellGate(int phase)585 int Spells::spellGate(int phase) {
586 	const Coords *moongate;
587 
588 	GameController::flashTile(g_context->_location->_coords, "moongate", 2);
589 
590 	moongate = g_moongates->getGateCoordsForPhase(phase);
591 	if (moongate)
592 		g_context->_location->_coords = *moongate;
593 
594 	return 1;
595 }
596 
spellHeal(int player)597 int Spells::spellHeal(int player) {
598 	assertMsg(player < 8, "player out of range: %d", player);
599 
600 	GameController::flashTile(g_context->_party->member(player)->getCoords(), "wisp", 1);
601 	g_context->_party->member(player)->heal(HT_HEAL);
602 	return 1;
603 }
604 
spellIceball(int dir)605 int Spells::spellIceball(int dir) {
606 	spellMagicAttack("magic_flash", (Direction)dir, 32, 224);
607 	return 1;
608 }
609 
spellJinx(int unused)610 int Spells::spellJinx(int unused) {
611 	g_context->_aura->set(Aura::JINX, 10);
612 	return 1;
613 }
614 
spellKill(int dir)615 int Spells::spellKill(int dir) {
616 	spellMagicAttack("whirlpool", (Direction)dir, -1, 232);
617 	return 1;
618 }
619 
spellLight(int unused)620 int Spells::spellLight(int unused) {
621 	g_context->_party->lightTorch(100, false);
622 	return 1;
623 }
624 
spellMMissle(int dir)625 int Spells::spellMMissle(int dir) {
626 	spellMagicAttack("miss_flash", (Direction)dir, 64, 16);
627 	return 1;
628 }
629 
spellNegate(int unused)630 int Spells::spellNegate(int unused) {
631 	g_context->_aura->set(Aura::NEGATE, 10);
632 	return 1;
633 }
634 
spellOpen(int unused)635 int Spells::spellOpen(int unused) {
636 	g_debugger->getChest();
637 	return 1;
638 }
639 
spellProtect(int unused)640 int Spells::spellProtect(int unused) {
641 	g_context->_aura->set(Aura::PROTECTION, 10);
642 	return 1;
643 }
644 
spellRez(int player)645 int Spells::spellRez(int player) {
646 	assertMsg(player < 8, "player out of range: %d", player);
647 
648 	return g_context->_party->member(player)->heal(HT_RESURRECT);
649 }
650 
spellQuick(int unused)651 int Spells::spellQuick(int unused) {
652 	g_context->_aura->set(Aura::QUICKNESS, 10);
653 	return 1;
654 }
655 
spellSleep(int unused)656 int Spells::spellSleep(int unused) {
657 	CombatMap *cm = getCombatMap();
658 	CreatureVector creatures = cm->getCreatures();
659 	CreatureVector::iterator i;
660 
661 	/* try to put each creature to sleep */
662 
663 	for (i = creatures.begin(); i != creatures.end(); i++) {
664 		Creature *m = *i;
665 		Coords coords = m->getCoords();
666 		GameController::flashTile(coords, "wisp", 1);
667 		if ((m->getResists() != EFFECT_SLEEP) &&
668 		        xu4_random(0xFF) >= m->getHp()) {
669 			soundPlay(SOUND_POISON_EFFECT);
670 			m->putToSleep();
671 			GameController::flashTile(coords, "sleep_field", 3);
672 		} else
673 			soundPlay(SOUND_EVADE);
674 	}
675 
676 	return 1;
677 }
678 
spellTremor(int unused)679 int Spells::spellTremor(int unused) {
680 	CombatController *ct = spellCombatController();
681 	CreatureVector creatures = ct->getMap()->getCreatures();
682 	CreatureVector::iterator i;
683 
684 	for (i = creatures.begin(); i != creatures.end(); i++) {
685 		Creature *m = *i;
686 
687 
688 		Coords coords = m->getCoords();
689 		//GameController::flashTile(coords, "rocks", 1);
690 
691 		/* creatures with over 192 hp are unaffected */
692 		if (m->getHp() > 192) {
693 			soundPlay(SOUND_EVADE);
694 			continue;
695 		} else {
696 			/* Deal maximum damage to creature */
697 			if (xu4_random(2) == 0) {
698 				soundPlay(SOUND_NPC_STRUCK);
699 				GameController::flashTile(coords, "hit_flash", 3);
700 				ct->getCurrentPlayer()->dealDamage(m, 0xFF);
701 			}
702 			/* Deal enough damage to creature to make it flee */
703 			else if (xu4_random(2) == 0) {
704 				soundPlay(SOUND_NPC_STRUCK);
705 				GameController::flashTile(coords, "hit_flash", 2);
706 				if (m->getHp() > 23)
707 					ct->getCurrentPlayer()->dealDamage(m, m->getHp() - 23);
708 			} else {
709 				soundPlay(SOUND_EVADE);
710 			}
711 		}
712 	}
713 
714 	return 1;
715 }
716 
spellUndead(int unused)717 int Spells::spellUndead(int unused) {
718 	CombatController *ct = spellCombatController();
719 	CreatureVector creatures = ct->getMap()->getCreatures();
720 	CreatureVector::iterator i;
721 
722 	for (i = creatures.begin(); i != creatures.end(); i++) {
723 		Creature *m = *i;
724 		if (m && m->isUndead() && xu4_random(2) == 0)
725 			m->setHp(23);
726 	}
727 
728 	return 1;
729 }
730 
spellView(int unsued)731 int Spells::spellView(int unsued) {
732 	peer(false);
733 	return 1;
734 }
735 
spellWinds(int fromdir)736 int Spells::spellWinds(int fromdir) {
737 	g_context->_windDirection = fromdir;
738 	return 1;
739 }
740 
spellXit(int unused)741 int Spells::spellXit(int unused) {
742 	if (!g_context->_location->_map->isWorldMap()) {
743 		g_screen->screenMessage("Leaving...\n");
744 		g_game->exitToParentMap();
745 		g_music->playMapMusic();
746 		return 1;
747 	}
748 	return 0;
749 }
750 
spellYup(int unused)751 int Spells::spellYup(int unused) {
752 	MapCoords coords = g_context->_location->_coords;
753 	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
754 
755 	/* can't cast in the Abyss */
756 	if (g_context->_location->_map->_id == MAP_ABYSS)
757 		return 0;
758 	/* staying in the dungeon */
759 	else if (coords.z > 0) {
760 		assert(dungeon);
761 		for (int i = 0; i < 0x20; i++) {
762 			coords = MapCoords(xu4_random(8), xu4_random(8), g_context->_location->_coords.z - 1);
763 			if (dungeon->validTeleportLocation(coords)) {
764 				g_context->_location->_coords = coords;
765 				return 1;
766 			}
767 		}
768 		/* exiting the dungeon */
769 	} else {
770 		g_screen->screenMessage("Leaving...\n");
771 		g_game->exitToParentMap();
772 		g_music->playMapMusic();
773 		return 1;
774 	}
775 
776 	/* didn't find a place to go, failed! */
777 	return 0;
778 }
779 
spellZdown(int unused)780 int Spells::spellZdown(int unused) {
781 	MapCoords coords = g_context->_location->_coords;
782 	Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
783 	assert(dungeon);
784 
785 	/* can't cast in the Abyss */
786 	if (g_context->_location->_map->_id == MAP_ABYSS)
787 		return 0;
788 	/* can't go lower than level 8 */
789 	else if (coords.z >= 7)
790 		return 0;
791 	else {
792 		for (int i = 0; i < 0x20; i++) {
793 			coords = MapCoords(xu4_random(8), xu4_random(8), g_context->_location->_coords.z + 1);
794 			if (dungeon->validTeleportLocation(coords)) {
795 				g_context->_location->_coords = coords;
796 				return 1;
797 			}
798 		}
799 	}
800 
801 	/* didn't find a place to go, failed! */
802 	return 0;
803 }
804 
getSpell(int i) const805 const Spell *Spells::getSpell(int i) const {
806 	return &SPELL_LIST[i];
807 }
808 
809 } // End of namespace Ultima4
810 } // End of namespace Ultima
811