MUSHcode tips

MUSHcode tips javelin Sat, 2007-11-03 13:28

As anyone who's read Amberyl's MUSH manual knows, there's much more to writing MUSHcode than @create. Large MUSHcoded systems like combat, economy, and dynamic space systems pose particular problems for MUSH Gods. These tips offer suggestions for tackling MUSHcode.

Editor's note: Many of these tips may be dated! MUSH servers have changed in several ways since these tips were first posted. Always doublecheck this information with someone knowledgeable about the server you're using!

Queue and CPU efficiency (Javelin)
If you haven't read Amberyl's MUSH manual thoroughly, read it. The psychocoder section does a nice job of explaining the difference between queue and CPU efficiency, and the tradeoff you make when you code.

DB Growth (Gohs)
The single biggest reason for a MUSH to die, other than having its site yanked, is excessive or out of control database growth. If you are running a social MUSH with minimal or very freeform roleplaying, you can simply slap belated @quotas on everybody and stop the growth that way - Rhostshyl had survived quite some time when I was on it, so this can work, although players will tend to find it a frustrating situation, and if they become too frustrated they may well pack up and move to another MUSH. Restricted building and @quotas from the start may seem harsh and may discourage some builders unless the admin are very conscientious about making sure that those who want to build have the opportunity to, but the reality is that most players come on to play, not to build, and restricted building and @quotas from the start can be tolerated, and in the longrun will be ignored and treated as normal. Though it may seem draconian, this can save you a great deal of grief later on, as well as extending the lifespan of your MUSH.

MUSHcode Systems (Talek)
Many MUSHes these days have large, complex systems of MUSHcode to do cool and whizzy things, ranging from automated combat to economic simulation to autocreation of terrain. I helped introduce this sort of code into the world of MUSHes, and now I'm having second thoughts.

The benefits of MUSHcode systems are numerous: they can make make information more easily available, they can act as an impartial (but stupid) judge for things like combat, they can make seemingly complex automatic responses simple to code, and many more things. The drawbacks are few but substantial: MUSHcode systems often use large amounts of CPU time, they require learning a new set of commands to use the system, and in many cases they just add complexity to the game instead of enjoyment.

A well coded, documented, and integrated MUSHcode system can be a great benefit to a MUSH. When I see one, I'll be sure to let everyone know.

Combat systems (Javelin)
I've now written 3 different complete MUSH combat systems. If you're considering a combat system for your MUSH, think carefully about how you want it to work. Should players have to type attack commands repeatedly, or should a single command run the whole fight? Should there be both attack and defense commands? Should the combat system do the emits and assess damage, or simply tell the players how to role-play? How should player healing be handled? Should there be elements of fatigue, agility, skill? How will you prevent players from abusing combat? How will you ensure that combat works fairly even if a player is lagged, the queue is lagged, or the game gets shut down during a combat? I'm not going to give suggestions here, but just mention these things in the hope that you'll have a head start if you decide to write one - it's a great way to improve your MUSHcoding!

Coded Systems (Gohs)
Recently, (as in the last couple of years) the use of large MUSH coded systems has arisen over a large number of MUSHs. Even in those cases where the "systems" are relatively small, the sheer number can often be daunting, and the phenomenon is somewhat like the coathangers, nuclear warheads, and rabbit slippers - they tend to multiply, often out of control. '+help' files often rival the news files (something which seems an obscenity to me, but...), and the proliferation of global systems has attained truly staggering proportions on many MUSHs.

If there is one thing to remember about global code and systems, it is this:

Only code a global if it will substantially add to the enjoyment of the players and this enjoyment is in excess of the lag it will inevitably result in. '+commands' are nifty and fun to make, but the vast majority fall under the category of 'gadgets' - make 'em, play with 'em, then throw them out. Every neat little code toy put in the Master Room (#2) will add to the lag. On large systems this may be bearable, but the chances are good you won't have the machine of your dreams, and in any event it's generally a good idea to save on unnecessary lag in preparation for unavoidable lag.

Large coded setups, or systems, can include everything from weather, ecology, magic, tides, economy, time, skills, and, most commonly, combat. They are generally only used on MUSHs that are dedicated to roleplaying, but there are other types of MUSHs that may see them. What goes for +commands goes tenfold for coded systems: use them, yes, but don't use them unless they are a major benefit to the gameplay. It is very, very easy to go out of control here, and the temptation to add 'one more system' is hard to resist. It's often a good idea to determine what systems you want and then sticking with that.

That being said, there are a number of tricks that can help improve efficiency. Every attribute in the Master Room (#2) is evaluated every time a player enters a command. Because of this, when coding a system (or any global, for that matter) store as much information as possible on objects outside of the Master Room that can be called to by the object in the Master Room. Another thing to keep an eye on is organization of the Master Room. Planning from the beginning what objects will be in there and what will be on each of the objects can make tracking down bugs and buggy code an enormously simplified task, and is almost always well worth the effort. Yet another thing to be wary of are systems or parts of systems that will require the MUSH to constantly check thins. A good example of this can be found on the way time is handled on GohsMUSH. Instead of running an eternal @wait that periodically checks to see if the time has changed, it only checks to see what the time is when a player performs an action that requires that the time be displayed. A description of a room that changes as time goes by would have in its code to check for the time whenever someone looks at it - this is far more efficient than having the MUSH actively check to see if the time has changed and then change the descriptions appropriately. A setup like that could result in rooms changing their description even when nobody is present in the room, thus resulting in a waste of computing resources.

If you do decide you want to use systems, don't reinvent the wheel and don't have code that will on average detract from the enjoyment of the game. Look around to see if what you want has already been done. Generally it won't, but things like bulletin boards and 'who' machines are very common, and it's much more efficient of your time to ask permission to use code that's already been done than to code it from scratch. Don't follow this *too* slavishly, though. If the only code you can find is inefficient or doesn't do quite what you want, you may well be better off coding it from scratch. In addition to not reinventing the wheel, it's important when determining what systems you are going to have (if any) to keep in mind that, in general, it's the enjoyment of the players that will determine the success or failure of the MUSH. To give a good example of a system that went too far, I'll take one of my own blunders: a coded system of eating and sleeping. On Mua'kaar, I had Helbergina code a system whereby players had to eat on a regular basis or else slowly begin to lose health and finally die of starvation. While this was definitely "neat" and quite "realistic", it contributed little to the MUSH save for frustrating players, who, damn it, wanted to *roleplay*, not have to sit around worrying about how they were going to pay for their next meal.

Combat
Probably the most commonly attempted system is combat. While on LPs and Dikus it is generally non-player automations that get the sharp end of the sword or the wrong end of the maula, on MUSHs it generally winds up being players and only to a much lesser extent non-player characters. This being the case, I've seen three ways of handling the lethality of combat systems. Each has their problems, and which one you use will depend on taste and on what exactly you intend for the MUSH. The first method is reminiscent of the "kill" command (which is usually taken out). The "kill" command would send a character to the room they are linked to, or their home. This may entail the loss of their possessions and often a time spent as a ghost. One of the Tolkein MUSHs, Elendor, used a system like this for a while. The second method is to make combat very non-lethal - to make it very hard to kill characters. Usually this works best if the characters go unconscious before they die, making it very hard to kill someone outright. This, like the first option, has the advantage of not having to deal with too many players who are upset because their favorite character was fried by a laser. The third option is to make the combat system anywhere from somewhat lethal to very lethal. If combat is supposed to play a peripheral or secondary role on the MUSH, this can often serve to maintain a sense of realism that many people are attracted to while at the same time discouraging too much combat - a system that is extremely lethal generally finds players loathe to risk their characters unless absolutely necessary. One rather interesting benefit of this form of combat system that I've noticed is that when a system is very lethal, players will often resort to tactics rather than just charging into combat. Ambushes, traps, and ganging up are all forms of this.

Economy
Another system that is very popular and has been attempted many times and only very rarely successfully is a MUSH economy. One of the first things to decide when setting up a coded economy is to decide whether you are going to use MUSH coins (pennies, credits, coins) or coded currency (where you see things like 'check purse' and 'pay = '). There are problems with both, and each has its advantages. One major advantage of using MUSH coins is that they are hard coded, they are faster than a system of coded currency can be, and they require little or no additional code. The disadvantage of using MUSH coins is that they are also used for building and for queue-intensive commands, meaning that a player may find their in character money supply dwindling because of out of character actions. On the other hand, a system of coded currency is additional code and represents additional commands for the players to learn. Dune and Pandemonium are good examples of some early attempts at setting up an economy, and both are good examples of why it is so difficult to set up a workable economy. On both Dune and Pandemonium there were only a very small handful of items or services that were exchanged with much frequency. Weapons, armor, and in the case of Dune, training fees and shuttle tickets were the primary items of value in the economy. Both systems worked, and both can claim to have had working economies, but they were nonetheless very simplistic, in that they had a very narrow range of commodities - and the fewer the number of commodities, the more difficult it is to stabalize and spread out the economy. In a system such as these, very often it is only the soldier or mercenary who has any major role in the economy, which can serve to focus people onto these aspects of the MUSH - specifically, combat. Thus, if you are going to run an economy, it is worth determining what items and services characters will be able to purchase - and, extending from this, it is important to find commodities that players will *want* to buy and have a need for.

From this point, there are two types of economic activity that are possible to be engaged in. The most common is the micro, or the part of the economy in a MUSH sense where the characters are buying personal items - weapons, armor, non-player character bodyguards, etc. Any item in this has to have a need for that item or else a desire for it. People don't like to die or have fun engaging in combat, so there is a demand for weapons and armor. On Mua'kaar, the ill-fated food system required that characters eat regularly or begin to starve, so there was a need for food. As a general rule, on a MUSH, focus on cmmodities that are desires rather than needs. Players come to a MUSH to have fun, and though they'll fulfill a need, they won't obtain any particular enjoyment out of it, which they will if they are fulfilling a desire. The other part of the economy is the macro. Dune II is a superlative example of how this can be done. In this instance, you see a system of Houses and factions that are trading, producing, and consuming goods - often abstract - for the purpose of attaining in character power and/or prestige. Needless to say, these two parts of the economy can overlap greatly, and there is no absolute requirement for either or both. It is entirely possible to code a macro economy without a micro economy, or to code a micro economy without a macro, or both, or none. It depends almost entirely on what you, as the administrator, want to do with the MUSH.

A micro economy has a further problem that the macro economy does not have to worry about. Weapons and armor are generally registered or otherwise marked as "official", and only these official weapons and armor will work in the context of a coded combat system. If any player can simply @create a weapon or piece of armor, then the value becomes nil, and it is virtually possible to sustain an economic relationship with that item. Registering those items that are integrated into the economy is a very good idea; one idea that seems to work particularly well here is to have, say, all weapons owned by a Weapon Master character, and all armor owned by an Armor Master character, and so on. Not only does this make it easy to find all items of a particular type if you need to change all of them for some reason, but it also provides for a built in check - Weapon #1 is legal if and only if it is owned by the Weapon Master character, for example. There is another, slightly more drastic tactic that can be used in tandem with this, this being to require that *all* in character objects be in some way registered or owned by a common character. Thus, if a character wants a lamp, he or she has to buy an "official" one. Needless to say, this will work considerably better if the item in question has a coded use that cannot be accomplished by an object that someone has just @created. A lamp that will change a room's flag from DARK to !DARK has value, for example, that a player-created lamp would not.

DSpace (Talek)
One of the things that I'm best known for is Dynamic Space (DSpace for short). I originally wrote it to deal with Belgariad I's oceans; at the time it seemed like a good solution to a small problem. Since then, DSpace has used for everything from open countryside to city streets to mazes. Unfortunately, while DSpace is good for large tracts of fairly uniform terrain, many of the uses it has seen are woefully inappropriate.

For those unfamiliar with DSpace, I'll give a brief description: DSpace is a MUSHcode system which automatically digs and destroys rooms as players (or other things) wander through them. This keeps the number of rooms that actually exist to a minimum without limiting the amount of terrain represented. It does this through the use of zone exits which compute where you should go based on where you currently are and which direction you are going; if the room that you should be going to doesn't exist, DSpace creates it, and if the room you leave is empty, DSpace queues it for destruction. The end result is that DSpace trades CPU time for DB size.

All of this is great stuff if you are dealing with miles and miles of uninhabited ocean, desert, or wilderness; anything which is huge and not very populated. However, when used for something like city streets, which are used nearly continuously and are not all that uniform, all DSpace does is drastically increase the amount of work that the MUSH server has to do, with very little savings in the DB.

Now, a few years after writing Dynamic Space, I almost wish I never had written it in the first place. Almost everything that it does can be better done by rethinking the layout of an area; the primary questions being "Does the area need to be this big?" and "Is the area uniform enough so that using DSpace will be less work than individually crafting the rooms?" and "Is the area going to be so rarely used that the CPU time is worth the reduction in DB size?". All too often, one (or more) of the answers to the above questions is "No"; unfortunately, people seem to use DSpace anyway.

DSpace is one of the big ugly warts on the face of MUSHdom. Despite efforts to make it a new, clean, shiny wart, it is still a wart. Avoid it if you can.

MUSHcode for player purges (Rhyanna)
This is the MUSH code that I use for purging. All this code exists on a wizard player that is @uselocked against all other players. It could be easily modified to exist on a wizard object that had an appropriate @uselock.

'check' is the equivalent of Javelin's /ppurge:


&DO_CHECK me=$check:&purgees me; @tr me/for-all-players=summarize
&FOR-ALL-PLAYERS me=&num-objs me=first(lstats(all)); @tr me/for-all-players-aux=%0, 0
&FOR-ALL-PLAYERS-AUX me=@switch/first 1=gte(%1, v(num-objs)), {think Lists done.}, match(type(#%1), PLAYER), {@tr me/%0=#%1, name(#%1); @tr me/for-all-players-aux=%0,add(%1,1)}, {@tr me/for-all-players-aux=%0, add(%1,1)}

The 'summarize' attribute is the one that contains the policy about who should be purged and who should not be purged. Castle D'Image's policy is that players can be purged after they have not logged on for 90 days, or 30 days if they log on only once. My 'summarize' attribute, formatted for readability, also shows the @comment and @away attributes. The @comment and @away attributes are used to record reasons why players should not be purged; these reasons are usually either 1) that they intend to come back by a definite time (if they are gone for summer vacation, or the like), or 2) That they own rooms or objects that other people would miss.


&SUMMARIZE me=think %1;
@stats %1;
think Last on: [get(%0/last)];
think get(%0/comment);
think get(%0/away);
@switch/first 1=
gte( sub(secs(), convtime(get(%0/last))), get(me/90-days) ),
{ think Recommendation: Purge--not logged in for 90 days.;
&purgees me=setunion(v(purgees), %0)
},
and( gte( sub(secs(), convtime(get(%0/last))), get(me/30-days) ),
lte( sub( convtime(get(%0/last)), convtime(get(%0/creation_date)) ),
100 )
),
{ Think Recommendation: Purge--Only logged in once.;
&purgees me=setunion(v(purgees), %0)
},
{ Think Recommendation: Leave Existing.}
&90-DAYS me=7776000
&30-DAYS me=2592000

My equivalent of Javelin's '/nuke' macro is the following set of commands. I chose to make my 'purge' command require confirmation of the purge; I don't want to purge any player accidentally.


&PURGE me=$purge *:@switch/first 1=match(%0,all),
{ think Surely you don't want to purge the whole MUSH?},
not(isdbref(num(*%0))),
{ think %0 is not a valid player.},
not(match(type(*%0),PLAYER)),
{think You can only purge players.},
hasflag(*%0, connected),
{think That player is currently connected!},
{ &purgee me=name(*%0);
think {Do you really want to purge [v(purgee)], with [first(lstats(v(0)))] items? ('pyes' or 'pno'.)};
@tr me/summarize=num(*%0),name(*%0);
@wait me/60=
{ think [switch(type(*%0),player,{'purge [name(*%0)]' canceled.}, {[v(purgee)] purged.})];
&purgee me
}
}

The 'pyes' command does the actual purging. It uses the 'iter(v(types),...)' so that it destroys all the exits the player owns before any of the rooms, so that the 'The room must have less than three exits' message doesn't happen. 'pno' cancels the purge; it also cancels if 'pyes' is not typed within 60 seconds.


&PYES me=$pyes:@dolist/notify iter(v(types),lsearch(v(purgee),type,##) )=
@nuke ##
&TYPES me=EXIT OBJECT ROOM PLAYER
&PNO me=$pno:think {'purge [v(purgee)]' cancelled.}; &purgee me

Portable offline MUSHcoding (Javelin)
Here's a useful trick that I've used when writing uploadable MUSHcode (I usually write in 'mpp' format, and upload by piping my files through mpp, with tf's /quote !mpp < filename. mpp can be found here.


@@ Create the global or whatever we're distributing.
@@ After this, me/OBJ will contain the object's dbref!

@set me=OBJ:[create(+finger global)]

@lock [v(OBJ)] = =me
@desc [v(OBJ)] = This is a +finger global object

Securing MUSHcode (BladedThoth)
These tips come from BladedThoth@ThemelessMUSH, with some editing by Javelin.

As PennMUSH has become more feature-rich, it has become easier to write insecure code -- code that allows a user to cause the object to run unexpected commands. Here are some simple precautions that can help avoid this:

* Try to make sure that as much of your code in which you require user code, such as +buy commands and so forth, are ran through using REGEXP matching. This can eliminate many security problems by strictly limiting the allowable user input. For example, a command like +buy could be matched with:

$^\+buy (\d+) (\w+): @@ Now %1 is the number and %2 is the resource name

You are insured that %1 will only contain digits, and %2 will only contain digits, letters, and the underscore. Just remember when using REGEXPs, to include ones that tell the people what they have done wrong with their command, so they don't just get a Huh? message
* If you need to store any user input on an object that is set !NO_COMMAND or LISTEN, remember to prevent someone from placing the ^ or $ in the front of their code. For example, if you had some command that directly stores text freely (For example, $store *:&storage01 me=%0) if this object, or whichever object it was stored upon, was set !NO_COMMAND or LISTEN, this would allow someone to place a command or a listen, and be able to do damage with this. So make sure the code is secured somehow (Using SECURE(), ESCAPE(), or using REGEXP command-matching), OR place some sort of header in front of what is stored, that can be stripped off. For example, store input with:

$store *: &storage01 me=0 %0

And retrieve it with REST(V(storage01)). Of course, you should always try to do data storage on a separate object that's set HALT and NO_COMMAND and not set LISTENER as well.
* Use V(), GET(), or XGET(), which don't evaluate the data they return, on any attribute that has been user-inputted at all. Avoid using EVAL() or U(), because the code could have dangerous side-effects.
* Avoid using ## in ITER() with user-inputted lists of any sorts. Because ## is lexically replaced into the surrounding code, it's possible to insert metacharacters that may run arbitrary functions. Instead, use ITEXT(), which is evaluated as you expect, rather than lexically replaced.
* Avoid running user-inputted information through S() or similar commands that reevaluate their input. If you need to do so, you may wish to use either SECURE() or ESCAPE() (See the help files on which would be more suitable.)
* SETQ() is safe to use on user-inputted values, as it copies without additional evaluating.