- Panel Basics
- Verb Panels
- Stat Panels
- Nifty Stat Panel Snippets
A typical stat and verb panel arrangement.
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 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.
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.
mob/verb/ attack() set category = "Combat" // attack code fireball() set category = "Spells" // fireball code
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.
client verb advanced() 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." basic() 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." help() 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')
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
obj/verb look() set src in view() // visible in default verb panel and right click context menus usr << "Look [src]" touch() // hidden from panels, but visible in right click menu set src in view() set category = null usr << "Touch [src]" smell() // visible in panels, but not in context menu set src in view() set popup_menu = 0 usr << "Smell [src]" taste() /* completely hidden from panels and context menus but available on the command line */ set src in view() set hidden = 1 usr << "Taste [src]" observe() // unavailable unless the usr's see_invisible is 100 or higher set src in view() set invisibility = 100 usr << "Observe [src]"
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.
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:
client 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 Stat() usr = mob statobj.Stat()
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
mob/Stat() stat("Health:","[hp] / [maxhp]") stat("Mana:","[mp] / [maxmp]") statpanel("Inventory", contents)
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.
Stat() statpanel("Letters") stat("A") stat("B") statpanel("Numbers") stat("1") stat("2") statpanel("Letters","C") stat("3")
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.
mob/Stat() statpanel("My Proc") stat(MyProc())
|Example: This Stat() proc only calls MyProc() when the player is looking at the "My Proc" panel.
mob/Stat() if(statpanel("My Proc")) stat(MyProc())
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
mob var inventory_panel = 1 verb/inventory() inventory_panel = !inventory_panel // toggle the value on or off if(inventory_panel) src << "Inventory opened." else src << "Inventory closed." Stat() if(inventory_panel && statpanel("Inventory")) stat(contents)
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 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:
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.
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.
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.
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.
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.
mob Stat() statpanel(name) stat(src) statpanel("Inventory") stat(contents) statpanel("Party") stat(group) statpanel("Time") stat(time2text(world.realtime)) statpanel("CPU") stat(world.cpu) statpanel("Empty statpanel") verb/inventory() if(client) client.statpanel = "Inventory"
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.
mob Stat() statpanel("Inventory") stat(contents) obj MouseDrop(over_object,src_location,over_location) // from the map to the Inventory panel if(isturf(src_location) && (over_location == "Inventory")) if(get_dist(usr,src_location)<=2) // check distance src.Move(usr) // from the Inventory panel to the map else if((src_location == "Inventory") && isturf(over_location)) if(get_dist(usr,over_location)<=2) src.Move(over_location)
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.
client/Stat() ..() // do the default client.Stat() stuff. sleep(24) // sleep 24 ticks
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.
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' say icon_state = "say" Click() var/msg = "What would you like to say?" var/T = input(msg, "Say \[text]") as text|null if(T) usr.say(T) tell icon_state = "tell" Click() 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() world/New() ..() // make all the commands except the parent /obj/command for(var/command in typesof(/obj/command)-/obj/command) fake_commands += new command() client show_verb_panel = 0 // turn off all verb panels mob Stat() statpanel("Commands") // our fake "Commands" panel stat(fake_commands) verb 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>"
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
mob var hp maxhp mp maxmp list attribute = list() view_mobs = list() New() ..() // 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) Stat() statpanel(name) stat(src) stat(attribute) // the rest of this is only for the mob viewing itself if(src == usr) statpanel("Group") stat(src) stat(group) for(var/mob/M in view_mobs) if(M in group) M.Stat() else view_mobs -= M Click(location) if((location == "Group") || (location == name)) if(src in usr.view_mobs) usr.view_mobs -= src else if(usr != src) usr.view_mobs += src usr.client.statpanel = name
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
obj var stt_container = 0 // set this to make an obj a container stt_open = 0 // flagged if a container is open // a demo container sack icon = 'sack.dmi' stt_container = 1 Click(location) // 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 MouseDrop(target,start,end) /* if it was dragged from the inventory panel to another part of the inventory panel */ if((start == "Inventory") && (end == "Inventory")) if(isobj(target)) var/obj/O = target // obj alias to target if(O.stt_container) Move(target) return Move(usr) mob Stat() statpanel("Inventory") if(contents.len) for(var/atom/A in contents) if(isobj(A)) var/obj/O = A if(O.stt_container) if(O.stt_open) stat("--",O) stat(O.contents) stat("----------") else stat("+",O) // skip to the next item in src.contents continue stat(A) else stat("nothing")