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