Spell
Targeting, Cost, and Chances
Spell effects usually target a single player, but this is not always the case.
At times,
the spell is either targeted at the spell caster or all the characters within an
area.
Also, not all characters can be affected by a spell. A spell cast by a monster,
for
example, should not hurt other monsters, just PCs. In the same vein, spells cast
by PCs should be directed only toward monsters.
Each spell has a range of
attack; that is, any target within this range can be targeted
by the spell. Once a spell is launched and takes effect, the spell has a
specific distance
at which the effect extends outward from the impact point. A spell targeting
multiple
characters can then affect those characters under the spell’s distance of
effect.
Assuming that a character knows
a spell (dictated by tracking a bit-encoded variable
for each character), you can determine how much mana is required to cast
the spell. Each spell has an associated cost assigned—a character must have that
much mana to cast the spell. Once cast, the spell’s cost in mana is deducted
from
the casting character’s mana points.
Merely casting a spell doesn’t
mean it will work, however; there are chances of failure.
The chance of the spell working or failing is called the spell effect chance,
and
this chance ranges from 0 percent (never works) to 100 percent (always works).
The Master
Spell List
Every aspect of a spell that
you’ve read about can be stored within a single structure,
making it much easier to work with.
This structure, sSpell, is as
follows:
typedef struct sSpell
{
char name[32];
char desc[128];
long damage_class; // class that spell does 2x damage
long cure_class; // class that spell aids
long cost; // spell casting cost in mp
float max_dist; // max distance to target
long effect; // spell effect
long chance; // percent of effect occurring
float value[4]; // misc values
long target; // target of spell
float range; // range (in game units)
long mesh_index[3]; // mesh index to use
long mesh_pos[3]; // positioning of mesh
float mesh_speed[3]; // speed of mesh movement
long mesh_sound[3]; // sound effect index to play
BOOL mesh_loop[3]; // loop animation
} *sSpellPtr;
As you can see, each spell is
assigned a name and a description, both of which are
contained with small buffers. Your game engine will display the name of each
spell
in anticipation of the player selecting one to cast when the time comes.
With spells, those classes come
into effect. Certain spells can do double damage to characters that have a
weak defense against them, which is the reason for the sSpell::damage_class
variable.
If the character’s class and
damage_class variables
match, the spell does double damage.
On the other hand, if a
character’s class is based on the spell’s class, that spell actually
heals the character. Imagine casting an ice spell at an ice dragon. Instead of
hurting the dragon, it heals him for half the damage amount of the spell. Thus,
the
purpose of sSpell::cure_class;
becomes apparent; if the character’s class and
cure_class;
match, the spell heals rather than hurts.
Moving on, you can see the
spell casting cost (sSpell::cost), measured in mana
points. A character must have at least this amount of mana (cost) in reserve to
cast
the spell. Once the spell is cast, the value in the Cost variable is deducted
from the
character’s mana.
Remember that spells have an
assigned range and distance; range (sSpell::range) is
the distance away from the caster that a spell can reach and strike a target,
whereas
distance (sSpell::max_dist)
is the parameter around the targeted position at which
the spell’s effects can take place.
Once a spell finds its mark,
the cSpell::target variable determines who or what is
affected—either the spell caster, a single target caught in the parameter of the
spell, or all characters caught in the parameter. Each type of target is defined
in
the engine as follows:
enum
SpellTargets {
TARGET_SINGLE = 0,
TARGET_SELF,
TARGET_AREA
};
The spell’s effect
(sSpell::effect) has an associated chance of success, which is
stored in sSpell::chance. Each value has an associated trio of variables
(sSpell::value)
at its disposal. The first value in the array is the amount of damage caused or
cured
or the bit values of the ailment to be used.
The values’ only other use is
for the teleport spell effect; for NPCs and monsters,
the first three values are those of the coordinates inside the current level
that the
character is moved to whenever the teleport spell is cast. As for PCs, the
fourth
variable is used to specify which map the player will be switched to when the
spell
is cast. Because of the complexity of teleporting PCs, let the game script
engine
handle such teleporting situations.
You use the final group of
variables (mesh_index,
mesh_pos,
mesh_speed,
mesh_sound,
and
mesh_loop)
for the graphical portion of the spell. Rather than reference the spell
meshes by name, it is much more efficient to use numbers. The
mesh_indexstores
a
mesh number that the spell control engine uses for drawing the spell’s graphics.
mesh_posis
the array of variables that contains the position of each mesh. Remember
that a mesh can hover over the caster or target, move to or from them, and even
stretch out between the two characters. You can set the
mesh_pos
variables to one of
the following values:
enum
AnimPositions {
POSITION_NONE = 0,
POSITION_CASTER,
POSITION_TOTARGET,
POSITION_TOCASTER,
POSITION_TARGET,
POSITION_SCALE
};
Again, each mesh has an
associated speed of travel or time that it remains in place
(as it hovers over a character or stretches out between two positions). Both
speed
and time are stored in the mesh_speed
variable, as only one of
those values is used
(depending on the movement of the mesh).
In speed calculations,
mesh_speed
determines the distance in 3-D units that the mesh
travels in one second. For time, the
mesh_speed variable is
converted into a long value
that represents the amount in milliseconds that the mesh remains in place.
If the mesh is able to complete
its animation cycle before it reaches its target or
before its time of display is up, the mesh_loop variables tell the spell control
engine to
loop the animation over and over until the mesh cycle is complete.
As a final bonus, each one of
the three meshes has the ability to emanate a sound
when the mesh is initialized (positioned). Imagine that your fireball spell is
sizzling
toward its target, only to blast forth in a speaker-shattering sound! You also
reference
each sound by a number and have your game engine play those sounds.
The Spell
List
You use an array of sSpell structures to contain the information about every
spell in
a game. This array of structures is called the master spell list (referred to as
MSL from
now on), which is stored as a sequential data file. The spell data structure is
relatively
small, which means that the list can be completely loaded at the start of the
game in order to save you time when accessing the data.
Looking back, you can see I’ve
designated that each character has the ability to use
64 spells, so the MSL should hold only 64 sSpell data structures, each
representing
a single spell that is available for use by all characters.
As I mentioned previously, it
becomes a matter of loading each sSpell structure
with the appropriate spell data needed. Even with only 64 spells at your
disposal,
trying to hard-code that many spell structures is too much work.