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