3.7 Programming Argo Spells and Psionic Abilities
The coded effects of spells and psionic abilities are added much like
those of skills: modify the skeleton program for spells or psiabs, asys-xspells or asys-xpsiabs.
The following example creates a spell called Energy, which gives a
boost of 1dX to the target player's Fatigue, where X is equal to the
caster's Mage skill plus Energy skill. For the example, let's assume
that the spell has already been defined, and requires a wizard's staff
as a component and a pinch of anise as a material. The example will
concentrate on topics specific to spells and generic topics not covered
in the last two examples. (Psionic abilites are added in exactly the
same way, except psiabs don't use components or materials.)
First, a quick review of the functions provided in asys-xspells.
DoInstall and DoUninstall are functional
equivalents of the corresponding functions in the other skeleton
programs. Replace all instances of `ppp ' with the name of
the program and all instances of `xxx ' with the name of the
spell. If multiple spells are to handled, duplicated the appropriate
`xxx ' lines from both functions, once per spell to be
handled.
DoTargetLoop starts an event loop for the target player
if needed, with the action set to `wait'. Ensuring that the target has
an event loop running is often important for spells because the spells
wear off on a turn-by-turn basis. If the target does not have an event
loop running, the spell will never wear off (Argo does its best
to close the resulting loophole, by which a player could do
+stop to keep the effects of a beneficial spell
indefinitely... when you execute a +stop , beneficial spells
expire immediately and harmful ones stay).
As in asys-xskills, DoChecks does some event
verification checks immediately before running a spell, in case the
situation has changed over the course of the turn... makes sure the
target is valid, make sure a valid spell is selected, makes sure the
mage has needed components and materials, etc.
DoBreakSomething is provided as a way of adding harmful
side effects for critical failures. In general, critical failures should
result in a negative effect related to the intended effect of the spell:
a spell that is suppose to increase the target's Strength, for example,
would decrease either the target's or caster's Strength on a critical
failure. For spells that don't really lend themselves to this, you can
include DoBreakSomething in the section of code handling
critical failures... DoBreakSomething searches the caster's
inventory for components used by the current spell, and if it finds one,
breaks it (sets it's @a/broken property to
yes ). A broken object cannot be used until it is
repaired.
As discussed in the Player Guide, mages must make a Presence roll to
maintain concentration if they are attacked on the turn they are casting
a spell, and another if the attack successfully deals damage. This is
handled by the DoPresenceChecks function, which should be
called right before success rolls are made for any spells. The function
works by checking the mage's @a/eloop/attacked and
@a/eloop/injured properties. Standard Argo combat
programs and martial magic and psionic programs set these to true values
when a player is attacked and/or injured; new programs should do the
same. The props are cleared by the event manager at the end of the
player's turn.
DoCastRoll is the functional equivalent of
DoUseRoll in asys-skills... it is used to determine if the
mage's spell succeeds. DoDefenceRoll is used to determine
if the target player's defence succeeds (very few skills invoke defense
rolls, but almost all spells do). Both functions return
"normsucc" ,
"normfail" , "critsucc" , or
"critfail" . DoCastRoll stores the amount the
caster made her roll by in local variable ourAttackVal .
DoDefenceRoll stores the amount the defender made his roll
by in local variable ourDefenceVal . These values will be
negative if the roll failed; comparing the two allows you to determine
which player made the roll by the greater amount in order to determine
the final result if both player's rolls result in a normal success. (For
psionics, this function is called DoFocusRoll
Using the standard naming conventions, the primary function for our
spell would be DoEnergy . Verifying that the spell is being
legitimately cast, ensuring that the mage has needed components and
materials, ensuring that the target is valid, making Presence rolls if
required, determining whether the spell goes off successfully... all
these tasks which are required with almost any Argo
spell can be handled with a few relatively short lines of
code:
: DoEnergy ( -- ) (* make checks and rolls to cast Energy spell *)
VerifyEvent
DoChecks not if TellWait exit then
DoPresenceChecks not if exit then
DoCastRoll
;
At this point, all necessary checks will have been made, and the roll
will have been made to see if the mage got the spell off. The top value
on the stack will be either "normsucc" ,
"normfail" , "critsucc" , or
"critfail" . Notice that we do not have to include code to
that specifies components and materials. DoChecks checks
the realm database to find out what components and materials are
required; DoPresenceChecks and/or DoCastRoll
will use up the materials as needed. The type and number of components
(or tools) and materials needed are set when the spell is defined. If
the spell is redefined with new components and materials, the code here
will not need to change.
So, we know if the spell `went off' or not, but we do not yet know
for sure if the results should be applied. If the top value on the stack
is "normsucc" , and the spell allows a defense roll, then we
need to determine what happens with the defender's roll. (The approach
used for the standard Argo spells is that even positive,
helpful spells invoke a defence roll... it's harder to improve something
that's already very high. Newly added spells do not necessarily have to
follow this approach.) If we are using defence rolls, we need to roll
for the defender, and then compare the results of the two rolls. There
are a number of ways this can be done; spells in the standard set use an
approach like the following:
: DoEnergy ( -- ) (* make checks and rolls to cast Energy spell *)
VerifyEvent
DoChecks not if TellWait exit then
DoPresenceChecks not if exit then
DoCastRoll
dup "critsucc" smatch if
DoEnergyCritSucc
else
dup "critfail" smatch if
DoEnergyCritFail
else
"normsucc" smatch if
DoDefenceRoll
dup "critsucc" smatch if
DoEnergyNormFail
else
"critfail" smatch if
DoEnergyNormSucc
else
ourAttackVal @ ourDefenceVal @ >= if
DoEnergyNormSucc
else
DoEnergyNormFail
then
then
then
else
DoEnergyNormFail
then
then
then
SetWait NukeStack
;
The nested IF-ELSE-THEN statements here do the
following: If the caster's roll results in a normal failure, critical
success, or critical failure, the results are immediately applied. If
the caster's roll results in a normal success, a roll is made for the
defender. If the defender gets a critical success, the spell
automatically fails. If the defender gets a critical failure, the spell
automatically succeeds. If the defender gets a normal success, the
amounts that each player made the roll by are compared: if the caster
made her roll by and amount equal to or greater than the defender, the
spell succeeds; if the defender made his roll by more, the spell fails.
If the defender gets a normal failure, the `made by' amounts are
compared in the same way, but the caster will always win. So, these
nested statements will route to one of four functions for any possible
combination of rolls (you can copy and paste this block of code into
your own spells, replacing Energy with the name of your own
spell).
So, now we need the four functions that handle the four possible
outcomes of the spell. Generally it's easiest to start with normal
success, and make critical success and critical failure variations upon
this (normal failure will just be a notice to the room that the spell
failed).
For the Energy spell, normal success should boost the player's
energy... or, more accurately, lower the amount of fatigue the player
has spent. Fatigue is stored in @a/stats/fat . A completely
rested player will have a zero in this prop, or will not have the prop
at all. A tired player will have a positive number (stored as a string)
in the prop... the more tired he is, the higher the number will be. So,
DoEnergyNormSucc should make a roll of 1dX, where X is
equal to the caster's Mage skill plus Energy skill, and
decrease the target's fatigue by that amount. Whether or not a
player should be allowed to have `negative fatigue', and be `more rested
than rested' is a rather interesting question, but it is also for the
most part moot: the event manager doesn't allow negative fatigue... if
the target gets a `negative fatigue', it will be reset to zero on his
next turn. So, we can either allow the very temporary negative fatigue,
or we can include some code to check, and make sure that the setting for
@a/stats/fat doesn't go below zero. Let's keep things
simple, and let asys-eventmgr take care of it... If the target player
gets a 10 second or so period to enjoy a magical energy high, fine.
: DoEnergyNormSucc ( -- ) (* apply success for energy spell *)
ourTarget @ "@a/stats/fat" over over
getpropstr atoi
me @ "@a/skills/mage" GetModAbility
me @ "@a/spells/energy" GetModAbility +
1 swap 0 Dice -
intostr setprop
">> $me casts $spell $target."
SpellTell DoTargetLoop
;
The GetModAbility library call returns the specified
player's ability level for the specified ability, as an integer,
modified for factors such as objects that influence abilities. So, we
can get the value of X in 1dX by adding the results of
GetModAbility for the Mage skill and the Energy spell. A
little stack handling puts this in the proper order to call
Dice . The result is subtracted from the target's current
fatigue and the result of this subtraction is applied as the new fatigue.
(The ourTarget variable is initialized to the target player
in DoChecks , which makes sure the target is valid and
stores its debref in ourTarget if so.)
SpellTell handles notifications of spell results to the
room. The substring $me is replaced at runtime with the
caster's name, $spell with the name of the spell, and
$target with the name of the target. If the target is the
caster herself, the $target portion of the string is
omitted. A couple examples:
+cast energy on antar
>> Nim casts Energy on Antar.
+cast energy on me
>> Nim casts Energy.
For critical failure, we could use DoBreakSomething to
break the caster's wizard's staff, but this spell does lend itself to
`backfire' type critical failures... we could have it increase the
fatigue of either the target or the caster. Since the caster is
expecting to spend her own fatigue by casting the spell, and evidently
really wants to reduce the fatigue of the target (or else she wouldn't
be casting the spell), applyng the backfire to the target seems a more
severe penalty, so let's use that:
: DoEnergyCritFail ( -- ) (* apply crit fail for energy spell *)
ourTarget @ "@a/stats/fat" over over
getpropstr atoi
me @ "@a/skills/mage" GetModAbility
me @ "@a/spells/energy" GetModAbility +
1 swap 0 Dice +
intostr setprop
">> $me's attempt to cast $spell $targetfails. [CRITICAL FAILURE]"
SpellTell DoTargetLoop
;
This is the same as the results for a normal success, except that the
result of the die roll is added to the amount of fatigue the target has
spent rather than subtracted. And the output of the notify is modified,
telling the room that the spell failed critically. In this case, there's
no harm in telling the truth about the critical failure; for
information-gathering spells, a critical failure should often instead
lie about the results.
A critical success is the same as a normal success, except the amount
of the roll is doubled:
: DoEnergyCritSucc ( -- ) (* apply crit success for energy spell *)
ourTarget @ "@a/stats/fat" over over
getpropstr atoi
me @ "@a/skills/mage" GetModAbility
me @ "@a/spells/energy" GetModAbility +
1 swap 0 Dice 2 * -
intostr setprop
">> $me casts $spell $target. [CRITICAL SUCCESS]"
SpellTell DoTargetLoop
;
Notice that there is no code in the critical success funtion to
handle automatic rolls for experience points. This is handled in
DoCastRoll . If you don't want automatic experience points
at all, use +tune to set the auto_xp sysparm
to no . If you want automatic experience points in general,
but not for this spell, delete or comment out the following line from
DoCastRoll :
dup 4 <= if me @ RollXPs then
After the header comment is created and the an entry for the spell is
created in the online manual, we're done. Here
is the final product.
prev |
toc |
top |
next
|