Initial commit
This commit is contained in:
527
engines/saga2/effects.cpp
Normal file
527
engines/saga2/effects.cpp
Normal file
@@ -0,0 +1,527 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* aint32 with this program; if not, write to the Free Software
|
||||
*
|
||||
*
|
||||
* Based on the original sources
|
||||
* Faery Tale II -- The Halls of the Dead
|
||||
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
|
||||
*/
|
||||
|
||||
#include "saga2/saga2.h"
|
||||
#include "saga2/cmisc.h"
|
||||
#include "saga2/spelshow.h"
|
||||
#include "saga2/script.h"
|
||||
#include "saga2/actor.h"
|
||||
|
||||
namespace Saga2 {
|
||||
|
||||
const int16 absoluteMaximumVitality = 255;
|
||||
|
||||
extern void updateIndicators(); // Kludge, put in intrface.h later (got to hurry)
|
||||
|
||||
// offensiveNotification gets 2 (Actor *) items
|
||||
// att is performing an offensive act on def
|
||||
#define offensiveNotification(att,def) ((def)->handleOffensiveAct(att))
|
||||
|
||||
/* ===================================================================== *
|
||||
Effect Implementations
|
||||
* ===================================================================== */
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Determine stat that modifies damage
|
||||
|
||||
int16 ProtoDamage::getRelevantStat(effectDamageTypes dt, Actor *a) {
|
||||
switch (dt) {
|
||||
|
||||
case kDamageImpact :
|
||||
case kDamageSlash :
|
||||
case kDamageProjectile :
|
||||
return a->getStats()->getSkillLevel(kSkillIDBrawn);
|
||||
case kDamageFire :
|
||||
case kDamageAcid :
|
||||
case kDamageHeat :
|
||||
case kDamageCold :
|
||||
case kDamageLightning :
|
||||
case kDamagePoison :
|
||||
case kDamageMental :
|
||||
case kDamageToUndead :
|
||||
return a->getStats()->getSkillLevel(kSkillIDSpellcraft);
|
||||
case kDamageDirMagic :
|
||||
case kDamageOther :
|
||||
case kDamageStarve :
|
||||
case kDamageEnergy :
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Cause damage to something _based on a damage proto-effect
|
||||
|
||||
void ProtoDamage::implement(GameObject *cst, SpellTarget *trg, int8 deltaDamage) {
|
||||
int8 totalDice;
|
||||
int8 totalBase;
|
||||
if (isActor(cst)) {
|
||||
Actor *a = (Actor *) cst;
|
||||
totalDice = _dice + _skillDice * getRelevantStat(_type, a);
|
||||
totalBase = _base + _skillBase * getRelevantStat(_type, a);
|
||||
if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
|
||||
offensiveNotification(a, (Actor *) trg->getObject());
|
||||
} else {
|
||||
totalDice = _dice;
|
||||
totalBase = _base;
|
||||
ObjectID pID = cst->possessor();
|
||||
if (pID != Nothing) {
|
||||
Actor *p = (Actor *) GameObject::objectAddress(pID);
|
||||
assert(isActor(p));
|
||||
if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
|
||||
offensiveNotification(p, (Actor *) trg->getObject());
|
||||
}
|
||||
}
|
||||
|
||||
totalBase -= deltaDamage;
|
||||
|
||||
assert(trg->getType() == SpellTarget::kSpellTargetObject);
|
||||
if (_self)
|
||||
cst->acceptDamage(cst->thisID(), totalBase, _type, totalDice, _sides);
|
||||
else
|
||||
trg->getObject()->acceptDamage(cst->thisID(), totalBase, _type, totalDice, _sides);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// drain something _based on a drainage proto-effect
|
||||
|
||||
int16 ProtoDrainage::currentLevel(Actor *a, effectDrainsTypes edt) {
|
||||
switch (edt) {
|
||||
case kDrainsManaRed:
|
||||
case kDrainsManaOrange:
|
||||
case kDrainsManaYellow:
|
||||
case kDrainsManaGreen:
|
||||
case kDrainsManaBlue:
|
||||
case kDrainsManaViolet:
|
||||
return (&a->_effectiveStats.redMana)[edt - kDrainsManaRed];
|
||||
|
||||
case kDrainsLifeLevel:
|
||||
return (a->getBaseStats())->vitality;
|
||||
case kDrainsVitality:
|
||||
return a->_effectiveStats.vitality;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtoDrainage::drainLevel(GameObject *cst, Actor *a, effectDrainsTypes edt, int16 amt) {
|
||||
switch (edt) {
|
||||
case kDrainsManaRed:
|
||||
case kDrainsManaOrange:
|
||||
case kDrainsManaYellow:
|
||||
case kDrainsManaGreen:
|
||||
case kDrainsManaBlue:
|
||||
case kDrainsManaViolet:
|
||||
{
|
||||
ActorManaID aType = (ActorManaID)(edt + (kManaIDRed - kDrainsManaRed));
|
||||
(&a->_effectiveStats.redMana)[aType] =
|
||||
clamp(
|
||||
0,
|
||||
(&a->_effectiveStats.redMana)[aType] - amt,
|
||||
(&(a->getBaseStats())->redMana)[aType]);
|
||||
}
|
||||
break;
|
||||
case kDrainsLifeLevel:
|
||||
{
|
||||
int16 &maxVit = (a->getBaseStats())->vitality;
|
||||
maxVit = clamp(0, maxVit - amt, absoluteMaximumVitality);
|
||||
a->acceptDamage(cst->thisID(), amt > 0 ? 1 : -1, kDamageOther);
|
||||
}
|
||||
break;
|
||||
case kDrainsVitality:
|
||||
a->acceptDamage(cst->thisID(), amt, kDamageOther);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
updateIndicators();
|
||||
}
|
||||
|
||||
void ProtoDrainage::implement(GameObject *cst, SpellTarget *trg, int8) {
|
||||
int8 totalDice;
|
||||
Actor *a;
|
||||
Actor *ac;
|
||||
if (isActor(cst)) {
|
||||
ac = (Actor *) cst;
|
||||
totalDice = _dice + _skillDice * ac->getStats()->spellcraft;
|
||||
if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
|
||||
offensiveNotification(ac, (Actor *) trg->getObject());
|
||||
} else {
|
||||
ac = nullptr;
|
||||
totalDice = _dice + 6;
|
||||
ObjectID pID = cst->possessor();
|
||||
if (pID != Nothing) {
|
||||
Actor *p = (Actor *) GameObject::objectAddress(pID);
|
||||
assert(isActor(p));
|
||||
if (totalDice > 0 && trg->getObject() && isActor(trg->getObject()))
|
||||
offensiveNotification(p, (Actor *) trg->getObject());
|
||||
}
|
||||
}
|
||||
int8 totalDamage = diceRoll(totalDice, 6, 0, 0);
|
||||
|
||||
|
||||
if (!(trg->getType() == SpellTarget::kSpellTargetObject))
|
||||
return;
|
||||
GameObject *target = _self ? cst : trg->getObject();
|
||||
if (!isActor(target))
|
||||
return;
|
||||
a = (Actor *) target;
|
||||
if (a->hasEffect(kActorNoDrain))
|
||||
return;
|
||||
|
||||
if (totalDamage > 0 && target->makeSavingThrow())
|
||||
totalDamage /= 2;
|
||||
totalDamage = clamp(0, totalDamage, currentLevel(a, _type));
|
||||
|
||||
drainLevel(cst, a, _type, totalDamage);
|
||||
if (ac != nullptr)
|
||||
drainLevel(cst, ac, _type, -totalDamage);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// enchant something _based on an enchantment proto-effect
|
||||
|
||||
bool ProtoEnchantment::realSavingThrow(Actor *a) {
|
||||
uint32 power = (a->getBaseStats())->vitality;
|
||||
power *= power;
|
||||
int32 saveSpace = absoluteMaximumVitality;
|
||||
saveSpace *= saveSpace;
|
||||
return (g_vm->_rnd->getRandomNumber(saveSpace - 1) < power);
|
||||
|
||||
}
|
||||
|
||||
void ProtoEnchantment::implement(GameObject *cst, SpellTarget *trg, int8) {
|
||||
if (isActor(trg->getObject())) {
|
||||
// can someone be angry at a wand?
|
||||
if (isHarmful(_enchID)) {
|
||||
if (isActor(cst)) {
|
||||
offensiveNotification((Actor *)cst, (Actor *) trg->getObject());
|
||||
} else {
|
||||
ObjectID pID = cst->possessor();
|
||||
if (pID != Nothing) {
|
||||
Actor *p = (Actor *) GameObject::objectAddress(pID);
|
||||
assert(isActor(p));
|
||||
offensiveNotification(p, (Actor *)trg->getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (((Actor *)(trg->getObject()))->hasEffect(kActorNoEnchant) &&
|
||||
isHarmful(_enchID))
|
||||
return;
|
||||
if (canFail() && realSavingThrow((Actor *)(trg->getObject())))
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHarmful(_enchID) && trg->getObject()->makeSavingThrow())
|
||||
return;
|
||||
EnchantObject(trg->getObject()->thisID(), _enchID, _minEnch + _dice.roll());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// effects on TAGs
|
||||
|
||||
void ProtoTAGEffect::implement(GameObject *cst, SpellTarget *trg, int8) {
|
||||
ActiveItem *tag = trg->getTAG();
|
||||
assert(tag);
|
||||
if (_affectBit == kSettagLocked) {
|
||||
//if ( tag->builtInBehavior()==ActiveItem::kBuiltInDoor )
|
||||
if (tag->isLocked() != (_onOff != 0))
|
||||
tag->acceptLockToggle(cst->thisID(), tag->lockType());
|
||||
} else if (_affectBit == kSettagOpen) {
|
||||
tag->trigger(cst->thisID(), _onOff);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// effects on non-Actors
|
||||
|
||||
void ProtoObjectEffect::implement(GameObject *, SpellTarget *trg, int8) {
|
||||
GameObject *go = trg->getObject();
|
||||
assert(go);
|
||||
if (!isActor(go))
|
||||
EnchantObject(go->thisID(), _affectBit, _dice.roll());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// effects on TAGs
|
||||
|
||||
void ProtoLocationEffect::implement(GameObject *, SpellTarget *, int8) {
|
||||
//TilePoint tp=trg->getPoint();
|
||||
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// use a special spell on something
|
||||
|
||||
void ProtoSpecialEffect::implement(GameObject *cst, SpellTarget *trg, int8) {
|
||||
assert(_handler);
|
||||
(*_handler)(cst, trg);
|
||||
}
|
||||
|
||||
/* ===================================================================== *
|
||||
Effect Applicability
|
||||
* ===================================================================== */
|
||||
|
||||
bool ProtoDamage::applicable(SpellTarget &trg) {
|
||||
return trg.getType() == SpellTarget::kSpellTargetObject ||
|
||||
trg.getType() == SpellTarget::kSpellTargetObjectPoint;
|
||||
}
|
||||
|
||||
bool ProtoDrainage::applicable(SpellTarget &trg) {
|
||||
return (trg.getType() == SpellTarget::kSpellTargetObject ||
|
||||
trg.getType() == SpellTarget::kSpellTargetObjectPoint) &&
|
||||
isActor(trg.getObject());
|
||||
}
|
||||
|
||||
bool ProtoEnchantment::applicable(SpellTarget &trg) {
|
||||
return (trg.getType() == SpellTarget::kSpellTargetObject ||
|
||||
trg.getType() == SpellTarget::kSpellTargetObjectPoint) &&
|
||||
(isActor(trg.getObject()) ||
|
||||
getEnchantmentSubType(_enchID) == kActorInvisible);
|
||||
}
|
||||
|
||||
bool ProtoTAGEffect::applicable(SpellTarget &trg) {
|
||||
return (trg.getType() == SpellTarget::kSpellTargetTAG);
|
||||
}
|
||||
|
||||
bool ProtoObjectEffect::applicable(SpellTarget &trg) {
|
||||
return (trg.getType() == SpellTarget::kSpellTargetObject ||
|
||||
trg.getType() == SpellTarget::kSpellTargetObjectPoint) &&
|
||||
!isActor(trg.getObject());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// These are effects for specific spells
|
||||
|
||||
#ifdef __WATCOMC__
|
||||
#pragma off (unreferenced);
|
||||
#endif
|
||||
|
||||
void createSpellCallFrame(GameObject *go, SpellTarget *trg, scriptCallFrame &scf) {
|
||||
assert(go);
|
||||
assert(trg);
|
||||
scf.invokedObject = Nothing;
|
||||
scf.enactor = go->thisID();
|
||||
scf.directObject = Nothing;
|
||||
scf.directTAI = NoActiveItem;
|
||||
scf.coords = Nowhere;
|
||||
|
||||
switch (trg->getType()) {
|
||||
case SpellTarget::kSpellTargetPoint :
|
||||
case SpellTarget::kSpellTargetObjectPoint:
|
||||
scf.value = 1;
|
||||
scf.coords = trg->getPoint();
|
||||
break;
|
||||
case SpellTarget::kSpellTargetObject :
|
||||
scf.value = 2;
|
||||
scf.directObject = trg->getObject()->thisID();
|
||||
break;
|
||||
case SpellTarget::kSpellTargetTAG :
|
||||
scf.value = 3;
|
||||
scf.directTAI = trg->getTAG()->thisID();
|
||||
break;
|
||||
case SpellTarget::kSpellTargetNone :
|
||||
default :
|
||||
scf.value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
SPECIALSPELL(CreateFireWisp) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateFireWisp, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(CreateWindWisp) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateWindWisp, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(CreateWraith) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateWraith, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(TeleportToShrine) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_TeleportToShrine, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(TeleportToLocation) {
|
||||
cst->move(trg->getPoint());
|
||||
}
|
||||
|
||||
SPECIALSPELL(Rejoin) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_Rejoin, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(CreateWWisp) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateWindWisp, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(CreateFWisp) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateFireWisp, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(CreateFood) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_CreateFood, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(Timequake) {
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_Timequake, scf);
|
||||
}
|
||||
|
||||
SPECIALSPELL(SagaSpellCall) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
SPECIALSPELL(DeathSpell) {
|
||||
// can someone be angry at a wand?
|
||||
if (isActor(trg->getObject())) {
|
||||
if (isActor(cst)) {
|
||||
offensiveNotification((Actor *) cst, (Actor *) trg->getObject());
|
||||
} else {
|
||||
ObjectID pID = cst->possessor();
|
||||
if (pID != Nothing) {
|
||||
Actor *p = (Actor *) GameObject::objectAddress(pID);
|
||||
assert(isActor(p));
|
||||
offensiveNotification(p, (Actor *) trg->getObject());
|
||||
}
|
||||
}
|
||||
if (ProtoEnchantment::realSavingThrow((Actor *)(trg->getObject())))
|
||||
return;
|
||||
Actor *a = (Actor *) trg->getObject();
|
||||
if (!a->makeSavingThrow()) {
|
||||
a->acceptDamage(cst->thisID(),
|
||||
a->_effectiveStats.vitality,
|
||||
kDamageEnergy, 1, 2, 0);
|
||||
a->die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SPECIALSPELL(Resurrect) {
|
||||
#if 0
|
||||
if (isActor(trg->getObject())) {
|
||||
Actor *a = (Actor *) trg->getObject();
|
||||
if (a->isDead()) a->imNotQuiteDead();
|
||||
}
|
||||
#else
|
||||
scriptCallFrame scf;
|
||||
createSpellCallFrame(cst, trg, scf);
|
||||
runScript(resImports->EXP_spellEffect_Timequake, scf);
|
||||
#endif
|
||||
}
|
||||
|
||||
SPECIALSPELL(DispellPoison) {
|
||||
if (isActor(trg->getObject())) {
|
||||
Actor *a = (Actor *) trg->getObject();
|
||||
DispelObjectEnchantment(a->thisID(), makeEnchantmentID(kActorPoisoned, true));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SPECIALSPELL(DispellProtections) {
|
||||
if (isActor(trg->getObject())) {
|
||||
Actor *a = (Actor *) trg->getObject();
|
||||
GameObject *obj;
|
||||
ContainerIterator iter(a);
|
||||
|
||||
if (isActor(cst)) {
|
||||
offensiveNotification((Actor *) cst, (Actor *) trg->getObject());
|
||||
} else {
|
||||
ObjectID pID = cst->possessor();
|
||||
if (pID != Nothing) {
|
||||
Actor *p = (Actor *) GameObject::objectAddress(pID);
|
||||
assert(isActor(p));
|
||||
offensiveNotification(p, (Actor *) trg->getObject());
|
||||
}
|
||||
}
|
||||
|
||||
if (ProtoEnchantment::realSavingThrow((Actor *)(trg->getObject())))
|
||||
return;
|
||||
|
||||
clearEnchantments(a);
|
||||
|
||||
while (iter.next(&obj) != Nothing) {
|
||||
ProtoObj *proto = obj->proto();
|
||||
|
||||
if (proto->containmentSet() & ProtoObj::kIsEnchantment) {
|
||||
uint16 enchantmentID = obj->getExtra();
|
||||
if (!isHarmful(enchantmentID)) {
|
||||
DispelObjectEnchantment(a->thisID(), enchantmentID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SPECIALSPELL(DispellCurses) {
|
||||
if (isActor(trg->getObject())) {
|
||||
Actor *a = (Actor *) trg->getObject();
|
||||
GameObject *obj;
|
||||
ContainerIterator iter(a);
|
||||
GameObject *ToBeDeleted = nullptr;
|
||||
|
||||
clearEnchantments(a);
|
||||
|
||||
while (iter.next(&obj) != Nothing) {
|
||||
ProtoObj *proto = obj->proto();
|
||||
|
||||
if (proto->containmentSet() & ProtoObj::kIsEnchantment) {
|
||||
uint16 enchantmentID = obj->getExtra();
|
||||
if (isHarmful(enchantmentID)) {
|
||||
if (ToBeDeleted) ToBeDeleted->deleteObject();
|
||||
ToBeDeleted = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ToBeDeleted) ToBeDeleted->deleteObject();
|
||||
|
||||
a->evalEnchantments();
|
||||
}
|
||||
}
|
||||
|
||||
} // end of namespace Saga2
|
||||
Reference in New Issue
Block a user