mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 22:28:10 +00:00
1059 lines
28 KiB
C++
1059 lines
28 KiB
C++
/* 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/idtypes.h"
|
|
#include "saga2/speldefs.h"
|
|
#include "saga2/spellbuk.h"
|
|
#include "saga2/spelshow.h"
|
|
#include "saga2/spelvals.h"
|
|
#include "saga2/tilevect.h"
|
|
#if DEBUG
|
|
#include "saga2/tileline.h"
|
|
#endif
|
|
|
|
namespace Saga2 {
|
|
|
|
extern WorldMapData *mapList;
|
|
extern SpellStuff *spellBook;
|
|
extern PlatformHandle platformList; // platform resource hunk
|
|
|
|
/* ===================================================================== *
|
|
Locals
|
|
* ===================================================================== */
|
|
|
|
static int16 prevMapNum;
|
|
static StaticTilePoint prevCoords = {(int16)minint16, (int16)minint16, (int16)minint16};
|
|
static MetaTilePtr prevMeta;
|
|
|
|
/* ===================================================================== *
|
|
Prototypes
|
|
* ===================================================================== */
|
|
|
|
blockageType checkNontact(Effectron *obj, const TilePoint &loc, GameObject **blockResultObj = nullptr);
|
|
int32 scatterer(int32 i, int32 m, int32 s);
|
|
|
|
/* ===================================================================== *
|
|
SpellStuff implementation
|
|
* ===================================================================== */
|
|
|
|
//-----------------------------------------------------------------------
|
|
// ctor
|
|
|
|
SpellStuff::SpellStuff() {
|
|
master = nullSpell;
|
|
display = nullSpell;
|
|
prototype = nullptr;
|
|
targetableTypes = spellTargNone;
|
|
targetTypes = spellApplyNone;
|
|
effects = nullptr;
|
|
targets = nullptr;
|
|
manaType = sManaIDSkill;
|
|
manaUse = 0;
|
|
shape = eAreaInvisible;
|
|
size = 0;
|
|
range = 0;
|
|
sound = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// is this spell harmful
|
|
|
|
bool SpellStuff::isOffensive() {
|
|
return (canTarget(spellTargActor) || canTarget(spellTargObject)) &&
|
|
(!canTarget(spellTargCaster));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// determine whether an area spell protects the caster
|
|
|
|
bool SpellStuff::safe() {
|
|
switch (shape) {
|
|
case eAreaInvisible:
|
|
case eAreaAura:
|
|
case eAreaGlow:
|
|
case eAreaProjectile:
|
|
case eAreaExchange:
|
|
case eAreaMissle:
|
|
case eAreaSquare:
|
|
case eAreaBall:
|
|
case eAreaWall:
|
|
case eAreaStorm:
|
|
return false;
|
|
case eAreaBolt:
|
|
case eAreaBeam:
|
|
case eAreaCone:
|
|
case eAreaWave:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// add an internal effect to a spell
|
|
|
|
void SpellStuff::addEffect(ProtoEffect *pe) {
|
|
if (effects == nullptr)
|
|
effects = pe;
|
|
else {
|
|
ProtoEffect *tail;
|
|
for (tail = effects; tail->next; tail = tail->next) ;
|
|
tail->next = pe;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// play the sound associated with a spell
|
|
|
|
void SpellStuff::playSound(GameObject *go) {
|
|
if (sound) {
|
|
Location cal = go->notGetWorldLocation(); //Location(go->getLocation(),go->IDParent());
|
|
Saga2::playSoundAt(MKTAG('S', 'P', 'L', sound), cal);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// cleanup
|
|
|
|
void SpellStuff::killEffects() {
|
|
if (effects) {
|
|
delete effects;
|
|
}
|
|
effects = nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// implement a spell - general target type
|
|
|
|
void SpellStuff::implement(GameObject *enactor, SpellTarget *target) {
|
|
assert(target);
|
|
switch (target->getType()) {
|
|
case SpellTarget::spellTargetPoint:
|
|
implement(enactor, Location(target->getPoint(), Nothing));
|
|
break;
|
|
case SpellTarget::spellTargetObjectPoint:
|
|
if (targetTypes == spellApplyObject)
|
|
implement(enactor, target->getObject());
|
|
else
|
|
implement(enactor, Location(target->getPoint(), Nothing));
|
|
break;
|
|
case SpellTarget::spellTargetObject:
|
|
implement(enactor, target->getObject());
|
|
break;
|
|
case SpellTarget::spellTargetTAG:
|
|
implement(enactor, target->getTAG());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// implement a spell - object target type
|
|
|
|
void SpellStuff::implement(GameObject *enactor, GameObject *target) {
|
|
SpellTarget st = SpellTarget(target);
|
|
if (safe() &&
|
|
target->thisID() == enactor->thisID() &&
|
|
!canTarget(spellTargCaster))
|
|
return;
|
|
if (effects) {
|
|
for (ProtoEffect *pe = effects; pe; pe = pe->next)
|
|
if (pe->applicable(st))
|
|
pe->implement(enactor, &st);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// implement a spell - TAG target type
|
|
|
|
void SpellStuff::implement(GameObject *enactor, ActiveItem *target) {
|
|
SpellTarget st = SpellTarget(target);
|
|
if (effects) {
|
|
for (ProtoEffect *pe = effects; pe; pe = pe->next)
|
|
if (pe->applicable(st))
|
|
pe->implement(enactor, &st);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// implement a spell - area target type
|
|
|
|
void SpellStuff::implement(GameObject *enactor, Location target) {
|
|
SpellTarget st = SpellTarget(target);
|
|
buildTargetList(enactor, st);
|
|
if (effects && targets) {
|
|
for (SpellTarget *t = targets; t; t = t->next) {
|
|
if (safe() &&
|
|
t->getObject() != nullptr &&
|
|
t->getObject()->thisID() == enactor->thisID() &&
|
|
!canTarget(spellTargCaster))
|
|
continue;
|
|
for (ProtoEffect *pe = effects; pe; pe = pe->next)
|
|
if (pe->applicable(*t))
|
|
pe->implement(enactor, t);
|
|
}
|
|
}
|
|
removeTargetList();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// determine target list for a spell
|
|
|
|
void SpellStuff::buildTargetList(GameObject *caster, SpellTarget &trg) {
|
|
int16 radius = size;
|
|
TilePoint tVect, orth, tBase;
|
|
show(caster, trg);
|
|
switch (shape) {
|
|
case eAreaInvisible:
|
|
case eAreaAura:
|
|
case eAreaGlow:
|
|
case eAreaProjectile:
|
|
case eAreaExchange:
|
|
case eAreaMissle:
|
|
targets = &trg;
|
|
break;
|
|
case eAreaSquare: {
|
|
tVect = trg.getPoint();
|
|
orth = TilePoint(squareSpellSize / 2, squareSpellSize / 2, 0);
|
|
|
|
RectangularObjectIterator it(currentWorld,
|
|
tVect - orth,
|
|
TilePoint(squareSpellSize, 0, 0),
|
|
TilePoint(0, squareSpellSize, 0));
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBolt: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, boltSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, boltSpellWidth / 2);
|
|
tBase = caster->getWorldLocation() + (isActor(caster) ? (tVect / 32) : TilePoint(0, 0, 0));
|
|
RectangularObjectIterator it(currentWorld, tBase - orth, tVect, orth * 2);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBeam: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, beamSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, beamSpellWidth / 2);
|
|
tBase = caster->getWorldLocation() + (isActor(caster) ? (tVect / 32) : TilePoint(0, 0, 0));
|
|
RectangularObjectIterator it(currentWorld, tBase - orth, tVect, orth * 2);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBall: {
|
|
radius = ballSpellRadius;
|
|
CircularObjectIterator it(currentWorld, trg.getPoint(), radius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaWall: {
|
|
radius = wallSpellRadius;
|
|
RingObjectIterator it(currentWorld, trg.getPoint(), radius, wallInnerRadius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaStorm: {
|
|
radius = stormSpellRadius;
|
|
CircularObjectIterator it(currentWorld, trg.getPoint(), radius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaCone: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, coneSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, coneSpellWidth / 2);
|
|
tBase = caster->getWorldLocation() + (isActor(caster) ? (tVect / 32) : TilePoint(0, 0, 0));
|
|
TriangularObjectIterator it(currentWorld, tBase, tBase + tVect - orth, tBase + tVect + orth);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaWave: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, waveSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, waveSpellWidth / 2);
|
|
tBase = caster->getWorldLocation() + (isActor(caster) ? (tVect / 32) : TilePoint(0, 0, 0));
|
|
TriangularObjectIterator it(currentWorld, tBase, tBase + tVect - orth, tBase + tVect + orth);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
addTarget(new SpellTarget(go));
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// add a target to the target list
|
|
|
|
void SpellStuff::addTarget(SpellTarget *trg) {
|
|
if (targets == nullptr)
|
|
targets = trg;
|
|
else {
|
|
SpellTarget *t = targets;
|
|
while (t->next) t = t->next;
|
|
t->next = trg;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// clean the target list
|
|
|
|
void SpellStuff::removeTargetList() {
|
|
switch (shape) {
|
|
case eAreaInvisible:
|
|
case eAreaAura:
|
|
case eAreaGlow:
|
|
case eAreaProjectile:
|
|
case eAreaExchange:
|
|
case eAreaMissle:
|
|
targets = nullptr;
|
|
break;
|
|
case eAreaWall:
|
|
case eAreaCone:
|
|
case eAreaBeam:
|
|
case eAreaBolt:
|
|
case eAreaBall:
|
|
case eAreaStorm:
|
|
case eAreaSquare:
|
|
if (targets) delete targets;
|
|
targets = nullptr;
|
|
break;
|
|
default:
|
|
error("bad spell");
|
|
}
|
|
assert(targets == nullptr);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// spell debugging
|
|
|
|
#if DEBUG
|
|
#define DSPELL_AREA_COLOR 3
|
|
#define DSPELL_TARGET_COLOR 7
|
|
|
|
void showTarg(const TilePoint &tp) {
|
|
TPCircle(tp, 8, DSPELL_TARGET_COLOR);
|
|
}
|
|
|
|
void SpellStuff::show(GameObject *caster, SpellTarget &trg) {
|
|
int16 radius = size;
|
|
TilePoint tVect, orth, tBase;
|
|
switch (shape) {
|
|
case eAreaInvisible:
|
|
showTarg(trg.getPoint());
|
|
break;
|
|
case eAreaAura:
|
|
case eAreaGlow:
|
|
case eAreaProjectile:
|
|
case eAreaExchange:
|
|
case eAreaMissle:
|
|
TPLine(caster->getWorldLocation(), trg.getPoint(), DSPELL_AREA_COLOR);
|
|
showTarg(trg.getPoint());
|
|
break;
|
|
case eAreaSquare: {
|
|
tVect = trg.getPoint();
|
|
orth = TilePoint(squareSpellSize / 2, squareSpellSize / 2, 0);
|
|
|
|
TPRectangle(tVect - orth,
|
|
tBase + TilePoint(squareSpellSize, 0, 0),
|
|
tBase + TilePoint(0, squareSpellSize, 0) + TilePoint(squareSpellSize, 0, 0),
|
|
tBase + TilePoint(0, squareSpellSize, 0),
|
|
DSPELL_AREA_COLOR);
|
|
RectangularObjectIterator it(currentWorld,
|
|
tVect - orth,
|
|
TilePoint(squareSpellSize, 0, 0),
|
|
TilePoint(0, squareSpellSize, 0));
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBolt: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, boltSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, boltSpellWidth / 2);
|
|
tBase = caster->getWorldLocation();
|
|
TPRectangle(tBase - orth, tBase + orth, tBase + tVect + orth, tBase + tVect - orth, DSPELL_AREA_COLOR);
|
|
RectangularObjectIterator it(currentWorld, tBase - orth, tVect, orth * 2);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBeam: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, beamSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, beamSpellWidth / 2);
|
|
tBase = caster->getWorldLocation();
|
|
TPRectangle(tBase - orth, tBase + orth, tBase + tVect + orth, tBase + tVect - orth, DSPELL_AREA_COLOR);
|
|
RectangularObjectIterator it(currentWorld, tBase - orth, tVect, orth * 2);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eAreaBall: {
|
|
radius = ballSpellRadius;
|
|
TPCircle(trg.getPoint(), ballSpellRadius, DSPELL_AREA_COLOR);
|
|
CircularObjectIterator it(currentWorld, trg.getPoint(), radius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaWall: {
|
|
radius = wallSpellRadius;
|
|
TPCircle(trg.getPoint(), radius, DSPELL_AREA_COLOR);
|
|
TPCircle(trg.getPoint(), radius / 2, DSPELL_AREA_COLOR);
|
|
RingObjectIterator it(currentWorld, trg.getPoint(), radius, wallInnerRadius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaStorm: {
|
|
radius = stormSpellRadius;
|
|
TPCircle(trg.getPoint(), stormSpellRadius, DSPELL_AREA_COLOR);
|
|
CircularObjectIterator it(currentWorld, trg.getPoint(), radius);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaCone: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, coneSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, coneSpellWidth / 2);
|
|
tBase = caster->getWorldLocation();
|
|
TPTriangle(tBase, tBase + tVect - orth, tBase + tVect + orth, DSPELL_AREA_COLOR);
|
|
TriangularObjectIterator it(currentWorld, tBase, tBase + tVect - orth, tBase + tVect + orth);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
case eAreaWave: {
|
|
tVect = trg.getPoint() - caster->getWorldLocation();
|
|
while (tVect.magnitude() == 0) {
|
|
tVect = randomVector(TilePoint(-1, -1, 0), TilePoint(1, 1, 0));
|
|
}
|
|
setMagnitude(tVect, waveSpellLength);
|
|
orth = rightVector(tVect, 0);
|
|
setMagnitude(orth, waveSpellWidth / 2);
|
|
tBase = caster->getWorldLocation();
|
|
TPTriangle(tBase, tBase + tVect - orth, tBase + tVect + orth, DSPELL_AREA_COLOR);
|
|
TriangularObjectIterator it(currentWorld, tBase, tBase + tVect - orth, tBase + tVect + orth);
|
|
GameObject *go;
|
|
it.first(&go);
|
|
while (go) {
|
|
showTarg(go->getWorldLocation());
|
|
it.next(&go);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
|
|
void SpellStuff::show(GameObject *, SpellTarget &) {}
|
|
|
|
#endif
|
|
|
|
/* ===================================================================== *
|
|
SpellInstance implementation
|
|
* ===================================================================== */
|
|
|
|
// ------------------------------------------------------------------
|
|
// ctor
|
|
|
|
SpellInstance::SpellInstance(SpellCaster *newCaster, SpellTarget *newTarget, SpellID spellNo) {
|
|
assert(newCaster);
|
|
assert(newTarget);
|
|
caster = newCaster;
|
|
target = new SpellTarget(*newTarget);
|
|
world = newCaster->world();
|
|
spell = spellNo;
|
|
init();
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// ctor
|
|
|
|
SpellInstance::SpellInstance(SpellCaster *newCaster, GameObject &newTarget, SpellID spellNo) {
|
|
assert(newCaster);
|
|
target = new SpellTarget(newTarget);
|
|
caster = newCaster;
|
|
world = newCaster->world();
|
|
spell = spellNo;
|
|
init();
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// ctor
|
|
|
|
SpellInstance::SpellInstance(SpellCaster *newCaster, GameObject *newTarget, SpellID spellNo) {
|
|
assert(newCaster);
|
|
assert(newTarget);
|
|
target = new SpellTarget(newTarget);
|
|
caster = newCaster;
|
|
world = newCaster->world();
|
|
spell = spellNo;
|
|
init();
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// ctor
|
|
|
|
SpellInstance::SpellInstance(SpellCaster *newCaster, TilePoint &newTarget, SpellID spellNo) {
|
|
assert(newCaster);
|
|
target = new SpellTarget(newTarget);
|
|
caster = newCaster;
|
|
world = newCaster->world();
|
|
spell = spellNo;
|
|
init();
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// dtor
|
|
|
|
|
|
SpellInstance::~SpellInstance() {
|
|
if (age < implementAge && g_vm->_gameRunning)
|
|
spellBook[spell].implement(caster, target);
|
|
for (int32 i = 0; i < eList.count; i++) {
|
|
if (eList.displayList[i].efx)
|
|
delete eList.displayList[i].efx;
|
|
eList.displayList[i].efx = nullptr;
|
|
}
|
|
if (target)
|
|
delete target;
|
|
target = nullptr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// common initialization code
|
|
|
|
void SpellInstance::init() {
|
|
dProto = (*g_vm->_sdpList)[spell];
|
|
ProtoObj *proto = caster->proto();
|
|
TilePoint sPoint = caster->getWorldLocation();
|
|
sPoint.z += proto->height / 2;
|
|
age = 0;
|
|
implementAge = 0;
|
|
effSeq = 0;
|
|
assert(dProto);
|
|
if (!dProto) return;
|
|
effect = (*g_vm->_edpList)[dProto->effect];
|
|
implementAge = dProto->implementAge;
|
|
maxAge = dProto->maxAge;
|
|
initEffect(sPoint);
|
|
|
|
if (implementAge == 0)
|
|
spellBook[spell].implement(caster, target);
|
|
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// common cleanup
|
|
|
|
void SpellInstance::termEffect() {
|
|
if (eList.count)
|
|
for (int32 i = 0; i < eList.count; i++) {
|
|
if (eList.displayList[i].efx) {
|
|
delete eList.displayList[i].efx;
|
|
eList.displayList[i].efx = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// visual init
|
|
|
|
void SpellInstance::initEffect(TilePoint startpoint) {
|
|
eList.count = effect->nodeCount; //sdp->effCount;
|
|
if (eList.count)
|
|
for (int32 i = 0; i < eList.count; i++) {
|
|
Effectron *e = new Effectron(0, i);
|
|
eList.displayList[i].efx = e;
|
|
e->parent = this;
|
|
e->start = startpoint;
|
|
e->current = startpoint;
|
|
e->partno = i;
|
|
e->stepNo = 0;
|
|
e->initCall(i);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// visual update
|
|
|
|
bool SpellInstance::buildList() {
|
|
if (eList.dissipated()) {
|
|
termEffect();
|
|
if (effect->next == nullptr)
|
|
return false;
|
|
effect = effect->next;
|
|
effSeq++;
|
|
//
|
|
initEffect(target->getPoint());
|
|
}
|
|
eList.buildEffects(false);
|
|
return true;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// visual update
|
|
|
|
bool SpellInstance::updateStates(int32 deltaTime) {
|
|
|
|
spellBook[spell].show(caster, *target);
|
|
age++;
|
|
if (age == implementAge || implementAge == continuouslyImplemented)
|
|
spellBook[spell].implement(caster, target);
|
|
if (maxAge > 0 && age > maxAge)
|
|
termEffect();
|
|
eList.updateEStates(deltaTime);
|
|
return true;
|
|
}
|
|
|
|
|
|
/* ===================================================================== *
|
|
General spell routines
|
|
* ===================================================================== */
|
|
|
|
//-----------------------------------------------------------------------
|
|
// effectron sctterer
|
|
|
|
int32 scatterer(int32 i, int32 m, int32 s) {
|
|
switch (s) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return i % m;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
return (i * s) % m;
|
|
case 5:
|
|
default:
|
|
return g_vm->_rnd->getRandomNumber(m - 1);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// point to point collision detection
|
|
|
|
|
|
TilePoint collideTo(Effectron *e, TilePoint nloc) {
|
|
GameObject *bumpy;
|
|
blockageType bt = checkNontact(e, nloc, &bumpy);
|
|
|
|
if (bt == blockageTerrain) {
|
|
e->bump();
|
|
}
|
|
|
|
return nloc;
|
|
}
|
|
|
|
/* ===================================================================== *
|
|
Effectron implementation
|
|
* ===================================================================== */
|
|
|
|
//-----------------------------------------------------------------------
|
|
// default constructor
|
|
|
|
Effectron::Effectron() {
|
|
age = 0;
|
|
pos = 0;
|
|
flags = effectronDead;
|
|
parent = nullptr;
|
|
partno = 0;
|
|
totalSteps = stepNo = 0;
|
|
hgt = 0;
|
|
brd = 0;
|
|
spr = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// constructor with starting position / velocity
|
|
|
|
Effectron::Effectron(uint16 newPos, uint16 newDir) {
|
|
age = 0;
|
|
pos = (newDir << 16) + newPos;
|
|
flags = 0;
|
|
parent = nullptr;
|
|
partno = 0;
|
|
totalSteps = stepNo = 0;
|
|
hgt = 0;
|
|
brd = 0;
|
|
spr = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Effectron display update
|
|
|
|
void Effectron::updateEffect(int32 deltaTime) {
|
|
age += deltaTime;
|
|
if (age > 1) {
|
|
age = 0;
|
|
pos++;
|
|
finish = parent->target->getPoint();
|
|
stepNo++;
|
|
|
|
flags = staCall();
|
|
if (isHidden() || isDead())
|
|
return;
|
|
spr = sprCall();
|
|
hgt = hgtCall();
|
|
brd = brdCall();
|
|
TilePoint oLoc = posCall();
|
|
|
|
// at this point we need to detect collisions
|
|
// between current ( or start ) and oLoc
|
|
|
|
current = collideTo(this, oLoc);
|
|
TileToScreenCoords(oLoc, screenCoords);
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
void Effectron::bump() {
|
|
switch (parent->dProto->elasticity) {
|
|
case ecFlagBounce :
|
|
velocity = -velocity;
|
|
break;
|
|
case ecFlagDie :
|
|
kill();
|
|
break;
|
|
case ecFlagStop :
|
|
velocity = TilePoint(0, 0, 0);
|
|
break;
|
|
case ecFlagNone :
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Effectron collision detection clones
|
|
// I don't know how they work, and they're cloned from routines
|
|
// that have long since changed.
|
|
|
|
|
|
//-----------------------------------------------------------------------
|
|
// clone of tileSlopeHeight()
|
|
int16 tileNopeHeight(
|
|
const TilePoint &pt,
|
|
Effectron *obj,
|
|
StandingTileInfo *sti = nullptr);
|
|
|
|
//-----------------------------------------------------------------------
|
|
// clone of objectCollision()
|
|
GameObject *objectNollision(Effectron *obj, const TilePoint &loc);
|
|
|
|
|
|
|
|
blockageType checkNontact(
|
|
Effectron *obj,
|
|
const TilePoint &loc,
|
|
GameObject **blockResultObj) {
|
|
int16 mapNum = obj->getMapNum();
|
|
int32 terrain;
|
|
GameObject *blockObj;
|
|
|
|
if (blockResultObj) *blockResultObj = nullptr;
|
|
|
|
terrain = volumeTerrain(mapNum,
|
|
loc,
|
|
obj->brdCall(),
|
|
obj->hgtCall());
|
|
|
|
// Check for intersection with a wall or obstacle
|
|
if (terrain & terrainRaised)
|
|
return blockageTerrain;
|
|
|
|
// Check for intersection with slope of the terrain.
|
|
if (((terrain & terrainSurface)
|
|
|| (!(terrain & terrainWater) && loc.z <= 0))
|
|
&& loc.z < tileNopeHeight(loc, obj))
|
|
return blockageTerrain;
|
|
|
|
// See if object collided with an object
|
|
blockObj = objectNollision(obj, loc);
|
|
if (blockObj) {
|
|
if (blockResultObj) *blockResultObj = blockObj;
|
|
return blockageObject;
|
|
}
|
|
|
|
return blockageNone;
|
|
}
|
|
|
|
int16 tileNopeHeight(
|
|
const TilePoint &pt,
|
|
Effectron *obj,
|
|
StandingTileInfo *stiResult) {
|
|
int16 mapNum = obj->getMapNum();
|
|
|
|
// Calculate coordinates of tile, metatile, and subtile
|
|
TilePoint tileCoords = pt >> kTileUVShift,
|
|
metaCoords = tileCoords >> kPlatShift,
|
|
origin = metaCoords << kPlatShift,
|
|
coords = tileCoords - origin,
|
|
subTile((pt.u >> kSubTileShift) & kSubTileMask,
|
|
(pt.v >> kSubTileShift) & kSubTileMask,
|
|
0);
|
|
|
|
MetaTilePtr metaPtr;
|
|
StandingTileInfo highestTile, // Represents highest tile which is below
|
|
// object's base
|
|
lowestTile; // Represents lowest tile at tile position
|
|
int16 supportHeight,
|
|
highestSupportHeight,
|
|
lowestSupportHeight;
|
|
|
|
{
|
|
// Look up the metatile on the map.
|
|
metaPtr = prevMeta = mapList[mapNum].lookupMeta(metaCoords);
|
|
prevMapNum = mapNum;
|
|
prevCoords.set(metaCoords.u, metaCoords.v, metaCoords.z);
|
|
}
|
|
|
|
if (metaPtr == nullptr) return 0L;
|
|
|
|
highestTile.surfaceTile = lowestTile.surfaceTile = nullptr;
|
|
highestSupportHeight = -100;
|
|
lowestSupportHeight = 0x7FFF;
|
|
|
|
// Search each platform until we find a tile which is under
|
|
// the character.
|
|
|
|
for (int i = 0; i < maxPlatforms; i++) {
|
|
Platform *p;
|
|
|
|
if ((p = metaPtr->fetchPlatform(mapNum, i)) == nullptr)
|
|
continue;
|
|
|
|
if (p->flags & plVisible) {
|
|
TileInfo *ti;
|
|
StandingTileInfo sti;
|
|
|
|
// Get the tile, and its base height
|
|
ti = p->fetchTAGInstance(
|
|
mapNum,
|
|
coords,
|
|
origin,
|
|
sti);
|
|
|
|
if (ti) {
|
|
int32 subTileTerrain =
|
|
ti->attrs.testTerrain(calcSubTileMask(subTile.u,
|
|
subTile.v));
|
|
if (subTileTerrain & terrainInsubstantial)
|
|
continue;
|
|
else if (subTileTerrain & terrainRaised)
|
|
// calculate height of raised surface
|
|
supportHeight = sti.surfaceHeight +
|
|
ti->attrs.terrainHeight;
|
|
else if (subTileTerrain & terrainWater)
|
|
// calculate depth of water
|
|
supportHeight = sti.surfaceHeight -
|
|
ti->attrs.terrainHeight;
|
|
else
|
|
// calculate height of unraised surface
|
|
supportHeight = sti.surfaceHeight +
|
|
ptHeight(TilePoint(pt.u & kTileUVMask,
|
|
pt.v & kTileUVMask,
|
|
0),
|
|
ti->attrs.cornerHeight);
|
|
|
|
// See if the tile is a potential supporting surface
|
|
if (supportHeight <= pt.z + obj->hgtCall()
|
|
&& supportHeight >= highestSupportHeight
|
|
&& (ti->combinedTerrainMask() &
|
|
(terrainSurface | terrainRaised))) {
|
|
highestTile = sti;
|
|
highestSupportHeight = supportHeight;
|
|
} else if (highestTile.surfaceTile == nullptr &&
|
|
supportHeight <= lowestSupportHeight &&
|
|
(ti->combinedTerrainMask() &
|
|
(terrainSurface | terrainRaised))) {
|
|
lowestTile = sti;
|
|
lowestSupportHeight = supportHeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (highestTile.surfaceTile) {
|
|
if (stiResult) *stiResult = highestTile;
|
|
return highestSupportHeight;
|
|
}
|
|
if (lowestTile.surfaceTile) {
|
|
if (stiResult) *stiResult = lowestTile;
|
|
return lowestSupportHeight;
|
|
}
|
|
if (stiResult) {
|
|
stiResult->surfaceTile = nullptr;
|
|
stiResult->surfaceTAG = nullptr;
|
|
stiResult->surfaceHeight = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
GameObject *objectNollision(Effectron *obj, const TilePoint &loc) {
|
|
// ProtoObj *proto = obj->proto();
|
|
TileRegion volume;
|
|
GameObject *obstacle = nullptr;
|
|
|
|
volume.min.u = loc.u - obj->brdCall();
|
|
volume.min.v = loc.v - obj->brdCall();
|
|
volume.max.u = loc.u + obj->brdCall();
|
|
volume.max.v = loc.v + obj->brdCall();
|
|
volume.min.z = loc.z;
|
|
volume.max.z = loc.z + obj->hgtCall();
|
|
|
|
// Adjust MIN Z for the fact that they can step over obstacles.
|
|
//if ( isActor( obj )) volume.min.z += kMaxStepHeight / 2;
|
|
|
|
// Constructor
|
|
CircularObjectIterator iter(obj->world(), loc, obj->brdCall() + 32);
|
|
|
|
for (iter.first(&obstacle);
|
|
obstacle != nullptr;
|
|
iter.next(&obstacle)) {
|
|
TilePoint tp = obstacle->getLocation();
|
|
ProtoObj *proto = obstacle->proto();
|
|
|
|
//if ( obstacle == obj ) continue;
|
|
|
|
if (tp.z < volume.max.z
|
|
&& tp.z + proto->height > volume.min.z
|
|
&& tp.u - proto->crossSection < volume.max.u
|
|
&& tp.u + proto->crossSection > volume.min.u
|
|
&& tp.v - proto->crossSection < volume.max.v
|
|
&& tp.v + proto->crossSection > volume.min.v) {
|
|
return obstacle;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // end of namespace Saga2
|