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/cmisc.h"
29 #include "saga2/spelshow.h"
30 #include "saga2/script.h"
31 #include "saga2/actor.h"
32 
33 namespace Saga2 {
34 
35 const int16 absoluteMaximumVitality = 255;
36 
37 extern void updateIndicators(void);      //  Kludge, put in intrface.h later (got to hurry)
38 
39 // offensiveNotification gets 2 (Actor *) items
40 // att is performing an offensive act on def
41 #define offensiveNotification(att,def) ((def)->handleOffensiveAct(att))
42 
43 /* ===================================================================== *
44    Effect Implementations
45  * ===================================================================== */
46 
47 // ------------------------------------------------------------------
48 // Determine stat that modifies damage
49 
getRelevantStat(effectDamageTypes dt,Actor * a)50 int16 ProtoDamage::getRelevantStat(effectDamageTypes dt, Actor *a) {
51 	switch (dt) {
52 
53 	case kDamageImpact     :
54 	case kDamageSlash      :
55 	case kDamageProjectile :
56 		return a->getStats()->getSkillLevel(skillIDBrawn);
57 	case kDamageFire       :
58 	case kDamageAcid       :
59 	case kDamageHeat       :
60 	case kDamageCold       :
61 	case kDamageLightning  :
62 	case kDamagePoison     :
63 	case kDamageMental     :
64 	case kDamageToUndead   :
65 		return a->getStats()->getSkillLevel(skillIDSpellcraft);
66 	case kDamageDirMagic   :
67 	case kDamageOther      :
68 	case kDamageStarve     :
69 	case kDamageEnergy     :
70 		return 0;
71 	}
72 	return 0;
73 }
74 
75 // ------------------------------------------------------------------
76 // Cause damage to something based on a damage proto-effect
77 
implement(GameObject * cst,SpellTarget * trg,int8 deltaDamage)78 void ProtoDamage::implement(GameObject *cst, SpellTarget *trg, int8 deltaDamage) {
79 	int8 totalDice;
80 	int8 totalBase;
81 	if (isActor(cst)) {
82 		Actor *a = (Actor *) cst;
83 		totalDice = dice + skillDice * getRelevantStat(type, a);
84 		totalBase = base + skillBase * getRelevantStat(type, a);
85 		if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
86 			offensiveNotification(a, (Actor *) trg->getObject());
87 	} else {
88 		totalDice = dice;
89 		totalBase = base;
90 		ObjectID pID = cst->possessor();
91 		if (pID != Nothing) {
92 			Actor *p = (Actor *) GameObject::objectAddress(pID);
93 			assert(isActor(p));
94 			if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
95 				offensiveNotification(p, (Actor *) trg->getObject());
96 		}
97 	}
98 
99 	totalBase -= deltaDamage;
100 
101 	assert(trg->getType() == SpellTarget::spellTargetObject);
102 	if (self)
103 		cst->acceptDamage(cst->thisID(), totalBase, type, totalDice, sides);
104 	else
105 		trg->getObject()->acceptDamage(cst->thisID(), totalBase, type, totalDice, sides);
106 }
107 
108 // ------------------------------------------------------------------
109 // drain something based on a drainage proto-effect
110 
currentLevel(Actor * a,effectDrainsTypes edt)111 int16 ProtoDrainage::currentLevel(Actor *a, effectDrainsTypes edt) {
112 	switch (edt) {
113 	case drainsManaRed:
114 	case drainsManaOrange:
115 	case drainsManaYellow:
116 	case drainsManaGreen:
117 	case drainsManaBlue:
118 	case drainsManaViolet:
119 		return (&a->_effectiveStats.redMana)[edt - drainsManaRed];
120 
121 	case drainsLifeLevel:
122 		return (a->getBaseStats())->vitality;
123 	case drainsVitality:
124 		return a->_effectiveStats.vitality;
125 	default:
126 		return 0;
127 	}
128 }
129 
drainLevel(GameObject * cst,Actor * a,effectDrainsTypes edt,int16 amt)130 void ProtoDrainage::drainLevel(GameObject *cst, Actor *a, effectDrainsTypes edt, int16 amt) {
131 	switch (edt) {
132 	case drainsManaRed:
133 	case drainsManaOrange:
134 	case drainsManaYellow:
135 	case drainsManaGreen:
136 	case drainsManaBlue:
137 	case drainsManaViolet:
138 		{
139 			ActorManaID aType = (ActorManaID)(edt + (manaIDRed - drainsManaRed));
140 			(&a->_effectiveStats.redMana)[aType] =
141 				clamp(
142 					0,
143 					(&a->_effectiveStats.redMana)[aType] - amt,
144 					(&(a->getBaseStats())->redMana)[aType]);
145 		}
146 		break;
147 	case drainsLifeLevel:
148 		{
149 			int16 &maxVit = (a->getBaseStats())->vitality;
150 			maxVit = clamp(0, maxVit - amt, absoluteMaximumVitality);
151 			a->acceptDamage(cst->thisID(), amt > 0 ? 1 : -1, kDamageOther);
152 		}
153 		break;
154 	case drainsVitality:
155 		a->acceptDamage(cst->thisID(), amt, kDamageOther);
156 		break;
157 	default:
158 		break;
159 	}
160 	updateIndicators();
161 }
162 
implement(GameObject * cst,SpellTarget * trg,int8)163 void ProtoDrainage::implement(GameObject *cst, SpellTarget *trg, int8) {
164 	int8 totalDice;
165 	Actor *a;
166 	Actor *ac;
167 	if (isActor(cst)) {
168 		ac = (Actor *) cst;
169 		totalDice = dice + skillDice * ac->getStats()->spellcraft;
170 		if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
171 			offensiveNotification(ac, (Actor *) trg->getObject());
172 	} else {
173 		ac = NULL;
174 		totalDice = dice + 6;
175 		ObjectID pID = cst->possessor();
176 		if (pID != Nothing) {
177 			Actor *p = (Actor *) GameObject::objectAddress(pID);
178 			assert(isActor(p));
179 			if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
180 				offensiveNotification(p, (Actor *) trg->getObject());
181 		}
182 	}
183 	int8 totalDamage = diceRoll(totalDice, 6, 0, 0);
184 
185 
186 	if (!(trg->getType() == SpellTarget::spellTargetObject))
187 		return;
188 	GameObject *target = self ? cst : trg->getObject();
189 	if (!isActor(target))
190 		return;
191 	a = (Actor *) target;
192 	if (a->hasEffect(actorNoDrain))
193 		return;
194 
195 	if (totalDamage > 0 && target->makeSavingThrow())
196 		totalDamage /= 2;
197 	totalDamage = clamp(0, totalDamage, currentLevel(a, type));
198 
199 	drainLevel(cst, a, type, totalDamage);
200 	if (ac != NULL)
201 		drainLevel(cst, ac, type, -totalDamage);
202 }
203 
204 // ------------------------------------------------------------------
205 // enchant something based on an enchantment proto-effect
206 
realSavingThrow(Actor * a)207 bool ProtoEnchantment::realSavingThrow(Actor *a) {
208 	uint32 power = (a->getBaseStats())->vitality;
209 	power *= power;
210 	int32 saveSpace = absoluteMaximumVitality;
211 	saveSpace *= saveSpace;
212 	return (g_vm->_rnd->getRandomNumber(saveSpace - 1) < power);
213 
214 }
215 
implement(GameObject * cst,SpellTarget * trg,int8)216 void ProtoEnchantment::implement(GameObject *cst, SpellTarget *trg, int8) {
217 	if (isActor(trg->getObject())) {
218 		// can someone be angry at a wand?
219 		if (isHarmful(enchID)) {
220 			if (isActor(cst)) {
221 				offensiveNotification((Actor *) cst, (Actor *) trg->getObject());
222 			} else {
223 				ObjectID pID = cst->possessor();
224 				if (pID != Nothing) {
225 					Actor *p = (Actor *) GameObject::objectAddress(pID);
226 					assert(isActor(p));
227 					offensiveNotification(p, (Actor *) trg->getObject());
228 				}
229 			}
230 		}
231 
232 
233 		if (((Actor *)(trg->getObject()))->hasEffect(actorNoEnchant) &&
234 		        isHarmful(enchID))
235 			return;
236 		if (canFail() && realSavingThrow((Actor *)(trg->getObject())))
237 			return;
238 	}
239 
240 	if (isHarmful(enchID) && trg->getObject()->makeSavingThrow())
241 		return;
242 	EnchantObject(trg->getObject()->thisID(), enchID, minEnch + dice.roll());
243 }
244 
245 // ------------------------------------------------------------------
246 // effects on TAGs
247 
implement(GameObject * cst,SpellTarget * trg,int8)248 void ProtoTAGEffect::implement(GameObject *cst, SpellTarget *trg, int8) {
249 	ActiveItem *tag = trg->getTAG();
250 	assert(tag);
251 	if (affectBit == settagLocked) {
252 		//if ( tag->builtInBehavior()==ActiveItem::builtInDoor )
253 		if (tag->isLocked() != onOff)
254 			tag->acceptLockToggle(cst->thisID(), tag->lockType());
255 	} else if (affectBit == settagOpen) {
256 		tag->trigger(cst->thisID(), onOff);
257 	}
258 }
259 
260 // ------------------------------------------------------------------
261 // effects on non-Actors
262 
implement(GameObject *,SpellTarget * trg,int8)263 void ProtoObjectEffect::implement(GameObject *, SpellTarget *trg, int8) {
264 	GameObject *go = trg->getObject();
265 	assert(go);
266 	if (!isActor(go))
267 		EnchantObject(go->thisID(), affectBit, dice.roll());
268 }
269 
270 // ------------------------------------------------------------------
271 // effects on TAGs
272 
implement(GameObject *,SpellTarget *,int8)273 void ProtoLocationEffect::implement(GameObject *, SpellTarget *, int8) {
274 	//TilePoint tp=trg->getPoint();
275 
276 }
277 
278 // ------------------------------------------------------------------
279 // use a special spell on something
280 
implement(GameObject * cst,SpellTarget * trg,int8)281 void ProtoSpecialEffect::implement(GameObject *cst, SpellTarget *trg, int8) {
282 	assert(handler);
283 	(*handler)(cst, trg);
284 }
285 
286 /* ===================================================================== *
287    Effect Applicability
288  * ===================================================================== */
289 
applicable(SpellTarget & trg)290 bool ProtoDamage::applicable(SpellTarget &trg) {
291 	return trg.getType() == SpellTarget::spellTargetObject ||
292 	       trg.getType() == SpellTarget::spellTargetObjectPoint;
293 }
294 
applicable(SpellTarget & trg)295 bool ProtoDrainage::applicable(SpellTarget &trg) {
296 	return (trg.getType() == SpellTarget::spellTargetObject ||
297 	        trg.getType() == SpellTarget::spellTargetObjectPoint) &&
298 	       isActor(trg.getObject());
299 }
300 
applicable(SpellTarget & trg)301 bool ProtoEnchantment::applicable(SpellTarget &trg) {
302 	return (trg.getType() == SpellTarget::spellTargetObject ||
303 	        trg.getType() == SpellTarget::spellTargetObjectPoint) &&
304 	       (isActor(trg.getObject()) ||
305 	        getEnchantmentSubType(enchID) == actorInvisible);
306 }
307 
applicable(SpellTarget & trg)308 bool ProtoTAGEffect::applicable(SpellTarget &trg) {
309 	return (trg.getType() == SpellTarget::spellTargetTAG);
310 }
311 
applicable(SpellTarget & trg)312 bool ProtoObjectEffect::applicable(SpellTarget &trg) {
313 	return (trg.getType() == SpellTarget::spellTargetObject ||
314 	        trg.getType() == SpellTarget::spellTargetObjectPoint) &&
315 	       !isActor(trg.getObject());
316 }
317 
318 // ------------------------------------------------------------------
319 // These are effects for specific spells
320 
321 #ifdef __WATCOMC__
322 #pragma off (unreferenced);
323 #endif
324 
createSpellCallFrame(GameObject * go,SpellTarget * trg,scriptCallFrame & scf)325 void createSpellCallFrame(GameObject *go, SpellTarget *trg, scriptCallFrame &scf) {
326 	assert(go);
327 	assert(trg);
328 	scf.invokedObject = Nothing;
329 	scf.enactor       = go->thisID();
330 	scf.directObject  = Nothing;
331 	scf.directTAI     = NoActiveItem;
332 	scf.coords        = Nowhere;
333 
334 	switch (trg->getType()) {
335 	case SpellTarget::spellTargetPoint      :
336 	case SpellTarget::spellTargetObjectPoint:
337 		scf.value = 1;
338 		scf.coords = trg->getPoint();
339 		break;
340 	case SpellTarget::spellTargetObject     :
341 		scf.value = 2;
342 		scf.directObject = trg->getObject()->thisID();
343 		break;
344 	case SpellTarget::spellTargetTAG        :
345 		scf.value = 3;
346 		scf.directTAI = trg->getTAG()->thisID();
347 		break;
348 	case SpellTarget::spellTargetNone       :
349 	default                                 :
350 		scf.value = 0;
351 		break;
352 	}
353 
354 }
355 
356 
357 
SPECIALSPELL(CreateFireWisp)358 SPECIALSPELL(CreateFireWisp) {
359 	scriptCallFrame scf;
360 	createSpellCallFrame(cst, trg, scf);
361 	runScript(resImports->EXP_spellEffect_CreateFireWisp, scf);
362 }
363 
SPECIALSPELL(CreateWindWisp)364 SPECIALSPELL(CreateWindWisp) {
365 	scriptCallFrame scf;
366 	createSpellCallFrame(cst, trg, scf);
367 	runScript(resImports->EXP_spellEffect_CreateWindWisp, scf);
368 }
369 
SPECIALSPELL(CreateWraith)370 SPECIALSPELL(CreateWraith) {
371 	scriptCallFrame scf;
372 	createSpellCallFrame(cst, trg, scf);
373 	runScript(resImports->EXP_spellEffect_CreateWraith, scf);
374 }
375 
SPECIALSPELL(TeleportToShrine)376 SPECIALSPELL(TeleportToShrine) {
377 	scriptCallFrame scf;
378 	createSpellCallFrame(cst, trg, scf);
379 	runScript(resImports->EXP_spellEffect_TeleportToShrine, scf);
380 }
381 
SPECIALSPELL(TeleportToLocation)382 SPECIALSPELL(TeleportToLocation) {
383 	cst->move(trg->getPoint());
384 }
385 
SPECIALSPELL(Rejoin)386 SPECIALSPELL(Rejoin) {
387 	scriptCallFrame scf;
388 	createSpellCallFrame(cst, trg, scf);
389 	runScript(resImports->EXP_spellEffect_Rejoin, scf);
390 }
391 
SPECIALSPELL(CreateWWisp)392 SPECIALSPELL(CreateWWisp) {
393 	scriptCallFrame scf;
394 	createSpellCallFrame(cst, trg, scf);
395 	runScript(resImports->EXP_spellEffect_CreateWindWisp, scf);
396 }
397 
SPECIALSPELL(CreateFWisp)398 SPECIALSPELL(CreateFWisp) {
399 	scriptCallFrame scf;
400 	createSpellCallFrame(cst, trg, scf);
401 	runScript(resImports->EXP_spellEffect_CreateFireWisp, scf);
402 }
403 
SPECIALSPELL(CreateFood)404 SPECIALSPELL(CreateFood) {
405 	scriptCallFrame scf;
406 	createSpellCallFrame(cst, trg, scf);
407 	runScript(resImports->EXP_spellEffect_CreateFood, scf);
408 }
409 
SPECIALSPELL(Timequake)410 SPECIALSPELL(Timequake) {
411 	scriptCallFrame scf;
412 	createSpellCallFrame(cst, trg, scf);
413 	runScript(resImports->EXP_spellEffect_Timequake, scf);
414 }
415 
SPECIALSPELL(SagaSpellCall)416 SPECIALSPELL(SagaSpellCall) {
417 
418 
419 }
420 
SPECIALSPELL(DeathSpell)421 SPECIALSPELL(DeathSpell) {
422 	// can someone be angry at a wand?
423 	if (isActor(trg->getObject())) {
424 		if (isActor(cst)) {
425 			offensiveNotification((Actor *) cst, (Actor *) trg->getObject());
426 		} else {
427 			ObjectID pID = cst->possessor();
428 			if (pID != Nothing) {
429 				Actor *p = (Actor *) GameObject::objectAddress(pID);
430 				assert(isActor(p));
431 				offensiveNotification(p, (Actor *) trg->getObject());
432 			}
433 		}
434 		if (ProtoEnchantment::realSavingThrow((Actor *)(trg->getObject())))
435 			return;
436 		Actor *a = (Actor *) trg->getObject();
437 		if (!a->makeSavingThrow()) {
438 			a->acceptDamage(cst->thisID(),
439 			                a->_effectiveStats.vitality,
440 			                kDamageEnergy, 1, 2, 0);
441 			a->die();
442 		}
443 	}
444 }
445 
SPECIALSPELL(Resurrect)446 SPECIALSPELL(Resurrect) {
447 #if 0
448 	if (isActor(trg->getObject())) {
449 		Actor *a = (Actor *) trg->getObject();
450 		if (a->isDead()) a->imNotQuiteDead();
451 	}
452 #else
453 	scriptCallFrame scf;
454 	createSpellCallFrame(cst, trg, scf);
455 	runScript(resImports->EXP_spellEffect_Timequake, scf);
456 #endif
457 }
458 
SPECIALSPELL(DispellPoison)459 SPECIALSPELL(DispellPoison) {
460 	if (isActor(trg->getObject())) {
461 		Actor *a = (Actor *) trg->getObject();
462 		DispelObjectEnchantment(a->thisID(), makeEnchantmentID(actorPoisoned, true));
463 
464 	}
465 }
466 
SPECIALSPELL(DispellProtections)467 SPECIALSPELL(DispellProtections) {
468 	if (isActor(trg->getObject())) {
469 		Actor               *a = (Actor *) trg->getObject();
470 		GameObject          *obj;
471 		ContainerIterator   iter(a);
472 
473 		if (isActor(cst)) {
474 			offensiveNotification((Actor *) cst, (Actor *) trg->getObject());
475 		} else {
476 			ObjectID pID = cst->possessor();
477 			if (pID != Nothing) {
478 				Actor *p = (Actor *) GameObject::objectAddress(pID);
479 				assert(isActor(p));
480 				offensiveNotification(p, (Actor *) trg->getObject());
481 			}
482 		}
483 
484 		if (ProtoEnchantment::realSavingThrow((Actor *)(trg->getObject())))
485 			return;
486 
487 		clearEnchantments(a);
488 
489 		while (iter.next(&obj) != Nothing) {
490 			ProtoObj *proto = obj->proto();
491 
492 			if (proto->containmentSet() & ProtoObj::isEnchantment) {
493 				uint16 enchantmentID = obj->getExtra();
494 				if (!isHarmful(enchantmentID)) {
495 					DispelObjectEnchantment(a->thisID(), enchantmentID);
496 				}
497 			}
498 		}
499 	}
500 }
501 
SPECIALSPELL(DispellCurses)502 SPECIALSPELL(DispellCurses) {
503 	if (isActor(trg->getObject())) {
504 		Actor               *a = (Actor *) trg->getObject();
505 		GameObject          *obj;
506 		ContainerIterator   iter(a);
507 		GameObject          *ToBeDeleted = NULL;
508 
509 		clearEnchantments(a);
510 
511 		while (iter.next(&obj) != Nothing) {
512 			ProtoObj *proto = obj->proto();
513 
514 			if (proto->containmentSet() & ProtoObj::isEnchantment) {
515 				uint16 enchantmentID = obj->getExtra();
516 				if (isHarmful(enchantmentID)) {
517 					if (ToBeDeleted) ToBeDeleted->deleteObject();
518 					ToBeDeleted = obj;
519 				}
520 			}
521 		}
522 		if (ToBeDeleted) ToBeDeleted->deleteObject();
523 
524 		a->evalEnchantments();
525 	}
526 }
527 
528 } // end of namespace Saga2
529