Panel Basics

A typical stat and verb panel arrangement.
The stat and verb panels are a block of tabbed pages that appear in the lower left or top right of your Dream Seeker display. You can switch the location of the panel block or turn it off (depending on the size of the game map and the size of your Dream Seeker window) by pressing CTRL + I or selecting "Show Info" in the Layout menu. When it is in the upper right, clicking the "Text", "Browser", "Pager", or "Hub" buttons will hide it. Clicking the "Info" button will show the panel block again.

You select a panel by clicking a tab. The selected tab will appear to be slightly larger and in front of the other tabs. In the sample picture, the "Combat" tab is selected. If there are too many tabs to display within the space provided, there will be small arrows to the right of the tabs, which you can use to bring other tabs into view.

Tabs with red text are stat panels and tabs with black text are verb panels. This article will go over each type of panel in more depth.

Verb Panels

Verb panels display verbs (also called commands) available to the player. When the player moves the mouse cursor over a verb, it will turn green and the status bar at the very bottom of the Dream Seeker window displays the usage and some basic helpful text about the verb. A player may click the verb in the verb panel, or type it in on the command line to activate the verb.

Making more verb panels

Verb panels are created by using the category proc setting. Since this is a proc setting, it is locked at compile time and cannot be modified dynamically while the game is running. You must set the category for each verb if you do not want them to go on the default "Commands" panel. (You may change the name of the default verb panel with the default_verb_category client var.)

The verb panels are automatically displayed in case-sensitive alphabetical order. (Uncapitalized words come after capitalized words. For example: "Bread, Eggs, Milk, cheese" are in case-sensitive alphabetical order.) The order that you define the verbs and proc categories does not affect the display.

Example: The attack verb will show up in the "Combat" panel, while the fireball verb shows in the "Spells" panel.
                set category = "Combat"
                // attack code

                set category = "Spells"
                // fireball code

Turning off the verb panels

The client var show_verb_panel controls whether Dream Seeker shows the verb panels to a player. If it is 0, the player sees no verb panels. If it is set (a non-zero value) the verb panels are displayed.

The default_verb_category client var can also be used to turn off the default verb category if you set it equal to null. Note that setting it to "" will not hide the panel.

These variables can both be modified at runtime.

Example: Using Beginner/basic/advanced user modes to selectively hide or show verb panels.
                        set desc = "Turn off the command panels for advanced users."
                        show_verb_panel = 0 // turn off the verb panels
                        src << "Advanced use mode selected. \
                                Type \"help\" to select beginner mode or \
                                type \"basic\" for basic user mode."
                        set desc = "Only display special command panels."
                        show_verb_panel = 1 // turn off the verb panels
                        default_verb_category = null // turn off the default command panel
                        src << "Basic use mode selected. \
                                Type \"help\" to select beginner mode or \
                                type \"advanced\" for advanced user mode."
                        set desc = "Read the manual and turn on the command panels."
                        default_verb_category = "Commands"
                        show_verb_panel = 1     // turn on the verb panels
                        src << browse('help.html')

Hiding specific verbs from the panels

A programmer may set the verb category to null to keep it out of verb panels. With this method, the verb may still show in popup menus when you right click on atoms the verb could be applied to.

If you want the verb hidden from panels and popup windows, use the hidden setting.

To allow verbs to be seen in the panels but not in popup menus, use the popup_menu setting.

There is also a slightly misleading invisibility verb setting. This actually restricts the availability of verbs, making it so a player can or cannot access the verb unless the see_invisible var is equal or greater than a specific value. The methods of hiding verbs listed above prevent the player from seeing the verb, but they still have full access to it by typing the verb in the command line.

Since these are each proc settings, they are locked at compile time and cannot be modified dynamically while the game is running.

Example: Hiding verbs
                set src in view()
                // visible in default verb panel and right click context menus
                usr << "Look [src]"
                // hidden from panels, but visible in right click menu
                set src in view()
                set category = null
                usr << "Touch [src]"
                // visible in panels, but not in context menu
                set src in view()
                set popup_menu = 0
                usr << "Smell [src]"
                /* completely hidden from panels and context menus
                        but available on the command line */
                set src in view()
                set hidden = 1
                usr << "Taste [src]"
                // unavailable unless the usr's see_invisible is 100 or higher
                set src in view()
                set invisibility = 100
                usr << "Observe [src]"

Stat Panels

The stat panels are a simple but powerful way to display data that is updated periodically. The layout is limited, but they are much more versatile than they may seem at first.

How Stat Panels Work

Stat Panel display is controlled by the Stat() proc. Many DM programmers think of Stat() as a mob proc, but actually all atoms (and client datums) have a Stat() proc. A Stat() call begins with the client.

The client's Stat() proc is called automatically every few ticks (usually 6 to 8 ticks, depending on server load). The client has a variable called statobj, that points to the atom loaded in the client's stat panels. The default action of client.Stat() is to call the statobj's Stat() proc.

It's also important to note that client.Stat() sets usr equal to client.mob. This can become very important if you want to be able to view another mob's stat panels.

Equivalent code: If you were to write the default Stat() behavior in DM code, it would look something like this:

        New()   // when the client is created
                ..()    // do the default New() stuff
                spawn() // start a separate execution thread so New() can finish
                        while(src)      // as long as this  client exists
                                sleep(7)        // pause 7 ticks (actual Stat() time varies)
                                Stat()  // call the client.Stat() proc
                usr = mob

The client's statobj defaults to the client's mob. For most cases, this is all you ever need and you don't have to worry about client/Stat() or statobj. You may change client.statobj at anytime in your code.

Important note: Since Stat() is only called for atoms loaded in a client's statobj, you should avoid using it for things that are not directly related to stat panel display. For instance, if you use mob.Stat() to heal the mob over time, NPCs would never heal.

Generally, the statobj's Stat() proc is where you place all your display code, but you can put it in client.Stat() as well. Most BYOND developers prefer to put stat panel code in mob.Stat().

There are two procs used to display data in stat panels, statpanel() and stat(). The statpanel() and stat() procs are covered in more detail in the Making More Stat Panels and StatPanel Layout sections.

Example: A typical Stat() proc
        stat("Health:","[hp] / [maxhp]")
        stat("Mana:","[mp] / [maxmp]")
        statpanel("Inventory", contents)

Making More Stat Panels

The statpanel() proc may be used to create multiple stat panels. The format for statpanel() is:

     statpanel(Panel, Name, Value)

Panel is the name of a statpanel.

Name and Value are optional. If one or both are provided, they are displayed on the Panel as a normal stat(Name, Value) call. (see Stat Panel Layout) The default stat panel will not change if either of these arguments are used. My personal preference is to never use the Name and Value arguments of statpanel(). It potentially leads to sloppy code.

If statpanel() is called with only the Panel argument, it changes the default panel of all following stat() calls. The default panel is "Stats" until another stat panel is specified.

Example: A, B and C will be in the "Letters" panel. 1, 2 and 3 will be in the "Numbers" panel.

When statpanel() is called with just the Panel argument, it will return a value of 1 if the client is looking at that stat panel, or 0 if the client is not. You can use this to cut program overhead by not calculating things that are not displayed.

Example: This Stat() proc calls MyProc() every time Stat() is run.
        statpanel("My Proc")

Example: This Stat() proc only calls MyProc() when the player is looking at the "My Proc" panel.
        if(statpanel("My Proc"))

Turning Off Stat Panels

You can turn off stat panel tabs simply by not calling the statpanel() proc with that tab name.

Example: This simple snippet allows the player to turn his or her "Inventory" panel on or off
                inventory_panel = 1
                inventory_panel = !inventory_panel // toggle the value on or off
                        src << "Inventory opened."
                        src << "Inventory closed."
                if(inventory_panel && statpanel("Inventory"))

This line

if(inventory_panel && statpanel("Inventory"))
takes advantage of a special BYOND feature of the && operator called short circuiting. The first false value, evaluating left to right, causes the && operation to return false. That means if inventory_panel is 0, statpanel("Inventory") will not be called.

If inventory_panel is set, the "Inventory" tab will show. If inventory_panel is set and the player is looking at the "Inventory" panel, then the mob.contents will be displayed in the stat panel.

Stat Panel Layout

Layout Examples
Basic Single Column Stat Panel
stat("A single black column.")
stat("HP: [hp]/[maxhp]")
A single black column.
HP: 4/10
Two Columns (red and black)
stat("A label","Some text")
A label Some text
HP: 4/10
Two Columns (black and blue)
var/obj/O = new() = "atom name"
O.suffix = "atom suffix"
atom name atom suffix
Three Columns
var/obj/O = new() = "atom name"
O.suffix = "atom suffix"
stat("A label", O)
A label atom name atom suffix
One Row, Multiple Text Lines
stat("Label","Line 1\nLine 2\nLine 3")
stat("Multi-\nline\nLabel","sample text")
Label Line 1
Line 2
Line 3
sample text
Icons in a Stat Panel
var/obj/O = new()
O.icon = 'smiley.dmi' = "atom name"
O.suffix = "atom suffix"
stat("Optional label", O)
stat("plain text")
Optional label atom name atom suffix
plain text
Lists in a Stat Panel
var/list/L = list("Eggs","Milk","Bread")
Lists of Atoms
// players_list is a list of player mobs
Guest Guest's suffix
Shadowdarke Shadowdarke's suffix
Associated Lists
L = list("Str" = 12, "Dex" = 14,"Con" = 10)
Str 12
Dex 14
Con 10
Associated Lists of Atoms
/* equip_list is a list of equipment
   indexed by location. For example,
   equip_list["Weapon"] = the weapon
   obj the player is wielding. */

Weapon longbow 18/20
Ammo poisoned arrow x24
Armor rusty chainmail 5/100
Equipment icons from AbyssDragon's website:

Stat panels have three columns of text. The left column is displayed with red text, the center column in black text, and the right column is blue text. All stat panels are on a white background. The columns will automatically adjust in width to accommodate your display. The red column and blue columns are optional and the simplest stat panels only use the center black column.

Stat panels do not support HTML, but you can use the newline macro (\n) or the tab macro (\t) to change the spacing. Each column is left justified and vertically aligned to the center compared to other fields in the same row.

The stat panel layout is controlled by the arguments you use to call the stat() proc. Each stat() call creates a new row in the stat panel. Calling stat() with a list argument will automatically produce a number of rows equal to the length of the list. There are several examples of stat calls in the table to the right.

The format for stat() is:

     stat(Name, Value)

Name is optional. If there is only one argument to a stat() proc, it becomes the Value displayed. Name will only be displayed if it is text or a variable containing text. It is always displayed in the left hand red column of the stat panel.

Value can be a variable, text string, or numeric value. The format of the output depends on the data type of Value.

A text string, numeric value, or non-atomic variable will only display in the center black column. An atom has several display options, determined by the atom's vars. Lists also have special stat() display properties, as discussed below.

Atom Stat Displays

If the atom has an icon, it is displayed on the left of the center black column. The icon is still in the black column and any stat() displays without an icon will be flush with the left edge of the icon.

If the atom has a name, it is displayed in black text within the center stat panel column.

If the atom has a suffix, it will be displayed in the right hand blue text column.

Note: It is not a good idea to create new atoms in the Stat() proc because it will create a lot of extra overhead and possibly cause server lag. The demo code to the right does it just so you can see how the atom was defined to produce particular output.

List Stat Displays

Lists are displayed across several rows in a stat panel. Lists won't display at all if the Name argument of the stat() proc is used.

For a normal list (not an associated list), it displays as if stat were called for each item in the list. This snippet shows an equivalent DM code for what BYOND does automatically.

for(var/V in my_list)
        if(V.type != /list) stat(V)

Associated lists display the index of each list item as the Name in the left red text column. This snippet shows an equivalent DM code for what BYOND does automatically.

for(var/N in my_associated_list)
        var/V = my_associated_list[N]
        if(V.type != /list) stat(N, V)

Automatically Changing to a Stat Panel

The client datum has a statpanel var that reports the name of the stat panel the player is currently looking at. If the client is looking at a verb panel, the statpanel value is "verbs".

The main use for client.statpanel is to set the player on a certain stat panel. Just set the statpanel var equal to the name of the stat panel you want to view.

If the player does not have a stat panel that matches the name, the stat panel will blank out, showing nothing but white. The BYOND reference also mentions that you can set the statpanel var to "verbs" to move the "Commands" verb panel to the top, but this feature does not work as of this writing.

Example: This Stat() proc shows a large number of panels that could be a pain to navigate through. The inventory() verb brings the player immediately to the "Inventory" panel.
                statpanel("Empty statpanel")
                        client.statpanel = "Inventory"

Mouse Actions in Stat Panels

The mouse procs (Click, DblClick, MouseDown, MouseDrag, MouseDrop, MouseEntered, MouseExited, MouseUp) all work on atoms displayed in stat panels. Each proc has one or two special arguments for the location of the mouse cursor when the event happens. The location(s) can be the name of a stat panel or a map location.

Example: This snippet lets a player get() an object by dragging it from a space on the map within 2 steps of the usr to the "Inventory" stat panel, or drop an object by dragging from the inventory panel to a turf within 2 steps.
                // from the map to the Inventory panel
                if(isturf(src_location) && (over_location == "Inventory"))
                        if(get_dist(usr,src_location)<=2)    // check distance
                // from the Inventory panel to the map
                else if((src_location == "Inventory") && isturf(over_location))

Stat Panel Timing

As mentioned earlier, stat panels typically update every 6 to 8 ticks. The number of ticks depends on how heavy the server processor is working. The harder it works, the longer the delay between stat panel updates.

If your Stat() proc performs a complex operations (potentially causing server lag) or if you don't need your stat panels to update as often, you can slow down the stat panel refresh rate by putting a sleep() call in client.Stat(). The client.Stat() proc must return before it will be called again.

Example: Delay the stat panels to update once every 30 ticks (give or take a few ticks.) This code specifies 24 ticks to sleep, so that the default 6 to 8 ticks will make it a total of 30 to 32 ticks between Stat() calls. client.Stat() usually runs at 6 ticks internally when you delay it with a sleep() call.
        ..()    // do the default client.Stat() stuff.
        sleep(24)       // sleep 24 ticks

The only way to speed up stat panel updates is to decrease world.tick_lag. If you need something that updates more often, you should consider HUD displays using client.screen.

Nifty Stat Panel Snippets

Now you know all about the internal workings of verb and stat panels, the rest of this article has some interesting stat panel snippets. The companion PanelDemo program has expanded models of each of these snippets. <!-- ***** make the companion demo program and provide a link here *** provide a demo for macro keys that set the statpanel to a specific tab -->

Fake Verb Panels with Icons

You can't actually put icons in verb panels. Verb panels just aren't built for them. You can fake it by turning off verb panels and putting clickable atoms with icon in stat panels.

Example: Fake Verb Panels with Icons

obj/command     // our fake verbs
        icon = 'commands.dmi'
                icon_state = "say"
                        var/msg = "What would you like to say?"

                        var/T = input(msg, "Say \[text]") as text|null
                icon_state = "tell"
                        var/msg = "Who would you like to tell?"
                        var/mob/M = input(msg, "Tell \[mob]") as null|mob in world
                        if(!M) return
                        msg = "What would you like to tell [M]?"
                        var/T = input(msg, "Tell [M] \[text]") as text
                        usr.tell(M, T)

/* I use a global list in this demo, since all mobs will share the same verbs.
        You may want to have separate command lists for each mob if certain
        verbs are restricted. */
var/list/fake_commands = list()

        // make all the commands except the parent /obj/command
        for(var/command in typesof(/obj/command)-/obj/command)
                fake_commands += new command()

        show_verb_panel = 0     // turn off all verb panels

                statpanel("Commands")   // our fake "Commands" panel
                say(T as text)
                        world << "<b>[src]:</b> [T]"
                tell(mob/M in world, T as text)
                        M << "<i><b>[src]:</b> [T]</i>"
                        src << "<i><b>to [M]:</b> [T]</i>"

Group Member Stat Panels

This snippet provides a very basic grouping structure and displays all the group members in a "Group" panel, then displays a detailed stat panel for each member. You may click a name in the "Group" panel to turn on the member's panel and automatically jump to it, or close the member panel if it is already open.

Example: Grouping viewing snippet
                        attribute = list()
                        view_mobs = list()

                // make random attributes for the demo
                maxhp = rand(4,20)
                hp = rand(1,maxhp)
                maxmp = rand(4,20)
                mp = rand(1,maxhp)
                suffix = "HP:[hp]/[maxhp]\nMP:[mp]/[maxmp]"
                for(var/stat in list ("Str", "Dex", "Con"))
                        attribute[stat] = roll(3,6)


                 // the rest of this is only for the mob viewing itself
                if(src == usr)

                        for(var/mob/M in view_mobs)
                                if(M in group)
                                        view_mobs -= M

                if((location == "Group") || (location == name))
                        if(src in usr.view_mobs)
                                usr.view_mobs -= src
                                if(usr != src)
                                        usr.view_mobs += src
                                usr.client.statpanel = name

Open/Close Container Objs

This snippet provides allows you to place objs inside other objs (flagged with the container var) by dragging and dropping within your "Inventory" panel. You can drag an obj to a non-container or any white space in the "Inventory" panel to remove it from a container.

Containers are flagged with a red symbol. A + represents a closed container. A -- represents an open container. The objs between an open container obj and the next "-------" divider line are inside the container. Just click a container in your inventory to open or close it.

Example: Open/close containers snippet
                stt_container = 0       // set this to make an obj a container
                stt_open = 0    // flagged if a container is open

        // a demo container
                icon = 'sack.dmi'
                stt_container = 1

                // if clinked in the Inventory panel, and this obj is a container
                if((location == "Inventory") && stt_container)
                        stt_open = !stt_open    // toggle it's open state

                /* if it was dragged from the inventory panel to
                        another part of the inventory panel */
                if((start == "Inventory") && (end == "Inventory"))
                                var/obj/O = target      // obj alias to target

                        for(var/atom/A in contents)
                                        var/obj/O = A
                                                // skip to the next item in src.contents

Whoa! Might want to set color a bit darker in TD.code. The default text color of this site is way different from BYONDscape. =)