<< Back to #SPECIALS Section | >> Forward to Example Area |
The basic structure of a MobProg is as follows:
>trigger args~
commands ~ |
Trigger name | Responds to |
---|---|
act_prog | an action by another character in the room |
all_greet_prog | any character entered the room |
bribe_prog | money is given to the mob |
buy_prog | someone buys something from the mob |
death_prog | the mob is about to die |
entry_prog | the mob just entered a room |
fight_prog | the mob enters a new round in a fight |
give_prog | an object is given to the mob |
greet_prog | a character, visible to the mob, entered the room |
hitprcnt_prog | mob is fighting and its hp drop below a given value |
in_file_prog | dummy trigger for mobprog linkage |
rand_prog | happens randomly once in a while |
speech_prog | a player utters a given word or phrase |
I'll be saying it again, but the argument on the trigger line must be followed by a tilde ( ~ ) !
Messages are crucially important in MobProgs. While a human player moves around in the MUD because he wants to, is curious, is on a quest or has some other motivation, most mobs do things only for two reasons:
Note the tilde ( ~ ) right after the end of the keyphrase.
There are two different and intermixable ways to connect a MobProg to a mob. The first one is simple and comes fairly naturally. For unique MobProgs affecting only a single mob vnum, this is all you need. The second way is harder to understand, and so far Dizzy MUD has no MobProgs attached that way. So, while it may seem illogical to break up the topic, I hope to avoid unnecessary confusion by explaining the easy way right after this paragraph and leaving the hard way for later.
The Simple Way
The last line of a mobile description (the whole thing you code for
a mobile, not its short, long or extended description) is the line that
reads
To attach a mobprog, code the mobprog right after this last line. You may attach one mobprog or several; essentially you are adding a list of mobprogs with one or more mobprogs in it. After the last line of the last mobprog, you must terminate the list with a single line containing a pipe symbol ( | ). Hunt around on your keyboard, it's there!
Although the above is only a cursory overview of mob programming, there is enough information there to code a couple of simple MobProgs, as follows. The example shows a mob description from the #MOBILES section of an area file, complete with two mobprogs.
#6902 beggar~ a poor beggar~ A poor beggar plies his demeaning trade here on the street. ~ The beggar is a Teilysa, unkempt and poorly clothed against the cold wind blowing through the streets. He is lacking both legs and an arm. The remaining arm holds a battered hat containing a few coins. No one is tempted to say, "Get a job!" ~ teilysa~ BGHZ KLMXd 0 S 10 0 2d6+130 10d9+100 1d8+4 punch 5 5 5 8 0 A 0 CD sleep rest male 15 0 0 small 0 >rand_prog 4~ wake say Alms, please! Some coins for a poor beggar! cough sleep ~ >bribe_prog 1~ wake smile say Thank you, thank you! May God bless you! sleep ~ | |
Future examples will show only the MobProg(s), not the mob. They will also assume that the MobProg or MobProgs are part of a list which is properly terminated with the pipe symbol ( | ).
First, some definitions:
Note the period. It's part of the "word", 'ribs.' . It's required for an exact match of the keyphrase.
The probability specifies how likely it is that this trigger will actually activate even if its condition is met. In this case, if (for example) you specified "bow" as a command and "50" as a probability, the mob would randomly bow to about half the people entering the room.
This is often used to have the mob throw insults at his opponent. If/when MPCAST is implemented, it could be used to throw spells instead. Failing that, perhaps the mob could scream for help?
You can have your mob respond to ANY object given to it by specifying all as the argument.
Some uses include shopkeepers who greet customers and picky thieves mobs who check players' wealth before attacking. Yeah, an attack can be a form of greeting too.
This is one of those triggers of which you can code multiple and of which only the first successful one is executed. I'll explain this in more detail below. For now, remember that you can have your mob do something different for various levels of hurt-ness by coding several mobprogs with percentages in ascending order, say 33, 66, 100 for three different stages of defeat. The first two will not trigger as long as the mob has better than 66% of his hp; after that the second one will trigger, etc.
If you're any good with math and probabilities, you will notice that the probabilities of the events in the example are not as simple as they look. There is really truly a 33% chance of the mob doing the first action. But the second one only happens in 33% of the 67% left over from the first case, which is only 22% chance. The chance of either of the first two events happening is 33+22 = 55%. That leaves us only a 100-55 = 45% chance to check the third event. That comes to 33% of 45%, or less than 15%. The events 'further down' sort of 'live in the shadow' of the preceding events. And yes: 33+22+15 do not add up to 100 by a long shot, thus there is a 31% chance that none of the 3 events will take place, although it looked like a 99% sure thing that one of them would.
What's to be learned from all this? Never mind the math. Just remember that triggers of the same kind further down the line have less of a chance of happening than you would think. Also, if you're despairing of ever getting the probabilities just right, take hope from the fact that there are IFs and probability checks coming up soon which may help you code exactly what you mean.
Something entirely different to be aware of: NONE of these triggers work if the mob is charmed! This keeps people from abusing mobprog behavior for their own evil plans.
>rand_prog 33~ snore ~ >rand_prog 50~ cough ~ >rand_prog 100~ moan ~ >hitprcnt_prog 10~ say A miserable life, but I'm about to lose it! ~ >hitprcnt_prog 35~ say Something tells me I'm not going to win this fight. ~ >hitprcnt_prog 70~ say Oh no! I'm bleeding! ~ >hitprcnt_prog 100~ say Have you nothing better to do than to attack defenseless beggars? ~ >bribe_prog 10000~ say My fortune is made! emote straps on a pair of wooden legs and a wooden arm. hop dance sing ~ >bribe_prog 1000~ say So much gold! This will feed me for days! cheer ~ >bribe_prog 100~ say A fine donation. Thank you so much! nod ~ >bribe_prog 1~ say If that's all you have, you might as well keep it! snicker ~ |
If you've written SOCIALS before, the syntax will seem familiar to you. Alas, most of the letter codes have different meanings for MobProgs. I will provide the variables in groups based on which character or object they refer to, with explanations and examples.
$n | the first of thenames of the Actor, who performed the action that set off the trigger. |
$N | the name and title of the Actor.
(rarely used) |
$e | he / she / it based on sex of Actor. |
$m | him / her / it based on sex of Actor. |
$s | his / hers / its based on sex of Actor. |
>bribe_prog 1~
thank $n yell $N is a very kind person! yell $e gave me some of $s money. yell I am forever grateful to $m! ~ |
if Elrac is the donor, this translates to:
thank Elrac
|
The 'he' in the second sentence should be capitalized, but is not. MobProgging does have limits, alas.
Here are the variables used to refer to such a Direct Object:
$o | the first of the keywordss of the direct object (The
object NActor does something to).
If an object's keyword field says 'bagel cream cheese', $o would yield 'bagel'. |
$O | the short description of the direct object |
$a | a / an based on first letter of direct object |
>act_prog drops~
point $n say Hey $n, you dropped $O! ~ |
If Elrac drops his Thunderstriker, this works out to:
point Elrac
|
$p | the first of the keywordss of the indirect object |
$P | the short description of the indirect object |
$A | a / an based on first letter of indirect object |
>act_prog puts in~
sigh say I wish I had $A $P to put my $O in! ~ |
"Elrac puts longsword in his scabbard" yields:
sigh
|
$t | the first of the names of a secondary character target, the one Actor does something to. | |||||
$T |
|
|||||
$E | he / she / it based on sex of Victim. | |||||
$M | him / her / it based on sex of Victim. | |||||
$S | his / hers / its based on sex of Victim. |
Doh, I can't think of one that's reasonably assured of working all the time. Triggers alone are not enough to tell whether the victim of an action was your mob or a third mob (i.e. a real Victim), so it's not possible at this point to produce an appropriate response. This problem is fixed by IF Statements, later on.
WARNING:
In the internal plumbing of act messages, the same data slot is
used for the Victim and for the Indirect Object. This saves space because
any action can have only either a victim or an indirect object but never
both. Well, it may be possible in English grammar but it just doesn't seem
to happen in the MUD. So what ends up happening here is that if an action
has a victim, that victim's name will end up both in $t and the indirect
object variable, $p (see below). If an action has an indirect object, the
object's name will end up both in $p and $t. If you're just using the name
in a command or a message that isn't so bad, it may look stupid but will
do no further harm. If you use $p or $t in an IF statement
(see below) that is specific to characters or objects and it's mismatched,
you're in trouble. In the happy case, your mobprog will just work incorrectly.
However, it's also quite likely that the MUD will crash!
Here are the 5 variables which your mobprog can use to refer to its own mob:
$i | the first of the names of your mob
(generally prefer $I) |
$I | the short description of your mob. |
$j | he / she / it based on sex of your mob. |
$k | him / her / it based on sex of your mob. |
$l | his / hers / its based on sex of your mob. |
>greet_prog 100~
say Hello, I am $I. ~ |
for the poor beggar, this translates to:
say Hello, I am a poor beggar. |
$r | the first of the names of a random char in the room with the
mobile
(never the same as your mob itself) |
|||||
$R |
|
|||||
$J | he / she / it based on sex of the random char. | |||||
$K | him / her / it based on sex of the random char. | |||||
$L | his / hers / its based on sex of the random char. |
Example:
>rand_prog 20~
say Mirror, mirror on the wall... ponder say $r is the ugliest here! ~ |
We don't know whom the beggar will point out. That's the idea behind 'random'. |
The 'actor', 'victim', 'direct object' and 'indirect object' variables
($n, $t, $o and $p) and their associated forms will not work in a rand_prog
or entry_prog!
This is just the only hard and fast rule on the topic. But there are
other examples of progs where variables are undefined. A give_prog,
for example, does not have a victim, nor an indirect object. Thus, you
need to
THINK about the trigger type you use, and decide whether the variable
you are planning to use makes sense in that trigger!
If you use a variable that's not defined for the trigger type or context
you use it in, your MobProg will CRASH the MUD! This will probably not
happen at load time. It is likely to happen at some time in the middle
of the game. In the case of a rand_prog, there does not even need to be
anyone present in the mob's room. This makes such bugs VERY hard to find
and fix. BE CAREFUL! Something about variables which you need to be aware of is dictated
by common sense but often overlooked.
IF Statements address our need to more finely control whether or not parts of a MobProg are executed. The need arises all over the place. Here are some examples:
General Description
Of course, the number of alternative (OR) conditions does not
have to be 2, it can be any number. This is just an example. BREAK statements may only be used as part of the command sets
inside IF statements. They make no sense anywhere else because if
you wanted to execute no more commands elsewhere, you could simply write
no more commands.
Even inside an IF, BREAK statements make sense only at the end
of a command set, because any commands afterthe BREAK
would never be performed anyway. Of course, it's legal and sometimes useful
to write a BREAK as the only statement of a command set,
as your needs and circumstances dictate. Correct indenting is very easy to do. As I mentioned before, all the
lines of a command set are indented 3 spaces from where the IF,
the ENDIF and (maybe) the OR or ELSE start. When you're
done, you should be able to draw a straight vertical line between the I
in any IF and the E in its ENDIF, and that line will
hit only white space, with the possible exception of the O in one
or more ORs and/or the E in an ELSE, if there is one.
If there is an IF inside a command set, then the IF
and ENDIF (as well as the possible ORs and/or ELSE)
must line up with the other commands (if any), while the command set(s)
of that IF must be indented another 3 spaces. Thus,
your indentation will visually reflect what programmers call "your nesting
of IFs" or, more generally, the structure of your program. If you
have trouble picturing all this, refer to a complex example. There should
be one coming up soon.
conditions are always coded between paired parentheses ( '('
and ')' ) . The parentheses, well, kinda hold the condition together.
As I mentioned, the condition must come out to be something that ends up
true or false. Here are the basic legal forms of a condition:
Logical functions as conditions
The isaffected() function is different from the rest. You need
to write an ampersand ( & ) and a number after the function.
The number has to correspond to the flag value of the AFF_whatever
affect you want to check for (see the Affect
Flag Table). You need to convert the letter codes you're used to using
for flags into a number; this is explained in Flag
Fields Explained.
Finally, we're ready for the next examples:
To finish coding up your condition (formulating your question), the
syntax diagram says you still need a number-compare-op and a number.
You provide the number. If you want to check if your mob is at
Recall, write 3001 (or whatever number is the right room vnum for
your MUD). If you want to check for a female character, write a 2.
If you're checking for a character with a million gold, write 1000000.
You get the idea. Some of the values may require digging through tables
or asking your friendly Coder.
What's left is the number-compare-op, the numeric comparison
operator. You can do most of the standard comparisons you know and love
from math and/or computer programming. All of these are coded as one or
two symbols, as follows:
As was the case of the numeric comparisons, you provide the string
to check against. You can either provide a complete keyword field
and test for equality (or non-equality) or part of a name and test for
a substring match. Whatever string you provide, you must not enclose
it in single or double quotes. Just write the string as is and the MUD
code will figure it out. To demonstrate this, here is another example:
The strategy encoded here is not perfect, of course (the beggar has
trouble walking around without his legs, too), but it gives you the general
idea: As often as he possibly can, the beggar randomly looks at a random
mob in the room. If that mob is Helga, then he is safe and need not check
for a guard. If the random character he is looking at is a guard, then
he tries to move to another room. He randomly chooses one of the 4 main
directions.
If he 'runs' away in any direction, the MobProg performs a BREAK. This
stops any other coding in this MobProg from happening at this time. Note
the mutually exclusive probabilities in his choice of directions, and how
the random values are stacked to make all 4 directions equally likely.
The beggar only begs in 10% of all of the rand_prog triggers, which
gives him 10 'scans' for guards for each 'beg'. This is not an airtight
strategy but it gives him a good chance of discovering a guard before being
caught begging. Although command can be practically anything, I do not recommend
coding another MPAT command as the command to do elsewhere. Not
only does this not make any sense (why not go to the third location in
the first place?) but also it's likely that your mob will never find its
way back to the first location. Either that, or you'll crash the MUD.
As promised earlier, for those of you who want to know EVERYthing, here
is
After all your M lines, code a line with only an S on
it to end your #MOBPROGS section.
An IF statement consists of one or more checks for a certain
condition which you specify, and one or two sets of commands you want performed
depending on whether the checked condition turns out to be true or false.
Before I show you the 'official' complete structure of an
IF ( condition )
"true" command set
ENDIF If the condition is true, then the command set is performed.
Otherwise, nothing happens.
IF ( condition )
OR ( condition )
OR ( condition )
"true" command set
ENDIF If any one of the conditions is met, then the command set
is performed.
Otherwise, nothing happens.
IF ( condition )
"true" command set
ELSE
"false" command set
ENDIF If the condition is true, then the "true" command set
is performed.
Otherwise, the "false" command set is performed.
IF ( condition )
OR ( condition )
OR ( condition )
"true" command set
ELSE
"false" command set
ENDIF If any one of the the conditions is true, then the "true"
command set is performed.
Otherwise (none of the conditions is true), the "false" command
set is performed.
If you are reading this in your browser (the only way, in my opinion) then
you may have been missing links to condition and command set.
I didn't want to complicate things any further and distract you with the
ability to jump around. Here are the explanations I owe you:
IF ( condition )
"true" command set
OR ( condition )
OR ( condition )
...
ENDIF
ELSE
"false" command setLooks frightening, doesn't it?
Notice that if you remove some of the boxes, this diagram looks like
one of the preceding ones.
There's no example here. Alas, you can't build an IF statement
without the as yet unexplained stuff that makes up a condition.
But once you know about those things, IFs fall into place and we're
almost done with all there is to MobProgramming. Hopefully this will motivate
you to read onward to
A command set is simply 0 or more commands. Yes, you are allowed
to code no statements between the IF line (or optional OR
line(s)) and the ELSE line. A statement like
The other two applications of empty command sets are also legal, but don't
make any sense. If you code
IF ( condition )
ELSE
command set
ENDIF is perfectly legal and essentially says that you want the command
set done if your condition is false.
If you build one of these,
IF ( condition )
command set
ELSE
ENDIF then you might as well leave out the ELSE line and make your
mobprog easier to read and debug.
So, having taken care of the empty case, what can be in a command
set if it's not empty? Three different things:
IF ( condition )
ENDIF then you can do without the whole IF statement.
Important:
(that's right, you can have IF statements inside of IF
statements inside of...)
A BREAK statement will bail you right out of the processing
of the current trigger, no matter how deeply you may find yourself nested
in IF statements. You write BREAK like a normal MUD command,
and it essentially means, "this is the last command I intend to do for
this trigger."
All my examples show the command set indented 3 spaces from
the column where the IF starts. The MUD program doesn't require
this of you, but I do! There is very little debugging support for
mobprogs, and badly written IF statements are a very sure way to
crash the MUD. Indentation helps you as the MobProg writer visualize the
structure of your code and me (or some other Coder) to understand it so
I have a chance at debugging it if you can't.
logical-function
numeric_function number-compare-op
number
string_function string-compare-op
string
Conditional Operands and Operators
The simplest thing to put inside a conditional is a logical
function.
The logical functions provided by the MobProg code all look at a
character and answer some question about him. Following an old C programming
convention, they all start with is and so they're easy to recognize:
What's a function, you ask? Well, a function is a definition of
a calculation or some other process that gets a number or some other thing
to work on, does some grinding, and comes up with a result. The square
function, in math, takes any old number, multiplies it by itself, and returns
the result. The square root function does the same thing, only backwards.
In the MobProgramming context, functions tend to work on characters and
objects.
function name
what question it answers
isnpc($*)
is $* an NPC (a mob)?
ispc($*)
is $* an PC (a player)?
isgood($*)
does $* have good alignment?
isfight($*)
is $* currently fighting?
isimmort($*)
is $* an Immortal?
(will also recognize mobs >= LEVEL_IMMORTAL)
ischarmed($*)
is $* affected by CHARM?
isfollow($*)
is $* a follower with their master in the room?
isaffected($*) & number
is $* affected by AFF_xxx?
Let's extend this example. This beggar knows that immortals are stingy,
and will not waste his time begging from them.
>greet_prog 100~
if ( isnpc($n) )
wink $n
else
say Alms for a poor beggar, please!
endif
~
This prog will react to everyone entering the mob's room.
Depending on whether it's a mob or a player, your mob will either give
the secret wink of the MUDwide mob conspiracy or simply beg for money.
Two more extensions. First, we decide to wink only at mobs of good alignment
and to growl at the rest. Second, we decide also not to beg for money if
the player entering the room is charmed:
>greet_prog 100~
if ( isnpc($n) )
wink $n
else
if ( isimmort($i) )
else
say Alms for a poor beggar, please!
endif
endif
~
Note the use of ELSE to reverse the question. We don't have an ismortal($*)
function, so we ask if the potential donor is immortal, do nothing if he
is and beg only if not.
Numeric comparisons as conditions
>greet_prog 100~
if ( isnpc($n) )
if ( isgood($i) )
wink $n
else
growl $n
endif
else
if ( isimmort($n) )
or ( ischarmed($n) )
else
say Alms for a poor beggar, please!
endif
endif
~
Here's our first use of OR. Meanwhile, our IFs are nested
deeply enough to give you some idea of what a properly indented complex
mobprog looks like. You may well end up coding even more complex stuff.
The next kind of condition is a comparison of one number with another.
First, a handful of functions which provide numbers you can test:
Remember, you can only use $o or $p with the last two, which
refer to objects only!
function name
Chars/
Objswhat number it yields
range or example
rand(percent)
-
true if a random number between 0 and 100 is less than percent
0 to 100
hitprcnt($*)
C
character's current percentage of his full hp
0 to 100
inroom($*)
C
Room vnum the char is in
in Dizzy, Recall is 3001
sex($*)
C
Character's sex (neutral=0, male=1, female=2)
0 - 2
position($*)
C
Character's position (dead=0, mortally wounded, incapacitated, stunned,
sleeping, resting, sitting, fighting, standing, sneak=9)
0 - 9
level($*)
C
Character's level
1 - MAX_LEVEL
class($*)
C
Character's class, as an integer
0 - MAX_CLASS
goldamt($*)
C
Amount of gold carried by char
0 - ?
objtype($*)
O
Object's item type, as a number
see
Shop Item Type
Table
objval0($*)
objval1($*)
objval2($*)
objval3($*)
objval4($*)O
Current value from one slot (0-4) of object's value line, as a number
see
Item Type and
Value Line Table
Operator
Tests for
==
equal
(two '=' signs!)
!=
not equal
>
greater
<
less than
>=
greater than or equal
<=
less than or equal
&
bitwise AND
|
bitwise OR
Time for more examples:
yields true if the numbers on both sides have at least one bit
set in the same position. This is generally used in tests for flag bits
and comes in handy for the isaffected() test but nowhere else I can think
of.
returns true if either of the numbers on both sides of it is
not 0. I can't think of any use for this.
String comparisons as conditions
>rand_prog 30~
if ( position($r) < 4 )
slap $n
endif
~
>rand_prog 40~
if ( goldamt($r) > 1000 )
if ( sex($r) == 2 )
beg $r
endif
endif
~
>greet_prog 100~
if ( class($n) != 2 )
whine $n
endif
~
The string comparison stuff is the simplest, and completes the list
of possible conditions you can have your MobProgs check for. There is only
one string function:
function name
Chars/
Objswhat string it yields
example
name($*)
C,O
the complete name (from the keywords field) of the char or object
name($i) gives:
beggar
Operator
Tests for
==
exactly equal
(two '=' signs!)
!=
not equal
/
contains
(right hand string is part of left hand string)
!/
does not contain
As it stands, the only strings you can test are the names of characters
and objects. This coding guide and others advise you to code everything
in the keyword fields in lower case, so in order for your
string comparisons to succeed, your tested strings need to be in lower
case as well. Of course, many older MUD areas have never heard of this
convention, so the example below shows an exception from this rule.
Player names, on the other hand, are always 'propercased'. That is,
the first character is a capital and the rest are lowercase. MobProgs that
check for player names are generally silly.
>rand_prog 100~
if ( name($r) != Super Helga )
if ( name($n) / guard )
if ( rand(25) )
north
break
endif
if ( rand(33) )
east
break
endif
if ( rand(50) )
south
break
endif
if ( rand(100) )
west
break
endif
endif
endif
if ( rand(10) )
beg
endif
~
Another contrived example: The beggar has been told to stop begging
in the city. The guards enforce this order and punish the beggar if they
catch him. He can only beg in safety if there are no guards around or if
he is in the same room with Super Helga, who protects him.
MobProg-Only Commands
This section would not be complete without... you guessed it, an example!
One example suggestion for a tricky application was the shopkeeper who,
as part of his fight_prog, does:
Your mob disappears from its previous room without a message of any sort,
so unless characters there eventually bother to LOOK, they will not notice
the mob left. Likewise, there is no message at the room where your mob
arrives. If characters are to be informed of its arrival, as they usually
should be, your mob will need to provide its own arrival message using
MPECHO or something.
Your mob's victims do not receive a message about having been transferred,
nor do they automatically LOOK in the new room. Thus, it is up to you to
inform them of what happened (perhaps using MPECHO
and/or MPFORCE ... LOOK), if you so choose.
MPCAST 'spell' victim
>hitprcnt_prog 50~
sigh
mpecho Realizing $j is going to lose the fight, $I calls to
Super Helga for help!
wave $n
mpecho A moment later...
mpechoaround $n $I disappears and $n is left fighting thin air.
mpechoat $n $I disappears and you are left fighting thin air.
mpgoto helga
~
This is a relatively simple MobProg. If the beggar is caught up in
a fight and down to 50% of his HP, he sighs, announces his departure and
cheats his way out of the fight by doing an MPGOTO Helga. Note the use
of MPECHOAROUND to give a message to bystanders, then MPECHOAT to give
the specific message to his foe.
The Hard Way To Connect MobProgs to Mobs
In addition, if you have your MobProgs in files, you can code in_file_progs
underneath your actual mobs to associate MobProg files with them. This
allows you to connect several files to one mob. The syntax for these in_file_progs
looks like any trigger prog:
Each of these lines names a mob that you want to attach a MobProg to, and
names the file that contains that MobProg.
M
mob-vnum
MobProg-filename
Congratulations! your area file is finished at
this point. Do not forget the #$ at the end of your file to terminate
your area file. The #$ must come on a line by itself and be followed
by at least one or more empty lines. Leaving off this file termination
or getting it wrong cause the MUD to crash at load time without even issuing
an error message!
<< Back to #SPECIALS
Section
This page was last updated
May 15, 2001.