ID:34510
 

A BYONDscape Classic! Yes, I know we just posted two big articles about the browser, but that just makes the time all the more opportune to post this. As your junior-high teachers told you: "Compare and contrast." --Gughunter

Dream Tutor: What Good is the Browser?

by Lummox JR

If you've spent a little time in BYOND, by now you're probably wondering what that "Browser" button in Dream Seeker is good for. A few games use it to show the game's official Web site, or you could check out the hub, but that's not so useful to you, is it?

Well, I didn't quite get it either until I played Gazoot's Bunniflip. Items, rules, score tallies, and just about everything else are shown in the mini-browser, and the results look darn good. Then I tried Foomer's graphical chat Ensya, which uses a browser interface for reading and writing scrolls--which means you can get interactive with this thing and make browser actions affect the game. Clearly, the browser has some powerful capabilities that are underexploited by most games. Reading a bit more into BYOND's reference, I got to know what some of those capabilities are.

Since the beginning of BYOND, the power of the mini-browser has increased significantly. It's time to see just what it can do for you.

Mini-Browser Basics

There are 3 procs that rule the world of the mini-browser, and they're all pretty easy to use. They are:

  • browse()
  • browse_rsc()
  • link()

The key proc is browse(). You can give this a URL in single quotes like browse('help.html') or the actual text as browse("Hello, world!"). With it you can generate HTML pages on the fly and send them to the user. That means you can create dynamic pages that respond to conditions in your game and report on what's going on.

Another helpful proc is browse_rsc(). What does this do? Well, it sends over a file to the user's cache so the browser can read it. This is good for graphics, style sheets, sounds, you name it. browse_rsc() takes one or two arguments: Either you can give it a file in single quotes, as in browse_rsc('background.gif'), or you can give it a var followed by a filename to use, like this:

usr << browse_rsc("body {font-family: 'Verdana', \
sans-serif}"
,"style.css")

Notice the usr << part? That's used for all these procs. You send browse() just like normal text; you can send it to src, world, a list, or anything else that could see text.

Now for the last proc: link(). Notice browse() can take a local file name? Well, link() can use a regular URL just as if you'd clicked on a link. Calling world << link("http://www.byondscape.com") will put BYONDscape in your mini-browser. It could also be used to take players to your game's official site.

Putting Together A Page

So now what? Well, let's just try a small example page to go with a "who" verb to show the players in your game.

mob
verb/Who()
set src = usr
usr << browse_rsc('mygame.css') // we'll use a style sheet
usr << browse_rsc('background.jpg') // and there's a background image
var/txt = "<html><head><link rel=stylesheet href='mygame.css'></head><body>"
txt += "<center><h1>Players in this game</h1><table>"
txt += "<tr><th>Name</th><th>Team</th><th>Points</th></tr>"
for(var/mob/M in world)
if(M.client) // ignore non-playing mobs
txt += "<tr><td>[M.name]</td><td>[M.team]</td><td>[M.score]</td></tr>"
txt += "</table></center></body></html>"
usr << browse(txt)

Notice how the page is built from scratch. We fill in the table using the list of players in the world. The most important thing to notice, however, is how browse_rsc() is being used here. If you include mygame.css and background.jpg in your project, the mini-browser needs them cached before it can use them. If you take a look at the HTML page, you'll notice there's no reference to background.jpg, but there is one to the style sheet. That style sheet can be as simple as this:

body {color: black; font-family: sans-serif;
background: #ffc0c0, url('background.jpg')}

That's a pink background; not everyone's first choice, naturally, but let's pretend it's appropriate. Whatever you do, you should always pick a background color that closely matches the image you use.

Want to know another trick? You can put together a style sheet at runtime just like you did with your HTML page.

var/txt = "body {"
txt += "color: black; "
txt += "font-family: 'Arial', sans-serif; "
txt += "background: white, url('paper.gif')"
txt += "}\n"
// in this example, src is a player
src << browse_rsc(txt, "paperpage.css")
src << browse('help.html')

Links That Work

Well this is great, but how do you make a link actually interact with the game? Now we get to the meat of it. There's this wonderful proc in the client called Topic() that does everything we need.

client/Topic(href, href_list[], hsrc)

So what's all that for? Well, it happens that whenever a link is clicked with byond://? as the first part of the URL, that link is diverted to this proc. BYOND's profiling feature even uses this proc. You can use it too--not just for links in the browser, but for anything in text output.

The href argument is everything following ? in the URL. (HTML gurus call this the query string.) The list href_list is a parsed version of that, so the URL ?name=Bob&value=10 is separated into an associative list where href_list["name"]=="Bob". (If this is confusing, don't worry. It's easier than it looks.) Finally, hsrc will point to any object referenced in the src= part of the URL, if there is one.

Let's try this out with a "private" verb, for speaking privately to another player. Suppose when you send a message to someone, you want them to be able to click your name to reply.

mob
verb/Private(mob/recip as mob, msg as text)
set src = usr
if(!recip || !msg) return // do nothing
msg = html_encode(msg) // strip out HTML
recip << "<B>\[From <A HREF='?private&src=\ref[usr]'>[name]</A>\]</B> [msg]"
usr << "<B>\[To [<A HREF='?private&src=\ref[recip]'>[recip]</A>\]</B> [msg]"

client
Topic(href, list/href_list, hsrc)
if("private" in href_list) // it's a link from our Private() verb
if(!hsrc) return // There needs to be a user to send to!
// mob is client.mob; that is, your mob if you clicked the link
usr = mob
var/msg = input("Send private message to [hsrc]:", "Message") \
as null|text

// call the Private() verb with usr=myself, recip=hsrc
if(msg) mob.Private(hsrc,msg)
return
..() // leave this in so profiling will still work

Now, when you click on a name in a private message, it'll prompt you for another message to send to that player.

When you use href_list, remember that the items in it are all text. If you get an object reference in anything other than hsrc, you need to use locate() to translate it back into an object (which could be a mob, obj, turf, icon, datum, etc.). If you have a number and want to change it from text to a number, use text2num():

var/mob/giveto = locate(href_list["giveto"])
var/G = text2num(href_list["gold"])
giveto.gold += G

The Mini-Browser Returns

That last section had nothing to do with the mini-browser, did it? Fair enough; let's talk about the browser again. Suppose, going back to that "who" verb example, we wanted to refresh the list every minute. (This is actually a bad idea if the player clicks away from the browser, because another call to browse() is going to pull it back up. It's just an example.) A little HTML research will tell you that in the

section, you'll need this:
<meta http-equiv="Refresh" content="60; url=myfile.html">

So that page will load myfile.html after 60 seconds. But wait! If we generated that HTML on the fly, we have no URL to put in there, do we? Ah, but we can put in a byond:// URL to take care of that. So, add this right after

:
<meta http-equiv='Refresh' content='60; url=byond://?who'>

Now, to go to the client:

client
Topic(href, list/href_list, hsrc)
if(href == "who")
mob.Who()
return
..()

Now the page generated by the who verb will refresh itself any minute. But woe to the user who clicks back to their info panel or the pager, because when the page refreshes they'll end up staring at it again. You should find a better way to do this. One way is just to put a simple text link in your HTML output and let users refresh the list themselves:

<A HREF="byond://?who">Refresh</A>

You can use links for just about anything. But don't forget, you can also use forms. The HTML

tag will work just fine here, too. As the URL for the form submission, just put in something like this:
<form method=get action="byond://">
<input type=hidden name="myform">
...
</form>

The hidden element will show up in href when it gets to client/Topic(). Instead of writing a complicated CGI script, your form will be rerouted to BYOND where you can write a complicated DM script instead. (Well I didn't say it'd be that easy....)

Icons

You know how you can use icons in text output using the \icon macro or the tag? In upcoming version of BYOND, you can do that in the mini-browser, too.

usr << browse_rsc(thing.icon,"thing.png")
usr << browse("<img src='thing.png' width=32 height=32 />")

Yep, you can use icons just like that. browse_rsc() will convert an icon to PNG format, and you can give it any file name you like. Since PNG isn't an animated image type, though, animations will be lost. You're stuck with just one frame of the animation, in one direction (by default it should be SOUTH), in the default icon_state. Suppose you want a particular icon state or direction, though. Well, you can do that with icon/New().

var/icon/ic = new(thing.icon, "state2",EAST)    // icon_state="state2", dir=EAST
usr << browse_rsc(ic, "thing_state2_east.png")

This technique can be used to duplicate the image of an obj or mob (except for its underlays and overlays) right in the browser.

What's one good use for icons? Well, you could provide in-game help this way. Suppose you want the user to be able to click on items to see what they are and how to use them, and have help appear in the browser. Simple:

atom
var/help
var/helptext

Click()
if(help)
var/code = ckey(help)
usr << browse_rsc(new /icon(icon,"[icon_state]", dir), "help_[code].png")
var/txt = "<html><body>"
var/txt += "<img src='help_[code].png' width=64 height=64 \
align=left hspace=5 vspace=5 />"

txt += "<h1>[help]</h1>"
txt += "<p>(\a [src])</p>" // a bell, an ostrich, some food...
txt += helptext
txt += "</body></html>"
usr << browse(txt)

Now for any obj or turf or mob you might create, just define a couple of vars:

obj/music
help="Musical Instruments"
helptext="If you play an instrument in the\
<a href='byond://?help=theater'>theater</A>, you may\
receive gold from the audience."

Watch For Falling Rocks

There's even more that the browse() proc can do, including controlling pop-ups. These features gives you a lot of power to control your games. Imagine an input box more flexible than input()--and better-looking. There's a lot you can do from here.

A word of warning before I end the article: You're probably going to be tempted now to make some really cool interface out of the mini-browser, like choosing characters at the beginning of an RPG or viewing the latest results in a free-for-all competition. That's great, and you by all means should go ahead and do it. But remember, in client/Topic(), always check to see if the input is valid--unless it doesn't matter.

Anyone can look at a link, copy the URL, and then later put in the same URL or something similar. They may try it just to see what it will do, while some may try to use these situations to cheat. (For example, a link to choose your character class once you've already chosen it could still be there. If you choose again, will it change you and cause unpredictable results within the game? Or will it know you already selected a class and do nothing?) Be sure to check thoroughly for bad or invalid input, or a link clicked at an inappropriate time, because some players can be sneaky.