3.6 Programming Argo Skills
Section 2.6.1 of the Staff Guide, Defining
Skills, discussed how to add skills to the Argo database.
Simply defining a skill, though, does not implement any coded effects of
the skill... It can be used in RP, with commands such as
+roll and +prove , but it doesn't really do
anything. For some skills, this is perfectly appropriate. For others,
though, coded effects add a lot to game play.
This page provides an example of the process with a skill called
Appraise Weapon. The skill, we will say, allows users to examine an
Argo weapon and make an informed judgment about its value.
Since this information is readily available with the +info
command, unless the weapon type has been +masked , this is a
less than awesome ability, but it would be a realistic skill for someone
like an arms dealer to have, and it will let us cover the basics of
programming the coded effects of skills. (A more ambitious and
interesting project would be to create a generic Appraise skill that can
be used on any type of object and requires a number of prerequisite
knowledge skills for reliable results.)
The skill's effects will never be invoked unless the skill is
defined, so that's probably the place to start. Use +define
to define Appraise Weapon as a skill with no prerequisites, no required
tools or materials, and an intelligence rating of 8. (In the example
spell on the next page, we'll cover tools and materials.)
The skill is now defined. Players can learn it if they want, and use
in RP with +roll and +prove . They can also use
it as though it were a coded skill... +use appraise
weapon on broadsword . But, since there is no program to handle
the skill's effects, the event manager would treat it as a `null skill',
rolling and displaying results on the user's turn, but not doing
anything else.
To create the coded effects, begin by copying the skeleton skill
program, asys-xskills, to a new file... call
it something like asys-appraiseweapon.
As in our discussion of programming
commands, the first step is to search for and replace all instances
of `ppp ' and `xxx '. The string
`ppp ' should be replaced with the `Argo name' of
the program... `appraiseweapon ' in this case. The string
`xxx ' should be replaced with the name of the skill,
`appraise weapon '.
Because our skill name includes a space, one instance of these
replacements requires special handling. In main :
"#usexxx" ourArg @ smatch if Doxxx else
If we do a literal replace on Doxxx we will end up with
"#useappraise weapon" ourArg @ smatch if Doappraise weapon else
The space in "#useappraise weapon" is fine, because it's
enclosed in a string. We need it in fact, so that the skill name can be
matched and found. But the other one is a problem: we're not planning to
make a function called weapon ; this won't compile... The
line should instead read something like:
"#useappraise weapon" ourArg @ smatch if DoAppraiseWeapon else
Again, the skeleton program is set up to handle one thing... one
skill in this case. If the program is to handle multiple skills (for
example, if we were making a program to handle a group of related skills
like Appraise Weapon, Appraise Armor, Appraise Shield, etc.), a few
sections of code would need to be duplicated for each skill. In
DoInstall , the line,
#0 "@a/calls/usexxx" prog setprop
would need to be duplicated for each skill, as would the following
line from DoUninstall ,
#0 "@a/calls/usexxx" remove_prop
and the line in main used to find the right function to
execute when the program is called:
"#usexxx" ourArg @ smatch if Doxxx else
A good deal of the code needed to integrate a skill's coded effects
into Argo is already provided. Asys-effects, invoked when
players use the skill with the +use command, will take care
of finding the target object, verifying tools and materials, making sure
that the user has an event loop, and so forth. Stateful data gathered by
asys-effects and possibly needed by asys-appraiseweapon will be stored
in the user's @a/eloop/ directory:
str /@a/eloop/act:useappraise weapon
str /@a/eloop/acting:preparing an action
int /@a/eloop/pid:636
str /@a/eloop/skill:Appraise Weapon
ref /@a/eloop/target:Broadsword(#1542)
The pid prop is the process ID of the user's event loop;
acting is the action that will be displayed for this user
with the +check command. We probably won't need to
reference either of these. The other three, though, will be useful. The
@a/eloop/act property is the user's current action, stored
in a format that lets the events manager and other programs identify the
action and act accordingly. It is this prop that lets asys-eventmgr
identify and call asys-appraiseweapon; if asys-appraiseweapon handled
multiple skills, this prop would let us route execution to the function
needed for a given skill. All this is taken care of at this point:
asys-effects and asys-eventmgr will set the prop and make the program
call; when we replaced "#usexxx" with "#useappraise weapon", we did what
was needed for asys-appraiseweapon to route execution appropriately once
it is called.
Once we get to a function like DoAppraiseWeapon , we'll
need to know the dbref of the weapon to appraise... it's stored in
@a/eloop/target , the property used to store the target of
any action, whether its a combat attack, spell casting, or as here
skill use. Our DoAppraiseWeapon function will need
to make a skill roll and, if successful, display information about the
weapon stored as the target of the skill.
The skeleton program provides two generic functions for determining
the succcess or failure of an action, DoUseRoll and
DoDefenseRoll . Unless a skill has special requirements for
determining success or failure, you can just call
DoUseRoll , makes a roll against the skill required by the
current action (stored by asys-effects in @a/eloop/skill ),
and returns "normsucc" , "normfail" ,
"critsucc" , or "critfail" , and stores the
amount by which the user made or failed the roll in the
ourAttackVal local variable. If applicable (that is, if
DoUseRoll returned "normsucc" and the skill
something that can be defended against), DoDefenseRoll can
then be called... our program won't need to make a defence roll, just a
skill roll.
: DoAppraiseWeapon ( -- ) (* user tries to appraise a weapon *)
VerifyEvent (* make sure called by asys-eventmgr *)
(* store target weapon obj *)
me @ "@a/eloop/target" getprop ourTarget !
DoUseRoll (* roll for appraisal *)
"normsucc" over smatch if DoApWeapNormSucc else
"normfail" over smatch if DoApWeapNormFail else
"critsucc" over smatch if DoApWeapCritSucc else
"critfail" over smatch if DoApWeapCritFail
then then then then
SetWait (* set action to wait *)
;
The VerifyEvent statement is a generic security
statement that makes sure that the skill is being legitimately invoked,
by the events manager... It's included in the skeleton program; you
should just leave it there, as is. The SetWait statement is
a library function that resets a user's action to `wait'... we stick it
at the end of DoAppraiseWeapon so that once the user has
appraised the weapon, they'll stop, instead of re-appraising it every
turn.
In between these is the code that actually determines what happens
when the users tries to appraise a weapon. DoUseRoll will
return a success or failure value... we just need to determine which it
is and then route to a function that can handle the appropriate
outcome.
If the roll succeeds, the user should be shown the value of the
weapon. If you examine the properties of an existing Argo weapon,
you'll see that there's not a lot of information on the weapon
itself:
str @a/class/melee weapons:1
str @a/class/swords:1
str @a/class/weapons:1
str @a/name:Broadsword
str @a/version:1.1
str _/de:{eval:{list:_desc}}
str _desc#/1:A straight-bladed, double-edged sword.
As little information as possible is kept on Argo objects
themselves. Instead, information about types of objects is kept on the
realm environment room. The `Argo name' of the object can be
used to look information in the realm database. This name is stored on
the object in the @a/name property. To determine the value
of a weapon, we would need to determine its Argo name, and look
up the cost of that object in the realm database:
: DoApWeapNormSucc ( -- ) (* notify with value of weapon *)
ourDataObj @ "@a/objects/$object/cost"
ourTarget @ "@a/name" getpropstr "$object" subst
getpropstr dup if
">> You appraise the $weapon at $cost."
ourTarget @ "@a/name" getpropstr "$weapon" subst
swap atoi ExpressLowestMoney "$cost" subst VerTell
else
">> The weapon is valueless." VerTell
then
;
VerTell rather than Tell is used to notify
the user of the results: VerTell appends a user's verification string
if he has one, and he has verification turned on to the
string being emitted, so that the user may be sure it's the output of
Argo rather than a spoof program. Although it's unlikey that
someone would be able to guess that the user is appraising the object,
and try fake the results with a spoof, and get away with it
because the user didn't notice the duplicate output, it's probably best
to use VerTell anyway, for consistency with other
Argo output... VerTell is used for any notifies
that either are prompted by another player or are delayed.
A normal failure can simply tell the user that he is unable to
determine the value. Critical success is the same as a normal success,
except the string "[CRITICAL SUCCSSS]" appended to the
output, so the user has some assurance the information is accurate, and
a roll is made so that the user may gain experience points. Critical
failure looks like a normal success, but the information is
inaccurate... We lie to the player, in other words:
: DoApWeapCritFail ( -- ) (* notify with incorrect price *)
ourDataObj @ "@a/objects/$object/cost"
ourTarget @ "@a/name" getpropstr "$object" subst
getpropstr dup if
">> You appraise the $weapon at $cost."
ourTarget @ "@a/name" getpropstr "$weapon" subst
swap atoi
random 2 % if (* toss a coin: either mult or div answer by 2 *)
2 /
else
2 *
then
ExpressLowestMoney "$cost" subst VerTell
else
">> The weapon is valueless." VerTell
then
;
Our program is pretty simple and not extremely useful... a better
Appraisal program would examine the user's skills, the classes of the
object, and any skills modified by the object, and implement an
algorithm that uses the character's knowledge of objects of this and
similar types to modify the chances for success... We won't do all that
here, but should point out the basic mechanism for applying such a
modification. The DoUseRoll function contains the
following line:
me @ "@a/eloop/genmod" getpropstr atoi +
The @a/eloop/genmod property is for `generic
modifiers'... DoUseRoll and other roll-for-success
functions add any value held in @a/eloop/genmod to the
user's level for the current roll. An example: if a user had an Appraise
Weapon skill of 10, but an algorithm such as the hypothetical one
discussed above determined that the user should get a +2 chance on the
roll, then @a/eloop/genmod should be set to the value of
"2" ... the result would be that the user succeeds on a roll
of 12 or less. Since other parts of Argo also use the
genmod prop, you should immediately clear it after setting
it and calling DoUseRoll (the once-per-turn upkeep routines
in the event manager also clear it, but there's a chance that it could
affect a defence roll before this happens).
Programs handling the coded effects of skills aren't directly called
from a command, and so don't have a #help option, so we
don't need to add a help screen. We should, though, include a header
comment and make an entry about the skill in the online manual. Then
we're done. Here is the final
product.
prev |
toc |
top |
next
|