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 * aint32 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 * Based on the original sources
23 * Faery Tale II -- The Halls of the Dead
24 * (c) 1993-1996 The Wyrmkeep Entertainment Co.
25 */
26
27 #include "saga2/saga2.h"
28 #include "saga2/magic.h"
29 #include "saga2/idtypes.h"
30 #include "saga2/speldefs.h"
31 #include "saga2/spellbuk.h"
32 #include "saga2/spelshow.h"
33 #include "saga2/motion.h"
34 #include "saga2/player.h"
35
36 namespace Saga2 {
37
38 //-----------------------------------------------------------------------
39 // ASSUMPTIONS
40 // Wands and the like will use only 1 charge per use
41 // The enactor will either be an object in the world or in the
42 // inventory of an actor in the world
43 // Spell objects will have IDs > the number of spells
44 //
45
46 /* ===================================================================== *
47 Constants
48 * ===================================================================== */
49
50 #define RANGE_CHECKING 0
51 #define NPC_MANA_CHECK 0
52
53 const int32 spellFailSound = 42;
54
55 /* ===================================================================== *
56 Imports
57 * ===================================================================== */
58
59 extern WorldMapData *mapList;
60 extern SpellStuff *spellBook;
61 extern Point32 lastMousePos; // Last mouse position over map
62
63 /* ===================================================================== *
64 Inlines
65 * ===================================================================== */
66
67 //-----------------------------------------------------------------------
68 // Notify target of ill intent
69 void logAggressiveAct(ObjectID attackerID, ObjectID attackeeID);
70
71 //-----------------------------------------------------------------------
72 // This hack is here so wands & such will be implemented as if
73 // the owner had cast the spell while the mana comes from the object
GetOwner(GameObject * go)74 GameObject *GetOwner(GameObject *go) {
75 GameObject *obj = go;
76 ObjectID id;
77 for (;;) {
78 id = obj->parent()->thisID();
79 if (isWorld(id))
80 return obj;
81 else if (id == Nothing)
82 return go;
83
84 obj = GameObject::objectAddress(id);
85 }
86 }
87
88
89 /* ===================================================================== *
90 Magic code
91 * ===================================================================== */
92
93 //-----------------------------------------------------------------------
94 // This call looks up a spells object prototype. It can accept either
95 // an object ID or a spell ID
skillProtoFromID(int16 spellOrObjectID)96 SkillProto *skillProtoFromID(int16 spellOrObjectID) {
97 if (spellOrObjectID > MAX_SPELLS)
98 return (SkillProto *)GameObject::protoAddress(spellOrObjectID);
99
100 if (spellOrObjectID >= totalSpellBookPages)
101 error("Wrong spellID: %d > %d", spellOrObjectID, totalSpellBookPages);
102
103 return spellBook[spellOrObjectID].getProto();
104 }
105
106 //-----------------------------------------------------------------------
107 // initialization call to connect skill prototypes with their spells
initializeSkill(SkillProto * oNo,SpellID sNo)108 void initializeSkill(SkillProto *oNo, SpellID sNo) {
109 if (sNo > 0 && sNo < totalSpellBookPages) {
110 if (spellBook[sNo].getProto() != NULL)
111 error("Duplicate prototype for spell %d", sNo);
112 spellBook[sNo].setProto(oNo);
113 } else
114 warning("Spell prototype has invalid spell ID %d (lockType)", sNo);
115 }
116
117 //-----------------------------------------------------------------------
118 // test for untargeted skill/spell which gets cast immediately
nonTargeted(SkillProto * spell)119 bool nonTargeted(SkillProto *spell) {
120 SpellID sid = spell->getSpellID();
121 return spellBook[sid].untargeted();
122 }
123
124 //-----------------------------------------------------------------------
125 // test for untargeted skill/spell which gets cast immediately
nonUsable(SkillProto * spell)126 bool nonUsable(SkillProto *spell) {
127 SpellID sid = spell->getSpellID();
128 return spellBook[sid].untargetable();
129 }
130
131 //-----------------------------------------------------------------------
132 // test a target for viability in a given spell
validTarget(GameObject * enactor,GameObject * target,ActiveItem * tag,SkillProto * skill)133 bool validTarget(GameObject *enactor, GameObject *target, ActiveItem *tag, SkillProto *skill) {
134 assert(enactor != NULL);
135 assert(skill != NULL);
136 SpellStuff &sp = spellBook[skill->getSpellID()];
137 int32 range = sp.getRange();
138 if (target != NULL && target->thisID() != Nothing) {
139 #if RANGE_CHECKING
140 if (range > 0 &&
141 range > (
142 enactor->getLocation() -
143 target->getLocation())
144 .magnitude()) {
145 return false;
146 }
147 #endif
148 if (target->IDParent() != enactor->IDParent()) {
149 return false;
150 }
151 if (!lineOfSight(enactor, target, terrainTransparent))
152 return false;
153
154 if (isActor(target)) {
155 Actor *a = (Actor *) target;
156 Actor *e = (Actor *) enactor;
157 if (a->hasEffect(actorInvisible) && !e->hasEffect(actorSeeInvis))
158 return false;
159 }
160 if (target->thisID() == enactor->thisID())
161 return sp.canTarget(spellTargCaster);
162 return sp.canTarget(spellTargObject);
163 }
164 if (tag != NULL) {
165 if (range > 0 &&
166 range > (
167 enactor->getWorldLocation() -
168 TAGPos(tag))
169 .magnitude()) {
170 return false;
171 }
172 return sp.canTarget(spellTargTAG);
173 }
174 #if RANGE_CHECKING
175 if (sp.range > 0 &&
176 sp.range > (
177 enactor->getLocation() -
178 )
179 .magnitude()) {
180 return false;
181 }
182 #endif
183 return sp.canTarget(spellTargLocation);
184 }
185
186 //-----------------------------------------------------------------------
187 // check for sufficient mana
canCast(GameObject * enactor,SkillProto * spell)188 bool canCast(GameObject *enactor, SkillProto *spell) {
189 SpellID s = spell->getSpellID();
190 SpellStuff &sProto = spellBook[s];
191 ActorManaID ami = (ActorManaID)(sProto.getManaType());
192 int amt = sProto.getManaAmt();
193
194 if (ami == numManas)
195 return true;
196 #if NPC_MANA_CHECK
197 if (isActor(enactor)) {
198 Actor *a = (Actor *) enactor;
199 assert(ami >= manaIDRed && ami <= manaIDViolet);
200 if ((&a->effectiveStats.redMana)[ami] < amt)
201 return false;
202 return true;
203 } else {
204 return true;
205 }
206 #endif
207 return enactor->hasCharge(ami, amt);
208
209 }
210
211 /* ===================================================================== *
212 Spell casting code
213 * ===================================================================== */
214
215 // There are two levels of routines here:
216 // Cast: This call makes the actor do his spell casting dance. The
217 // motion task calls the implement routine at the appropriate
218 // time. If the caster is not an actor the implement routine is
219 // chained directly.
220 // Implement: This call actually causes the spell to 'fire'. The
221 // display effect is started, and the internal effects are
222 // implemented.
223 //
224
225 //-----------------------------------------------------------------------
226 // cast untargeted spell
castUntargetedSpell(GameObject * enactor,SkillProto * spell)227 bool castUntargetedSpell(GameObject *enactor, SkillProto *spell) {
228 castSpell(enactor, enactor, spell);
229 return true;
230 }
231
232 //-----------------------------------------------------------------------
233 // cast a spell at a location
castSpell(GameObject * enactor,Location & target,SkillProto * spell)234 bool castSpell(GameObject *enactor, Location &target, SkillProto *spell) {
235 if (enactor) {
236 if (isActor(enactor)) {
237 Actor *a = (Actor *) enactor;
238 MotionTask::castSpell(*a, *spell, target);
239 } else
240 implementSpell(enactor, target, spell);
241 }
242 return true;
243 }
244
245 //-----------------------------------------------------------------------
246 // cast a spell at a TAG
castSpell(GameObject * enactor,ActiveItem * target,SkillProto * spell)247 bool castSpell(GameObject *enactor, ActiveItem *target, SkillProto *spell) {
248 if (enactor && target) {
249 if (isActor(enactor)) {
250 Actor *a = (Actor *) enactor;
251 MotionTask::castSpell(*a, *spell, *target);
252 } else
253 implementSpell(enactor, target, spell);
254 }
255 return true;
256 }
257
258 //-----------------------------------------------------------------------
259 // cast a spell at an object
castSpell(GameObject * enactor,GameObject * target,SkillProto * spell)260 bool castSpell(GameObject *enactor, GameObject *target, SkillProto *spell) {
261 SpellID s = spell->getSpellID();
262 SpellStuff &sProto = spellBook[s];
263 if (sProto.isOffensive())
264 logAggressiveAct(enactor->thisID(), target->thisID());
265
266 if (enactor && target) {
267 if (isActor(enactor)) {
268 Actor *a = (Actor *) enactor;
269 MotionTask::castSpell(*a, *spell, *target);
270 } else
271 implementSpell(enactor, target, spell);
272 }
273 return true;
274 }
275
276 //-----------------------------------------------------------------------
277 // implement a spell at a location
implementSpell(GameObject * enactor,Location & target,SkillProto * spell)278 bool implementSpell(GameObject *enactor, Location &target, SkillProto *spell) {
279 SpellID s = spell->getSpellID();
280 SpellStuff &sProto = spellBook[s];
281
282 assert(sProto.shouldTarget(spellApplyLocation));
283
284 ActorManaID ami = (ActorManaID)(sProto.getManaType());
285
286 if (isActor(enactor)) {
287 Actor *a = (Actor *) enactor;
288 bool r = a->takeMana(ami, sProto.getManaAmt());
289 if (!r) {
290 Location cal = Location(a->getLocation(), a->IDParent());
291 Saga2::playSoundAt(MKTAG('S', 'P', 'L', spellFailSound), cal);
292 return false;
293 }
294 PlayerActorID playerID;
295
296 if (actorIDToPlayerID(enactor->thisID(), playerID)) {
297 PlayerActor *player = getPlayerActorAddress(playerID);
298
299 player->skillAdvance(skillIDSpellcraft, sProto.getManaAmt() / 10);
300 }
301 } else {
302 if (!enactor->deductCharge(ami, sProto.getManaAmt())) {
303 return false;
304 }
305 }
306
307 g_vm->_activeSpells->add(new SpellInstance(GetOwner(enactor), target, sProto.getDisplayID()));
308 sProto.playSound(enactor);
309 return true;
310 }
311
312 //-----------------------------------------------------------------------
313 // implement a spell at a TAG
implementSpell(GameObject * enactor,ActiveItem * target,SkillProto * spell)314 bool implementSpell(GameObject *enactor, ActiveItem *target, SkillProto *spell) {
315 SpellID s = spell->getSpellID();
316 SpellStuff &sProto = spellBook[s];
317 Location l = Location(TAGPos(target), enactor->world()->thisID());
318 if (sProto.shouldTarget(spellApplyLocation)) {
319 return implementSpell(enactor, l, spell);
320 }
321 assert(sProto.shouldTarget(spellApplyTAG));
322 assert(target->_data.itemType == activeTypeInstance);
323
324 ActorManaID ami = (ActorManaID)(sProto.getManaType());
325
326 if (isActor(enactor)) {
327 Actor *a = (Actor *) enactor;
328 bool r = a->takeMana(ami, sProto.getManaAmt());
329 if (!r) {
330 Location cal = Location(a->getLocation(), a->IDParent());
331 Saga2::playSoundAt(MKTAG('S', 'P', 'L', spellFailSound), cal);
332 return false;
333 }
334 PlayerActorID playerID;
335
336 if (actorIDToPlayerID(enactor->thisID(), playerID)) {
337 PlayerActor *player = getPlayerActorAddress(playerID);
338
339 player->skillAdvance(skillIDSpellcraft, sProto.getManaAmt() / 10);
340 }
341 } else {
342 if (!enactor->deductCharge(ami, sProto.getManaAmt())) {
343 return false;
344 }
345 }
346
347 g_vm->_activeSpells->add(new SpellInstance(GetOwner(enactor), l, sProto.getDisplayID()));
348 sProto.playSound(enactor);
349 return true;
350 }
351
352 //-----------------------------------------------------------------------
353 // implement a spell at an object
implementSpell(GameObject * enactor,GameObject * target,SkillProto * spell)354 bool implementSpell(GameObject *enactor, GameObject *target, SkillProto *spell) {
355 SpellID s = spell->getSpellID();
356 SpellStuff &sProto = spellBook[s];
357 Location l = Location(target->getWorldLocation(), enactor->world()->thisID());
358 if (sProto.shouldTarget(spellApplyLocation))
359 return implementSpell(enactor, l, spell);
360 assert(sProto.shouldTarget(spellApplyObject));
361
362 ActorManaID ami = (ActorManaID)(sProto.getManaType());
363
364 if (isActor(enactor)) {
365 Actor *a = (Actor *) enactor;
366 bool r = a->takeMana(ami, sProto.getManaAmt());
367 if (!r) {
368 Location cal = Location(a->getLocation(), a->IDParent());
369 Saga2::playSoundAt(MKTAG('S', 'P', 'L', spellFailSound), cal);
370 return false;
371 }
372 PlayerActorID playerID;
373
374 if (actorIDToPlayerID(enactor->thisID(), playerID)) {
375 PlayerActor *player = getPlayerActorAddress(playerID);
376
377 player->skillAdvance(skillIDSpellcraft, sProto.getManaAmt() / 10);
378 }
379 } else {
380 if (!enactor->deductCharge(ami, sProto.getManaAmt())) {
381 return false;
382 }
383 }
384
385 g_vm->_activeSpells->add(new SpellInstance(GetOwner(enactor), target, sProto.getDisplayID()));
386 sProto.playSound(enactor);
387 return true;
388 }
389
390 } // end of namespace Saga2
391