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