ID:35932
 

A BYONDscape Classic! And this one is a classic among classics. -- Gughunter

Dream Tutor: usr Unfriendly

by Lummox JR

The single most confusing aspect of the DM language, but very important to good programming, is knowing when and where to use the usr var. Many beginning coders don't understand it, as I didn't when I first started out, and they only really learn once they reach an intermediate level--if even then. But learning this difference is crucial, because it will eliminate all kinds of otherwise difficult-to-find bugs and will force you to use better programming techniques.

The people this column is most intended to help are usually not very familiar with this distinction. You might even be one of them and not even know it. They know there's a difference between src and usr, but they don't know when to apply usr safely. Disasters result from this. I'm here to help.

Where It Works

First off, we should go over when usr is valid and when it isn't:

usr is valid in: usr is usually wrong in:
Verbs, including mob/Click(), client/North(), etc. Most procs
client/New() and mob/Login(); mob/Login() can't use usr safely if you call it manually or if character loading is used atom/movable/Move() and related procs like atom/Enter()
mob/Stat(), where usr is the player who sees the statpanel, and src is client.statobj which is usually the same. mob/Logout() and client/Del(); usr is only valid here if the player logs out themselves, not if something forces them out like an admin boot

According to the DM Reference:

The only time usr is assigned for you is when a player (in Dream Seeker) executes a verb, clicks something with the mouse, or any other such action.

So if the player does something like click the mouse or run a verb, usr is set. When they view a stat panel, usr is set to them, since the stat panel they look at (set by client.statobj) doesn't have to be their own. When they log in, or log out on their own, usr is set. When they move of their own volition, usr is set.

So why isn't usr valid in movement procs like turf/Entered() or in mob/Logout()? The problem is that a player can be moved by other code in the game, not necessarily of their own free will, and they can be logged out automatically too. usr could well be null, or could be another player.

And this is where it gets ugly for most people who don't realize the problem: Code that employs usr improperly will usually work perfectly well in single-player tests, but fail in strange and unexpected ways when the game goes online for a multiplayer run. Debugging these problems can be a huge headache, and the programmer's eye will usually skip right over the problem: usr is there in place of src or another var.

By the way, when I say usr is wrong in most procs, that means as a rule of thumb you should never put it in a proc. A proc called by a verb, or by some other routine where usr is known to be good, should be all right, but if you rely on this then you set yourself up for the debugging nightmare I just described.

The Profile

There are a few more details about usr you should know, that aren't immediately obvious from the reference.

First, usr is a mob. It is always considered by DM to be type /mob. Your player may be /mob/player or /mob/elf/wood, but usr is always going to be the base type /mob. If you need a var or proc belonging to your more specific type, you should do some type casting. (That's a term from C, where you tell the compiler that a var is actually a different type.)

mob
player
proc/UpdateScreen()
...

obj/screenitem
Click()
var/mob/player/M = usr
M.UpdateScreen()

Why not just use the : operator and call usr:UpdateScreen() instead? Well, you could, but that's kind of sloppy. Abusing the : operator is about as bad as abusing usr. You should avoid the : operator whenever you can; DM will catch more errors for you this way, so they don't bite you in the butt at runtime. (In this case, though, it doesn't actually make much difference.)

The second thing you should know about usr is that it's not a global var--it's local. That means that usr in one proc might not be the same as usr in another; you can call sleep() and it won't change unexpectedly on you. (Except usr could change to null if the mob it's pointing to gets deleted while the proc sleeps. Just keep that in mind every time you call sleep()--check usr again afterward to make sure it's still valid.) The DM Reference says that usr is assigned a value by something the player does, and that's sent as an invisible argument to the verb or proc that gets called. Any procs called by that routine will be passed the same value.

One other thing about changes: Even if usr doesn't become null, remember that usr.client might. If your proc sleeps, or waits for input on something, when it regains control the player might have disconnected. Their mob won't be deleted unless you want it to be, but usr.client will suddenly become null if the usr disconnects.

There's a third thing you should know about usr now: For several built-in procs, usr is a default argument. For instance, view() and range() always use usr as their frame of reference unless you explicitly tell them to use something else. oview(1) is the same as oview(1,usr), but not necessarily equal to oview(1,src). The reason usr is the default here is that these procs are used to limit the items that can be used with verbs.

Other procs that have usr as a default are input() and alert(). Unless you tell your code to send an input() box to someone else, usr gets it. If you create a verb where you offer another player a choice to accept or refuse a gift, for example, you'll have to override this.

mob
verb/Give(obj/item as obj in usr)
set src in oview(1) // src is the recipient
if(!item || !client) return
var/choice = alert(src, "Will you accept \a [item] from [usr]?",\
"Gift from [usr]", "Yes", "No")
if(choice == "Yes")
// if the item has been deleted or dropped...
if(!item || !(item in usr)) return
item.loc = src
usr << "You give [src] \a [item]."
src << "[usr] gives you \a [item]."

How Not To Use It

One common place where people apply usr wrong is in movement procs. Not many people make the worst mistake of putting it in Move(), since src is obviously the correct choice there, but too many authors put it in Enter() or Bump().

As an example, let's consider a turf that might have a trap in it. This is how some people would write it:

turf
var/trap

// this code is wrong
Entered()
if(trap)
usr << "BLAM! A trap explodes and kills you!"
usr.Die()

What's wrong with that, you ask? Well, what if the player is moved there using the Move() proc by someone else, or by an object in the game? This could easily come up in any number of games, where a player doesn't move on their own. Then usr isn't the player at all. Even worse, what happens if you have a monster that uses one of the default walking procs like walk()? If the monster calls Move(), usr could be null. You'll have a stream of errors.

But a proc like this has more information; Entered() takes two arguments, of which you usually only use the first: The thing that entered, and where it was last. You can ignore the second argument if you're not going to use it, but the first is what we really want instead of usr.

turf
var/trap

Entered(A)
if(trap && ismob(A))
var/mob/M = A // type casting
M << "BLAM! A trap explodes and kills you!"
M.Die()

Now that's one dead monster! I put in the ismob() check because A might also be an obj. Maybe you actually want an obj to trigger the turf too, like a ball rolling over a pressure plate, but you might not have a Die() proc to take care of that. I wrote this example just as a reminder that either an obj or a mob can trigger this proc.

When bumping into things, Bump() is called. The thing that's moving around (or rather, failing to move) is src, the owner of the proc; the thing it bumped into is an argument to the proc. A lot of people put usr in here by mistake too.

mob
// this code is wrong
Bump()
if(--usr.HP < 0)
usr << "[src] killed you!"
src << "You kill [usr]!"
else
usr << "[src] hits you!"
src << "You hit [usr]!"

The author usually understands that src wouldn't be right for the thing that gets hit, but they forget that Bump() takes an argument; so they decide usr is the only thing that fits, and it's a huge mistake. There's a right way to do this:

mob
Bump(obstacle)
if(ismob(obstacle))
var/mob/M = obstacle
if(--M.HP < 0)
M << "[src] killed you!"
src << "You kill [M]!"
else
M << "[src] hits you!"
src << "You hit [M]!"

And now it works.

Even More Big Mistakes

Authors put usr in other places, too, when they don't know what else to do. For example, how would you handle a proc that checks if you're out of health, and if so credits your attacker with a kill? It makes sense that src would be the mob who just got hit, but who is the attacker? A lot of people are quick to assume usr would be the attacker--but that's not necessarily true.

mob
// this code is wrong
proc/TakeDamage(amount=0)
HP = max(HP - amount, 0)
if(!HP)
src << "[usr] kills you!"
usr << "You kill [src]!"
++usr.frags
--deaths
HP = maxHP
Move(locate(/area/start))
else
src << "[usr] hits you for <B>[amount] hit point\s</B>."
usr << "You hit [src] for <B>[amount] hit point\s</B>."

That's fine if the TakeDamage() proc is always called by a verb, but what if a free-flying projectile delivers the hit? Well, then usr isn't going to be valid, and you're screwed.

The key to this is that the TakeDamage() proc needs more information. You need to tell it who or what is doing the damage. It might be the environment, an object, a trapped turf, or an actual player. We don't know, but we do know that usr won't always be it.

mob
proc/TakeDamage(amount=0, attacker)
HP = max(HP - amount, 0)
if(!HP)
if(attacker)
src << "[attacker] kills you!"
if(ismob(attacker))
var/mob/M = attacker
M << "You kill [src]!"
++M.frags
else
src << "You die!"
--deaths
HP = maxHP
Move(locate(/area/start))
else
if(attacker)
src << "[attacker] hits you for <B>[amount] hit point\s</B>."
attacker << "You hit [src] for <B>[amount] hit point\s</B>."
else
src << "You lose <B>[amount] hit point\s</B>."

The result is longer, but it's worth it because now your proc is more flexible, and won't give you strange errors when your projectile slams into its target. All you have to do is call TakeDamage() with the extra argument telling it who or what is doing the damage.

obj/mine
proc/Explode()
for(var/mob/M in view(3, src))
M.TakeDamage(5, src)
del(src)

Doing It Right

As you see, this isn't very intuitive to a lot of people at first. Most beginners sort of gloss over the distinction between usr and src, or think usr should be used instead of a proc's arguments like in turf/Entered(), and go on without suspecting a thing. Small wonder, then, that they tend to encounter so many strange bugs they can't seem to pin down.

Partly this is the Reference's fault. The DM Reference for atom/Entered() includes a sample proc--which used to have usr in it. Many other parts of the Reference had the same problem. Who knows how long it was there, but this little documentation error confused newbies left to right for quite a while.

The Blue Book has a similar problem: In it the movement procs are all documented with usr meaning something, and from the text it's actually a little ambiguous as to which should be used. Actually usr should almost never be used in those procs; I say "almost" because it can be useful in some rare situations. In a movement proc, usr is the player (if any) who initiated the movement. It means that one of the players used a verb or an arrow or such to move, and there may be times that it's useful to know that.

The most important thing you can walk away from here is this: In a proc, don't assume usr means anything. (Verbs are okay. This includes routines like Click(), which people think of as a proc even though it's really a verb.) If the proc has an argument that tells it who's performing an action, use that. If it doesn't have one, but needs one, add an argument to the proc so it has more information to work with. Pretend usr doesn't exist and ask yourself: How can my proc figure out who's using it? Maybe your proc doesn't know who, and needs to be told; or maybe it already knows, from an argument passed by the caller.

Putting usr to safe use is essential. Use it wrong, and your game may work just fine for a while, only to leave you wondering weeks later why the wrong player is getting experience points or your monsters are triggering runtime errors. These are the bugs you can prevent by knowing when usr isn't "the user" at all.

Glad this finally made it in. You might want to fix the intro just a bit. First off, the "but there site" bit could be reworded and, for archival purposes, the whole "last night" blurb could really be removed I think (in days, months, years, do people really want to read about how it mysteriously appeared and disappeared the night before Oct. 20?).
Fixed!